0

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

7
  • 1
    Try to load x509 from the bytes (req_cert_bytes) itself cryptography.io/en/latest/x509/reference Commented Oct 12, 2020 at 6:15
  • I'll try that now. The docs say Deserialize a certificate from PEM encoded data. How do I know that X-ARR-ClientCert is PEM encoded data? The client uploaded a cert in .crt format (as Postman only currently supports .crt. and .pfx formats). Commented Oct 12, 2020 at 14:32
  • for .crt/.pfx, use load_der_x509_certificate - Deserialize a certificate from DER encoded data. DER is a binary format and is commonly found in files with the .cer extension (although file extensions are not a guarantee of encoding type). Commented Oct 12, 2020 at 14:35
  • Thank you @krishg. load_der_x509_certificate says it takes data of type bytes. The X-ARR-ClientCert header if of type str. Do I need to convert first? Commented Oct 12, 2020 at 14:39
  • 1
    yes, like you were doing req_cert_str = req.headers.get("X-ARR-ClientCert") req_cert_bytes = base64.b64decode(req_cert_str) then pass req_cert_bytes to load_der_x509_certificate Commented Oct 12, 2020 at 14:41

1 Answer 1

1

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)

Sign up to request clarification or add additional context in comments.

5 Comments

Does Azure do any conversion between the client certificate and the X-ARR-ClientCert header? Example: User sends .pem encoded client cert in request. Does Azure convert that to .der or will it pass the .pem?
It does not do anything with this client certificate in X-ARR-ClientCert header other than forwarding it to your app. Your app code is responsible for validating the client certificate.
Got it. So if client uploads a .crt, it passes a .crt; client uploads a .der, it passes a .der and so on for .cer and .pem formats as well. Again, thank you @krishg.
If clients are uploading certs in various formats (.pem, .der, .crt, .cer), how can the cert type be identified using Python?
I think one simple way is to check the first byte is char '-' in the cert bytes, then its pem format (always has header like -----BEGIN CERTIFICATE-----), so use load_pem_x509_certificate, else use load_der_x509_certificate (will cover crt, cer, der). So check first byte from req_cert_bytes to decide on which load method to invoke.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.