--- status: reviewed last_updated: 2026-09-11 --- # alktls — Server side `TlsServerConfig` and its resolvers: the server-side TLS setup, extracted from alknet (`crates/alknet-tls/src/server.rs`) and ported per the ADRs. Statuses of the decisions referenced here: see [open-questions.md](open-questions.md) and the ADR index in [overview.md](overview.md). ## `TlsServerConfig` The central server-side type. Built once from a `TlsIdentity` + ALPN list, shared across transports via `Arc` (not `Clone` — it holds the ACME task's `JoinHandle`). ```rust pub struct TlsServerConfig { rustls_config: rustls::ServerConfig, // Clone-safe — Arc internally acme_handle: Option>, // acme-gated } impl TlsServerConfig { pub async fn new(identity: &TlsIdentity, alpns: &[Vec]) -> Result; #[cfg(feature = "noq")] pub fn for_noq(&self) -> Result; #[cfg(feature = "tcp")] pub fn for_tcp_tls(&self) -> tokio_rustls::TlsAcceptor; pub fn rustls_config(&self) -> &rustls::ServerConfig; } ``` Construction dispatch (ADR-001's identity model): - `X509 { cert, key }` — loads the chain + key from disk (`pem.rs`), `with_single_cert`. - `RawKey(Ed25519SecretKey)` — `RawKeyCertResolver` presents the Ed25519 key as an RFC 7250 raw public key server cert. - `SelfSigned` — `generate_self_signed_cert()` (rcgen), in-memory. - `Acme { domains, cache_dir, directory, contact }` — the ACME path (below). **Server-only**: on the client path it is a config error. ## Behavior-preservation invariants These are load-bearing (ADR-001); an implementation that omits any of them compiles but silently changes TLS behavior. Each is asserted by test (ADR-006): - **`max_early_data_size = u32::MAX`** on every server config path — enables 0-RTT / early data. Omitting it silently breaks 0-RTT clients. - **`rustls::crypto::aws_lc_rs::default_provider()`** as the crypto provider on all paths (alknet ADR-084). Never `ring`, never the process-default provider, without a new ADR. - **The nine-scheme `supported_verify_schemes()`** (shared by `VerifyPresentedCertVerifier` and `AcceptAnyCertVerifier`) returns ED25519 + ECDSA P-256/P-384 + RSA PSS (SHA256/384/512) + RSA PKCS1 (SHA256/384/512) — nine schemes, verbatim, pinned by an exact-list integration test. - **The default client-cert verifier verifies possession** (ADR-008): `VerifyPresentedCertVerifier` is installed on every path; the CertificateVerify is checked against the presented cert's public key. `AcceptAnyCertVerifier` exists only as the explicit escape hatch. - **`acme-tls/1` ALPN append** for the ACME path only, done by the crate, not the caller (alknet ADR-027 §7). - **Non-empty root store** — the client CA path merges `webpki-roots` when the platform store is empty (see [client.md](client.md)). ## `VerifyPresentedCertVerifier` (the default) and `AcceptAnyCertVerifier` (the escape hatch) The server-side client-cert verifier: **request, don't require, verify possession** (ADR-008, resolving OQ-TLS-09). [`VerifyPresentedCertVerifier`](decisions/008-server-path-possession-verification.md) is the default on every `TlsServerConfig` path (X509 / RawKey / SelfSigned / ACME). It asks for a client cert (X.509 or RFC 7250 raw key) so the caller can extract the fingerprint via `peer_identity()`, does not require one, and does not verify the presented cert against a CA — self-signed chains and bare SPKIs are valid presentation. The client's **CertificateVerify signature is verified** against the presented cert's public key (Ed25519 SPKIs route through `verify_tls13_signature_with_raw_key`; X.509 through the standard route): the extracted fingerprint is possession-checked — presenting a victim's public bytes under an attacker's key fails the handshake. Who the fingerprint maps to remains the auth layer's concern (ADR-005). **The escape hatch**: `AcceptAnyCertVerifier` is the documented no-pop verifier — same request-not-require shape, no CertificateVerify check, so the fingerprint it extracts is attacker-suppliable (S-1). Install it explicitly only when a deployment deliberately wants that posture. Both postures are pinned by `tests/impersonation_posture.rs` (default rejects the attacker, escape hatch accepts — both cert types); a change must update that test together with this doc. ## `RawKeyCertResolver` Presents an `Ed25519SecretKey` as an RFC 7250 raw public key server certificate: the SPKI DER (Ed25519 OID + 32-byte key) is the "cert", `only_raw_public_keys() == true`, and the signing key is the shared `Ed25519SigningKey` helper (`signing.rs`). **Interop note (ADR-007, executed in `tests/handshake_behavior.rs`)**: a raw-key server resolver requires the client to offer `[RawPublicKey]` *server* cert types, which rustls sends only when the client verifier overrides `requires_raw_public_keys() == true`. The crate's own `FingerprintPinVerifier` now does exactly that for `ed25519:` pins (the offer follows the pin format), so a crate pin client completes against this raw-key server (`raw_key_server_path_completes_with_crate_pin_client`). A raw-key *client* identity presents its SPKI under the X.509 offer, which the verifiers (`requires_raw_public_keys() == false` — correctly; both accept X.509-or-raw cert types) accept end-to-end, the default possession-verifying the presentation (`raw_key_client_presents_spki_and_server_extracts_fingerprint`, `raw_key_client_vs_raw_key_server_default_verifier_checks_possession`). Raw-key peers riding iroh/noq are unaffected (their TLS stacks own their negotiation). ## The ACME path For `TlsIdentity::Acme`, `new` (feature `acme`): 1. Builds `rustls_acme::AcmeConfig` — the upstream builder, distinct from `TlsError::AcmeConfig` (domains, `DirCache` cache dir, directory URL from `AcmeDirectory`, contacts). 2. Wires `state.resolver()` as the cert resolver into the server config. 3. Appends `acme-tls/1` to the ALPN list (TLS-ALPN-01 challenge). 4. Spawns the event-loop task (`tokio::spawn`) matching `EventOk`/`EventError` variants to `tracing` logs, and returns **immediately** — it does not await the first certificate. Lifecycle semantics: - The returned config is usable right away; handshakes fail transiently until the first order completes (or a cached cert deploys). - Order errors log at `warn!` and retry inside `rustls-acme`; the task exits only when the event stream ends. - The task is detached: the stored `JoinHandle` is never aborted; ACME runs for the process lifetime (OQ-TLS-06 resolved detached-only; a `shutdown()` surface would be additive later if the rewrite's graceful-shutdown design wants one). - **One state machine per domain** — never spawn a second ACME task for an already-served domain (duplicate orders = Let's Encrypt rate-limit risk + cert-cache divergence). This is why `TlsServerConfig` is not `Clone`. - Runtime errors are stream events, not `TlsError` variants (ADR-002's scope boundary). `TlsError::AcmeConfig` covers config-mismatch mistakes ("ACME feature not enabled but `Acme` configured" — and on the client path, `Acme` used for client auth), not runtime failures. ## What the server side does NOT do - No accept loop: `for_tcp_tls()` yields a `TlsAcceptor`; the `TcpListener::accept` → `TlsAcceptor::accept` → dispatch loop belongs to the caller (alknet ADR-083 — the endpoint takes no TLS config; the assembly layer builds configs and transports). - No ALPN policy: the ALPN list is a parameter (the assembly layer filters per endpoint type — alknet ADR-086 §3); the crate appends only `acme-tls/1` on the ACME path. - No handshake: verifier selection and handshake outcomes on the *server* side are the default `VerifyPresentedCertVerifier` (or the explicitly-installed escape hatch) + the caller's fingerprint extraction; a rejected handshake is the transport's error, not `TlsError`. - No peer resolution: the extracted fingerprint string goes to the caller; `PeerEntry`/`AuthPolicy` live in the auth layer. ## References - [overview.md](overview.md) — the index; [client.md](client.md) — the client side - alknet `crates/tls/README.md` §Architecture — the full server-side spec this doc mirrors - ADR-001 (invariants), ADR-002 (`TlsError`), ADR-003 (`for_noq`), ADR-004 (accessors), ADR-005 (identity types), ADR-006 (modules, tests), ADR-007 (cert-type negotiation), ADR-008 (possession verification)