Kenneth Buckler
Kenneth Buckler

Reputation: 31

Obtain Ceritificate Information from Invalid Certificate in Python

I'm working on a Python 2.7.13 (Win x64) script to verify SSL certificates, and alert for problems. However, I'm running into an issue where the script will return information only if the certificate is valid.

If the certificate is invalid, I receive a CERTIFICATE_VERIFY_FAILED SSL error. Normally I would simply use a try/catch when the error is raised and just alert that the cert is invalid, but the issue here is that the I need the actual date the certificate expired.

Per https://docs.python.org/2/library/ssl.html I tried to use conn._https_verify_certificates(enable=False) to disable certificate validation, but get an error that the attribute _https_verify_certificates doesn't exist.

Here is my code so far. I'm sure I'm missing something obvious. Surely Python can pull the SSL certificate without validating it, right?

import socket
import ssl

def ssl_expiry_datetime(hostname):
    ssl_date_fmt = r'%b %d %H:%M:%S %Y %Z'

    context = ssl.create_default_context()
    conn = context.wrap_socket(
        socket.socket(socket.AF_INET),
        server_hostname=hostname,
    )
    # 3 second timeout because Lambda has runtime limitations
    conn.settimeout(3.0)
    #conn._https_verify_certificates(enable=False)
    conn.connect((hostname, 443))
    ssl_info = conn.getpeercert()
    # parse the string from the certificate into a Python datetime object
    return ['notAfter']

myhost = 'www.google.com'

print ssl_expiry_datetime(myhost)

Many thanks!!!!

Upvotes: 3

Views: 1544

Answers (1)

superhedgy
superhedgy

Reputation: 111

After a lot of trial and error I have found that you can switch off the SSLcertificate hostname verification by using the check_hostname feature.

context.check_hostname = False

This will allow your program to connect on the web server if the target does not match with the Common Name (CN) on the SSL certificate. However, when the web server uses an invalid SSL certificate the connection will fail and throw a ConnetionError error. If the aim is to fetch all SSL certificate even the invalidate ones the following solution will only partially accommodate your needs.

Here is a proposed solution:

import socket, ssl

def ssl_expiry_datetime(hostname):
    ssl_date_fmt = r'%b %d %H:%M:%S %Y %Z'

    context = ssl.create_default_context()
    context.check_hostname = False
    conn = context.wrap_socket(
        socket.socket(socket.AF_INET),
        server_hostname=hostname,
    )
    # 3 second timeout because Lambda has runtime limitations
    conn.settimeout(3.0)
    #conn._https_verify_certificates(enable=False)
    conn.connect((hostname, 443))
    ssl_info = conn.getpeercert()
    # parse the string from the certificate into a Python datetime object
    return ['notAfter']

myhost = 'example.com'

print ssl_expiry_datetime(myhost)

Alternatively you can use the requests library which allows you to switch off verification completely.

References:

  1. 21.6. urllib.request — Extensible library for opening URLs - https://docs.python.org/3/library/urllib.request.html#urllib.request.urlretrieve
  2. 17.3.3. SSL Contexts - https://docs.python.org/2/library/ssl.html#ssl-contexts

Upvotes: 1

Related Questions