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 function returns a map that contains a lot of information from the SSL certificate, including the getpeercert()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
https://stackoverflow.com/a/52867893/1862500get_server_certificatetreats the given(host,port)only as the target for connection but does not sethostas theserver_namein the TLS SNI extension like browsers would do
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



