One of the challenges of running your own startup is the number of SSL certificates that you have to purchase and manage. If you are on the Let’s Encrypt bandwagon, you are now working with a ‘wizard’ style interface that just works as long as you setup all the cron jobs for setup and management of your certificates.
For those who still prefer to do this by hand, you would need a tool that will check and make sure that
- Your certificate is not expired and still valid
- Your chain of trust of intermediate certificates are still valid and not expired.
- Your root certificate is not revoked and not expired.
I was looking for a readymade tool that could do something like this and couldn’t find one out there so decided let me start writing one on my own.
I created a PEM file with multiple certificates for the entire chain. The first one being the server certificates followed by the intermediary certificates and finally the root certificate.
I know that I can do a series of checks using openssl but I wanted all the steps performed in one go so decided to build a tool for it.
My favourite language of choice is currently python and so I started looking at SSL libraries that I can use. First step I tried the pyOpenSSL python library to see if it will do the job.
import OpenSSL.crypto def open_certificate_file(filename): try: cert = OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_PEM, open(filename).read() ) return cert except FileNotFoundError: return None
Calling this function loaded the first certificate from a PEM file and none of the subsequent ones (that formed the rest of the chain) There was no way to make pyopenssl look at it. I found a patch request from over four years ago where this issue is discussed in length but never fixed. PyOpenSSL clearly was not a viable option.
The next library to try out was cryptography which claims to provide both high level and low level abilities to implement various cryptographic capabilities in Python.
from cryptography import x509 from cryptography.hazmat.backends import default_backend def open_certificate_file(filename): try: with open(filename, 'r') as thefile: pem_data = thefile.read() # Convert to ASCII pem_data = pem_data.encode('ascii', 'ignore') cert = x509.load_pem_x509_certificate(pem_data, default_backend()) return cert except FileNotFoundError: return None
I ran into the same problem here was well. Only once certificate loaded. After a lot of Googling and reading other people’s posts I found a five year old issue talking about a similar problem that never went anywhere with Cryptography.
Only good thing about the cryptography thread was that there was one clue, a proejct called certvalidator. This is one comprehensive library that covers a lot of ground on validation of various forms and one of them includes the validation of the certificate chain! Viola! This is exactly what we are looking for and more.
Here is a simple program to put the library through its paces
import sys from asn1crypto import pem from certvalidator import CertificateValidator def open_certificate_file(filename): end_entity_cert = None intermediates =  with open(filename, 'rb') as f: for type_name, headers, der_bytes in pem.unarmor( f.read(), multiple=True): if end_entity_cert is None: end_entity_cert = der_bytes else: intermediates.append(der_bytes) return end_entity_cert, intermediates if __name__ == '__main__': end_entity_cert, intermediates = open_certificate_file(sys.argv) validator = CertificateValidator(end_entity_cert, intermediates) vpath = validator.validate_usage( set([ "digital_signature", "key_encipherment", ]), extended_key_usage=set(["server_auth", "client_auth"])) if vpath & validator.validate_tls(sys.argv): print ('everything worked')
SSL certificates are complex beasts with a lot of validations going on in them. Attempting to roll out our own libraries is fraught with various security risks. I think the certvalidator does an excellent job of validating the certificate and also serves my original need of validating the entire chain very well.