Certitude: Platform-Specific TLS Certificate Verification

Certitude provides Python bindings to the Windows and OS X system-native TLS certificate libraries. This allows Python programs to validate TLS certificates using the exact same logic used by native browsers on those platforms (e.g. Safari and Internet Explorer/Edge).

This means that by combining Certitude with the OpenSSL used by default on most Linuxes and BSDs, it is possible for a Python program to perform certificate validation in a platform-native manner on all major operating systems. This lets a Python program behave like a full native citizen of whatever platform it is installed on.

This documentation discusses how to use Certitude.

Contents:

Installing Certitude

Certitude involves bindings to native-code libraries, and is a library that functions only on Windows and OS X. These two platforms traditionally have problems with native-code bindings from Python, due to their lack of compilers or fully-fledged dependency resolution.

For this reason, while Certitude is available as a source distribution, it also provides pre-compiled binary wheels for both Windows and OS X. For this reason it is strongly preferred that a recent pip is used for installing Certitude, as this will remove the need for a compiler toolchain that you may not have installed.

To install Certitude, the pip command is very simple:

$ pip install certitude

This should download and install a wheel that makes certitude available.

Using Certitude

Certitude has one job: validating the TLS certificates a server has sent you. To do that, you need to pass Certitude the TLS certificate chain sent by the server, and the hostname you’re expecting to connect to.

Getting A Certificate Chain

Certitude expects the TLS certificate chain as a list of TLS certificates stored in the DER representation. Unfortunately, the Python standard library’s ssl module is not capable of providing the entire certificate chain, only the leaf certificate. This means that to use Certitude you will need to use pyopenssl or something like it: it’s just the only way to guarantee that you get the complete certificate chain.

To get a certificate chain from PyOpenSSL, you’ll want to make the connection as normal and then call get_peer_cert_chain(). This will get you your cert chain as a list of X509 objects. These will need decoding.

Given an already existing connection cnx, you can get your list of certificates like this:

certs = cnx.get_peer_cert_chain()

encoded_certs = [
    OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert)
    for cert in certs
]

Validating The Chain

Once you have the chain in place, it’s simple enough to validate it. Simply pass the chain into certitude.validate_cert_chain along with a unicode string containing the expected hostname. For example:

valid = validate_cert_chain(encoded_certs, u'http2bin.org')

The validate_cert_chain function returns True if the cert chain is valid, and False in any other case.

Notes

When validating certificates using certitude you’ll likely want to disable OpenSSL’s certificate validation. This is because OpenSSL and the platform-specific TLS validation code will build their certificate chains differently. In particular, OpenSSL may be unable to validate a chain that the system library believes is valid. For that reason, put OpenSSL into the VERIFY_NONE mode and then handle the validation manually, after the connection is made but before you send any data on it.

We cannot stress this enough: you must validate the certificates before sending or receiving data on the connection.