Search code examples
pythonssl

Get certificate information when SSL: certificate_verify_failed


I need to read the issuer CN from a bunch of devices with self signed certificates. Some have an error such that the issuer CN is not unique when it is supposed to be and I wish to identify which ones have this condition. I found a code fragment here and modified it a little:

import ssl, socket

myhostname = 'some_host'
myctx = ssl.create_default_context()
myctx.check_hostname = False
myctx.verify_mode = ssl.CERT_NONE # ssl.CERT_OPTIONAL
s = myctx.wrap_socket(socket.socket(), server_hostname=myhostname)
s.connect((hostname, 443))
cert = s.getpeercert()

When verify_mode is CERT_NONE the connection is made but the certificate is discarded; when CERT_OPTIONAL the connection fails because verification fails.

How can I connect and read details from a bad certificate without using openssl?

This is a once off problem solving script; I just need to output some_host and issuer CN. Openssl is not available on this system.

EDIT 1 Following Patrick's PyOpenSSL suggestion (also not on this system) this post indicates that cert = s.getpeercert(binary_form=True) returns the certificate in DER format.

EDIT 2 duplicate or not duplicate? Ultimately I think yes, it is a duplicate but not of the nominated "duplicate-of" because I don't have Openssl on my system. I believe it's still useful. The working solution below uses information from several other posts with a couple of others on along the way. The neat bit is using ssl.get_server_certificate (thanks Steffen, no need for getpeercert; returns pem not der) and the ugly bit is storing the cert to file and then using an undocumented method to get details. It does not use getpeercert nor does it use openssl.

If there's a neater way to process the pem cert received to read details I'd love to know, but it's good enough for me as it is.


Solution

  • The following does the job but writes the certificate to file. Is it possible to decode the certificate directly and avoid saving it to a file?

    import ssl, socket
    
    myhostname = 'some_host'
    myctx = ssl.create_default_context()
    myctx.check_hostname = False
    myctx.verify_mode = ssl.CERT_NONE
    s = myctx.wrap_socket(socket.socket(), server_hostname=myhostname)
    s.connect((myhostname, 443))
    
    bcert = s.getpeercert(binary_form=True)
    cert = ssl.DER_cert_to_PEM_cert(bcert)
    # workaround, ssl._ssl._test_decode_cert method expects a filename
    f = open('mycert.pem','w')
    f.write(cert)
    f.close()
    cert_dict = ssl._ssl._test_decode_cert('mycert.pem')   # expects a filename?
    
    subject = dict(x[0] for x in cert_dict['subject'])
    issued_to = subject['commonName']
    issuer = dict(x[0] for x in cert_dict['issuer'])
    issued_by = issuer['commonName']
    print(issued_to)
    print(issued_by)
    

    EDIT more simply, but still writes to file

    import ssl
    
    myhostname = 'some_host'
    
    FILE = 'cert.pem'
    with open(FILE, 'w', encoding='utf8') as fp:
        fp.write(ssl.get_server_certificate((myhostname, 443)))
    
    cert_dict = ssl._ssl._test_decode_cert(FILE)