Files @ 25cd67c03d81
Branch filter:

Location: C3L-NOC/tls-expiry-tracker/backend/check_domains.py - annotation

x
feat: add smtp and imap connections, with starttls
bb749f282c4e
bb749f282c4e
bb749f282c4e
25cd67c03d81
25cd67c03d81
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
bb749f282c4e
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
bb749f282c4e
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
25cd67c03d81
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
25cd67c03d81
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
25cd67c03d81
bb749f282c4e
bb749f282c4e
bb749f282c4e
bb749f282c4e
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
25cd67c03d81
#!/usr/bin/env python3
import json
import ssl
import smtplib
import imaplib
import socket
from rich.console import Console
from cryptography import x509
import datetime
import math

def get_expiry_timestamps(expiry_timestamp: int, now_timestamp: int = datetime.datetime.now().timestamp()) -> (bool, int):
    seconds_left = expiry_timestamp - now_timestamp
    days_left = math.floor(seconds_left / 86400)
    return (seconds_left >= 0,days_left)

def get_validity_days(cert) -> (bool, int):
    # Get expiry date
    notAfter = cert['notAfter']
    notAfter_date = datetime.datetime.strptime(notAfter, '%b %d %H:%M:%S %Y %Z')

    # datetime to UNIX time
    notAfter_timestamp = notAfter_date.timestamp()
    expiry = get_expiry_timestamps(notAfter_timestamp)
    return (expiry[0], abs(expiry[1]))

console = Console()

# Parse the input file
with open('input.json') as raw_data:
    input = json.load(raw_data)

console.log("[white]Checking web domains...")

context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)

def web_noconn_expiry_days(web_domain: str) -> int | None:
    try:
        pem_cert = ssl.get_server_certificate((web_domain, 443), timeout=5)
        cert = x509.load_pem_x509_certificate(pem_cert.encode())
    except Exception as e:
        console = Console()
        console.log("Could not grab server cert for", "[orange bold underline]"+web_domain, ":", e, style="orange")
        return None
    
    not_after = cert.not_valid_after.timestamp()
    return get_expiry_timestamps(not_after)

for web_domain in input["domains"]["web"]:
    # Initiate TLS connection
    with context.wrap_socket(socket.socket(), server_hostname=web_domain) as s:
        try:
            s.connect((web_domain, 443))
            cert = s.getpeercert()
        except ssl.SSLCertVerificationError as e:
            saved = e
            if e.verify_code == 10:
                expiry = web_noconn_expiry_days(web_domain)[1]
                if(expiry != None):
                    # TODO: add the TLS expiry stuff here
                    # possibly a list of domains that have expired
                    # if its already in here, dont add it again
                    console.log("[red bold underline]" + web_domain, "expired", abs(expiry), "days ago.", style="red")
            elif e.verify_code == 23:
                console.log("[red bold underline]" + web_domain, "was revoked.", style="red")
            elif e.verify_code == 18:
                console.log("[red bold underline]" + web_domain, "is self-signed.", style="red")
            elif e.verify_code == 19:
                console.log("[red bold underline]" + web_domain, "invalid: root not trusted.", style="red")
            else:
                console.log("[red bold underline]" + web_domain, "failed verification:", e.verify_message + ".", style="red")
            continue
        except ssl.SSLError as e:
            console.log("[orange bold underline]" + web_domain, "could not establish a secure connection:", e.reason, style="orange")
            continue
        except Exception as e:
            print(e)
            continue

    validity = get_validity_days(cert)[1]
    # Print expiry date
    console.log("[green bold underline]" + web_domain, "expires in", validity, "days", style="green")
    # TODO: remove known expired certs
    # If the cert was expired before, we know that it is now valid
    # -> remove it from the list of expirjuded certs

def __mail_connection(host, port, verification: bool, initializer, starttls_args, closer) -> (bool, int):
    connection = initializer(host, port)
    if verification:
        connection.starttls(**starttls_args)
    else:
        connection.starttls()
    cert = connection.sock.getpeercert()
    closer(connection)
    return get_validity_days(cert)

def __smtp_closing(connection):
    connection.quit()
    
def __smtp_connection(domain, port, verification: bool) -> (bool, int):
    initializer = smtplib.SMTP
    starttls_args = {"context": context}
    return __mail_connection(domain, port, verification, initializer, starttls_args, __smtp_closing)

def __imap_closing(connection):
    connection.logout()

def __imap_connection(domain, port, verification: bool) -> (bool, int):
    initializer = imaplib.IMAP4
    starttls_args = {"ssl_context": context}
    return __mail_connection(domain, port, verification, initializer, starttls_args, __imap_closing)

def __mail_connect(domain, port, protocol_func):
    try:
        expiry = protocol_func(domain, port, True)[1]
        console.log("[green bold underline]" + domain, "expires in", expiry, "days", style="green")
    except ssl.SSLCertVerificationError as e:
        if (e.verify_code == 10):
            expiry = protocol_func(domain, port, False)[1]
            console.log("[red bold underline]" + web_domain, "expired", abs(expiry), "days ago.", style="red")
        else:
            console.log("[red bold underline]" + domain, "failed verification:", e.verify_message + ".", style="red")

def smtp_connect(domain, port):
    __mail_connect(domain, port, __smtp_connection)

def imap_connect(domain, port):
    __mail_connect(domain, port, __imap_connection)

for smtp_entry in input["domains"]["smtp"]:
    smtp_connect(smtp_entry["host"], smtp_entry["port"])

for imap_entry in input["domains"]["imap"]:
    imap_connect(imap_entry["host"], imap_entry["port"])