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
This commit is contained in:
+77
-11
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: port-client
|
||||
name: Port client side — TlsClientConfig, verifier selection, client auth (src/client.rs)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [port-identity-types, port-fingerprint, port-pem-signing]
|
||||
scope: broad
|
||||
risk: medium
|
||||
@@ -73,24 +73,24 @@ co-location prevents semantic drift (ADR-005).
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Selection-matrix test: all four client-auth presentations × both
|
||||
- [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)
|
||||
- [ ] `Acme` local identity → `TlsError::AcmeConfig`
|
||||
- [ ] `enable_early_data == true` pinned
|
||||
- [ ] Root store non-empty (fallback exercised)
|
||||
- [ ] FingerprintPinVerifier unit tests ported (pin match, mismatch,
|
||||
- [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)
|
||||
- [ ] `cargo test` (default), `cargo test --all-features`,
|
||||
- [x] `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`
|
||||
- [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
|
||||
@@ -105,6 +105,72 @@ co-location prevents semantic drift (ADR-005).
|
||||
|
||||
> 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.
|
||||
> 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).
|
||||
+68
-12
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: port-server
|
||||
name: Port server side — TlsServerConfig, resolvers, ACME path (src/server.rs)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [port-identity-types, port-pem-signing]
|
||||
scope: broad
|
||||
risk: medium
|
||||
@@ -70,25 +70,25 @@ Without the feature, `Acme` identities return `TlsError::AcmeConfig`.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Invariant pins green: `max_early_data_size` per path,
|
||||
- [x] Invariant pins green: `max_early_data_size` per path,
|
||||
nine-scheme exact list, resolver behavior, verifier behavior
|
||||
- [ ] ACME branch (with `--features acme`): spawns, returns
|
||||
- [x] ACME branch (with `--features acme`): spawns, returns
|
||||
immediately, appends `acme-tls/1` (test with a staging URL +
|
||||
tempdir cache; do NOT hit Let's Encrypt — construct and assert
|
||||
config state, assert the ALPN list)
|
||||
- [ ] `Acme` identity without the `acme` feature → `TlsError::AcmeConfig`
|
||||
- [ ] `for_noq` / `for_tcp_tls` round-trip per identity variant
|
||||
- [x] `Acme` identity without the `acme` feature → `TlsError::AcmeConfig`
|
||||
- [x] `for_noq` / `for_tcp_tls` round-trip per identity variant
|
||||
(construction-level; handshakes are out of scope)
|
||||
- [ ] `cargo test` (default), `cargo test --all-features`,
|
||||
- [x] `cargo test` (default), `cargo test --all-features`,
|
||||
`cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] All five server invariants are test-asserted (not just compiled)
|
||||
- [ ] `for_noq`, `for_tcp_tls`, `rustls_config` exist with ADR-004
|
||||
- [x] All five server invariants are test-asserted (not just compiled)
|
||||
- [x] `for_noq`, `for_tcp_tls`, `rustls_config` exist with ADR-004
|
||||
signatures (`&self`; `for_tcp_tls` infallible)
|
||||
- [ ] No `unreachable!`/panics in library code
|
||||
- [ ] `lib.rs` re-exports the server surface
|
||||
- [x] No `unreachable!`/panics in library code
|
||||
- [x] `lib.rs` re-exports the server surface
|
||||
|
||||
## References
|
||||
|
||||
@@ -99,8 +99,64 @@ Without the feature, `Acme` identities return `TlsError::AcmeConfig`.
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills this during implementation.
|
||||
- **The extracted `unreachable!` is now the defensive arm**: the Acme
|
||||
arm in `build_rustls_server_config` returns
|
||||
`TlsError::AcmeConfig("TlsIdentity::Acme is handled by
|
||||
TlsServerConfig::new_acme, not build_rustls_server_config")` —
|
||||
test-asserted (`build_rustls_server_config_acme_returns_config_error`).
|
||||
- **Error mapping**: every extraction `TlsError::Config(e.to_string())`
|
||||
site at a `rustls::Error` producer (`with_safe_default_protocol_versions`,
|
||||
`with_single_cert`) became `?` into `TlsError::Rustls` (`#[from]`);
|
||||
rcgen sites became `?` into `TlsError::SelfSigned` (`#[from]
|
||||
rcgen::Error`). The non-acme `TlsServerConfig::new` Acme arm keeps its
|
||||
message verbatim but under `AcmeConfig`.
|
||||
- **The ACME test uses a `Custom("http://127.0.0.1:9/directory")`
|
||||
directory rather than the staging URL** — never contacted either way
|
||||
(port 9 discard; no network I/O beyond rustls-acme's async task start);
|
||||
the task's "staging URL" intent (do-not-hit-Let's-Encrypt) is satisfied
|
||||
with a stronger guarantee: a blackhole address. Staging would risk a
|
||||
real contact if a test misfired; `127.0.0.1:9` cannot succeed.
|
||||
- **`for_noq(&self)` clones the inner config** (`try_from` consumes) —
|
||||
the ADR-004 borrow shape requires it; the clone is cheap (Arc-shared
|
||||
resolvers).
|
||||
- **Cargo.toml deltas (two, both required)**:
|
||||
1. `noq` gains `"aws-lc-rs"` in its feature list. ADR-003's TOML
|
||||
block (`features = ["rustls"]`) was written against noq 1.2's
|
||||
API, but the lockfile resolves `noq-proto 1.3.0`, where
|
||||
`ServerConfig::with_crypto(crypto)` (the single-arg constructor
|
||||
`for_noq` calls) is `#[cfg(any(feature = "aws-lc-rs",
|
||||
feature = "ring"))]` — the retry-token key comes from noq's
|
||||
`ring_like` module, which needs one of the two provider features.
|
||||
`"aws-lc-rs"` (not `"ring"`) matches the crate's provider posture;
|
||||
it does not fight ADR-084: the config's internal provider is still
|
||||
the crate's explicit `aws_lc_rs::default_provider()` (noq consumes
|
||||
it from the config — ADR-003's provider paragraph). One new
|
||||
lockfile line (`aws-lc-rs` under noq-proto). ADR-003 needs a
|
||||
one-line amendment (recorded for review-impl docs sync).
|
||||
2. `acme = ["dep:rustls-acme", "dep:futures"]` — ADR-006 §Feature
|
||||
gates already records this ("the `acme` feature gates ... the
|
||||
spawned task + the `futures` dep"); the scaffold's TOML omitted
|
||||
the `futures` half. Without it, `--features acme` alone fails to
|
||||
compile (the spawned task uses `futures::StreamExt`).
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
Ported `src/server.rs` (614 lines) from alknet-tls server.rs with all
|
||||
five ADR-pinned deltas applied: `for_noq(&self)` (borrow + clone +
|
||||
`NoqWrap` via `#[from]`), `for_tcp_tls(&self)` adopted (infallible
|
||||
TlsAcceptor), `rustls_config(&self)` adopted, `acme_handle` renamed,
|
||||
Acme arm → `TlsError::AcmeConfig` (no `unreachable!`). All error sites
|
||||
remapped to the typed variants (`Rustls`/`SelfSigned`/`AcmeConfig`).
|
||||
The ACME path is verbatim (DirCache + directory URL + contacts +
|
||||
resolver + `acme-tls/1` + spawned event loop with the extracted
|
||||
tracing lines, returns immediately, handle never aborted).
|
||||
|
||||
14 tests: 12 ported (with the Acme-unreachable → AcmeConfig and
|
||||
for_quinn → for_noq rewires; the acme_directory URL tests live in
|
||||
identity.rs now) + the nine-scheme exact-list pin + ACME
|
||||
spawn/return/ALPN assertion + no-feature AcmeConfig test +
|
||||
`for_tcp_tls`/`rustls_config` round-trips for X509 (rcgen PEM pair in
|
||||
tempdir)/RawKey/SelfSigned. Verification: `cargo test` 68 pass,
|
||||
`--all-features` 75 pass, `--features noq` 72 pass, clippy -D
|
||||
warnings (default + all-features), `fmt --check`, feature checks
|
||||
(tcp/acme) — all green.
|
||||
Reference in New Issue
Block a user