Introduction
I wrote this article to pass on my knowledge to other developers who might have stumbled upon a different version of Python (Python 2.7.x VS Python 3.7.x) when using OpenSSL to download, view, and save certificates.
Background
While doing POC, I stumbled upon the versioning conflict of Python 2.7.x and Python 3.7.x.
I searched through lots of public articles and lots of Q & A on this topic. Not much information was found on this topic. My purpose is to retrieve certificate chain and store the certificates on my local drive which can be further used in other modules.
Using the Code
The code below is a sample Python snippet that will connect to host (e.g., any www.host.com) at specified port (e.g., 443
), download certificate chain from host, and store the certificates on the specified cert_file_pathname
(e.g., c:\testfolder\certfile). In the code snippet, I iterate through the certificate list and retrieve the certificate's CN, then print out the CN string. But you can retrieve other fields as needed.
Notice that I manipulate cert_file_pathname
and append the index of the certificate to certfile
in the code so that I can store all the downloaded certificate with the same prefix. You can modify the code to serve your purpose as needed.
I would like to share this code with everyone as I realized that there aren't many article about this topic. I have learned the feature through articles and Q & A along with OpenSSL and Python documentation.
def get_certificate(host, port, cert_file_pathname):
s = socket()
context = SSL.Context(SSL.TLSv1_2_METHOD)
print('Connecting to {0} to get certificate...'.format(host))
conn = SSL.Connection(context, s)
certs = []
try:
conn.connect((host, port))
conn.do_handshake()
certs = conn.get_peer_cert_chain()
except SSL.Error as e:
print('Error: {0}'.format(str(e)))
exit(1)
try:
for index, cert in enumerate(certs):
cert_components = dict(cert.get_subject().get_components())
if(sys.version_info[0] >= 3):
cn = (cert_components.get(b'CN')).decode('utf-8')
else:
cn = cert_components.get('CN')
print('Centificate {0} - CN: {1}'.format(index, cn))
try:
temp_certname = '{0}_{1}.crt'.format(cert_file_pathname, index)
with open(temp_certname, 'w+') as output_file:
if(sys.version_info[0] >= 3):
output_file.write((crypto.dump_certificate
(crypto.FILETYPE_PEM, cert).decode('utf-8')))
else:
output_file.write((crypto.dump_certificate(crypto.FILETYPE_PEM, cert)))
except IOError:
print('Exception: {0}'.format(IOError.strerror))
except SSL.Error as e:
print('Error: {0}'.format(str(e)))
exit(1)
Point of Interest
I learned the difference between Python 2.7.15 and 3.7.2 while writing this code. I have learned different certificate formats and security protocol that can be used as well. I am hoping this code will help those that are new to Python and SSL like me.
Note that I use crypto to dump the certificate off in PEM format, but the cert can be dumped in FILETYPE_ASN1
as well. See additional reference: https://pyopenssl.org/en/stable/api/crypto.html.
History
The code above will generate the following output:
Connecting to <host> to get certificate...
Centificate 0 - CN: <certificate CN>
Centificate 1 - CN: <certificate CN>