Changeset - b3456703e541
[Not reviewed]
0 4 0
x - 11 months ago 2024-05-09 18:17:36
xbr@c3l.lu
feat: replace web with more generic SSL verificator and handlers
4 files changed with 66 insertions and 59 deletions:
0 comments (0 inline, 0 general)
backend/check_domains.py
Show inline comments
 
#!/usr/bin/env python3
 
import json
 
import ssl
 
import socket
 
import os
 
from rich.console import Console
 
from cryptography import x509
 

	
 
import web
 
from web import SSLVerificator
 
from mail import MailVerificator
 
import tls_utils
 

	
 
if __name__ == "__main__":
 
    console = Console()
 
@@ -22,43 +19,10 @@ if __name__ == "__main__":
 

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

	
 
    ssl = SSLVerificator(context)
 
    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.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 = tls_utils.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
 
        result = ssl.connect(web_domain, 443)
 
        result.print(console)
 

	
 
    mail = MailVerificator(context)
 
    for smtp_entry in input["domains"]["smtp"]:
backend/mail.py
Show inline comments
 
@@ -14,7 +14,7 @@ class MailHandler(ABC):
 
        self.port = port
 
        self.context = context
 
    
 
    def connect(self, verification: bool) -> tuple[bool, int]:
 
    def connect(self, verification: bool) -> int:
 
        connection = self.protocol_init(self.host, self.port)
 
        if verification:
 
            connection.starttls(**self.protocol_starttls_args())
 
@@ -22,7 +22,7 @@ class MailHandler(ABC):
 
            connection.starttls()
 
        cert = connection.sock.getpeercert()
 
        self.protocol_close(connection)
 
        return tls_utils.get_validity_days(cert)
 
        return tls_utils.get_validity_days(cert)[1]
 

	
 
    @abstractmethod
 
    def protocol_init(self, host, port):
 
@@ -66,11 +66,11 @@ class MailVerificator:
 
    def connect(self, domain: str, port: int, protocol: str) -> TLSDetails:
 
        mail = MailHandler.create_handler(protocol)(domain, port, self.context)
 
        try:
 
            expiry = mail.connect(True)[1]
 
            expiry = mail.connect(True)
 
            return TLSDetails(domain_name=domain, expires_in_days=expiry)
 
        except ssl.SSLCertVerificationError as e:
 
            if (e.verify_code == 10):
 
                expiry = mail.connect(False)[1]
 
            if (e.verify_code == tls_utils.EXPIRED_VERIFY_CODE):
 
                expiry = mail.connect(False)
 
                return TLSDetails(domain_name=domain, expires_in_days=expiry)
 
            else:
 
                error = "failed verification:", e.verify_message + "."
backend/tls_utils.py
Show inline comments
 
@@ -3,18 +3,27 @@ from rich.console import Console
 
import datetime
 
import math
 

	
 
EXPIRED = 10
 
REVOKED = 23
 
SELF_SIGNED = 18
 
ROOT_NOT_TRUSTED = 19
 

	
 
class TLSDetails:
 
    domain_name = None
 
    expires_in_days = None
 
    error_message = None
 
    connection_error = False
 

	
 
    def __init__(self, domain_name : str = None, expires_in_days : str = None, error_message : str = None):
 
    def __init__(self, domain_name : str = None, expires_in_days : str = None, error_message : str = None, connection_error : bool = False):
 
        self.domain_name = domain_name
 
        self.expires_in_days = expires_in_days
 
        self.error_message = error_message
 
        self.connection_error = connection_error
 
    
 
    def print(self, console: Console):
 
        if self.error_message != None:
 
        if self.connection_error:
 
            console.log("[orange bold underline]" + self.domain_name, self.error_message, style="orange")
 
        elif self.error_message != None:
 
            console.log("[red bold underline]" + self.domain_name, self.error_message, style="red")
 
        elif self.expires_in_days < 0:
 
            console.log("[red bold underline]" + self.domain_name, "expired", abs(self.expires_in_days), "days ago.", style="red")
backend/web.py
Show inline comments
 
@@ -2,17 +2,51 @@
 
import ssl
 
from rich.console import Console
 
from cryptography import x509
 
import socket
 

	
 
import tls_utils as tls_utils
 
import tls_utils
 
from tls_utils import TLSDetails
 

	
 
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 tls_utils.get_expiry_timestamps(not_after)
 
\ No newline at end of file
 
class SSLHandler:
 
    def __init__(self, host: str, port: int, context: ssl.SSLContext):
 
        self.host = host
 
        self.port = port
 
        self.context = context
 

	
 
    def connect(self, verification: bool) -> int:
 
        if verification:
 
            with self.context.wrap_socket(socket.socket(), server_hostname=self.host) as s:
 
                s.connect((self.host, self.port))
 
                cert = s.getpeercert()
 
                return tls_utils.get_validity_days(cert)[1]
 
        else:
 
            pem_cert = ssl.get_server_certificate((self.host, self.port), timeout=5)
 
            cert = x509.load_pem_x509_certificate(pem_cert.encode())
 
            not_after = cert.not_valid_after.timestamp()
 
            return tls_utils.get_expiry_timestamps(not_after)[1]
 

	
 
class SSLVerificator:
 
    def __init__(self, context: ssl.SSLContext):
 
        self.context = context
 

	
 
    def connect(self, domain: str, port: int) -> TLSDetails:
 
        handler = SSLHandler(domain, port, self.context)
 
        try:
 
            expiry = handler.connect(True)
 
            return TLSDetails(domain_name=domain, expires_in_days=expiry)
 
        except ssl.SSLCertVerificationError as e:
 
            if e.verify_code == tls_utils.EXPIRED:
 
                expiry = handler.connect(False)
 
                return TLSDetails(domain_name=domain, expires_in_days=expiry)
 
            elif e.verify_code == tls_utils.REVOKED:
 
                return TLSDetails(domain_name=domain, error_message="was revoked.")
 
            elif e.verify_code == tls_utils.SELF_SIGNED:
 
                return TLSDetails(domain_name=domain, error_message="is self-signed.")
 
            elif e.verify_code == tls_utils.ROOT_NOT_TRUSTED:
 
                return TLSDetails(domain_name=domain, error_message="invalid: root not trusted.")
 
            else:
 
                return TLSDetails(domain_name=domain, error_message="failed verification: " + e.verify_message + ".")
 
        except ssl.SSLError as e:
 
            return TLSDetails(domain_name=domain, error_message="could not establish a secure connection: " + e.reason + ".")
 
        except Exception as e:
 
            return TLSDetails(domain_name=domain, error_message="could not connect: " + str(e) + ".")
 
\ No newline at end of file
0 comments (0 inline, 0 general)