--- status: draft last_updated: 2026-10-27 --- # ADR-004: Auth dispositions — publickey-only, fail-closed, fingerprint identity ## Status **Accepted** — 2026-10-26. Evidence-backed (POC-1 end to end, POC-2's signature-pipeline facts); open to amendment only via a new ADR. Not a ledger item (no wire format changes), but the decline matrix is contract territory: every decline is tested on both sides of every feature. ## Context The family identity model has exactly one SSH-shaped resolution path: a verified key's fingerprint → alkcall `IdentityProvider:: resolve_from_fingerprint` → `Identity` (`auth-bridge.md`). russh 0.64.1 performs the cryptographic verification itself — `auth_publickey` fires *after* signature and ownership verification (POC-1 observed), and invalid/malformed signatures produce `USERAUTH_FAILURE` before any handler hook fires (POC-2). alkssh therefore never touches signature material; its auth surface is dispositional. The fail-closed posture (family ADR-003 decision 3) applies to auth twice: which methods exist, and what unauthenticated attempts receive. ## Decision 1. **`auth_publickey` is implemented — the only method.** Handler: fingerprint (`ssh-key` re-export, `HashAlg::Sha256`, `SHA256:` + unpadded url-safe base64) → `resolve_from_fingerprint` → `Some` → accept with the resolved identity; `None` → reject. The fingerprint *string format* is a contract invariant: registration strings from any standard issuer (ssh-keygen, alkcall-side computation) must compare equal — POC-1 proved parity against `ssh-keygen -lf` output. 2. **`auth_publickey_offered` is accepted-and-delegated.** The probe hook (key seen, ownership unverified) accepts the offer and lets `auth_publickey` decide — which keeps `max_auth_attempts` counting honest (confirmed: the hook does not consume an attempt). 3. **Every other method is explicitly declined, including when inert at the pin.** `auth_none`, `auth_password`, `auth_keyboard_interactive`, `auth_openssh_certificate` each get explicit reject implementations. At russh 0.64.1 the defaults already reject (POC-1's correction of the 0.5x-era assumption) — the explicit implementations are family contract, not redundancy: the pin is a one-way door and a future bump could silently change defaults (an auth-posture change would otherwise be invisible). The certificate decline is the standing disposition (OQ-04 tracks whether a CA-trust use case ever arrives). 4. **Honest advertisement.** `Config::methods` advertises exactly what is implemented: `{publickey}` in every current configuration. OpenSSH honors the subset (POC-1: non-advertised methods are never attempted), so the advertisement itself is part of the fail-closed contract. 5. **The username is not identity and carries no semantics.** Keys resolve independently of the username string (POC-1: `ssh anything@host` authenticates identically). Whether a username appears in logs is deployment policy, outside the crate (OQ-03). 6. **Bounded auth resources.** `max_auth_attempts` is assembly-configurable with a default sized for OpenSSH's counting `none`-probe: POC-1 measured that the probe consumes one attempt, so a default of `10` leaves `IdentitiesOnly`-style clients at least nine real key attempts; a small limit eats one attempt on every connection (recorded for the serve-config defaults). Constant-time rejection is inherited from `Config::auth_rejection_time` (russh-managed). ## Consequences - **Positive**: the entire auth surface is evidence-backed end to end (vanilla clients, both directions of failure); the decline matrix is small and fully testable per build; auth posture survives a russh bump that changes defaults. - **Negative**: none material. The explicit-declines cost a few handler lines per method — deliberately purchased immovability. - **Neutral**: password/keyboard-interactive exist client-side only as non-goals (`vision.md`) until a consuming use case arrives; the server never gains them without amending this ADR (a wire-visible capability, not a two-way door). ## References - `auth-bridge.md` (the mapping + disposition table + 0.64.1 default note) - `docs/research/poc-1-auth-bridge.md` §2–4, §OQ seeds 1–4 - `docs/research/poc-2-vault-keys.md` §"Server-side auth pipeline" (invalid signatures pre-hook) - Family ADR-003 (decisions 2, 3), alkcall ADR-002/003/006 (the `IdentityProvider` surface and its single-backend posture; alkhttp ADR-073's one-backend-per-door analog) - OQ-03 (resolved), OQ-04 (deferred)