Changeset - bb749f282c4e
[Not reviewed]
0 2 3
x - 15 months ago 2024-01-06 00:42:18
xbr@c3l.lu
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.
5 files changed with 133 insertions and 1 deletions:
0 comments (0 inline, 0 general)
.gitignore
Show inline comments
 
# MacOS directories
 
.DS_Store
 
\ No newline at end of file
 
.DS_Store
 

	
 
# Ignore virutal environment directory
 
backend/venv
 
\ No newline at end of file
README.md
Show inline comments
 
@@ -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
backend/check_domains.py
Show inline comments
 
new file 100644
 
#!/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
backend/input.json
Show inline comments
 
new file 100644
 
{
 
    "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
backend/requirements.txt
Show inline comments
 
new file 100644
 
rich==13.*
 
cryptography==41.*
 
\ No newline at end of file
0 comments (0 inline, 0 general)