From bb749f282c4e0189af832afeb645ee8a8a09bdd8 2024-01-06 00:42:18 From: x Date: 2024-01-06 00:42:18 Subject: [PATCH] feat: add initial backend current backend only prints out the state of the certificates in stdout. it takes input domains from input.json, and checks only web ones. various fail states are recogzied. --- diff --git a/.gitignore b/.gitignore index 3b66ff282a782123c04ee976094c0f769a57c81b..296bbf08194fd3c98c4a7e7cf4d558c9124fd047 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ # MacOS directories -.DS_Store \ No newline at end of file +.DS_Store + +# Ignore virutal environment directory +backend/venv \ No newline at end of file diff --git a/README.md b/README.md index db6211faddfe671dddf046552092b05c9a077ba9..02a8b3ac2c7f29c0fb9c35d45af24b369c98df1f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,15 @@ How often does your infrastructure break because you forgot to automate TLS rene Find out by rolling out this tool with the domains and services! +## Guide: how to set up + +To set up the backend: + +- `cd backend` +- `python -m venv venv` +- `source venv/bin/activate` +- `pip install -r requirements.txt` + ## Plans (TODO) ### Backend diff --git a/backend/check_domains.py b/backend/check_domains.py new file mode 100644 index 0000000000000000000000000000000000000000..50963475a6b81c4d1c4b56a26c9d79ccd1f7fa94 --- /dev/null +++ b/backend/check_domains.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import json +import ssl +import socket +from rich.console import Console +from cryptography import x509 +import datetime +import math + +def get_expiry_days(expiry_timestamp: int, now_timestamp: int = datetime.datetime.now().timestamp()) -> int: + expiry_seconds = now_timestamp - expiry_timestamp + expiry_days = math.floor(expiry_seconds / 86400) + return expiry_days + +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_days(not_after) + + +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) + +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) + 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", 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 + + # Get expiry date + expiry = cert['notAfter'] + expiry = datetime.datetime.strptime(expiry, '%b %d %H:%M:%S %Y %Z') + + # datetime to UNIX time + expiry = expiry.timestamp() + validity = abs(get_expiry_days(expiry)) + + # 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 expired certs diff --git a/backend/input.json b/backend/input.json new file mode 100644 index 0000000000000000000000000000000000000000..a27f8596fefc0afa4b436a104625e3fd5719018a --- /dev/null +++ b/backend/input.json @@ -0,0 +1,37 @@ +{ + "domains": { + "web": [ + "expired.badssl.com", + "wrong.host.badssl.com", + "self-signed.badssl.com", + "untrusted-root.badssl.com", + "revoked.badssl.com", + "pinning-test.badssl.com", + "c3l.lu", + "www.c3l.lu", + "wiki.c3l.lu", + "social.c3l.lu", + "xmpp.c3l.lu", + "statutes.c3l.lu", + "spaceapi.c3l.lu", + "membership.c3l.lu", + "cloud.c3l.lu", + "fichiercentral.c3l.lu", + "tickets.c3l.lu", + "projects.c3l.lu", + "pad.c3l.lu", + "matrix.c3l.lu", + "payment.c3l.lu", + "tails.c3l.lu", + "rt.c3l.lu", + "devuan.c3l.lu", + "cpan.c3l.lu", + "lists.c3l.lu", + "freifunk.lu", + "www.freifunk.lu", + "api.freifunk.lu", + "firmware.freifunk.lu", + "map.freifunk.lu" + ] + } +} \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..abd3956dfc2fdf22970856cad8d9fe89ee7cdd04 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,2 @@ +rich==13.* +cryptography==41.* \ No newline at end of file