ADR-108 — air-keystore : manager du cycle de vie des clés et secrets (couche 1), face privilégiée air-keysign, interop OpenSSH
Statut : Accepté (2026-07-26, décision BDFL). RFC de direction
(ADR-015).
S’appuie sur ADR-077 (managers de domaine
couche 1, médiateurs), ADR-034 (discipline
crypto ; air-crypto = primitives pures), ADR-096
(magasin authorized_keys binaire), ADR-040
(enveloppe d’artefact AIRCFGv1 + Cap’n Proto), ADR-073
(doctrine config binaire), ADR-089
(réservation SecurityManager), ADR-019 (modèle
d’erreurs), ADR-029 (nommage), ADR-101
(gate mot de passe admin), ADR-088 (std/PAL
sans libc C).
Compagnon d’ l’ADR Certificats SSH + CA (à venir, vague 1 #2) : celui-ci gère le
matériel de clé ; l’ADR certificats gère le format wire et l’orchestration
d’émission.
Catégorie : Architecture couche 1 (nouveau manager de domaine) + couche 2 (service
privilégié air-keysign). Premier ADR de la vague 1 issue du triage des écarts
OpenSSH.
Contexte
Le triage OpenSSH (serveur + client, 2026-07-26) a fait émerger un besoin transverse
qu’aucune crate ne porte aujourd’hui : le cycle de vie des clés et secrets. Il est
aujourd’hui éparpillé — air-sshd charge sa clé d’hôte via air-crypto
(AirSigningKey) en direct et lit authorized_keys via air-filesystem ([ADR-096]) ;
rien ne gère la génération, l’import/export de formats de clés, la péremption,
la révocation, ni un magasin d’hôtes connus (known_hosts). Or presque tous les
[A] retenus en dépendent : certificats/CA (matériel de clé), RevokedKeys,
known_hosts client, agent maison, hostbased.
air-crypto ne doit pas absorber cela : par [ADR-034] c’est une bibliothèque de
primitives pures, sans I/O ni stockage (Ed25519 sign/verify, X25519 ECDH,
hash/AEAD/Argon2/HMAC, aléa). Un composant qui gère l’état des clés (fichiers, formats,
durées de vie) est, par [ADR-077], un manager de domaine couche 1 — une surface objet
médiatrice, distincte des primitives.
Directive BDFL (2026-07-26). « Un seul air-keystore. On réutilise notre format
binaire. La clé d’hôte privée : on en parle [→ décision 5]. Interop avec OpenSSH : oui,
nécessaire. » Et, sur la doctrine de sourcing : « en crypto on ne réinvente pas, on
utilise ce qui est fait — mais os-free ; les formats, on peut les faire nous-mêmes
sérieusement » ([[doctrine-sourcing-crypto]] : formats/protocoles = DIY à faible risque,
on possède).
Décision
1. air-keystore est un manager de domaine couche 1, stateful
Une seule crate air-keystore (pas d’éparpillement), manager de domaine au sens
[ADR-077] : surface objet médiatrice, consommée par les toits (air-sshd, air-ssh, CLI,
agent), jamais des free-functions. Stateful : elle fait de l’I/O — exclusivement
via la couche 1 (air-filesystem pour lire/écrire les magasins), jamais la couche 0
en direct (règle dure ; un manque en couche 1 = additif couche 1 d’abord). Elle délègue
toute primitive crypto à air-crypto (génération de clés, signature, dérivation,
chiffrement d’enveloppe) — air-crypto reste primitives pures. Sous-modules nets :
keys (matériel + génération), formats (sérialisation/interop), store (magasin scellé),
hosts (known_hosts), revocation, ca (matériel CA). Monnaie d’erreurs AirError
([ADR-019]) ; no_std + alloc ; secrets zeroize en mémoire. Coverage 100 %
(couche 1).
2. Stockage : format binaire scellé, réutilisé partout
Le magasin interne réutilise l’enveloppe d’artefact AIRCFGv1 + Cap’n Proto ([ADR-040])
et le motif du magasin authorized_keys binaire ([ADR-096]) — cohérent avec la
doctrine de config binaire ([ADR-073]) et la base de comptes binaire. Écriture
atomique, permissions 0600 pour les magasins de secrets. Aucun secret en clair
texte. Les secrets au repos peuvent être chiffrés sous une clé dérivée (Argon2id via
air-crypto) gardée par le [gate admin ADR-101] selon le magasin.
3. Interop OpenSSH requise — aux frontières, pas au cœur
air-keystore lit et écrit les formats OpenSSH : clés publiques
(ssh-ed25519 AAAA…), clés privées (-----BEGIN OPENSSH PRIVATE KEY-----), PEM, et
certificats OpenSSH. Ce n’est pas seulement notre format binaire : Air doit
coexister avec l’écosystème (migration de clés existantes, ssh/git classiques,
déploiement). Frontière nette : l’interop vit dans le sous-module formats
(import/export) ; le stockage interne reste binaire scellé (§2). Ces parseurs
acceptent des données externes ⇒ fuzzing obligatoire (cargo-fuzz), parsing
schema-first, zéro unwrap (Principe 3). Les formats étant du DIY à faible risque
([[doctrine-sourcing-crypto]]), Air les possède (pas de dépendance qui traînerait du
C).
4. Périmètre fonctionnel
Génération (délègue air-crypto) · import/export (OpenSSH/PEM ↔ binaire scellé) · lookup ·
péremption (dates de validité, listage des clés/certs expirés) · révocation
(liste de révocation, RevokedKeys, vérification d’appartenance) · magasin d’hôtes
connus known_hosts (TOFU, empreintes, rotation UpdateHostKeys). Couvre ainsi, côté
serveur : clé d’hôte, vérification CA, clés révoquées ; côté client :
known_hosts, clés utilisateur, certificats client, et le futur agent maison. La
vérification d’autorisation d’une clé (comparaison de secret) doit rester
constant-time (dudect, prolonge dudect_authorization d’air-sshd,
[[doctrine-sourcing-crypto]]).
5. Deux faces : bibliothèque non privilégiée + service air-keysign
La clé d’hôte privée et les signatures sensibles (hostbased) ne doivent jamais
transiter par un binaire setuid (leçon OpenSSH ssh-keysign). air-keystore a donc
deux faces :
- Face bibliothèque non privilégiée (couche 1) — le gros de l’usage : génération,
formats, lookup,
known_hosts, révocation, matériel non secret. - Face service privilégiée
air-keysign(couche 2, service socket-activé, root ou capability ciblée, jamais setuid) — détient les clés privilégiées et signe à la demande via une interface oracle-safe : elle valide la structure du défi (liésession_id/hôte/utilisateur — RFC 4252 §9 pour hostbased), refuse de signer des octets arbitraires, et restreint les appelants (peer credentials du socket Unix). Nécessaire seulement pour hostbased client et les opérations sur la clé d’hôte ; jamais sur le chemin publickey ordinaire. Ce service est un livrable packagé ultérieur (couche 2) ; le présent ADR fixe le contrat (2 faces, oracle-safe, no-setuid).
6. Frontières actées
- Format wire des certificats SSH = couche 2 (
air-ssh-proto), pas le keystore. La CA est une orchestration : keystore (détient la clé CA + cycle de vie/ révocation des certs émis) + air-crypto (signature) + air-ssh-proto (format). Pas de monolithe « CA ». (Détail dans l’ADR certificats, vague 1 #2.) air-cryptoreste primitives pures — jamais d’I/O ni de stockage (ça vit ici).SecurityManager([ADR-089]) reste distinct : il porte la politique (Allow/Deny) et consomme le keystore ; il ne le contient pas. De même, le futurair-sandbox= mécanisme, orthogonal.
7. Consommateurs et report en vague 2
Consommateurs prévus : air-sshd (clé d’hôte, autorisation, vérification CA, révocation),
air-ssh (known_hosts, clés/certs client, agent), le futur air-agent (agent maison
keystore-backed — ADR séparé), et l’outillage. Hors de cet ADR (reportés en
vague 2, déjà tranchés) : la CLI air-keystore (équivalent ssh-keygen élargi +
mode JSON in/out agent-safe) et l’extraction d’air-json (crate partagée fuzzée). Ils
consomment l’API définie ici et servent de test bout-en-bout (dogfooding). Le
nommage suit [ADR-029] (termes hérités d’OpenSSH — known_hosts, authorized_keys —
conservés verbatim ; surface conçue par Air = noms explicites sans abréviation).
Conséquences
Positives.
- Un point unique pour les clés/secrets : fin de l’éparpillement (clé d’hôte,
authorized_keys, à termeknown_hosts/certs/révocation passent par le même médiateur, [ADR-077]). - Débloque la vague 1 : certificats/CA,
RevokedKeys,known_hosts, hostbased, agent — tous s’appuient sur ce socle. - Interop OpenSSH ⇒ migration et coexistence réelles avec l’écosystème, sans renier le stockage binaire scellé interne.
- Pas de setuid : la face
air-keysignoracle-safe ferme la classe de vulnérabilités des helpers de signature privilégiés. - Cohérent doctrine : formats possédés (DIY faible risque, os-free), primitives déléguées à air-crypto (pas de réinvention de la crypto).
Négatives / coûts assumés.
- Nouvelle crate couche 1 à porter au niveau des exigences (100 % coverage, fuzz des
parseurs OpenSSH, dudect sur la comparaison de secrets,
zeroize,// SAFETY:siunsafe). Coût réel, assumé (Principe 1). - Surface d’interop = surface d’attaque : parser des formats OpenSSH externes est un vecteur ⇒ schema-first + fuzz obligatoires ; le risque est borné, pas nul.
- Service privilégié
air-keysignà concevoir avec soin (couche 2, socket-activé, peer-cred, oracle-safe) — introduit un composant privilégié de plus dans le TCB, justifié par l’élimination du setuid. - Migration des consommateurs existants (
air-sshdclé d’hôte +authorized_keys) vers le manager : incrément dédié, piloté pour ne rien casser (tests de non-régression).
Mise en œuvre (référence, hors décision). Incréments : (a) crate air-keystore
couche 1 (sous-modules §1) — génération/formats/store/hosts/revocation ; (b) fuzz des
parseurs OpenSSH + dudect autorisation ; (c) migration air-sshd (clé d’hôte,
authorized_keys) vers le manager ; (d) service air-keysign (couche 2) quand hostbased/
clé d’hôte privilégiée sera câblé ; (e) CLI + air-json = vague 2. Ces incréments ne
sont pas 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.
Amendement — « manager de domaine » revendiqué, fonctions libres livrées (2026-08-03)
Constat d’audit du 2026-08-03, dans le cadre de la remise en conformité des ADR nommant des Managers. L’état faisant autorité vit dans l’amendement d’ADR-077.
Le §1 déclare air-keystore manager de domaine couche 1 au sens d’ADR-077, et le manifeste
de la crate affirmait jusqu’à ce jour une « surface objet médiatrice (AirKeystoreManager)
consommée par les toits — jamais des free-functions ».
Les deux affirmations étaient fausses. Aucun type AirKeystoreManager n’a jamais existé. La
crate expose des types par artefact — AirHostKey, AirKnownHost, AirCaKey, AirKrl,
AirRevocationStore, AirRevokedKey, AirSshKeyPair, AirSshPublicKey — et des fonctions
libres par module : load, save, encode_artifact, decode_artifact, verify_host.
Ce que cela ne remet pas en cause. Le fond d’ADR-108 tient : une seule crate pour le domaine,
délégation de toute primitive crypto à air-crypto, artefacts binaires, cycle de vie centralisé.
Ce qui ne tient pas, c’est la forme de la surface — et surtout la phrase « jamais des
free-functions », qui décrivait exactement le contraire de ce qui a été livré.
Ce qui reste à décider — et qui n’est pas tranché ici : soit air-keystore se dote du
médiateur qu’il revendique, soit ADR-108 §1 est amendé pour assumer une surface par artefact.
La seconde branche est défendable : contrairement au système de fichiers ou aux sockets, le
domaine des clés n’a pas d’état global à médier, et un AirKeystoreManager risquerait d’être une
façade sans substance. Mais il faut choisir, pas laisser la doctrine dire une chose et le code
une autre.