How to decode `X-ARR-ClientCert` header using Python?

1.7k views Asked by At

How do I decode the X-ARR-ClientCert header passed by Azure App Service to my Azure Function code?

Example:

  • HTTP-triggered, Python Azure Function
  • Azure App Service configured to accept client certs
  • Requestor sends a client certificate with GET request (per Postman instructions here)
  • Azure App Service passes client cert to Function code via a X-ARR-ClientCert header

Issue:

  • I cannot find documentation on how this header is encoded
  • I cannot find an example of how to decode this header using Python

The closest I've got is this code:

import logging
import base64
import azure.functions as func

def main(req: func.HttpRequest) -> func.HttpResponse:
    
    logging.info('####### Python HTTP trigger certificate validation function processing a request. #######')

    # Retrieve client cert from headers
    req_cert_str = req.headers.get("X-ARR-ClientCert")
    
    req_cert_bytes = base64.b64decode(req_cert_str)
    
    decoded_string = req_cert_bytes.decode('cp1252')

    return func.HttpResponse(
        decoded_string
    )
  • Which results in Status 500 Internal server error and:
Exception while executing function: Functions.certiFunc <--- Result: Failure Exception: UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 403: character maps to <undefined> Stack: File "/azure-functions-host/workers/python/3.8/LINUX/X64/azure_functions_worker/dispatcher.py", line 343, in _handle__invocation_request call_result = await self._loop.run_in_executor( File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run result = self.fn(*self.args, **self.kwargs) File "/azure-functions-host/workers/python/3.8/LINUX/X64/azure_functions_worker/dispatcher.py", line 480, in __run_sync_func return func(**params) File "/home/site/wwwroot/certiFunc/__init__.py", line 14, in main decoded_string = req_cert_bytes.decode('cp1252') File "/usr/local/lib/python3.8/encodings/cp1252.py", line 15, in decode return codecs.charmap_decode(input,errors,decoding_table) 
  • When substituting decoded_string = req_cert_bytes.decode('utf-8'), I get:
Exception while executing function: Functions.certiFunc <--- Result: Failure Exception: UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte Stack: File "/azure-functions-host/workers/python/3.8/LINUX/X64/azure_functions_worker/dispatcher.py", line 343, in _handle__invocation_request call_result = await self._loop.run_in_executor( File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run result = self.fn(*self.args, **self.kwargs) File "/azure-functions-host/workers/python/3.8/LINUX/X64/azure_functions_worker/dispatcher.py", line 480, in __run_sync_func return func(**params) File "/home/site/wwwroot/certiFunc/__init__.py", line 14, in main decoded_string = req_cert_bytes.decode('utf-8') 
  • When running the following (directly decoding the header)...
req_cert_str = req.headers.get("X-ARR-ClientCert")
decoded_string = base64.b64decode(req_cert_str) 

...I get a Status 200 Success but the response is a mashup of binary(?) characters and plain text:

enter image description here

What is the correct method for decoding this header using Python?

Further reading on the Github issue raised here

1

There are 1 answers

5
krishg On BEST ANSWER

Since you are adding the client certificate from Postman, it's in DER (binary) format. You can decode the x509 certificate from bytes itself using Python cryptography.

from cryptography import x509

# header is base64 encoded string, so extract the bytes first
req_cert_str = req.headers.get("X-ARR-ClientCert") 
req_cert_bytes = base64.b64decode(req_cert_str)

cert = x509.load_der_x509_certificate(req_cert_bytes)

# do stuffs with cert
logging.info(f'Received client cert with serial number: {cert.serial_number}')

Note: If the certificate was PEM format, you would need to use x509.load_pem_x509_certificate(req_cert_bytes)