Renames the written-usage rule into practice across the contract surface: docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files, taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the .opencode/agents/ prompts. Every replacement is a sense-preserving rewrite (colons, semicolons, commas, parentheses, sentence splits, varied), never a mechanical swap; no spaced hyphens were substituted. Enum fields, frontmatter, tables, wire data, and cited identifiers survive verbatim; en dashes inside numeric ranges keep their meaning. Verification: taskgraph validate (43 tasks), repo grep clean (single exempt hit: the verification command's own search pattern in AGENTS.md), cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
101 lines
4.6 KiB
Markdown
101 lines
4.6 KiB
Markdown
---
|
|
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. |