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

8.1 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
port-client Port client side — TlsClientConfig, verifier selection, client auth (src/client.rs) completed
port-identity-types
port-fingerprint
port-pem-signing
broad medium phase implementation
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 Options 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; NoneWebPkiServerVerifier 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/NoneNoClientCertResolver (has_certs() == false); AcmeTlsError::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.

  • 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_quinnfor_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 (SomeFingerprintPinVerifier, NoneWebPkiServerVerifier); 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).