Validate x509 Certificate in Python

Posted by Mark Wright on Tue 15 March 2016

I need to validate a x509 certificate's chain of trust in python. TL;DR version is that you can use PyOpenSSL. The code below gives an example.

The python standard library modules like urllib, http, and the popular third party module requests all perform certificate validation by default when connecting over HTTPS. Unfortunately they don't expose an API that will let me verify a chain of trust by providing the certificate and the chain outside of a HTTPS connection.

I have a certificate in a file, cert.pem. I have an intermediate certificate in a file, int-cert.pem. I have a root certificate in a file, root-cert.pem. I want a way to validate that cert.pem was issued by int-cert.pem and int-cert.pem was issued by root-cert.pem. OpenSSL provides the verify command to do this:

# concatenate the certs together into a single file representing the chain of trust
$ cat int-cert.pem root-cert.pem > trust.pem

$ openssl verify -CAfile trust.pem cert.pem

There are three python modules that are often used for cryptography: M2Crypto, cryptography, and PyOpenSSL.

PyOpenSSL and M2Crypto are wrappers around OpenSSL but they only wrap a subset of the OpenSSL APIs. The cryptography module is a more ambitious project. It uses multiple backends (openssl, commoncrypto) to provide a suite of cryptographic primitives and programmer friendly APIs. The authors consider M2Crypto and PyOpenSSL to be flawed in a number of areas and are trying to create a better, more pythonic, cryptographic library. PyOpenSSL actually requires the cryptography module since version 0.14 as it relies on it for the OpenSSL bindings.

The M2Crypto module doesn't support validating a chain of trust. There were some attempts to patch it by the Fedora and Pulp projects in 2012 but the patch hasn't made it upstream and I couldn't locate the code. It is discussed here.

The cryptography module doesn't support validating a chain of trust. A lot of work has been done on it but it isn't ready. You can see progress in this issue and this pull request.

The PyOpenSSL module does support validating a chain of trust.

PyOpenSSL Example

I installed PyOpenSSL with pip $ pip install pyopenssl.

The code below illustrates how to read in the three certificates (in the pem file format) detailed in my scenario above and then validates cert.pem's chain of trust.

from OpenSSL import crypto


def verify():
    with open('./cert.pem', 'r') as cert_file:
        cert = cert_file.read()

    with open('./int-cert.pem', 'r') as int_cert_file:
        int_cert = int_cert_file.read()

    with open('./root-cert.pem', 'r') as root_cert_file:
        root_cert = root_cert_file .read()

    trusted_certs = (int_cert, root_cert)
    verified = verify_chain_of_trust(cert, trusted_certs)

    if verified:
        print('Certificate verified')


def verify_chain_of_trust(cert_pem, trusted_cert_pems):

    certificate = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)

    # Create and fill a X509Sore with trusted certs
    store = crypto.X509Store()
    for trusted_cert_pem in trusted_cert_pems:
        trusted_cert = crypto.load_certificate(crypto.FILETYPE_PEM, trusted_cert_pem)
        store.add_cert(trusted_cert)

    # Create a X590StoreContext with the cert and trusted certs
    # and verify the the chain of trust
    store_ctx = crypto.X509StoreContext(store, certificate)
    # Returns None if certificate can be validated
    result = store_ctx.verify_certificate()

    if result is None:
        return True
    else:
        return False

Cover photo of a Female Corynura Bee

tags: python


Comments !