pythonazureopensslazure-keyvaultpkcs

OpenSSL.crypto.Error when trying to load certificate from Azure Key Vault


I need to implement certificate-based authentication for web API hosted in app service on Azure. To do this I firstly generated .crt certificate file and private key .key file like this:

openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout private_key.key -out cert.crt -subj '/CN=myapi.azurewebsites.net' -addext 'subjectAltName=DNS:myapi.azurewebsites.net'

Then I've created certificate in.pfx format using following command:

openssl pkcs12 -inkey private_key.key -in cert.crt -export -out certificate.pfx

My code for fetching certificate data looks something like this:


from azure.identity import DefaultAzureCredential
from azure.keyvault.certificates import CertificateClient
from OpenSSL import crypto

key_client = CertificateClient(
    vault_url=f"https://myteskeyvault.vault.azure.net/",
    credential=DefaultAzureCredential(),
)
cert = key_client.get_certificate("certificate")
loaded_cert = crypto.load_pkcs12(
    bytes(cert.cer), None
)

I get error on line when I'm trying to load certificate using crypto.load_pkcs12 function:

Traceback (most recent call last):
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 375, in run_asgi    
    result = await app(self.scope, self.receive, self.send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\uvicorn\middleware\message_logger.py", line 82, in __call__
    raise exc from None
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\uvicorn\middleware\message_logger.py", line 78, in __call__
    await self.app(scope, inner_receive, inner_send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\fastapi\applications.py", line 261, in __call__
    await super().__call__(scope, receive, send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
    raise exc
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\middleware\cors.py", line 92, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\middleware\cors.py", line 147, in simple_response
    await self.app(scope, receive, send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\exceptions.py", line 82, in __call__
    raise exc
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 21, in __call__
    raise e
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\routing.py", line 656, in __call__
    await route.handle(scope, receive, send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\routing.py", line 259, in handle
    await self.app(scope, receive, send)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\routing.py", line 61, in app
    response = await func(request)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\fastapi\routing.py", line 217, in app
    solved_result = await solve_dependencies(
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\fastapi\dependencies\utils.py", line 529, in solve_dependencies     
    solved = await run_in_threadpool(call, **sub_values)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\starlette\concurrency.py", line 39, in run_in_threadpool
    return await anyio.to_thread.run_sync(func, *args)
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\anyio\to_thread.py", line 31, in run_sync
    return await get_asynclib().run_sync_in_worker_thread(
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\anyio\_backends\_asyncio.py", line 937, in run_sync_in_worker_thread    return await future
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\anyio\_backends\_asyncio.py", line 867, in run
    result = context.run(func, *args)
  File "C:\Users\devdz\Projects\myapp\.\myapp\dependencies.py", line 204, in __call__
    api_token = self.__get_api_token(token, settings)
  File "C:\Users\devdz\Projects\myapp\.\myapp\dependencies.py", line 149, in __get_api_token
    msal_client = settings.aad.get_msal_client_using_certificate()
  File "C:\Users\devdz\Projects\myapp\.\myapp\settings\aad.py", line 62, in get_msal_client_using_certificate
    private_key = self.az_key_vault.get_private_key()
  File "C:\Users\devdz\Projects\myapp\.\myapp\settings\aad.py", line 29, in get_private_key
    loaded_cert = crypto.load_pkcs12(
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\OpenSSL\crypto.py", line 3295, in load_pkcs12
    _raise_current_error()
  File "C:\Users\devdz\Projects\myapp\.venv\lib\site-packages\OpenSSL\_util.py", line 57, in exception_from_error_queue
    raise exception_type(errors)
OpenSSL.crypto.Error: [('asn1 encoding routines', '', 'wrong tag'), ('asn1 encoding routines', '', 'nested asn1 error'), ('asn1 encoding routines', '', 'nested asn1 error')]

What am I doing wrong that can cause such error? I've also tried approaches mentioned in this thread but it also did not help me.


Solution

  • I am not wrong, when you get the certificate as a Certificate, using:

    cert = key_client.get_certificate("certificate")
    

    You only get the public key part of the certificate, if you want to get it with the private key, then you need to load the certificate as a Secret instead.

    Something like:

    cert = key_client.get_secret("certificate")
    

    Keyvault returns the certificate in base64 encoded pfx file.