1

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.

gloopy
  • 103
  • 2
  • 9
  • You will probably instead need to use the `PyOpenSSL` library where you can inject various callbacks at various steps of the TLS handshake and hence get access to the certificates exchanged. – Patrick Mevzek Sep 04 '18 at 23:17

2 Answers2

4

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'
cert = ssl.get_server_certificate((myhostname, 443))
[..]
gloopy
  • 103
  • 2
  • 9
2

Since you said this is a once off problem solving script and your missing many components to do this in python natively. Would a shell script be acceptable (or using python to execute and read a shell/system command)? If so and curl is available you could try using it as such:

    curl -vvI https://www.google.com

* About to connect() to www.google.com port 443 (#0)
*   Trying 172.217.197.103...
* Connected to www.google.com (172.217.197.103) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* SSL connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate:
*   subject: CN=www.google.com,O=Google LLC,L=Mountain View,ST=California,C=US
*   start date: Aug 14 07:44:35 2018 GMT
*   expire date: Oct 23 07:38:00 2018 GMT
*   common name: www.google.com
*   issuer: CN=Google Internet Authority G3,O=Google Trust Services,C=US
> HEAD / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.google.com
> Accept: */*

Without openssl to run s_client to save/parse the certificate this is the simplest thing that comes to mind to quickly be able to grab the Issuer and parse it in whatever method you prefer.