Software Development

Get Unverified SSL Certificate Expiry Date with Python

Getting verified SSL information with Python (3.x) is very easy. Code examples for it are also scattered everywhere in the internet. This is one of the example where only few lines required with only built-in libraries (socket and ssl):

import datetime
import socket
import ssl

def get_num_days_before_expired(hostname: str, port: str = '443') -> int:
    """
    Get number of days before an TLS/SSL of a domain expired
    """    
    context = ssl.create_default_context()
    with socket.create_connection((hostname, port)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            ssl_info = ssock.getpeercert()
            expiry_date = datetime.datetime.strptime(ssl_info['notAfter'], '%b %d %H:%M:%S %Y %Z')
            delta = expiry_date - datetime.datetime.utcnow()
            print(f'{hostname} expires in {delta.days} day(s)')
            return delta.days


if __name__ == '__main__':
    get_num_days_before_expired('google.com')

The getpeercert() function returns a map that contains a lot of information from the SSL certificate, including the notAfter which represents the expiry date. For most cases, this code would be sufficient.

But in my case, I need to get information from a domain that used SSL certficate that I generated manually using Let’s Encrypt certbot and upload it to a shared hosting. This is because the shared hosting package doesn’t include feature to automatically generate Let’s Encrypt certificates and renew them. For some reason the code above will throw exception CERTIFICATE_VERIFY_FAILED when I run against that domain.

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)

From various solutions offered by the internet, using OpenSSL (pip install pyOpenSSL) package initially seemed suitable for me as it doesn’t throw any error and return notAfter information. This is one of the code I got from Stackoverflow:

import datetime
import socket
import ssl
import OpenSSL

def get_num_days_before_expired(hostname: str, port: str = '443') -> int:
    """
    Get number of days before an TLS/SSL of a domain expired
    """    
    cert = ssl.get_server_certificate((hostname, int(port)))
    x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
    expiry_date = datetime.datetime.strptime(x509.get_notAfter().decode('utf-8'), '%Y%m%d%H%M%S%z')
    delta = expiry_date - datetime.datetime.now(datetime.timezone.utc)
    print(f'{hostname} expires in {delta.days} day(s)')
    return delta.days

But for some reason, this code doesn’t return the same notAfter information with the one we see using Chrome tools for my domain. Apparently, it’s due to the behaviour of ssl.get_server_certificate() as described in this Stackoverflow answer:

While not documented as such get_server_certificate treats the given (host,port) only as the target for connection but does not set host as the server_name in the TLS SNI extension like browsers would do

https://stackoverflow.com/a/52867893/1862500

So finally, I decided to just mash up those two solutions above ?

from urllib.request import ssl, socket
from datetime import datetime, timezone
import OpenSSL

def get_num_days_before_expired(hostname: str, port: str = '443') -> int:
    """
    Get number of days before an TLS/SSL of a domain expired
    """
    context = ssl.SSLContext()
    with socket.create_connection((hostname, port)) as sock:
        with context.wrap_socket(sock, server_hostname = hostname) as ssock:
            certificate = ssock.getpeercert(True)
            cert = ssl.DER_cert_to_PEM_cert(certificate)
            x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
            cert_expires = datetime.strptime(x509.get_notAfter().decode('utf-8'), '%Y%m%d%H%M%S%z')
            num_days = (cert_expires - datetime.now(timezone.utc)).days
            print(f'{hostname} expires in {num_days} day(s)')
            return num_days

if __name__ == '__main__':
    get_num_days_before_expired('ssis.sionministry.org')

Using this code, I’m able to retrieve correct expiry information from my unverified SSL certificate. So I can create a scheduled script to notify me when the expired date is close ?

I couldn’t find good documentation for the x509 object, fortunately we can still check the source code to know how to fetch other information. So we can extend the script to fetch information about the certificate and the domain.

Hopefully this can also help someone out there. Cheers! ?

Image by Mudassar Iqbal from Pixabay
5 2 votes
Article Rating
yohanes.gultom@gmail.com

View Comments

  • Indeed, If you are using WP Engine for hosting your WordPress website, then you can easily change the current PHP version from there. Eiliana.com, is a freelancing website where professional freelancers can easily find next-generation projects, sign up today.

  • The first example was everything I was looking for and more. Thanks for posting. It works with letsencrypt for me.

Share
Published by
yohanes.gultom@gmail.com
Tags: python

Recent Posts

Spring Data Couchbase 4 Multibuckets in Spring Boot 2

By default, Spring Data Couchbase implements single-bucket configuration. In this default implementation, all POJO (Plain…

3 years ago

Firebase Auth Emulator with Python

Last year, Google released Firebase Auth Emulator as a new component in Firebase Emulator. In…

3 years ago

Google OIDC token generation/validation

One of the authentication protocol that is supported by most of Google Cloud services is…

4 years ago

Fast geolocation query with PostGIS

If you need to to add a spatial information querying in your application, PostGIS is…

4 years ago

Auto speech-to-text (Indonesian) with AWS Transcribe and Python

Amazon Web Service Transcribe provides API to automatically convert an audio speech file (mp3/wav) into…

4 years ago

Splitting video with ffmpeg and Python

I had a project to build a simple website that split uploaded video into parts…

4 years ago