ADR-0002

Stratégie cryptographique

source : docs/adr/0002-crypto-strategy.md · versionné · MADR-lite

ADR-0002 — Stratégie cryptographique

  • Statut : Accepted
  • Date : 2026-04-24
  • Décideurs : Core team OmbrysWeb (security lead + crypto reviewer externe)
  • Issue GitHub : #2
  • Références cahier des charges : § 5.1, § 5.3, § 5.4, § 6.2, § 6.3
  • Dépend de : ADR-0001

Contexte

Le projet repose sur un modèle Zero-Knowledge (§ 5.3) : le serveur ne doit jamais accéder en clair aux données utilisateur. Cela implique de définir de bout en bout :

  1. Les primitives symétriques et asymétriques utilisées (choix NIST-aligned + post-quantique).
  2. La dérivation de clé à partir de l'identité utilisateur (Passkey / WebAuthn Level 3 PRF extension) pour obtenir une clé maîtresse client-side, jamais exportée.
  3. L'échange de clé entre composants (TLS 1.3, mTLS interne, gRPC) avec résistance post-quantique via schéma hybride.
  4. La signature pour authentification, attestation de build, journaux d'audit.
  5. Les rotations, la révocation, et le cycle de vie des clés.

Les contraintes extérieures : pas de crypto custom, aucune lib JS tierce douteuse côté navigateur (§ 5.1), pas de dépendance bloquante sur standards non finalisés. L'horizon « attaquant quantique » ~10 ans impose de bâtir un schéma hybride dès maintenant, pas uniquement post-quantique pur (maturité insuffisante des implémentations).

Options considérées

Primitives symétriques (chiffrement de données utilisateur)

| Option | Avantages | Inconvénients | Décision | |---|---|---|---| | AES-256-GCM | Accéléré matériel (AES-NI, ARMv8 crypto), WebCrypto natif, NIST-approved | Limite 2^32 messages par clé avec nonce aléatoire | Retenu | | ChaCha20-Poly1305 | Constant-time logiciel, bon fallback sans AES-NI | Moins performant avec AES-NI, WebCrypto supporte l'inverse sur certains navigateurs | Fallback possible si profil client sans AES-NI | | AES-256-GCM-SIV | Nonce misuse-resistant | Moins répandu, WebCrypto ne le propose pas nativement | Rejeté pour v1 |

Dérivation de clé

| Option | Avantages | Inconvénients | Décision | |---|---|---|---| | Passkey PRF extension + HKDF-SHA-384 | Clé jamais exportée, isolée matériel/biométrie, résiste au phishing, WebAuthn Level 3 | Support navigateur encore en rampe (Chrome/Edge OK, Safari 17+, Firefox via prefs) | Retenu, avec feature detection + message clair si non supporté | | Argon2id sur secret dérivé Passkey | Extra-coût sur attaquant offline | Pas nécessaire si PRF utilise déjà HMAC côté authenticator | Rejeté (redondant) | | Password-based KDF (PBKDF2/Argon2id sur mot de passe) | Support universel | Incompatible avec politique « pas de mot de passe » (§ 6.1) | Rejeté |

Signatures (identités, attestations, audit)

| Option | Avantages | Inconvénients | Décision | |---|---|---|---| | ECDSA P-384 | NIST-approved, WebCrypto natif, taille clé raisonnable | Non PQ-résistant seul | Retenu pour signatures classiques | | Ed25519 | Constant-time, rapide | Moins répandu sur WebCrypto (couvert depuis Chrome 113 / Safari 17 / Firefox 129) | Possible usage interne (Tonic, Paseto v4) | | Dilithium-3 (ML-DSA) | Standard NIST FIPS 204, résistance quantique | Taille signature ~3.3 KB, écosystème en rampe | Retenu en hybride avec ECDSA pour chemins sensibles |

Échange de clés (TLS, mTLS, gRPC)

