How can i get Certificate issuer information in python?

Question:

I want the ‘issued to’ information from certificate in python. I try to use the SSL and SSLSocket library but did not happen.

enter image description here

Asked By: Raj

||

Answers:

Updated answer

If you can establish a connection to the remote server you can use the ssl standard library module:

import ssl, socket

hostname = 'google.com'
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.socket(), server_hostname=hostname) as s:
    s.connect((hostname, 443))
    cert = s.getpeercert()

subject = dict(x[0] for x in cert['subject'])
issued_to = subject['commonName']
issuer = dict(x[0] for x in cert['issuer'])
issued_by = issuer['commonName']

>>> issued_to
u'*.google.com'
>>> issued_by
u'Google Internet Authority G2'

Original answer

Use pyOpenSSL.

from OpenSSL import crypto

cert_file = '/path/to/your/certificate'
cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read())
subject = cert.get_subject()
issued_to = subject.CN    # the Common Name field
issuer = cert.get_issuer()
issued_by = issuer.CN

You can also access additional components, e.g. organisation (subject.O/issuer.O), organisational unit (subject.OU/issuer.OU).

Your certificate file might be in another format, so you could try crypto.FILETYPE_ASN1 instead of crypto.FILETYPE_PEM.

Answered By: mhawke
import os
import re
os.system('keytool -printcert -sslserver google.com:443 >cert.txt')
fh = open("cert.txt", "r")
content = fh.readlines()
fh.close()
content = content[2]
m = re.search('CN=(.+?),', content)
if m:
    found = m.group(1)
print found

If you use requests, a simple code is here:

#!/usr/bin/python
# -*- coding: utf-8 -*-


from requests.packages.urllib3.contrib import pyopenssl as reqs


def https_cert_subject_alt_names(host, port):
    """Read subject domains in https cert from remote server"""

    x509 = reqs.OpenSSL.crypto.load_certificate(
        reqs.OpenSSL.crypto.FILETYPE_PEM,
        reqs.ssl.get_server_certificate((host, port))
    )
    return reqs.get_subj_alt_name(x509)

if __name__ == '__main__':
    domains = https_cert_subject_alt_names("www.yahoo.com", 443)
    print(domains)

The result is as follow:

[('DNS', '*.www.yahoo.com'), 
 ('DNS', 'www.yahoo.com'), 
 ('DNS', 'add.my.yahoo.com'), 
 ('DNS', 'au.yahoo.com'), 
 ('DNS', 'be.yahoo.com'), 
 ('DNS', 'br.yahoo.com'), 
 ('DNS', 'ca.my.yahoo.com'), 
 ('DNS', 'ca.rogers.yahoo.com'), 
 ('DNS', 'ca.yahoo.com'), 
 ('DNS', 'ddl.fp.yahoo.com'), 
 ('DNS', 'de.yahoo.com'), 
 ...
 ('DNS', 'mbp.yimg.com')]
Answered By: debug

The pyOpenSSL library does not seem to be well suited for this task. Here is what they say in official docs

Note: The Python Cryptographic Authority strongly suggests the use of
pyca/cryptography where possible.

There are some workarounds using Python’s standard library, but most of those seem to be messy.

Here is the way to do it through pyca/cryptography. It looks pretty straightforward and clean

from cryptography.x509 import load_pem_x509_certificate

cert = load_pem_x509_certificate(certificate_content)
certificate_serial_number = cert.serial_number
certificate_issuer_info = cert.issuer.rfc4514_string()
certificate_subject_info = cert.subject.rfc4514_string()

the result will print the whole line with issuer details

CN=name,O=Org, Inc.,L=Tucson,ST=Arizona,C=US
CN=Sectigo RSA Organization Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB

To access particular details about the certificate, you can use the cert.issuer object.

Answered By: Henry Harutyunyan

The problem with the currently accepted answer, recommending the use of the ssl module, is that it’ll work only if the certificate of interest can be successfully verified. If, for any reason, verification fails, like, for example, with expired or a self-signed certificate, we’ll get ssl.SSLCertVerificationError instead of the requested info. This is because the SSLContext‘s default verify_mode is CERT_REQUIRED.

We can change it:

context = ssl.create_default_context()
    
context.check_hostname = False
    
context.verify_mode = ssl.CERT_NONE

But then we’ll find out that secure socket’s getpeercert() method returns an empty dictionary. That’s a bummer!

We can workaround this by asking for the certificate in the binary form:

getpeercert(binary_form=True)

But now we have to convert it, and thus we need a third party cryptography module:

from cryptography.x509 import load_der_x509_certificate

# create SSLContext and wrap the socket as ssock here

certificate_content = ssock.getpeercert(binary_form=True)

cert = load_der_x509_certificate(certificate_content) 
certificate_subject_info = cert.subject.rfc4514_string()


This example is adapted from Henry’s answer with the DER loader being used instead of the PEM loader.

Now, I do understand why anyone would prefer built-in modules instead of additional dependencies, but, since a third party module is needed anyway (by "anyway" I mean if we want to handle corner cases), I believe an easier choice for this task would be to use pyOpenSSL:

import socket
from OpenSSL import SSL

context = SSL.Context(SSL.TLS_CLIENT_METHOD)
    
conn = SSL.Connection(context, socket.socket())
 
conn.connect(address)
print(conn.get_peer_certificate().get_subject())
    
# or: print(conn.get_peer_certificate().get_subject().get_components())
# or: print(conn.get_peer_certificate().to_cryptography().subject.rfc4514_string())

pyOpenSSL uses cryptography module under the hood and provides convenient methods to access certificate’s properties.

Answered By: wombatonfire