7

I'm writing a script that connects to a bunch of URLs over HTTPS, downloads their SSL certificate and extracts the CN. Everything works except when I stumble on a site with an invalid SSL certificate. I absolutely do not care if the certificate is valid or not. I just want the CN but Python stubbornly refuses to extract the certificate information if the certificate is not validated. Is there any way to get around this profoundly stupid behavior? Oh, I'm using the built-in socket and ssl libraries only. I don't want to use third-party libraries like M2Crypto or pyOpenSSL because I'm trying to keep the script as portable as possible.

Here's the relevant code:

    file = open("list.txt", "r")
    for x in file:
    server = socket.getaddrinfo(x.rstrip(), "443")[0][4][0]
    sslsocket = socket.socket()
    sslsocket.connect((server, 443))
    sslsocket = ssl.wrap_socket(sslsocket, cert_reqs=ssl.CERT_REQUIRED, ca_certs="cacerts.txt")
    certificate = sslsocket.getpeercert()`
1
  • If you don't want it secure why are you using HTTPS?
    – user207421
    Commented Dec 27, 2023 at 10:13

3 Answers 3

6

The ssl.get_server_certificate can do it:

import ssl
ssl.get_server_certificate(("www.sefaz.ce.gov.br",443)) 

I think function doc string is more clear than python doc site:

"""Retrieve the certificate from the server at the specified address,
   and return it as a PEM-encoded string.
   If 'ca_certs' is specified, validate the server cert against it.
   If 'ssl_version' is specified, use it in the connection attempt."""

So you can extract common name from binary DER certificate searching for common name object identifier:

def get_commonname(host,port=443):
    oid='\x06\x03U\x04\x03' # Object Identifier 2.5.4.3 (COMMON NAME)
    pem=ssl.get_server_certificate((host,port))
    der=ssl.PEM_cert_to_DER_cert(pem)
    i=der.find(oid) # find first common name (certificate authority)
    if i!=-1:
        i=der.find(oid,i+1) # skip and find second common name
        if i!=-1:
            begin=i+len(oid)+2
            end=begin+ord(der[begin-1])
            return der[begin:end]
    return None
4
  • This works. Thank you. There's a typo in your code len(id) should be len(oid), but that was easy enough to figure out. What does \x03U mean? Specifically the U. Commented Aug 18, 2012 at 20:05
  • Ok, I answered my own question. It's a literal U. Why didn't you just do \x55 to keep things consistent? Commented Aug 18, 2012 at 20:23
  • You're right about the typo, its fixed now, thanks. I knew that DER format uses signatures to identify objects and I did a simple copy and paste, so that's the reason to have a U instead 0x55.
    – olivecoder
    Commented Aug 19, 2012 at 4:45
  • FYI, ssl.get_server_certificate() uses SSLSocket.getpeercert(True) behind the scenes. docs.python.org/3/library/ssl.html#ssl.SSLSocket.getpeercert "If the binary_form parameter is True, and a certificate was provided, this method returns the DER-encoded form of the entire certificate as a sequence of bytes, or None if the peer did not provide a certificate." Commented Nov 23, 2015 at 17:51
2

Ok. I cleaned up olivecoder's code to solve the problem that it assumes there will always be three CNs in the certificate chain (root, intermediate, server) and I condensed it. This is the final code I will be using.

cert = ssl.get_server_certificate(("www.google.com", 443)) #Retrieve SSL server certificate
cert = ssl.PEM_cert_to_DER_cert(cert) #Convert certificate to DER format
begin = cert.rfind('\x06\x03\x55\x04\x03') + 7 #Find the last occurence of this byte string indicating the CN, add 7 bytes to startpoint to account for length of byte string and padding
end = begin + ord(cert[begin - 1]) #Set endpoint to startpoint + the length of the CN
print cert[begin:end] #Retrieve the CN from the DER encoded certificate
0

This thread has some solutions that don't work with python 3 (because bytes/str change), so I kept looking and found a solution that requires another library but the output i simple and doesn't require the usage of requests library.

import ssl
from OpenSSL import crypto
port = 443
cert_pem = ssl.get_server_certificate(('www.google.com', port))  # Retrieve SSL server certificate
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
subject = cert.get_subject()
issued_to = subject.CN  # the Common Name field
issuer = cert.get_issuer()
issued_by = issuer.CN

Not the answer you're looking for? Browse other questions tagged or ask your own question.