generation 2: port identity types, fingerprint, pem + signing
port-identity-types (src/identity.rs): - TlsIdentity (four variants), Ed25519SecretKey, AcmeDirectory ported verbatim from alknet-core config.rs; OQ-TLS-02 + server-only docs on the variants; 9 in-module tests incl. Debug-no-leak - dep decision: rand_core 0.6 (+getrandom) with ed25519-dalek rand_core feature, NOT rand (lockfile rand 0.10/rand_core 0.10 traits are incompatible with ed25519-dalek 2.2's CryptoRngCore); rand stays out of the tree entirely port-fingerprint (src/fingerprint.rs): - fingerprint_from_cert_der, extract_ed25519_raw_key_from_spki, DerParser ported verbatim; production code sha2 + manual DER (+hex for the normalized formats) - 16 tests: 7 ported + 9 new malformed-DER edges (the extraction had none despite the invariant naming them) - empty-input behavior: matches extraction's actual code (always Some via the SHA-256 fallback); doc records the deviation from the stale None claim port-pem-signing (src/pem.rs, src/signing.rs): - load_cert_chain/load_private_key remapped to TlsError::CertLoad per ADR-002; InvalidData no-key path kept - Ed25519SigningKey rewired to crate::identity::Ed25519SecretKey (the one intentional change); rcgen PEM round-trip test added lib.rs re-export block: fingerprint + pem + signing + identity lines landed; server/client/credentials pending their port tasks Verification: cargo test (36), cargo test --all-features (37), clippy -D warnings (default+all-features), fmt --check, feature checks (noq/tcp/acme) — all green
This commit is contained in:
+46
-10
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: port-fingerprint
|
||||
name: Port fingerprint helpers + DER parser (src/fingerprint.rs)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [crate-init]
|
||||
scope: narrow
|
||||
risk: low
|
||||
@@ -44,20 +44,20 @@ the private manual DER parser (`DerParser`).
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Ported tests green (`cargo test fingerprint`)
|
||||
- [ ] Round-trip: an Ed25519 SPKI built by `signing.rs`'s
|
||||
- [x] Ported tests green (`cargo test fingerprint`)
|
||||
- [x] Round-trip: an Ed25519 SPKI built by `signing.rs`'s
|
||||
`spki_public_key()` yields `ed25519:<hex>` matching the source
|
||||
key (integration assert — this pins the normalization across
|
||||
the raw-key paths)
|
||||
- [ ] Malformed-DER inputs yield `None` / SHA fallback (ported edge
|
||||
- [x] Malformed-DER inputs yield `None` / SHA fallback (ported edge
|
||||
tests)
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
- [x] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] The module compiles without `rustls` in production code
|
||||
- [ ] Both normalized fingerprint formats are test-pinned
|
||||
- [ ] `lib.rs` re-exports the two public functions
|
||||
- [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
|
||||
|
||||
@@ -68,8 +68,44 @@ the private manual DER parser (`DerParser`).
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
- **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
|
||||
|
||||
> Agent fills this on completion.
|
||||
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.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: port-identity-types
|
||||
name: Port identity types — TlsIdentity, Ed25519SecretKey, AcmeDirectory (src/identity.rs)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [crate-init]
|
||||
scope: narrow
|
||||
risk: low
|
||||
@@ -51,19 +51,19 @@ new surface beyond ADR-005's list without noting it).
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] `cargo test -p alktls identity` passes (ported tests green)
|
||||
- [ ] `as_bytes`/`from_bytes` round-trip asserted
|
||||
- [ ] `AcmeDirectory` URLs asserted (production + staging + custom)
|
||||
- [ ] `Debug` output contains no key bytes (test: format then assert
|
||||
- [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)
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
- [x] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `src/identity.rs` holds exactly the ADR-005 type set; no auth
|
||||
- [x] `src/identity.rs` holds exactly the ADR-005 type set; no auth
|
||||
layer types present
|
||||
- [ ] The byte surface matches the load-bearing list above verbatim
|
||||
- [ ] `lib.rs` re-exports the three types
|
||||
- [x] The byte surface matches the load-bearing list above verbatim
|
||||
- [x] `lib.rs` re-exports the three types
|
||||
|
||||
## References
|
||||
|
||||
@@ -76,6 +76,84 @@ new surface beyond ADR-005's list without noting it).
|
||||
|
||||
> 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.
|
||||
> 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.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: port-pem-signing
|
||||
name: Port PEM loading + Ed25519 signing helper (src/pem.rs, src/signing.rs)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [crate-init]
|
||||
scope: narrow
|
||||
risk: low
|
||||
@@ -50,16 +50,16 @@ Port the two shared helper modules from alknet-tls per ADR-005/ADR-006:
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] `cargo test pem signing` green (ported + round-trip tests)
|
||||
- [ ] Signature length is 64 bytes; scheme is ED25519
|
||||
- [ ] PEM error paths return `TlsError::CertLoad` (not panics)
|
||||
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
- [x] `cargo test pem signing` green (ported + round-trip tests)
|
||||
- [x] Signature length is 64 bytes; scheme is ED25519
|
||||
- [x] PEM error paths return `TlsError::CertLoad` (not panics)
|
||||
- [x] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Both modules match the extracted source modulo the identity-type
|
||||
- [x] Both modules match the extracted source modulo the identity-type
|
||||
import and error mapping
|
||||
- [ ] `lib.rs` re-exports `load_cert_chain`, `load_private_key`,
|
||||
- [x] `lib.rs` re-exports `load_cert_chain`, `load_private_key`,
|
||||
`Ed25519SigningKey`
|
||||
|
||||
## References
|
||||
@@ -73,6 +73,22 @@ Port the two shared helper modules from alknet-tls per ADR-005/ADR-006:
|
||||
|
||||
> Agent fills this during implementation.
|
||||
|
||||
- Build initially failed because `src/identity.rs` was still a stub
|
||||
(the `port-identity-types` task was still `pending` when my retry
|
||||
budget ran out); verified the modules in a /tmp scratch copy with a
|
||||
faithful stand-in of `Ed25519SecretKey` (exact ported surface), then
|
||||
re-ran everything green in the workspace once identity.rs landed.
|
||||
- Error mapping: `fs::read` failure → `TlsError::CertLoad`
|
||||
(via `#[from] io::Error` — `?` / `map_err(TlsError::CertLoad)`);
|
||||
`rustls_pemfile` collect error wrapped with `io::Error::other` then
|
||||
`CertLoad`; no-key-found keeps `io::ErrorKind::InvalidData` then
|
||||
`CertLoad`. Extraction's `TlsError::Io` sites all became `CertLoad`.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
> Ported `src/pem.rs` (`load_cert_chain`, `load_private_key`) and
|
||||
> `src/signing.rs` (`Ed25519SigningKey` over
|
||||
> `crate::identity::Ed25519SecretKey`) from alknet-tls; error paths
|
||||
> remapped from the extraction's `TlsError::Io` to `TlsError::CertLoad`
|
||||
> per ADR-002. 9 module tests (incl. the rcgen round-trip) green;
|
||||
> `lib.rs` re-exports added.
|
||||
Reference in New Issue
Block a user