Python Notes: server cert check

  1. The information presented here is intended for educational use.
  2. The information presented here is provided free of charge, as-is, with no warranty of any kind.
  3. Edit: 2025-10-24

server_cert_check

#!/bin/python3.9
'''
=============================================================================
title  : /var/www/cgi-bin/server_cert_check_100.py
author : Neil Rieck
created: 2025-03-05
notes  :
1) this is a quick hack which should be good enough for Bell-ATS use
2) This program can be run interatively or from cron
3) if we were a bit larger then I would read URL_LIST from a database
4) ping fails should be logged in a database (limit emails to one per day?)
ver who when   what
--- --- ------ -----------------------------------------------------------
100 NSR 250305 derived from bell_ats_server_health_100.py
    NSR 250306 started adding code to check certs
    NSR 241024 changes for public use at my personal website
=============================================================================
'''
import platform
from time import sleep
import smtplib
import datetime
import sys
import email.utils  # needed for rfc822 support
import requests
# 1) the next line fixed our python3.6 environment
# 2) it also fixed our python3.9 environment after I reinstalled python3.9
# 3) the next line is required when fetching from OpenVMS systems
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'ALL:@SECLEVEL=1'
#
# ===========================================================================
#
#   constants
#
PGM = "server_cert_check"
VER = "100"
#
#   note: URL_LIST4 is all that is really required after I retire next month
#
URL_LIST4 = [               # 4 plus one alias
    "ats.on.bell.ca",       # alias
    "kawc96.on.bell.ca",    # OpenVMS prod (KAWC90)
    "kawc09.on.bell.ca",    # OpenVMS dvlp (KAWC09)
    "kawc0f.on.bell.ca",    # Linux prod
    "kawc4n.on.bell.ca"     # Linux dvlp
]
URL_LIST1 = [               # 1 address (program development)
    "neilrieck.net"
]
#
#   globals
#
this_host = ""
DEBUG = 2
#
# ===========================================================================
#
#   code area begins
#
def test_cert(cert):
    state = 0                                       # init
    url = f"https://{cert}"
    if DEBUG >= 1:
        print(f"-i-debug: {DEBUG}")
        print(f"-i-url: {url}")
    try:
        # CAVEATS:
        # 1) If you connect with "verify=False" then the certificate is not loaded for testing.
        #    So at this point, I do not know if an expired certificate will ever be seen.
        # 2) If the url fails with requests.get but works from your browser, then the web server's
        #    chain file (which contains the root and intermediate certs) must be updated.
        #    Browsers maintain a cache of known root servers; this subsystem does not)
        with requests.get(url, stream=True, timeout=(5, 4)) as response:
            certificate_info = response.raw.connection.sock.getpeercert()
        state = 1                                   # fetch was successful
        if type(certificate_info) != dict:
            certificate_info = dict(certificate_info)
        if DEBUG >= 2:
            print("cert: ", certificate_info)
            print("="*60)
            subject = dict(x[0] for x in certificate_info['subject'])
            print("commonName:", subject['commonName'])
            issuer = dict(x[0] for x in certificate_info['issuer'])
            print("issuer:", issuer['commonName'])
        # ----------------------------------------
        expiry = certificate_info['notAfter']
        print("-i-cert expiry:", expiry)            # rfc822 datetime stamp
        tup = email.utils.parsedate_tz(expiry)      #
        utc = email.utils.mktime_tz(tup)            # utc time stamp (integer)
        dt1 = datetime.datetime.fromtimestamp(utc)  #
        dt0 = datetime.datetime.now()               #
        # ----------------------------------------
        dt9 = dt1 - dt0                             #
        days = dt9.days                             #
        if days > 30:
            msg = f"-i-certificate '{cert}' is okay"
        elif days > 0:
            msg = f"-w-certificate '{cert}' will expire in {days} days"
        else:
            msg = f"-e-certificate '{cert}' is expired"
    except Exception as e:                          #
        state = 2                                   # some kind of error (connect, or verify)
        msg = f"-e-error: {e} while fetching certificate '{cert}'"
    return state, msg                               #

#
#   the work horse
#
def test_url_list(this_host=""):
    total = fail = 0
    for cert in URL_LIST1:
        for retry in range(1, 5):                   #
            if retry < 4:                           #
                print(f'-i-testing cert: {cert} try: {retry}')
                state, msg = test_cert(cert)        #
                if DEBUG >= 1:
                    print(f"-i-debug-state: {state} msg: {msg}")
                if state == 1:                      # we made a connection...
                    break                           # ...so exit from here
                else:
                    print(f"-w-state: {state} so falling through")
            else:                                   # oops, no more counts remaining
                msg = f"-e-could not connect/verify '{cert}' after {retry} tries"
                print(msg)                          #
                break                               #
            print(f"-w-oops, something went wrong on try: {retry}")
            sleep(1)                                #
        # ------------------------------------------
        if msg[0:3] == "-i-":                       #
            print(f"-i-resp: {msg} (pass)")
        else:
            print(f"-e-resp: {msg} (fail)")
            stamp = datetime.datetime.now().strftime("%Y%m%d.%H%M%S")
            msg = (
                f"reporting host: {this_host}\n"
                f"program: {PGM}\n"
                f"time: {stamp}\n"
                f"message: {msg}"
            )
            mail_option = 1                         # 0=none; 1=developer; 2=distribution list; 3=both
            if (mail_option & 1) == 1:
                send_mail(msg, cert, "n.rieck@bell.net")
            if (mail_option & 2) == 2:
                send_mail(msg, cert, "ats_adm_list")
            fail += 1
        total += 1
        sleep(2)
    print("\nreport card:")
    print(f" total tests: {total}")
    print(f" fails      : {fail}")
    print(f" passes     : {total - fail}")
    print("="*40)
    if fail != 0:
        return(1)
    return(0)
#
#   send mail
#
def send_mail(msg="no-message", url="", dst="neil.rieck@bell.ca"):
    global this_host
    if url == "":
        details = ""
    else:
        details = f" for: {url}"
    try:
        print(f"-i-mailing to: {dst}")
        sender = f"root@{this_host}"
        receivers = dst
        message = (
            f"From: {sender}\n"
            f"To: {receivers}\n"
            f"Subject: certificate test failed {details}\n\n"
            f"{msg}")
        print(message)
        smtpObj = smtplib.SMTP('localhost')
        smtpObj.sendmail(sender, receivers, message)
    except Exception as e:
        print(f"-e-error: {e}")

#
#   that grand poobah
#
def main():
    global this_host
    print(f"\n{PGM}-{VER}")
    print("="*40)
    this_host = platform.node()
    stamp = datetime.datetime.now().strftime("%Y%m%d.%H%M%S")
    print(f"-i-running on host: {this_host} at: {stamp}")
    rc = test_url_list(this_host)
    print("adios")
    sys.exit(rc)
#
#   catch-all
#
if __name__ == "__main__":
    main()
#

Links


 Back to Home
 Neil Rieck
 Waterloo, Ontario, Canada.