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
8.1 KiB
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 |
|
broad | medium | phase | implementation |
|
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/RemoteIdentitycome from this crate'scredentials.rs(ADR-005); fingerprint helpers from this crate'sfingerprint.rs; the identity from this crate'sidentity.rs. for_noq(self)replacesfor_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;None→WebPkiServerVerifierover the root store. Fail-closed for unknown raw-key remotes is structural (the CA verifier is whatNoneinstalls; a raw-key remote cannot satisfy it — the failure manifests at handshake, never viaTlsError). - 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_storereturns 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 viaCertifiedKey::from_der(errors →TlsError::Rustlsper ADR-002);SelfSigned/None→NoClientCertResolver(has_certs() == false);Acme→TlsError::AcmeConfig. FingerprintPinVerifier: pin match / mismatch onverify_server_cert; TLS 1.2/1.3 signature verification routes Ed25519 SPKI throughverify_tls13_signature_with_raw_key; a mismatched pin fails verification.
Work
- Port
credentials.rs(types + builders + doc comments). - Port
client.rs; apply the deltas; rewire imports. - Port the extracted in-module tests.
- 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)
Acmelocal identity →TlsError::AcmeConfigenable_early_data == truepinned- 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.rsre-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::ClientConfigDebug output (ClientConfigderivesDebugand embeds the verifier's debug name —FingerprintPinVerifier/WebPkiServerVerifier) and the publicclient_auth_cert_resolverfield; theverifierfield itself ispub(super)at rustls 0.23.44, so the debug-string probe is the structural assertion shape. select_server_verifier'sWebPkiServerVerifierbuild error maps toTlsError::VerifierBuildvia#[from] rustls::client::VerifierBuilderError(therustls::webpkimodule is private at 0.23.44; same type, public path — lib.rs's variant doc already records this).- The signature-routing unit tests construct
DigitallySignedStructfrom its wire encoding through the doc-hiddenrustls::internal::msgssurface (Codec::read;newispub(crate)), then driveverify_tls12_signature/verify_tls13_signatureon the SPKI-as-CertificateDerexactly as rustls presents it in an RFC 7250 handshake — pin match + signature possession-proof both asserted. - The
for_noq()wrap relies onTlsError::NoqWrap(#[from])(NoInitialCipherSuite→?), ADR-002/ADR-003. - The extracted
TlsClientConfig's#[allow(dead_code)]is kept (the server-siderustls_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:
TlsErrormapping per ADR-002:with_safe_default_protocol_versions/CertifiedKey::from_der/RootCertStore::adderrors →TlsError::Rustls(#[from] rustls::Error); theWebPkiServerVerifierbuild error →TlsError::VerifierBuild(#[from] rustls::client::VerifierBuilderError); the Acme client-auth error string →TlsError::AcmeConfig. NoConfig(String)catch-all.for_quinn→for_noq(ADR-003/004): consuming, noq-gated; the wrap error flows throughTlsError::NoqWrap(#[from])— nomap_errstringification.- Imports rewired to
crate::{credentials, fingerprint, identity}; thePeerEntryreference in the extraction'sNoClientCertResolverdoc 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).