From dbe255c31e2fc1ded66056199f39256f791fb98d Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sat, 13 Jun 2026 18:14:22 +0200 Subject: [PATCH] fix(toolbox): /ca/fingerprint surfaces the R3 CA on the tunnel (closes #562) It always read the R1/R2 captive CA, so R3 users verifying their installed cert saw the wrong fingerprint. Now ?ca=wg|default|auto; auto returns the R3 CA (/etc/secubox/toolbox/ca-wg/mitmproxy-ca-cert.pem) when the request arrives over the R3 tunnel (X-R3-Peer / 10.99.1.x), else R1/R2; response gains a 'ca' field. Landing cert-probe fetches ?ca=wg. secubox-toolbox 2.6.21. Co-Authored-By: Claude Opus 4.8 --- packages/secubox-toolbox/conf/landing.html.j2 | 2 +- packages/secubox-toolbox/debian/changelog | 14 ++++ .../secubox-toolbox/secubox_toolbox/api.py | 67 ++++++++++++------- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/packages/secubox-toolbox/conf/landing.html.j2 b/packages/secubox-toolbox/conf/landing.html.j2 index 665c3463..f6c5a242 100644 --- a/packages/secubox-toolbox/conf/landing.html.j2 +++ b/packages/secubox-toolbox/conf/landing.html.j2 @@ -423,7 +423,7 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard var fp = document.getElementById('cert-fp-r3'); if (!btn) return; - fetch('/ca/fingerprint').then(function(r){return r.json();}).then(function(d){ + fetch('/ca/fingerprint?ca=wg').then(function(r){return r.json();}).then(function(d){ fp.textContent = d.sha1 || d.sha256 || '?'; }).catch(function(){fp.textContent='?';}); diff --git a/packages/secubox-toolbox/debian/changelog b/packages/secubox-toolbox/debian/changelog index 97a80e94..175204b0 100644 --- a/packages/secubox-toolbox/debian/changelog +++ b/packages/secubox-toolbox/debian/changelog @@ -1,3 +1,17 @@ +secubox-toolbox (2.6.21-1~bookworm1) bookworm; urgency=medium + + * fix(ca): /ca/fingerprint surfaces the R3 CA on the tunnel (#562). + - Was always reading the R1/R2 captive CA (/etc/secubox/toolbox/ca/ + ca.pem, "Gondwana ToolBoX CA") — so R3 users verifying their + installed cert saw the WRONG fingerprint. The real R3 CA is + /etc/secubox/toolbox/ca-wg/mitmproxy-ca-cert.pem ("R3 CA"). + - /ca/fingerprint now takes ?ca=wg|default|auto ; auto returns the + R3 CA when the request arrives over the R3 tunnel (X-R3-Peer / + 10.99.1.x), else R1/R2. Response gains a "ca" field. Landing + cert-probe fetches ?ca=wg (it is the R3 probe). + + -- Gerald KERMA Sat, 13 Jun 2026 16:45:00 +0200 + secubox-toolbox (2.6.20-1~bookworm1) bookworm; urgency=medium * Protective mode — tracker alerting + active spoofer (#560), DEFAULT OFF. diff --git a/packages/secubox-toolbox/secubox_toolbox/api.py b/packages/secubox-toolbox/secubox_toolbox/api.py index 470a38ed..c2cd93cd 100644 --- a/packages/secubox-toolbox/secubox_toolbox/api.py +++ b/packages/secubox-toolbox/secubox_toolbox/api.py @@ -944,31 +944,52 @@ async def admin_filter_regex() -> dict: return {"regex": regex, "count": len(entries)} -@router.get("/ca/fingerprint") -async def ca_fingerprint() -> dict: - """Expose CA SHA1/SHA256 fingerprints so user can verify against their - iPhone Settings → Cert Trust UI. CSPN R2 transparency requirement.""" +def _ca_fp(ca_pem) -> dict: + """SHA1/SHA256/subject of a CA pem via openssl. '?' on any failure.""" import subprocess + out = {"sha1": "?", "sha256": "?", "subject": "?"} + if not ca_pem.exists(): + return out + try: + for key, flag in (("sha1", "-sha1"), ("sha256", "-sha256")): + out[key] = subprocess.run( + ["openssl", "x509", "-in", str(ca_pem), "-noout", "-fingerprint", flag], + capture_output=True, text=True, timeout=2, check=False, + ).stdout.split("=", 1)[-1].strip() + out["subject"] = subprocess.run( + ["openssl", "x509", "-in", str(ca_pem), "-noout", "-subject"], + capture_output=True, text=True, timeout=2, check=False, + ).stdout.split("=", 1)[-1].strip() + except Exception: + pass + return out + + +@router.get("/ca/fingerprint") +async def ca_fingerprint(request: Request, ca: str = "auto") -> dict: + """Expose CA SHA1/SHA256 fingerprints so the user can verify against + their device's Cert Trust UI. CSPN R2/R3 transparency requirement. + + ?ca=wg → the R3 WireGuard CA ; ?ca=default → the R1/R2 captive CA ; + auto (default) → R3 when the request arrives over the R3 tunnel + (X-R3-Peer / 10.99.1.x), else R1/R2. Fixes the landing cert-probe + showing the wrong (R1/R2) fingerprint to R3 users. + """ from pathlib import Path - ca_pem = Path("/etc/secubox/toolbox/ca/ca.pem") - sha1 = sha256 = subject = "?" - if ca_pem.exists(): - try: - sha1 = subprocess.run( - ["openssl", "x509", "-in", str(ca_pem), "-noout", "-fingerprint", "-sha1"], - capture_output=True, text=True, timeout=2, check=False, - ).stdout.split("=", 1)[-1].strip() - sha256 = subprocess.run( - ["openssl", "x509", "-in", str(ca_pem), "-noout", "-fingerprint", "-sha256"], - capture_output=True, text=True, timeout=2, check=False, - ).stdout.split("=", 1)[-1].strip() - subject = subprocess.run( - ["openssl", "x509", "-in", str(ca_pem), "-noout", "-subject"], - capture_output=True, text=True, timeout=2, check=False, - ).stdout.split("=", 1)[-1].strip() - except Exception: - pass - return {"sha1": sha1, "sha256": sha256, "subject": subject} + r3 = Path("/etc/secubox/toolbox/ca-wg/mitmproxy-ca-cert.pem") + default = Path("/etc/secubox/toolbox/ca/ca.pem") + want_wg = False + if ca == "wg": + want_wg = True + elif ca == "auto": + peer = request.headers.get("x-r3-peer", "") or "" + xff = request.headers.get("x-forwarded-for", "") or "" + if peer.startswith("10.99.1.") or "10.99.1." in xff: + want_wg = True + path = r3 if (want_wg and r3.exists()) else default + out = _ca_fp(path) + out["ca"] = "r3" if path == r3 else "default" + return out # Phase 3.x (#497) : 3 QR code endpoints — splash, cert install, webclip