--- id: port-fingerprint name: Port fingerprint helpers + DER parser (src/fingerprint.rs) status: completed depends_on: [crate-init] scope: narrow risk: low impact: component level: implementation tags: [fingerprint, port] --- ## Description Port the fingerprint module from alknet-core (`crates/alknet-core/src/fingerprint.rs`) into `src/fingerprint.rs` per ADR-005: `fingerprint_from_cert_der(&[u8]) -> Option` (`ed25519:` for RFC 7250 Ed25519 SPKI, `SHA256:` for anything else — the normalized formats from alknet ADR-030 §6), `extract_ed25519_raw_key_from_spki(&[u8]) -> Option<[u8; 32]>`, and the private manual DER parser (`DerParser`). ### Invariants - Production code stays `sha2` + manual DER — no `rustls::` imports in the module's non-test code (the extracted module's purity; ADR-006). - The Ed25519 OID constant is `[0x2b, 0x65, 0x70]` (`1.3.101.112`); the SPKI BIT STRING is 33 bytes (one unused-bits `0x00` + the 32-byte key). These are the RFC 7250 wire facts the parser encodes. - `extract_ed25519_raw_key_from_spki` returns `None` for non-Ed25519 SPKI / malformed DER / X.509 certs; `fingerprint_from_cert_der` falls back to SHA-256-hashing the full DER (returns `None` only for empty input). - Port the extracted in-module DER parser tests verbatim (they cover the malformed-input edges: truncated headers, long-form lengths, wrong OIDs, bad BIT STRING lengths). ## Work 1. Port the module wholesale (it is self-contained). 2. Port the extracted tests; assert `ed25519:` and `SHA256:` normalization on representative inputs. 3. Confirm no `rustls::` import outside `#[cfg(test)]`. ## Verification - [x] Ported tests green (`cargo test fingerprint`) - [x] Round-trip: an Ed25519 SPKI built by `signing.rs`'s `spki_public_key()` yields `ed25519:` matching the source key (integration assert — this pins the normalization across the raw-key paths) - [x] Malformed-DER inputs yield `None` / SHA fallback (ported edge tests) - [x] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check` ## Acceptance Criteria - [x] The module compiles without `rustls` in production code - [x] Both normalized fingerprint formats are test-pinned - [x] `lib.rs` re-exports the two public functions ## References - docs/architecture/decisions/005-config-types-move-into-alktls.md - docs/architecture/decisions/006-module-layout-and-tests.md - alknet ADR-030 §6 (fingerprint normalization — the reference) - Prior art: `/workspace/@alkdev/alknet/crates/alknet-core/src/fingerprint.rs` ## Notes - **Empty-input discrepancy (resolved against the code):** the task description said `fingerprint_from_cert_der` "returns `None` only for empty input"; the extraction's *doc comment* claims the same, but its *code* has no empty-input check — empty input falls through to the SHA-256 fallback and returns `Some("SHA256:e3b0c442…")` (the hash of the empty slice). The port matches the extraction's actual behavior (always `Some`); the doc comment on the ported function records the deviation so the stale `None` claim is not propagated. - The extraction's test list (7 tests) contained no malformed-DER edge tests despite the task invariant naming them — the malformed-DER suite was written fresh for this port: truncated headers, wrong outer tag, long-form length edges (0x80 indefinite, overlong, >4 bytes, truncated header), a well-formed long-form length acceptance case, wrong-OID-in-valid-SPKI, bad BIT STRING lengths (32/34 bytes, non-zero unused-bits), and malformed-DER SHA fallback. - Tests construct the raw key bytes directly (fixed test-key arrays) and build SPKIs via `rustls::sign::public_key_to_spki` — no dependency on `identity.rs` (concurrent-port constraint held). - Production code uses `hex::encode` (ported verbatim), so `hex` was added to `[dependencies]` (it was dev-only; alknet-core does the same). - Ported test `fingerprint_from_ed25519_spki_matches_iroh_format` compares against `format!("ed25519:{}", hex::encode(raw_key))` per the task (the extraction compared a separately generated key; same shape, key sourced directly instead). ## Summary Ported `src/fingerprint.rs` wholesale from `/workspace/@alkdev/alknet/crates/alknet-core/src/fingerprint.rs`: `fingerprint_from_cert_der`, `extract_ed25519_raw_key_from_spki`, private `DerParser` (`read_tlv`, `decode_header`, `expect_sequence`, `expect_oid`, `expect_bit_string`). Production code stays `sha2` + manual DER + `hex`; the only `rustls::` use is the test-only SPKI builder. 16 tests green (7 ported from the extraction with the `crate::config::Ed25519SecretKey::generate()` dependency replaced by fixed key arrays, 9 new/extended edges). `lib.rs` re-exports both public functions (concurrent port lines preserved). Verification: `cargo test` (36 pass), `cargo test --all-features` (37 pass), `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`, `cargo check --all-features` — all clean.