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