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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-06-13 18:14:22 +02:00
parent 52e51b2766
commit dbe255c31e
3 changed files with 59 additions and 24 deletions

View File

@ -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='?';});

View File

@ -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 <devel@cybermind.fr> 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.

View File

@ -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