--- id: port-identity-types name: Port identity types — TlsIdentity, Ed25519SecretKey, AcmeDirectory (src/identity.rs) status: completed depends_on: [crate-init] scope: narrow risk: low impact: component level: implementation tags: [identity, types, port] --- ## Description Port the identity types from alknet-core `config.rs` into `src/identity.rs` per ADR-005: `TlsIdentity` (four variants: `X509 { cert, key }`, `RawKey(Ed25519SecretKey)`, `SelfSigned`, `Acme { domains, cache_dir, directory, contact }`), `Ed25519SecretKey`, and `AcmeDirectory` (`Production` / `Staging` / `Custom(String)` with `url()`). ### The load-bearing surface (do not change) - `Ed25519SecretKey`: `generate()`, `from_bytes(&[u8; 32])`, `as_bytes() -> [u8; 32]`, `public() -> ed25519_dalek::VerifyingKey`, `sign(&self, message) -> ed25519_dalek::Signature`. The byte surface is what iroh's `iroh_base::SecretKey` consumes (ADR-005; verified against iroh 1.1 in Phase 0). Backed by `ed25519_dalek::SigningKey`. - `Debug` must NOT leak key material (the extracted type formats as `Ed25519SecretKey(..)` — keep it). - `AcmeDirectory::Production` / `Staging` URLs are pinned strings (Let's Encrypt production + staging) — assert them in tests. - Doc comments carry the OQ-TLS-02 resolution: `SelfSigned` on the client path presents nothing (documented on the variant). ### What moves vs stays `TlsIdentity`/`Ed25519SecretKey`/`AcmeDirectory` move here wholesale. `PeerEntry`, `AuthPolicy`, `Identity`, fingerprint → peer-id resolution stay OUT (auth layer — ADR-005's carve-out). No `serde` derives unless the extracted code has them (check; do not add new surface beyond ADR-005's list without noting it). ## Work 1. Port the three types + their inherent methods from `crates/alknet-core/src/config.rs`. 2. Port the associated in-module tests (`generate`/`from_bytes` round-trip, `AcmeDirectory` URL assertions). 3. Add the Debug-no-leak test if not present in the extracted tests. ## Verification - [x] `cargo test -p alktls identity` passes (ported tests green) - [x] `as_bytes`/`from_bytes` round-trip asserted - [x] `AcmeDirectory` URLs asserted (production + staging + custom) - [x] `Debug` output contains no key bytes (test: format then assert hex key absent) - [x] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check` ## Acceptance Criteria - [x] `src/identity.rs` holds exactly the ADR-005 type set; no auth layer types present - [x] The byte surface matches the load-bearing list above verbatim - [x] `lib.rs` re-exports the three types ## References - docs/architecture/decisions/005-config-types-move-into-alktls.md - docs/architecture/decisions/006-module-layout-and-tests.md (module map) - Prior art: `/workspace/@alkdev/alknet/crates/alknet-core/src/config.rs` (lines 33–80: `Ed25519SecretKey`, `TlsIdentity`, `AcmeDirectory`) ## Notes > Agent fills this during implementation. ### Decisions / deviations 1. **rand decision: `rand_core = "0.6"` + ed25519-dalek's `rand_core` feature — NOT `rand`.** The alktls lockfile's transitive `rand` is 0.10.2 (`rand_core 0.10.1`); ed25519-dalek 2.2.0's `SigningKey::generate` needs `rand_core 0.6.4`'s `CryptoRngCore` (its Cargo.toml pins `rand_core = "0.6.4"`, optional). rand 0.10's `OsRng` only implements rand_core 0.10 traits → incompatible, so `rand::rngs::OsRng` would not compile against ed25519-dalek 2.2.0. Additionally, `generate` is gated `#[cfg(any(test, feature = "rand_core"))]` inside ed25519-dalek — the non-default `rand_core` feature must be enabled (alknet-core does the same: `rand = "0.8"` + `ed25519-dalek = { features = ["rand_core"] }`). Fix: `ed25519-dalek = { version = "2", features = ["rand_core"] }` + `rand_core = { version = "0.6", features = ["getrandom"] }` in `[dependencies]`, using `rand_core::OsRng`. `rand_core 0.6.4` + `getrandom 0.2.17` were already in the lockfile transitively — no version churn; `rand` stays out of the dep tree (leaner than the extraction, which depended on `rand 0.8`). 2. **`zeroize::ZeroizeOnDrop` not ported (deliberate omission).** The extracted `Ed25519SecretKey` had `impl zeroize::ZeroizeOnDrop`, but ADR-005's type list doesn't mention it and the task says not to add surface beyond the ADR's list. Note: ed25519-dalek 2.2.0's default features include `zeroize`, and its `SigningKey` itself implements `ZeroizeOnDrop` internally — the wrapped key material is still zeroized on drop through the inner type. Adding the crate-level impl would require a `zeroize` dep for zero behavioral gain; can be added later if the vault (rewrite's config side) wants the explicit marker impl. 3. **serde derives: none ported.** The extracted three types carry no serde derives (checked `config.rs` lines 32–98) — nothing to add, consistent with "no new surface beyond ADR-005's list". 4. **`TlsIdentity::Acme`'s doc comment added** (task instruction): server-only, config error (`TlsError::AcmeConfig`) on the client path — per ADR-001's identity model and the client spec's presentation table. 5. **Sibling-task fix (tree-state deviation, not this port).** The working tree contained port-pem-signing's uncommitted work (`pem.rs`, `signing.rs`, `lib.rs` re-exports). Its `signing.rs` test module was missing `use crate::{load_cert_chain, load_private_key};` and `pem.rs` lacked a trailing newline, which broke `cargo test` / `cargo fmt --check` for the whole crate. Two mechanical fixes applied (test-only `use` line; trailing newline) so whole-crate verification could run; no production code touched. ## Summary > Agent fills this on completion. Brief description of what was > implemented, files changed, and any follow-up needed. ### What landed - `src/identity.rs`: the three ADR-005 types ported verbatim from alknet-core `config.rs` lines 32–98 — `Ed25519SecretKey` (exact byte surface: `generate` / `from_bytes` / `as_bytes` / `public` / `sign` via in-method `Signer` import; custom `Debug` with `finish_non_exhaustive`), `AcmeDirectory` (pinned production/staging URLs), `TlsIdentity` (four variants; doc comments carry the OQ-TLS-02 resolution on `SelfSigned` and the server-only note on `Acme`). - Tests: all five extracted `Ed25519SecretKey` tests ported (round-trip, sign-verifies, tampered-reject, Debug-no-leak with hex assertion, public-length) + the two extracted `TlsIdentity` construct tests + a new `AcmeDirectory` URL-pinning test + an `Acme` construct test. - `Cargo.toml`: `ed25519-dalek` gains the `rand_core` feature; `rand_core 0.6` (feature `getrandom`) added as a dependency. - `src/lib.rs`: `pub use identity::{AcmeDirectory, Ed25519SecretKey, TlsIdentity};` added to the incremental re-export block. ### Verification - `cargo test` ✓ (20 passed); `cargo test -p alktls identity` ✓ (9 passed); `cargo clippy --all-targets -- -D warnings` ✓; `cargo fmt --check` ✓; `cargo check --all-features` ✓; `cargo test --all-features` ✓ (21 passed); `cargo clippy --all-targets --all-features -- -D warnings` ✓. ### Follow-up - None for this module. port-client completes the re-export block.