Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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éro unsafe, constant-time (hors rho, public par construction).
  • Dépendances C-free confirméeshybrid-array, module-lattice, sha3, kem, rand_core : aucun cpufeatures/cc/libc (contrairement à aes/sha2 qui tirent cpufeatures sur aarch64 — dette C-free connue). C’est déterminant sur cible *-linux-air sans libc C ([ADR-088]).
  • Même famille que notre air-crypto (sha3 dé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-air sans réintroduire de libc C (contrairement au piège cpufeatures).
  • 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-sha256 né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).
  • sntrup761 non 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.