Files
alktls/docs/architecture/overview.md
T
glm-5.3-flash ac440f3a9a ADR-008: server-path possession verification — the verifying verifier is the default (OQ-TLS-09 resolved)
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.
2026-09-11 11:12:48 +00:00

5.9 KiB

status, last_updated
status last_updated
reviewed 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 TlsServerConfigs / TlsClientConfigs, 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/.

ADR Decision Summary
001 Inherit the alknet TLS design The alknet ADRs + crate spec are the baseline; deviations recorded as alktls ADRs
002 TlsError — ADR-088 shape from day one Six typed variants, #[non_exhaustive], no string catch-all; config-construction scope boundary
003 The QUIC feature is noq noq 1.2 (iroh's extracted fork) replaces quinn pre-consumer; iroh stays key-not-config
004 Complete the accessors for_tcp_tls() adopted; server accessors borrow (&self), client accessors consume
005 Config types move into alktls Identity + credentials + fingerprint move in; auth layer stays out
006 Module layout and test surface Eight modules; in-module seed tests + integration invariant pins
007 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 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. 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.mdTlsServerConfig, resolvers, the ACME path, and the server-side invariants
  • client.mdTlsClientConfig, 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.