phase 2: task decomposition — 8-task port graph

- crate-init: skeleton, feature gates (ADR-003), TlsError (ADR-002)
- three parallel foundation ports: identity types, fingerprint, pem+signing
- port-server / port-client in parallel (each consumes the foundation;
  client also carries credentials.rs per ADR-005 co-location)
- integration-suite: invariant pins + feature-matrix + seam round-trips
- review-impl: spec-conformance gate — the API-freeze point before the
  alknet rewrite consumes the crate
- validated: taskgraph topo (5 generations), no cycles, critical path
  len 5, risk-path 1.45; medium risk concentrated on the two broad
  ports + the suite (each a diff against a fixed extraction source,
  not open-ended work)
This commit is contained in:
2026-09-10 06:32:44 +00:00
parent d74a27f764
commit 05b3832c88
8 changed files with 706 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
---
id: port-client
name: Port client side — TlsClientConfig, verifier selection, client auth (src/client.rs)
status: pending
depends_on: [port-identity-types, port-fingerprint, port-pem-signing]
scope: broad
risk: medium
impact: phase
level: implementation
tags: [client, verifiers, port, invariants]
---
## Description
Port `src/client.rs` from alknet-tls per the ADRs: `TlsClientConfig`,
`select_server_verifier`, `build_client_auth`,
`RawKeyClientCertResolver`, `NoClientCertResolver`,
`FingerprintPinVerifier`, `load_platform_root_cert_store`.
### The API deltas (from the extracted code)
- Types rewire: `ConnectionCredentials`/`RemoteIdentity` come from this
crate's `credentials.rs` (ADR-005); fingerprint helpers from this
crate's `fingerprint.rs`; the identity from this crate's
`identity.rs`.
- `for_noq(self)` replaces `for_quinn(self)` (consuming — ADR-004);
`into_rustls_config(self)` unchanged.
### Credentials module (small, rides here)
`src/credentials.rs` — port `ConnectionCredentials` (`local_identity:
Option<TlsIdentity>`, `remote_identity: Option<RemoteIdentity>`, the
builder methods) and `RemoteIdentity` (`fingerprint: String`) from
alknet-core `credentials.rs`, including the load-bearing doc comments
(`None` is the public-X.509-endpoint state, not a placeholder — the
`Option`s drive verifier selection). This task owns the module because
the verifier selection is the semantic consumer of the bundle; the
co-location prevents semantic drift (ADR-005).
### The invariants / selection matrix (test-pinned here)
- Every config: `enable_early_data = true` (the client half of the
0-RTT invariant — ADR-001; pinned as a test) and the aws-lc-rs
provider.
- Verifier selection matrix: `Some(fingerprint)`
`FingerprintPinVerifier`; `None``WebPkiServerVerifier` over the
root store. Fail-closed for unknown raw-key remotes is structural
(the CA verifier is what `None` installs; a raw-key remote cannot
satisfy it — the failure manifests at handshake, never via
`TlsError`).
- Root-store fallback: platform certs first; if empty, merge
`webpki-roots` (never empty); native-certs load errors logged, not
returned. Testable by asserting the merge path (construct with an
empty platform store simulation if the API permits; otherwise assert
the fallback branch by construction — `load_platform_root_cert_store`
returns a non-empty store).
- Client-auth presentation: RawKey → RFC 7250 SPKI cert with
`only_raw_public_keys()` auto-detected from the DER; X509 → loaded
chain via `CertifiedKey::from_der` (errors → `TlsError::Rustls` per
ADR-002); `SelfSigned`/`None``NoClientCertResolver`
(`has_certs() == false`); `Acme``TlsError::AcmeConfig`.
- `FingerprintPinVerifier`: pin match / mismatch on
`verify_server_cert`; TLS 1.2/1.3 signature verification routes
Ed25519 SPKI through `verify_tls13_signature_with_raw_key`; a
mismatched pin fails verification.
## Work
1. Port `credentials.rs` (types + builders + doc comments).
2. Port `client.rs`; apply the deltas; rewire imports.
3. Port the extracted in-module tests.
4. Add the selection-matrix integration test (below).
## Verification
- [ ] Selection-matrix test: all four client-auth presentations × both
verifier branches construct and select the expected resolver
types (inspect via the config's client-auth/verifier state where
the API permits; otherwise assert construction success/error
kind per cell)
- [ ] `Acme` local identity → `TlsError::AcmeConfig`
- [ ] `enable_early_data == true` pinned
- [ ] Root store non-empty (fallback exercised)
- [ ] FingerprintPinVerifier unit tests ported (pin match, mismatch,
raw-key signature routing)
- [ ] `cargo test` (default), `cargo test --all-features`,
`cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
## Acceptance Criteria
- [ ] `for_noq(self)` / `into_rustls_config(self)` per ADR-004
- [ ] The selection matrix has no fourth path (fail-closed structural)
- [ ] `lib.rs` re-exports the client surface + `ConnectionCredentials`
/ `RemoteIdentity`
## References
- docs/architecture/client.md (the normative doc)
- docs/architecture/decisions/002-tlserror-shape.md, 004, 005
- alknet ADR-034 §3, ADR-088 §5, ADR-091
- Prior art: `/workspace/@alkdev/alknet/crates/alknet-tls/src/client.rs`,
`/workspace/@alkdev/alknet/crates/alknet-core/src/credentials.rs`
## Notes
> Agent fills this during implementation.
## Summary
> Agent fills this on completion.