Search code examples
pythonhttp-headersazure-functionsclient-certificatesazure-appservice

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


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


Solution

  • 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)