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-094 — air-sshd phase 2 : ssh-userauth (authentification publickey Ed25519, conf par-utilisateur binaire, politique de login, events AirCom, durcissement Terrapin)

Statut : Accepté (2026-07-23, décision BDFL). (Proposé le 2026-07-14, ratifié le 2026-07-23.) Met en œuvre la phase 2 d’ADR-074 (vision air-sshd), au-dessus de la phase 1 transport (ADR-093, complète et wire-compat OpenSSH scellée en CI — T.1→T.4b, main e61d952). Reste sur le motif sans-IO (ADR-091). S’appuie sur air-crypto Ed25519 (ADR-034), air-account (ADR-041), air-config binaire (ADR-073), air-process (privsep/drop_to_user), AirCom (ADR-001, events de session ADR-093 §5).

Catégorie : Architecture (couche 2, service réseau). Toute évolution structurante passe par un RFC (ADR-015).

Contexte

Le transport de la phase 1 s’arrête à SSH_MSG_SERVICE_ACCEPT : un vrai client OpenSSH atteint ssh-userauth (prouvé en CI, T.4b) puis échoue l’auth faute de méthode. La phase 2 implémente le protocole d’authentification SSH (RFC 4252) : le client prouve son identité, le serveur décide, puis la session (phase 3) peut s’ouvrir. C’est aussi là qu’atterrissent trois directives BDFL déjà gravées (ADR-093 §5/§7) : events AirCom AuthOk/AuthFailed, conf binaire par-utilisateur en HomeDirectory (l’équivalent authorized_keys), politique de login sur air-account. Enfin, la phase 2 est le bon moment pour trancher le durcissement Terrapin / strict-KEX noté en T.4b.

Décision

1. Protocole — RFC 4252 sur le transport chiffré, sans-IO

