--- status: reviewed last_updated: 2026-10-28 --- # Auth: identity is the family's, not SSH's ## The model One resolution path exists on every surface: a **verified key's fingerprint → alkcall `IdentityProvider::resolve_from_fingerprint` → `Identity`**. russh performs all cryptographic work (ADR-002 pin): `auth_publickey` fires *after* signature and ownership verification, and invalid/malformed signatures produce `USERAUTH_FAILURE` before any handler hook runs (POC-1/POC-2 facts). alkssh never sees or verifies a signature; its auth surface is *dispositional* (ADR-004). ## Server direction ### The bridge 1. `auth_publickey(user, VerifiedPublicKey)` fires (post-verification; the 0.65.0 shape has the key as the `.public_key` field, and the client's signature `.algorithm` is exposed for optional hardening). 2. alkssh computes the fingerprint via the `ssh-key` re-export (`HashAlg::Sha256`): `SHA256:` + unpadded base64 over the standard alphabet (`ssh-keygen -lf` byte-format; not the url-safe alphabet). The string format is a **contract invariant** (ADR-004): parity with assembly-registered values is proven against independent issuers (`ssh-keygen -lf`; POC-1 §1, pinned by the committed vectors in `tests/vectors/`). 3. `resolve_from_fingerprint` → `Some(identity)` → accept; `None` → reject, fail-closed; the resolved-identity-`None` path never creates a "weakly authorized" session. 4. On success the identity rides the session (`auth_succeeded`): for mapped channels it propagates through the alkcall transport (`set_identity`, once) so `AccessControl::check(peer_identity)` gates every mapped open; the **visible-surface invariant** holds in the SSH direction with no SSH-shaped exception. POC-3 §C.3 observed the propagation live (the establisher witness). ### Dispositions (ADR-004, summarized) `publickey` is the only implemented method, and the only accepted key algorithm is **ed25519**, so `auth_publickey` declines non-ed25519 public keys explicitly (the family's ed25519-only posture, ADR-002 decision 4; one key algorithm carries the family's identity: the same ed25519 keys behind alktls's self-signed certs, iroh, and the alknet rewrite). `auth_publickey_offered` accepts-and-delegates (keeping `max_auth_attempts` honest; the hook does not consume an attempt, POC-1 verified); `auth_none` / `auth_password` / `auth_keyboard_interactive` / `auth_openssh_certificate` reject explicitly (at this pin russh's defaults already reject; the explicit implementations are the family contract, since a pin bump could change defaults silently). `Config::methods` advertises `{publickey}`, exactly what is implemented. The username is **not identity and carries no semantics** (OQ-03 resolved); log exposure is deployment policy. `max_auth_attempts` defaults `10` (the OpenSSH `none`-probe consumes one, POC-1 §4). ## Client direction ### Key custody (ADR-005) Held keys are bearer-token-class secrets: **vault-encrypted at rest (alkvault `EncryptedData`, OpenSSH-PEM inner), decrypted at use, zeroized ownership at the adapter seam**; never env vars, never plaintext at rest, never persisted after use. The vault is a secret store on this path, not a key generator, not a custodian, and never involved in resolving *peers'* fingerprints. Failure modes are all fail-closed with distinct errors: locked / absent / decryption failure / unknown `key_version`, and rotation preserves key identity. The same model covers **host keys** (decrypt-at-construction into the server's `Config::keys`; locked vault → **no server is constructed**; POC-2 §5). ### Host-key policy (OQ-05 resolution; the client-side trust contract) Two cases, one seam; the crate is **policy-blind**: 1. **Known beforehand** (anchors exist; family-built runners mint their host key at assembly time): assembly-supplied pins, with a **hard decline on mismatch** at `check_server_key`. 2. **Unknown beforehand** (vanilla `sshd` on rental fleets; no anchor until contact): the crate surfaces (host, port, presented fingerprint) to an **assembly-provided `HostKeyPolicy`**, which chooses prompt-and-record, provider-metadata match, learn-and-record, or accept-and-continue (the ephemeral-fleet shape). The OpenSSH "add to known_hosts?" moment becomes assembly policy. Crate rules: default policy is fail-closed (reject unknown); the crate never decides trust and never hard-codes a trust model, so accept-all is not a built-in crate option, but a permissive case-2 policy is the assembly's to choose where its fleet model calls for it (alkminer's accept-all for rental fleets is that shape, made deliberately: fresh container per rent → fresh host key, so no pin can exist beforehand; the security anchor there is hub-held per-instance dial keys (ADR-005's custody model) plus what rides the tunnels, not host pins). ## What is out of scope - Password / keyboard-interactive anywhere (server: declined; client: non-goal until a use case). - OpenSSH certificate auth (`auth_openssh_certificate` declined unconditionally; OQ-04 resolved: no CA-trust story exists in the family identity model; future support is new-ADR territory with the upstream identity-model work as its prerequisite). - ssh-agent integration/forwarding (external signer processes are out of family scope; the vault is the custody story). - TOFU *as crate behavior*, since policy lives at the assembly: the crate is policy-blind (above). ## Open Questions None open or deferred; see [open-questions.md](open-questions.md) (all of OQ-01…13 resolved). Doc-local contract anchors: OQ-03 (the username disposition, §Dispositions), OQ-04 (the certificate decline, §out-of-scope), OQ-05 (the host-key policy seam, §Host-key policy), OQ-06 / OQ-07 (custody boundaries, ADR-005). ## References - ADR-004 (dispositions + parity invariant), ADR-005 (custody), ADR-002 (the pinned russh/auth pipeline facts) - `docs/research/auth-bridge.md` (the surface mapping + host-key refinement), `poc-1-auth-bridge.md` (§1–4), `poc-2-vault-keys.md` (§2–5), `reverse-runner.md` (lessons 5–6) - alkcall ADRs 002/003/006 (identity surface) and 010 (secret-material flow, alkhttp ADR-014's alkcall analog), alkhttp ADR-073 (one backend behind every door surface), family ADR-002 decision 5 / family ADR-003 decision 2