--- status: draft last_updated: 2026-09-10 --- # alktls — Overview ## Purpose alktls is the TLS layer of the alk* stack: shared TLS setup types — server and client `rustls` configs, cert resolvers, verifiers, and ACME state-machine wiring — transport-agnostic and shareable across transports. It is the extraction of the TLS handling from alknet (`crates/alknet-tls` + the identity/credential/fingerprint types in `crates/alknet-core`), and it is the TLS crate the alknet rewrite consumes. The crate owns **config construction**: given an identity and an ALPN list, produce a `rustls::ServerConfig` or `rustls::ClientConfig`, and hand it to whichever transport wrapper the deployment runs. It does not dial, accept, dispatch, or resolve peer identities — those are the dial seam's, the accept loop's, and the auth layer's jobs (alknet ADR-083/089; the scope boundary in ADR-001). **Terminology.** The **assembly layer** is the deployment binary that builds the `TlsServerConfig`s / `TlsClientConfig`s, builds the transports from them, and wires the results together (alknet ADR-014's term — in practice, the hub/worker/endpoint binary). The dial seam is the outbound connection point the assembly layer (or a client crate) consumes; the accept loop is the inbound counterpart. alktls is the cert provider for both, never the loops themselves. ## The transport picture A deployment (endpoint / hub) assembles a subset of three transports; alktls serves the first two directly and feeds the third a key: | Transport | Stack | What alktls provides | |-----------|-------|----------------------| | TCP+TLS | `tokio-rustls` | `TlsServerConfig::for_tcp_tls()` → `TlsAcceptor` (feature `tcp`); `TlsClientConfig::into_rustls_config()` → `TlsConnector` (ungated) | | QUIC | `noq` (iroh's extracted quinn fork) | `for_noq()` on both configs (feature `noq`) | | iroh | iroh's own TLS | nothing — `Ed25519SecretKey` 32-byte access feeds `iroh_base::SecretKey` (key-not-config) | One identity, N transports: the inner rustls config is Clone (Arc-shared resolvers); one `TlsServerConfig` feeds every transport an endpoint runs. One ACME state machine per domain, shared across transports — duplicate orders risk Let's Encrypt rate limits and cert-cache divergence (ADR-001). ## What the crate is The public API surface (ADR-004, ADR-005): - `TlsServerConfig` — built once from a `TlsIdentity` + ALPN list; accessors `for_noq()`, `for_tcp_tls()`, `rustls_config()`. Not `Clone` (holds the ACME task's `JoinHandle`); share via `Arc`. - `TlsClientConfig` — built per dial from `ConnectionCredentials` + ALPN; accessors `for_noq()`, `into_rustls_config()`. - `TlsError` — the ADR-088 six-variant `#[non_exhaustive]` config-construction error type (ADR-002). - Identity types — `TlsIdentity` (`X509` / `RawKey` / `SelfSigned` / `Acme`), `Ed25519SecretKey`, `AcmeDirectory` (ADR-005). - Credential bundle — `ConnectionCredentials`, `RemoteIdentity` (ADR-005); drives the verifier selection matrix (alknet ADR-034). - Fingerprint helpers — `fingerprint_from_cert_der`, `extract_ed25519_raw_key_from_spki` (ADR-005); normalized `ed25519:` / `SHA256:` formats. ## Design Decisions All design decisions are documented as ADRs in [decisions/](decisions/). | ADR | Decision | Summary | |-----|----------|---------| | [001](decisions/001-inherit-alknet-tls-design.md) | Inherit the alknet TLS design | The alknet ADRs + crate spec are the baseline; deviations recorded as alktls ADRs | | [002](decisions/002-tlserror-shape.md) | `TlsError` — ADR-088 shape from day one | Six typed variants, `#[non_exhaustive]`, no string catch-all; config-construction scope boundary | | [003](decisions/003-noq-replaces-quinn.md) | The QUIC feature is `noq` | noq 1.2 (iroh's extracted fork) replaces quinn pre-consumer; iroh stays key-not-config | | [004](decisions/004-accessor-surface.md) | Complete the accessors | `for_tcp_tls()` adopted; server accessors borrow (`&self`), client accessors consume | | [005](decisions/005-config-types-move-into-alktls.md) | Config types move into alktls | Identity + credentials + fingerprint move in; auth layer stays out | | [006](decisions/006-module-layout-and-tests.md) | Module layout and test surface | Eight modules; in-module seed tests + integration invariant pins | ## Open Questions Open questions are tracked in [open-questions.md](open-questions.md). All Phase 0 questions (OQ-TLS-01..08) were resolved at Phase 1 entry (2026-09-10) — six via ADR-001..006, two resolved as documented behavior (OQ-TLS-02, OQ-TLS-06); the tracker records the resolutions with pointers. ## Components - [server.md](server.md) — `TlsServerConfig`, resolvers, the ACME path, and the server-side invariants - [client.md](client.md) — `TlsClientConfig`, verifier selection, client-auth presentation, the root-store fallback ## Relationship to the alknet docs The full design rationale lives in alknet's architecture docs (`/workspace/@alkdev/alknet/docs/architecture/`): ADR-082 (why a standalone TLS crate — the cert-reuse problem), ADR-083 (why the endpoint takes no TLS config), ADR-084 (why aws-lc-rs), ADR-027 (the identity model), ADR-034 (verifier selection), ADR-086 §3 (split ALPN lists), ADR-087 (`TlsClientConfig`), ADR-088 (`TlsError`, root-store fallback), ADR-089 (the dial seam), ADR-091 (`ConnectionCredentials`), and `crates/tls/README.md` (the full crate spec). alktls' ADR series records its own posture and points at the alknet reasoning rather than duplicating it.