How to validate / verify an X509 Certificate chain of trust in Python?

speznot picture speznot · Jun 8, 2015 · Viewed 18.8k times · Source

I am working on implementing a web application that utilizes an API. During a response, the API server sends over a link to an X509 certificate (in PEM format, composed of a signing certificate and one or more intermediate certificates to a root CA certificate ) that I must download and use to do further verification.

Before using the certificate, I need to ensure that all certificates in the chain combine to create a chain of trust to a trusted root CA certificate (to detect and avoid any malicious requests). I am having a hard time doing this in python and my research into the subject is not yielding anything useful.

The certificate is easily grabbed and loaded using requests and M2Crypto

import requests
from M2Crypto import RSA, X509

mypem = requests.get('https://server.com/my_certificate.pem')   
cert = X509.load_cert_string(str(mypem.text), X509.FORMAT_PEM)

However, validating the certificate chain is a problem. It is not feasible for me to write the certificate to disk in order to use a command line utility like openssl through something like subprocess, so it must be done through python. I also do not have any open connections and so using a connection based validation solution (like is mentioned in this answer / thread: https://stackoverflow.com/a/1088224/4984533) will not work either.

On another thread about this problem (at https://stackoverflow.com/a/4427081) abbot explains that m2crypto is incapable of doing this validation and says that he has written an extension to allow validation (using the module m2ext) but his patch never seems work, always returning false even though I know it's valid:

from m2ext import SSL
ctx = SSL.Context()
ctx.load_verify_locations(capath='/etc/ssl/certs/') # I have run c_rehash in this directory to generate a list of cert files with signature based names
if not ctx.validate_certificate(cert): # always happens
    print('Invalid certificate!') 

There's also this answer on a similar thread here https://stackoverflow.com/a/9007764/4984533 in which John Matthews claims to have a patch written which will do it, but unfortunately the patch link is now dead -- and anyway there is a comment on that thread stating that the patch did not work with openssl 0.9.8e.

All answers relating to validating a certificates chain of trust in python seem to either link to the dead patch or go back to m2ext.

Is there a simple, straightforward way to go about validating my certificates chain of trust in Python?

Answer

ralphje picture ralphje · Mar 14, 2018

While the response of Avi Das is valid for the trivial case of verifying a single trust anchor with a single leaf certificate, it places trust in the intermediate certificate. That means that in the case where the intermediate is sent, as well as the client certificate, the entire chain is trusted.

Do not do this. The code found in pyOpenSSL's tests are flawed!

I found this thread on Python's cryptography-dev mailing lists (which links back to this answer): https://mail.python.org/pipermail/cryptography-dev/2016-August/000676.html

We see this code makes no distinction between the root_cert and the intermediate. If we look at the documentation, add_cert itself adds a trusted cert (maybe add_trusted_cert would be a better name?).

It includes examples on why this is a terrible idea. I can not stress this enough: verifying your chain by trusting the intermediates is similar to not performing any check at all.


Having said that, how do you verify a certificate chain in Python? The best alternative I found is https://github.com/wbond/certvalidator, which seems to do the job.

There are also some flawed alternatives:

This is the current state for some respectable Python cryptography libraries:

Both threads seem stale at this time.

I know this does not match the question's case, but: if you are building something using certificate validation in TLS sockets, just use the modules already available in Python. Never re-invent the wheel, especially regarding cryptography. Crypto is hard; the only easy thing about it is messing it up.