"""Scene 2, going deeper: today's stamp vs. a post-quantum stamp, for real. Builds the same tiny badge office twice with openssl (3.5 or later): 1. Today's stamp: ECDSA P-256 (what this site's own certificate uses) 2. Post-quantum stamp: ML-DSA-65 (NIST FIPS 204) For each: a root that stamps itself, a badge for robo.school.example, a chain check, and the sizes that change. Then an inventory: which badges still use the old stamp. Everything runs on this laptop; keys stay in lab/out/ (git-ignored). python3 lab/quantum_stamp.py # print transcript python3 lab/quantum_stamp.py --save # also write replays/quantum-stamp.txt """ import shutil import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OUT = ROOT / "lab" / "out" / "quantum-stamp" REPLAY = ROOT / "replays" / "quantum-stamp.txt" OPENSSL = shutil.which("openssl") or "openssl" STAMPS = { "today": ["-newkey", "ec", "-pkeyopt", "ec_paramgen_curve:P-256"], "post-quantum": ["-newkey", "ML-DSA-65"], } lines = [] def say(text=""): print(text) lines.append(text) def ossl(*args): r = subprocess.run([OPENSSL, *args], cwd=OUT, capture_output=True, text=True) if r.returncode != 0: sys.exit(r.stderr.strip()) return r.stdout def build(name, keyargs): ossl("req", "-x509", *keyargs, "-nodes", "-keyout", f"{name}-root.key", "-out", f"{name}-root.crt", "-days", "365", "-subj", f"/CN=Maple Hill {name} stamp", "-addext", "basicConstraints=critical,CA:TRUE", "-addext", "keyUsage=critical,keyCertSign") ossl("req", *keyargs, "-nodes", "-keyout", f"{name}-robo.key", "-out", f"{name}-robo.csr", "-subj", "/CN=robo.school.example") ossl("x509", "-req", "-in", f"{name}-robo.csr", "-CA", f"{name}-root.crt", "-CAkey", f"{name}-root.key", "-CAcreateserial", "-days", "1", "-out", f"{name}-robo.crt") verified = ossl("verify", "-CAfile", f"{name}-root.crt", f"{name}-robo.crt").strip() text = ossl("x509", "-in", f"{name}-robo.crt", "-noout", "-text") alg = next(l.split(":", 1)[1].strip() for l in text.splitlines() if "Signature Algorithm" in l) der_len = len(subprocess.run([OPENSSL, "x509", "-in", f"{name}-robo.crt", "-outform", "DER"], cwd=OUT, capture_output=True).stdout) return alg, verified, der_len def main(): if OUT.exists(): shutil.rmtree(OUT) OUT.mkdir(parents=True) ver = ossl("version").strip() say(f"# {ver.split(' (')[0]}: build the same badge with two kinds of stamp") results = {} for name, keyargs in STAMPS.items(): alg, verified, size = build(name, keyargs) results[name] = size say(f"$ openssl req -x509 -newkey {keyargs[1] if name == 'post-quantum' else 'ec (P-256)'} ... # {name} stamp") say(f" signature algorithm: {alg}") say(f" chain check: {verified}") say(f" badge size: {size:,} bytes") say() say(f"# The post-quantum badge is about {results['post-quantum'] / results['today']:.0f}x larger.") say(" Bigger keys and signatures mean more bytes in every handshake: one reason to plan the switch early.") say() say("# Inventory: which badges still carry today's stamp?") for name in STAMPS: alg = next(l.split(":", 1)[1].strip() for l in ossl("x509", "-in", f"{name}-robo.crt", "-noout", "-text").splitlines() if "Signature Algorithm" in l) say(f" robo.school.example ({name:<12}) {alg:<22} {'-> swap stamp' if 'ecdsa' in alg.lower() else 'ok'}") if "--save" in sys.argv: REPLAY.write_text("\n".join(lines) + "\n") print(f"\nSaved {REPLAY.relative_to(ROOT)}") if __name__ == "__main__": main()