Files
alktls/tasks/port-client.md
T
glm-5.3-flash 0cd565fc28 generation 3: port server + client — the two big ports
port-server (src/server.rs, 614 lines):
- TlsServerConfig with ADR-004 accessors: for_noq(&self) (borrow +
  inner-clone + NoqWrap via #[from]), for_tcp_tls(&self) adopted
  (infallible TlsAcceptor), rustls_config(&self) adopted
- acme_handle renamed per ADR-006; the defensive Acme arm returns
  TlsError::AcmeConfig (unreachable! eliminated)
- error sites remapped to typed variants (Rustls/SelfSigned/AcmeConfig)
- ACME path verbatim: DirCache + directory + contacts + resolver +
  acme-tls/1 + spawned event loop (tracing lines ported), returns
  immediately, detached handle
- 14 tests incl. the nine-scheme exact-list pin, ACME
  spawn/return/ALPN assertion (blackhole URL — no network I/O),
  no-feature AcmeConfig, for_tcp_tls/rustls_config round-trips

port-client (src/credentials.rs + src/client.rs):
- ConnectionCredentials/RemoteIdentity wholesale with load-bearing
  Option-semantics docs (None = public-X.509 state, Some = pin)
- verifier selection matrix + client-auth presentation matrix
  test-pinned at unit level; enable_early_data=true pinned;
  root-store fallback asserted non-empty
- FingerprintPinVerifier: pin match/mismatch + raw-key signature
  routing (verify_tls13_signature_with_raw_key) asserted
- resolvers/verifier made pub for the re-export block

Cargo.toml (two required deltas):
- noq feature gains aws-lc-rs: lockfile resolves noq-proto 1.3.0
  where ServerConfig::with_crypto is #[cfg(any(aws-lc-rs, ring))];
  ADR-003's TOML block was written against the 1.2 API. Amendment
  note recorded in tasks/port-server.md for the review-impl sync
- acme = [dep:rustls-acme, dep:futures] — ADR-006 already gates
  the futures dep on acme; the scaffold omitted it

lib.rs: re-export block complete (all eight modules)

Verification: cargo test 68, --all-features 75, --features noq 72,
clippy -D warnings, fmt --check, doc --no-deps (0 warnings) — green
2026-09-10 14:51:44 +00:00

176 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: port-client
name: Port client side — TlsClientConfig, verifier selection, client auth (src/client.rs)
status: completed
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
- [x] 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)
- [x] `Acme` local identity → `TlsError::AcmeConfig`
- [x] `enable_early_data == true` pinned
- [x] Root store non-empty (fallback exercised)
- [x] FingerprintPinVerifier unit tests ported (pin match, mismatch,
raw-key signature routing)
- [x] `cargo test` (default), `cargo test --all-features`,
`cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
## Acceptance Criteria
- [x] `for_noq(self)` / `into_rustls_config(self)` per ADR-004
- [x] The selection matrix has no fourth path (fail-closed structural)
- [x] `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.
- Verifier selection / client-auth presentation are inspected through
the `rustls::ClientConfig` Debug output (`ClientConfig` derives
`Debug` and embeds the verifier's debug name —
`FingerprintPinVerifier` / `WebPkiServerVerifier`) and the public
`client_auth_cert_resolver` field; the `verifier` field itself is
`pub(super)` at rustls 0.23.44, so the debug-string probe is the
structural assertion shape.
- `select_server_verifier`'s `WebPkiServerVerifier` build error maps to
`TlsError::VerifierBuild` via `#[from] rustls::client::VerifierBuilderError`
(the `rustls::webpki` module is private at 0.23.44; same type,
public path — lib.rs's variant doc already records this).
- The signature-routing unit tests construct `DigitallySignedStruct`
from its wire encoding through the doc-hidden
`rustls::internal::msgs` surface (`Codec::read`; `new` is
`pub(crate)`), then drive `verify_tls12_signature` /
`verify_tls13_signature` on the SPKI-as-`CertificateDer` exactly as
rustls presents it in an RFC 7250 handshake — pin match + signature
possession-proof both asserted.
- The `for_noq()` wrap relies on `TlsError::NoqWrap(#[from])`
(`NoInitialCipherSuite``?`), ADR-002/ADR-003.
- The extracted `TlsClientConfig`'s `#[allow(dead_code)]` is kept
(the server-side `rustls_config()` accessor shape is ADR-004's;
no consumer touches the field within this crate yet).
## Summary
> Agent fills this on completion.
Ported `src/credentials.rs` (wholesale from alknet-core, doc comments
rewired to this crate's ADRs — ADR-005, alknet ADR-091/034/030
semantics preserved verbatim in substance) and `src/client.rs` (port of
alknet-tls `client.rs` with the task's error-mapping deltas and import
rewires). Visibility: `RawKeyClientCertResolver`, `NoClientCertResolver`,
`FingerprintPinVerifier` made `pub` (lib.rs re-exports them);
`build_client_auth`, `select_server_verifier`, `load_platform_root_cert_store`
`pub`. `lib.rs` gained the client + credentials re-export lines; the
pending-modules comment now notes only the server line remains (its port
task owns it).
Deltas vs the extraction:
- `TlsError` mapping per ADR-002: `with_safe_default_protocol_versions` /
`CertifiedKey::from_der` / `RootCertStore::add` errors →
`TlsError::Rustls` (`#[from] rustls::Error`); the
`WebPkiServerVerifier` build error → `TlsError::VerifierBuild`
(`#[from] rustls::client::VerifierBuilderError`); the Acme
client-auth error string → `TlsError::AcmeConfig`. No
`Config(String)` catch-all.
- `for_quinn``for_noq` (ADR-003/004): consuming, noq-gated; the
wrap error flows through `TlsError::NoqWrap(#[from])` — no
`map_err` stringification.
- Imports rewired to `crate::{credentials, fingerprint, identity}`;
the `PeerEntry` reference in the extraction's `NoClientCertResolver`
doc became the peer-id resolution language (auth layer stays out,
ADR-005).
- Invariants pinned by tests: `enable_early_data = true` + exact ALPN;
aws-lc-rs default provider (9-suite set + `crypto_provider()`
identity); root store non-empty; verifier-selection matrix
(`Some``FingerprintPinVerifier`, `None``WebPkiServerVerifier`);
client-auth presentation matrix (RawKey → RFC 7250 raw pub keys,
X509 → loaded chain, SelfSigned/None → nothing, Acme →
`TlsError::AcmeConfig`); FingerprintPinVerifier pin match/mismatch
(Ed25519 SPKI + SHA256 X.509) and raw-key signature routing
(TLS 1.2 + 1.3, forged-signature rejection).
Verification: `cargo test` (56 pass), `cargo test --all-features`
(59 pass, incl. both `for_noq` tests), `cargo clippy --all-targets
--all-features -- -D warnings` (clean), `cargo fmt --check` (clean),
`cargo check --features noq` / `--features tcp` / default (clean).