fix(secubox-toolbox): R3 CA — remove SAN (Android null bug) + /wg/ca.crt DER endpoint (ref #496)

User reported : 'toujours impossible d'installer le certificat null sur android'.

## Root cause

Previous CA had subjectAltName with arbitrary text containing spaces :
  subjectAltName = DNS:Gondwana ToolBoX R3 CA, DNS:cabine.gondwana.local

Android Chrome's certificate install dialog parses SAN. When SAN
contains spaces in a DNS name (technically invalid per RFC 5280),
Chrome's parser fails silently and renders 'émis par null'. User
can't install the cert because the dialog refuses → tells them to
go via Settings, which then ALSO chokes on the SAN.

## Fix

Regenerate CA without subjectAltName (root CAs don't need SAN
anyway — SAN is for end-entity certs). Minimal DN :
  C=FR, O=CyberMind Gondwana, CN=Gondwana ToolBoX R3 CA

Added basicConstraints pathlen:0 (cleaner — explicit no
intermediate sub-CAs allowed). sha256 signature explicit.

Live verification (after live regen) :
  subject = CN=Gondwana ToolBoX R3 CA, O=CyberMind Gondwana, C=FR
  issuer  = same
  basicConstraints critical CA:TRUE, pathlen:0
  keyUsage critical Certificate Sign, CRL Sign
  SHA1: D5:E4:3A:C1:AD:4E:25:8A:A9:D4:2A:26:52:2C:D8:82:50:63:EA:0E
  NO SAN — Android Settings parses cleanly

Backported to sbin/secubox-toolbox-wg-provision so fresh installs
get the clean CA.

## /wg/ca.crt (NEW)

Same content as /wg/ca.pem but :
  - DER format (binary X.509, not base64)
  - Filename: gondwana-toolbox-r3-ca.crt (Android Settings recognizes .crt)
  - Some Android 11+ installers reject .pem extension entirely

Aliased as /wg/ca.der too.

## R3 install page — Android tab refresh

Primary download button now points to /wg/ca.crt (DER).
.pem kept as fallback link.
Added note for users who saw 'émis par null' on previous attempt :
'supprime l'ancien fichier dans Téléchargements + relance'.

## Logger fix

Earlier commit's '/wg/profile/new' upsert used 'logger.warning' but
this module uses 'log = logging.getLogger(...)'. Renamed to log.warning.
This commit is contained in:
CyberMind-FR 2026-06-05 15:12:21 +02:00
parent 13e48c93c6
commit ba7144a342
2 changed files with 39 additions and 16 deletions

View File

@ -96,7 +96,9 @@ if [ ! -f "$CA_WG_DIR/mitmproxy-ca.pem" ]; then
log "generating dedicated WG CA (full DN, 10y)"
cd "$CA_WG_DIR"
# OpenSSL config — full DN, CA extensions, SKI/AKI
# OpenSSL config — minimal DN, NO SAN (Android Settings parser chokes
# on SAN with spaces — shows "émis par null" in install dialog).
# Classic RFC 5280 X.509v3 root, sha256, pathlen 0.
cat > _ca.cnf <<'CNF'
[req]
distinguished_name = dn
@ -105,23 +107,19 @@ x509_extensions = v3_ca
[dn]
C = FR
ST = Auvergne-Rhone-Alpes
L = Notre-Dame-du-Cruet
O = CyberMind Gondwana
OU = ToolBoX R3 WireGuard
CN = Gondwana ToolBoX R3 CA
[v3_ca]
basicConstraints = critical, CA:TRUE
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
subjectAltName = DNS:Gondwana ToolBoX R3 CA, DNS:cabine.gondwana.local
CNF
openssl genrsa -out _key.pem 2048 2>/dev/null
openssl req -new -x509 -days 3650 -key _key.pem -out _cert.pem \
-config _ca.cnf 2>/dev/null
-config _ca.cnf -sha256 2>/dev/null
# Combined key+cert for mitm (mitmproxy-ca.pem)
cat _key.pem _cert.pem > mitmproxy-ca.pem

View File

@ -515,21 +515,30 @@ pre{background:#1a1a25;color:var(--phos-hot);padding:0.6rem 0.8rem;border-radius
</div>
<div class="tab-content" data-content=android>
<a href="/wg/ca.pem" class="btn btn-warn">📥 Télécharger ca.pem</a>
<a href="/wg/ca.crt" class="btn btn-warn">📥 Télécharger CA (.crt format Android)</a>
<div class=warn>
Chrome ne peut PAS installer un CA directement (sécurité Android 11+).
Tu DOIS passer par les <b>Paramètres système</b>.
<br><br>
🆕 Si tu as déjà un essai raté avec « émis par null » : <b>supprime l'ancien fichier</b>
dans Téléchargements + relance le téléchargement (le CA a été régénéré).
</div>
<h3>Procédure (toutes versions Android) :</h3>
<h3>Procédure (toutes versions Android 7+) :</h3>
<ol>
<li>Télécharge le .pem (bouton ci-dessus, sauvé dans Téléchargements)</li>
<li>Ouvre <b>Paramètres système</b> (pas Chrome)</li>
<li>Tap <b>📥 Télécharger CA</b> ci-dessus sauvé dans Téléchargements
sous <code>gondwana-toolbox-r3-ca.crt</code></li>
<li>Ouvre <b>Paramètres système</b> (pas Chrome / pas Firefox)</li>
<li> <b>Sécurité et confidentialité</b> (ou « Sécurité »)</li>
<li> <b>Chiffrement et authentifiants</b> (ou « Identifiants »)</li>
<li> <b>Installer un certificat</b> <b>Certificat d'autorité</b></li>
<li>Sélectionne <code>ca.pem</code> dans Téléchargements</li>
<li>Confirme le PIN/empreinte du téléphone si demandé</li>
<li> <b>Plus de paramètres de sécurité</b> <b>Chiffrement et authentifiants</b>
(ou « Identifiants » selon constructeur)</li>
<li> <b>Installer un certificat</b> <b>Certificat d'autorité (CA)</b></li>
<li>Accepter l'avertissement de sécurité</li>
<li>Sélectionne <code>gondwana-toolbox-r3-ca.crt</code> dans Téléchargements</li>
<li>Confirme le PIN/empreinte si demandé</li>
</ol>
<h3>Fallback PEM :</h3>
<a href="/wg/ca.pem" style="color:var(--orange);font-size:0.85rem">📥 Télécharger en .pem</a>
si le .crt ne s'ouvre pas (rare).
<h3>Vérification :</h3>
<pre>Paramètres Sécurité Identifiants
de confiance onglet UTILISATEUR
@ -864,7 +873,7 @@ async def wg_profile_new(request: Request) -> Response:
try:
store.upsert_client(wg_hash, profile["client_ip"], level="r3")
except Exception as e:
logger.warning("wg peer upsert failed for %s: %s", wg_hash, e)
log.warning("wg peer upsert failed for %s: %s", wg_hash, e)
return Response(
content=profile["conf_text"],
@ -910,6 +919,22 @@ async def wg_ca_pem() -> Response:
)
@router.get("/wg/ca.der")
@router.get("/wg/ca.crt")
async def wg_ca_der() -> Response:
"""DER (binary) version of the mitm-wg CA. Some Android installers
(especially Android 11+ Settings CA certificate) prefer DER over PEM
and reject the .pem extension. Same cert, just binary-encoded."""
p = Path("/etc/secubox/toolbox/ca-wg/mitmproxy-ca-cert.cer")
if not p.exists():
raise HTTPException(404, "mitm-wg CA DER not yet generated")
return Response(
content=p.read_bytes(),
media_type="application/x-x509-ca-cert",
headers={"Content-Disposition": "attachment; filename=gondwana-toolbox-r3-ca.crt"},
)
@router.get("/wg/ca.mobileconfig")
async def wg_ca_mobileconfig() -> Response:
"""iOS profile that installs the mitm-wg CA in trust store."""