| Option | Avantages | Inconvénients | Décision | |---|---|---|---| | X25519 seul | Standard actuel, perf | Non PQ-résistant | Insuffisant seul | | X25519 + Kyber-768 (hybride, aka X25519Kyber768Draft00 / IETF hybrid) | Compromis : sécurité classique + PQ, déjà déployé par Cloudflare/Chrome/Firefox | Taille handshake augmentée (+1 KB) | Retenu sur TLS 1.3 edge et mTLS interne | | Kyber seul | PQ pur | Maturité insuffisante pour ne pas garder une jambe classique | Rejeté |

HKDF / hash

| Option | Décision | |---|---| | HKDF-SHA-384 | Retenu comme KDF par défaut | | SHA-512 | Autorisé là où SHA-384 n'est pas disponible natif | | BLAKE3 | Autorisé uniquement en interne Rust (hors surface exposée client) |

Librairies cryptographiques

| Côté | Librairie | Justification | |---|---|---| | Navigateur | Web Crypto API native | Pas de JS tiers, isolation navigateur, performances matérielles (§ 5.1) | | Rust | ring, RustCrypto (aes-gcm, hkdf, p384, ed25519-dalek), rustls | Crates auditées, memory-safe, pas de binding OpenSSL | | Post-quantique | liboqs via crate FFI interne ombrys-liboqs-sys | Seul écosystème mature pour Kyber/Dilithium hybrid ; FFI unsafe isolé et revu (§ ADR-0001) | | TLS | rustls avec provider ring ou aws-lc-rs selon support PQ | Pas d'OpenSSL (historique de CVE et complexité de build) |

Décision

Primitives retenues

| Usage | Primitive | |---|---| | AEAD symétrique (contenu utilisateur) | AES-256-GCM (fallback ChaCha20-Poly1305) | | Dérivation clé maîtresse client | Passkey PRF → HKDF-SHA-384 | | Dérivation clés subséquentes | HKDF-SHA-384 | | Signatures classiques | ECDSA P-384 | | Signatures internes (Paseto v4, SPIFFE SVID, audit) | Ed25519 | | Signatures post-quantiques | Dilithium-3 (hybride avec ECDSA sur chemins sensibles) | | Échange de clés TLS 1.3 | X25519 + Kyber-768 (hybride) | | Échange de clés messagerie (double ratchet) | X25519 + Kyber-768 (hybride) pour X3DH-like ; ratchet symétrique HKDF + AES-GCM | | KDF | HKDF-SHA-384 | | Hash | SHA-384 par défaut |

Dérivation de clé utilisateur (flux)

Passkey (WebAuthn L3, authenticator)
    │   PRF extension : prf.eval(salt = "ombrys.web/v1/master")
    ▼
prf_output  (32 octets, jamais exporté hors navigateur)
    │   HKDF-SHA-384(prf_output, salt = accountId, info = "master-key/v1")
    ▼
K_master  (32 octets, mémoire uniquement)
    │   HKDF-SHA-384(K_master, info = "notes/v1" | "files/v1" | "msg/v1" | …)
    ▼
K_notes, K_files, K_msg_root, K_audit  (32 octets chacune)
    │   Chiffrement AEAD AES-256-GCM par objet : K_obj = HKDF(K_bucket, info = objectId)
    ▼
ciphertext + nonce + aad envoyés serveur ; K_obj jamais transmise