Extension du cœur air-ssh-proto (pur, #![forbid(unsafe_code)]) : nouveau module userauth (codec + StateMachine d’authentification), piloté par air-sshd. Messages : SSH_MSG_USERAUTH_REQUEST (50), _FAILURE (51), _SUCCESS (52), _BANNER (53), et pour publickey : SSH_MSG_USERAUTH_PK_OK (60). Le parseur de USERAUTH_REQUEST est une surface hostile (nom d’utilisateur, service, méthode, blob de clé, signature — tous longueur-préfixés venant du réseau) → décodage borné anti-hostile, from_utf8 (nom d’utilisateur = UTF-8 validé, borné), fuzz obligatoire.

2. Méthodes — publickey Ed25519 d’abord, moderne uniquement

  • none : rend la liste des méthodes autorisées (= publickey) sans authentifier (RFC 4252 §5.2) — jamais un succès.
  • publickey (RFC 4252 §7) avec ssh-ed25519 (RFC 8709) uniquement. Flux en deux temps : (a) has_signature = false → si la clé est autorisée pour l’utilisateur, le serveur répond PK_OK (sonde) ; (b) has_signature = true → le serveur vérifie la signature sur string(session_id) ‖ SSH_MSG_USERAUTH_REQUEST‖… (RFC 4252 §7, session_id = le H du premier KEX, figé en phase 1) via air-crypto::AirVerifyingKey::verify (constant-time), puis USERAUTH_SUCCESS.
  • Refusés : password (différé — la doctrine binaire/clés d’abord ; à réserver via un incrément dédié si un besoin apparaît), types de clé legacy (ssh-rsa/ssh-dss/ECDSA-NIST).

3. Autorisation — conf binaire par-utilisateur en HomeDirectory (air-config)

L’équivalent d’~/.ssh/authorized_keys, binaire (jamais texte, ADR-073) : un artefact air-config (schéma capnp dédié sshd_authorized_keys) dans le HomeDirectory de l’utilisateur (emplacement figé par l’incrément, p.ex. ~/.config/air/sshd/authorized_keys) listant les clés publiques ssh-ed25519 autorisées (+ options éventuelles : from, command, expiration — différables). Le serveur, après résolution de l’utilisateur (§4), lit cet artefact via air-config et décide si la clé présentée est autorisée. Un artefact système (host keys, utilisateurs/ méthodes permis — déjà en phase 1) complète la politique globale. Import/export possibles (ADR-073).

4. Politique de login — air-account + air-process

  • Résolution de l’utilisateur via air-account (/etc/passwd/group → uid/gid, HomeDirectory, shell) ; utilisateur inexistant / login interdit → USERAUTH_FAILURE (sans révéler pourquoi — anti-énumération, cf. §6).
  • Privsep : le modèle moniteur↔enfant (SCM_RIGHTS déjà livré) — le composant privilégié authentifie, la session (phase 3) tourne après drop_to_user (air-process). La phase 2 prépare l’identité résolue+autorisée ; le largage effectif de privilèges et le PTY/shell sont phase 3 (ssh-connection).

5. Observabilité — events AirCom (directive BDFL)

Le pilote publie sur AirCom (pub-sub §7, schéma air-sshd-schema figé en T.1) : AuthOk{user, method, peer} et AuthFailed{user, method, peer} — un consommateur suit qui s’authentifie et d’où. SessionOpened/ShellStarted/SessionClosed viendront en phase 3.

6. Durcissements de sécurité (partie intégrante de la phase 2)

  • Terrapin / strict-KEX (CVE-2023-48795) : implémenter kex-strict-s-v00@openssh.com (annoncé dans KEXINIT serveur) — si négocié, réinitialiser les numéros de séquence à NEWKEYS et refuser tout message inattendu pendant le KEX. Modifie le transport (phase 1) : traité comme incrément de durcissement en tête de phase 2 (U.0). (C’est la levée de la note T.4b.)
  • Login grace timeout (via le Timer air-async), nombre d’essais borné, anti-énumération (échecs indistinguables entre utilisateur inconnu / clé non autorisée / signature invalide ; temps de réponse non dépendant du secret autant que possible).

7. Discipline sans-IO & couche

userauth (cœur, calcul pur : codec, state machine, vérification de signature = calcul via air-crypto) reste dans air-ssh-proto ; le pilote air-sshd fait l’I/O, la lecture air-config du HomeDirectory, la résolution air-account, la publication AirCom, le privsep. Arêtes 2→2 / 2→1 uniquement (jamais 2→0, check-layers).

8. Cible d’acceptation (wire-compat, scellée en CI comme T.4b)

Un vrai client ssh -i <clé_ed25519> de stock s’authentifie avec succès (USERAUTH_SUCCESS, debug1: Authentication succeeded (publickey)) contre air-sshd, la clé étant dans l’artefact authorized_keys binaire de l’utilisateur ; et échoue proprement (USERAUTH_FAILURE) avec une clé non autorisée. Test d’intégration ssh réel automatisé (extension de ssh_interop.rs).

9. Incréments (phase 2)

Inc.ContenuPreuve
U.0Durcissement Terrapin / strict-KEX (kex-strict-s-v00@openssh.com, reset seqnr à NEWKEYS, refus messages hors-KEX).interop ssh conservée + strict-kex négocié
U.1Codec + StateMachine userauth (REQUEST/FAILURE/SUCCESS/BANNER/PK_OK, méthode none→liste). Câblage SERVICE_REQUEST→userauth. Fuzz du parseur USERAUTH_REQUEST.ssh reçoit la liste publickey
U.2publickey Ed25519 : sonde PK_OK + vérif de signature sur session_id (RFC 4252 §7, air-crypto).signature valide → SUCCESS (clé codée en dur de test)
U.3authorized_keys binaire par-utilisateur (air-config, HomeDirectory) + résolution air-account + décision d’autorisation + events AuthOk/AuthFailed.vrai ssh -i cléAuthentication succeeded ; clé non autorisée → FAILURE
U.4Login grace / essais bornés / anti-énumération + preuve d’intégration ssh scellée en CI (succès + échec).test CI vert (succès & refus)

Chaque incrément : cœur ~100 % (unit + property + fuzz + model-based), pilote > 90 %, barrière verte, délégué à carbon puis re-vérifié et mergé (barrière + couverture + fuzz + revue adverse ciblant le parseur USERAUTH_REQUEST et la vérif de signature). Additif air-crypto improbable (Ed25519 verify déjà public) — sinon STOP-and-report/escalade BDFL comme en phase 1.

Conséquences

  • air-sshd devient un serveur SSH authentifiant : un utilisateur se connecte réellement (sans shell tant que la phase 3 n’est pas là — la session/PTY/exec = ssh-connection).
  • La conf binaire par-utilisateur matérialise la doctrine ADR-073 côté HomeDirectory ; jamais d’authorized_keys texte.
  • Observabilité AirCom réalisée pour l’auth ; air-sshd reste le premier citoyen AirCom.
  • Terrapin fermé dès la phase 2 (dette T.4b levée).
  • Prépare la phase 3 (ssh-connection : canaux, pty-req/shell/exec, drop_to_user, provenance d’exécution).
  • Dette de phase 1 à solder en parallèle (hors phase 2 stricto sensu) : re-sceau couche-1-v2.2 groupé (air-socket::into_owned_fd, air-shm, constructeurs seedés #356, AEAD SSH #358) ; F2 (contributory X25519 air-crypto).

Alternatives rejetées

  • password d’abord (ou du tout, en v1). Refusé : clés d’abord (plus sûr, aligné doctrine) ; password réservé à un incrément dédié si besoin.
  • Types de clé legacy (RSA/DSA/ECDSA-NIST). Refusé (profil moderne ADR-074).
  • authorized_keys texte façon OpenSSH. Refusé : air-config binaire (ADR-073).
  • Différer Terrapin/strict-KEX. Rejeté : la mitigation est cheap et touche le transport qu’on vient de bâtir — la faire maintenant évite une reprise ultérieure.
  • Auth dans le pilote plutôt que le cœur. La vérification de signature est du calcul pur → elle reste dans le cœur sans-IO (fuzzable) ; seuls l’I/O, air-config, air-account sont au pilote.

Références : RFC 4251 (architecture), RFC 4252 (authentification SSH), RFC 8709 (ssh-ed25519), CVE-2023-48795 (Terrapin / strict-KEX).