ADR-110 — KEX post-quantique : ML-KEM-768 via ml-kem (RustCrypto) re-validé, combineur hybride mlkem768x25519 maison, câblage air-ssh-proto
Statut : Accepté (2026-07-26, décision BDFL). RFC de direction (ADR-015). S’appuie sur ADR-034 (discipline crypto ; air-crypto = primitives pures), ADR-024 (règle des 80 % + vendoring), ADR-093 (transport SSH-2 / KEX), ADR-091 (motif sans-IO — handshaker fuzzable), ADR-088 (sans libc C), ADR-029 (nommage). Applique la doctrine de sourcing crypto ([[doctrine-sourcing-crypto]], à formaliser en ADR en vague 2 — raffine [ADR-034]/[ADR-024]) : pur Rust os-free d’abord ; ne pas porter de C ; DIY sérieux sinon ; fiable > performant.
Catégorie : Architecture crypto (couche 1 air-crypto) + câblage KEX (couche 2
air-ssh-proto). Vague 1 #3 du triage des écarts OpenSSH.
Contexte
Le seul vrai retard de sécurité relevé au triage OpenSSH est l’absence de KEX
post-quantique. OpenSSH ≥ 9.9 négocie par défaut mlkem768x25519-sha256 : un échange
de clés hybride combinant X25519 (sécurité classique éprouvée) et ML-KEM-768 (FIPS
203, résistance à un adversaire quantique). Sans lui, une session Air aujourd’hui est
exposée au modèle « récolter maintenant, déchiffrer plus tard » (harvest now, decrypt
later).
La doctrine crypto ([[doctrine-sourcing-crypto]]) place le post-quantique en risque très élevé pour un DIY (formules de treillis, échantillonnage constant-time, marges de bruit) : la primitive ne doit pas être réécrite maison au premier jet. La règle est donc : crate pur-Rust auditée d’abord, DIY en dernier recours. Une évaluation a été menée (2026-07-26).
Décision
1. Adopter ml-kem (RustCrypto) pour ML-KEM-768, pas de DIY treillis
Verdict de l’évaluation : ml-kem (RustCrypto), pin =0.3.2. Motifs :
- Pur Rust,
no_std, sans heap, zérounsafe, constant-time (horsrho, public par construction). - Dépendances C-free confirmées —
hybrid-array,module-lattice,sha3,kem,rand_core: aucuncpufeatures/cc/libc(contrairement àaes/sha2qui tirentcpufeaturessur aarch64 — dette C-free connue). C’est déterminant sur cible*-linux-airsans libc C ([ADR-088]). - Même famille que notre
air-crypto(sha3déjà vendoré), licences MIT/Apache. - Règle des 80 % ([ADR-024]) : satisfaite — on utilise quasi toute la surface (keygen / encaps / décaps / sérialisation).
Alternative écartée : fips203 (integritychain) — pur Rust également et constant-time
(vérifié dudect en amont), mais arbre de dépendances moins détaillé et intégration
famille moindre.
2. Re-validé à notre barre — pas de confiance aveugle
L’upstream n’est pas audité ; l’adoption n’est donc pas un blanc-seing. ml-kem
est vendoré + pinné ([ADR-024]) et re-validé aux exigences des couches fondatrices
(Principe 1) :
- KATs officiels FIPS 203 (vecteurs de test connus).
- Notre
dudect(constant-time,AIR_DUDECT=1, job CI dédié [ADR-107]) sur encapsulation et décapsulation. - Fuzz de la décapsulation (cargo-fuzz) — c’est la surface d’entrée réseau (ciphertext contrôlé par le pair).
- Miri + proptest.
3. Le combineur hybride mlkem768x25519 est écrit par nous (faible risque)
Combiner deux mécanismes déjà sûrs est du DIY à faible risque ([[doctrine-sourcing-crypto]])
⇒ Air le possède, dans air-crypto (primitive pure, sans I/O — [ADR-034]) :
X25519 (via la primitive dalek existante) ⊕ ML-KEM-768, par concaténation puis
hachage du secret partagé selon la construction IETF/OpenSSH (mlkem768x25519-sha256).
Testé par KATs de bout en bout + proptest (round-trip encaps/décaps hybride).
4. Câblage KEX dans air-ssh-proto (couche 2, sans-IO)
La méthode d’échange de clés mlkem768x25519-sha256 est ajoutée au handshaker de
air-ssh-proto ([ADR-093]), dans le respect du motif sans-IO ([ADR-091] : cœur pur,
déterministe, fuzzable). Elle devient la méthode préférée (négociée en tête),
curve25519-sha256 (X25519 pur) restant en repli d’interop pour les pairs sans PQ.
strict-KEX/Terrapin ([ADR-093]) s’applique inchangé. sntrup761x25519 (NTRU Prime,
ancien défaut OpenSSH, non-FIPS) n’est pas implémenté : éviter un second primitif de
treillis/NTRU ; ML-KEM est le standard, et le repli X25519 couvre les pairs trop anciens
pour ML-KEM.
Conséquences
Positives.
- Ferme le seul vrai retard de sécurité : sessions Air résistantes au « harvest now, decrypt later », au niveau du défaut OpenSSH actuel.
- C-free vérifié : la primitive PQ tient sur
*-linux-airsans réintroduire de libc C (contrairement au piègecpufeatures). - Risque maîtrisé : on n’écrit pas le treillis (crate re-validée), on n’écrit que le combineur (faible risque) — fidèle à la doctrine.
- Interop :
mlkem768x25519-sha256négocié avec OpenSSH ≥ 9.9 ; repli X25519 pour le reste.
Négatives / coûts assumés.
- Dépendance vendorée à re-valider dans le temps (l’upstream n’étant pas audité) : KATs
- dudect + fuzz sont notre filet ; à re-passer à chaque bump de pin.
- Surface fuzz supplémentaire (décapsulation) — entrée réseau ⇒ obligatoire.
- Taille des messages KEX accrue (ML-KEM ≫ X25519) : impact bande passante au handshake, négligeable devant la session ; mesuré si besoin (Principe 5).
sntrup761non supporté : incompatibilité avec des pairs configurés uniquement sntrup (rare ; le repli X25519 les couvre sauf configuration PQ-only exotique).
Mise en œuvre (référence, hors décision). Incréments : (a) vendoring + pin de ml-kem
([ADR-024], DEPENDENCIES.md, deny.toml) ; (b) KATs FIPS 203 + dudect encaps/décaps +
fuzz décaps + Miri/proptest ; (c) combineur mlkem768x25519 dans air-crypto ;
(d) méthode KEX dans air-ssh-proto ([ADR-093]) + négociation/repli. Non engagés par cet
ADR ; il en fixe les décisions normatives.
Alternatives rejetées
Aucune alternative n’a été consignée lors de l’instruction.