Propriétés garanties :

  • prf_output ne quitte jamais l'authenticator / navigateur (contrainte WebAuthn L3).
  • K_master existe uniquement en mémoire JavaScript (pas de localStorage, pas d'IndexedDB non chiffré).
  • Chaque bucket (notes, files, messaging, audit) dérive une clé distincte → compartimentage cryptographique.
  • Zeroization de la clé maîtresse à la déconnexion (limitation JS reconnue, best effort + crypto.subtle retient la clé comme CryptoKey non extractable).

Rotations

| Objet | Période | Déclencheur additionnel | |---|---|---| | Session (cookie / token Paseto v4) | 15 min | Chaque action sensible | | Clés collectives (pair d'auth groupe, clé publique de contact) | 90 jours | Incident, départ membre | | Certificats mTLS (SPIFFE SVID) | 24 h | SPIRE rotation automatique | | Certificats TLS edge (Let's Encrypt) | 60 jours | Standard ACME | | Clés utilisateur dérivées | À chaque login | + rotation explicite possible côté utilisateur (re-wrap § 5.6.5 → issue #59) | | Clé de chiffrement des backups | 180 jours | Après chaque restauration drill |

Cérémonie CA racine

  • CA racine interne hors-ligne, stockée sur HSM matériel (YubiHSM 2 ou Nitrokey HSM 2), backup M-of-N (3-sur-5) sur clés physiques détenues par des membres distincts dans des lieux distincts.
  • ICA en ligne avec durée de vie ≤ 1 an, émise par la CA racine lors d'une cérémonie documentée.
  • Aucune clé privée de CA racine en mémoire d'une machine connectée. Signature par HSM uniquement.
  • SPIFFE/SPIRE utilise une chaîne dédiée pour les workloads internes.

Interdictions explicites

  • Pas de crypto custom (pas de construction ad hoc d'AEAD, pas de KDF maison, pas de protocole inventé).
  • Pas d'AES-CBC, pas de RSA-PKCS#1-v1.5, pas de SHA-1, pas de MD5 même en interne.
  • Pas de JWT HS256 pour l'auth user-facing. Paseto v4 ou JWT EdDSA autorisés pour tokens internes courts.
  • Pas d'OpenSSL dans le chemin critique (§ ADR-0001).
  • Pas de stockage de mots de passe (§ 6.1).
  • Pas d'envoi de clé privée sur le réseau, jamais.

Conséquences

Positives

  • Élimination par design du phishing d'identifiants (Passkeys).
  • Résistance quantique graduelle (schéma hybride) sans dépendre de la finalisation complète des standards PQ.
  • Cryptographie entièrement isolée dans des primitives auditées (WebCrypto, ring/RustCrypto, liboqs).
  • Compartimentage : compromission d'un bucket (ex : K_files) n'implique pas compromission de K_msg.
  • Alignement avec les attentes d'un audit cryptographique externe (M6, issue #70).

Négatives

  • Passkey PRF pas universel (Firefox nécessite flag, certains authenticators pas encore compatibles). Mitigation : feature detection + UX explicite + authenticators recommandés (YubiKey 5+ firmware 5.7+, Nitrokey 3, biométrie intégrée Chrome/Safari).
  • Kyber-768 augmente la taille des handshakes TLS (~1 KB). Acceptable pour le profil (cible navigateur moderne, pas IoT).
  • Dilithium-3 signatures lourdes (~3.3 KB). Réservé aux chemins rares (attestations, rotations de clé, audit critique). Pas d'usage par requête.
  • Complexité opérationnelle d'une CA racine hors-ligne sur HSM → cérémonie documentée, drill annuel.

Neutres

  • Compatible RGPD : la suppression d'un utilisateur = destruction de la clé dérivée → les blobs restants deviennent indéchiffrables (droit à l'oubli cryptographique).

Suivi

  • Spec crypto détaillée (issue #5, docs/security/crypto-spec.md) : codifie nonces, formats d'enveloppe, identifiants de version, test vectors.
  • ADR-0003 : applique ce schéma au modèle de stockage Zero-Knowledge.
  • Audit externe (M6, issue #70) : validation indépendante par un cryptographe reconnu obligatoire avant go-live.
  • Revue post-standardisation : à chaque mise à jour majeure FIPS 203/204/205 ou draft IETF hybrid, ADR de révision si le paysage change.

Changelog

  • 2026-04-24 : rédaction initiale, statut Accepted conditionné à la validation audit externe (M6).