--- id: w1-fingerprint name: "Wave 1: fingerprint computation (the contract invariant)" status: completed depends_on: [w1-manifest] scope: narrow risk: low impact: project level: implementation --- ## Description Land the fingerprint computation the auth bridge (server) and the host-key policy path (client) both consume; this is ADR-004 decision 1's contract invariant in code: - Compute a key's fingerprint via the `ssh-key` re-export (`HashAlg::Sha256`): the `SHA256:` + **unpadded url-safe base64** OpenSSH format. The string format is a **contract invariant**: registration strings from any standard issuer (`ssh-keygen -lf`, alkcall-side computation) must compare equal to what this crate computes; parity is proven, not assumed (POC-1 §1). - The function takes the russh/ssh-key public-key type and returns the string; it is the single fingerprint path in the crate (no second formatting anywhere, since a drift here breaks every identity resolution silently). - Parity tests against independent-issuer vectors: static ed25519 test keys with their `ssh-keygen -lf`-recorded expected fingerprints (the POC-1 §1 checkpoint shape). The vectors are committed test fixtures (throwaway test keys; no secret material). ## Acceptance Criteria - [x] Single fingerprint function via `HashAlg::Sha256`; `SHA256:` + unpadded url-safe base64 output - [x] Parity tests against `ssh-keygen -lf`-recorded vectors pass (independent-issuer format parity proven) - [x] No second fingerprint-formatting path exists in `src/` - [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check` pass ## References - docs/architecture/decisions/004-auth-dispositions.md (decision 1: the format invariant) - docs/architecture/auth.md §The bridge (step 2) - docs/research/poc-1-auth-bridge.md §1 (the parity checkpoint) - docs/research/auth-bridge.md §The mapping (step 2) ## Notes > Design: `src/fingerprint.rs` exposes `fingerprint(&public_key) -> > String`, the thin wrapper over the `ssh-key` re-export > (`key.fingerprint(HashAlg::Sha256).to_string()`; russh 0.65 > re-exports `ssh_key::{PublicKey, HashAlg}` via `russh::keys`), so > the *computation and formatting stay ssh-key's own code* and parity > is structural, not re-implemented. `lib.rs` re-exports > `alkssh::fingerprint`; a grep over `src/` confirms no second > formatting path (doc mentions only). The russh 0.65 handler shape > feeds exactly this type: `VerifiedPublicKey.public_key: & > ssh_key::PublicKey`, with the auth bridge (wave 2, > w1-host-key-policy) passing that field straight through. > > **Fact-check finding (doc correction landed):** the ADR-004 / > auth.md / task wording "unpadded **url-safe** base64" is a misnomer. > The ssh-key crate's `Fingerprint` Display emits `SHA256:` + > *unpadded standard* base64 (base64ct `Base64Unpadded` > = the standard `+/` alphabet), and OpenSSH does the same, as the > committed `ssh-keygen -lf` vectors contain `+` and `/`. The binding > invariant was never "url-safe"; it is byte-parity with standard > issuers, which POC-1 proved and the committed vectors now pin. > Corrected the wording in ADR-004 and `auth.md` (the two places that > spell the format); the parity contract itself is unchanged on both > sides. > > Vectors: 4 throwaway ed25519 keys generated with local ssh-keygen > (OpenSSH 9.6p1), blobs committed under `tests/vectors/*.pub`, > expected fingerprints byte-verbatim from `ssh-keygen -lf` recorded > in `tests/vectors/fingerprints.txt` (provenance header included) and > inlined in `tests/fingerprint_parity.rs`. Tests cross-check the > inlined vectors against the committed files and manifest so a stale > fixture edit cannot silently pass. No secret material committed > (private halves never left the tmpdir and were discarded). ## Summary > Landed `src/fingerprint.rs`, the crate's single fingerprint path > (`alkssh::fingerprint`), delegating computation/formatting to the > ssh-key re-export behind russh 0.65's keys module with > `HashAlg::Sha256`. Parity proven, not assumed: 4 committed > ed25519 fixtures with `ssh-keygen -lf` (OpenSSH 9.6p1) recorded > fingerprints reproduce byte-identically (`tests/ > fingerprint_parity.rs`, incl. shape/format-of-registered-string > assertions). Found and corrected a misnomer in the docs: the format > is unpadded *standard* base64, not url-safe (OpenSSH fingerprints > contain `+/`; byte-parity with `ssh-keygen` is and always was the > contract). Verified: `cargo test` (10/10 incl. all-features), > `cargo clippy --all-targets -- -D warnings` (default + > all-features), `cargo fmt --check`, `cargo doc --no-deps` clean; > `taskgraph validate` clean.