Close review 001 §S-1: the default client-cert verifier never checked the client's CertificateVerify, so anyone holding a peer's *public* cert/SPKI bytes (public by design — peers publish them to be dialable) could complete a handshake as that peer, and the auth layer could not detect it. The consumer designs are known (X.509 and raw-key TCP/QUIC endpoints with identity-bearing clients), so implementing now — the zero-consumer moment — avoids the guaranteed breaking republish of flipping the default later. - VerifyPresentedCertVerifier (new): request, don't require, verify possession — permissive verify_client_cert (self-signed chains and bare SPKIs stay valid presentation) + CertificateVerify routing by presented cert kind (Ed25519 SPKI -> verify_tls13_signature_with_ raw_key both TLS versions; X.509 -> standard route), the same routing FingerprintPinVerifier implements. Nine-scheme list verbatim (shared fn, exact-list pin covers both). - Default on every TlsServerConfig path — X509 / RawKey / SelfSigned / ACME (the verifier install is crate-side rustls in new_acme, not rustls-acme's). - AcceptAnyCertVerifier stays public as the explicit no-pop escape hatch, no longer installed by any crate path. - tests/impersonation_posture.rs: four pins — default rejects the attacker (X.509: UnsupportedSignatureAlgorithmForPublicKeyContext; raw-key: BadSignature), escape hatch still accepts + extracts the victim's fingerprint (both cert types). - tests/handshake_behavior.rs: suites 4/4b — possession-checked legit clients (raw-key pin vs raw-key server; X.509 client vs X.509 server) complete and the server extracts the fingerprint; suite 3b doc updated. - Docs: ADR-008 written; OQ-TLS-09 -> resolved (option (b)); ADR-007 §Limits deferral retired to not-planned; server.md/client.md invariants/README/overview synced. Verification: cargo test 81 / --features tcp 95 / --all-features 104 green; clippy -D warnings clean (default + all-features); fmt clean; cargo doc warning-free.
110 lines
5.9 KiB
Markdown
110 lines
5.9 KiB
Markdown
---
|
|
status: reviewed
|
|
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:<hex>` / `SHA256:<hex>` 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 |
|
|
| [007](decisions/007-cert-type-negotiation.md) | RFC 7250 cert-type negotiation | The cert-type offer follows the identity/pin format — raw-key-over-TCP works (deviation from alknet; resolves OQ-TLS-10) |
|
|
| [008](decisions/008-server-path-possession-verification.md) | Server-path possession verification | The verifying verifier is the default on every server path; `AcceptAnyCertVerifier` is the explicit escape hatch (resolves OQ-TLS-09) |
|
|
|
|
## 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. |