Files
alktls/docs/architecture/client.md
T
glm-5.3-flash d74a27f764 phase 1: architecture spec — overview, server/client, ADR-001..006
- ADR-001: inherit the alknet TLS design as the baseline; deviations
  recorded as alktls ADRs
- ADR-002: TlsError ships the ADR-088 six-variant shape from day one
  (typed #[from] sources; NoqWrap; no string catch-all)
- ADR-003: the QUIC feature is noq (iroh's extracted fork), pre-
  consumer rename; default = [] per the lean-crate convention
  (corrects the extracted code's default = ["quinn"])
- ADR-004: complete accessors — for_tcp_tls() adopted, rustls_config()
  adopted; server accessors borrow (&self), client accessors consume
- ADR-005: identity + credentials + fingerprint types move into
  alktls; auth layer stays out
- ADR-006: eight-module layout; seed tests + integration invariant
  pins (exact nine-scheme list, client enable_early_data)
- specs: overview (transport picture, terminology), server.md (ACME
  lifecycle, invariants), client.md (verifier selection matrix, root-
  store fallback); open-questions.md promotes OQ-TLS-01..08 (all
  resolved at entry)
- Cargo.toml: quinn feature -> noq (per ADR-003); AGENTS.md aligned

Architecture review pass done: 0 critical, 2 major (ADR-002 AcmeConfig
doc comment contradiction; ADR-003 unrecorded default deviation) and
8 minors all addressed; cross-references verified against alknet ADRs,
rustls/noq/iroh sources.

Verified: cargo test, test --all-features, clippy -D warnings,
fmt --check, doc --no-deps
2026-09-10 05:37:55 +00:00

5.5 KiB

status, last_updated
status last_updated
draft 2026-09-10

alktls — Client side

TlsClientConfig and its verifiers: the client-side TLS setup, extracted from alknet (crates/alknet-tls/src/client.rs) and ported per the ADRs. Decisions referenced here: see open-questions.md and the index in overview.md.

TlsClientConfig

Built per dial from a ConnectionCredentials + ALPN (the dial-seam pattern, alknet ADR-089). Consumed by its accessors — a TlsClientConfig is not reused across dials (ADR-004).

pub struct TlsClientConfig {
    rustls_config: rustls::ClientConfig,
}

impl TlsClientConfig {
    pub fn new(credentials: &ConnectionCredentials, alpn: &[u8])
        -> Result<Self, TlsError>;

    #[cfg(feature = "noq")]
    pub fn for_noq(self) -> Result<noq::ClientConfig, TlsError>;

    pub fn into_rustls_config(self) -> rustls::ClientConfig;
}

new is sync and infallible-free aside from config construction (cert file loading for an X.509 local identity is the only I/O). Every config sets enable_early_data = true (the client half of the 0-RTT invariant) and carries the aws-lc-rs provider (alknet ADR-084).

The two credential dimensions

ConnectionCredentials (ADR-005, moved from alknet ADR-091) carries exactly the two inputs the client config consumes:

  1. local_identity: Option<TlsIdentity> — the client-auth cert presentation:

    Local identity Presented
    RawKey RFC 7250 raw public key (SPKI DER; only_raw_public_keys() auto-detected from the DER)
    X509 the cert chain + key, loaded from disk
    SelfSigned / None nothing (NoClientCertResolver) — documented, resolved in OQ-TLS-02
    Acme config error (TlsError::AcmeConfig) — server-only identity
  2. remote_identity: Option<RemoteIdentity> — the verifier selection matrix (below). Both Options are load-bearing, not cosmetic: Some means "pin this", None means "trust the CA or fail" — never a placeholder default.

Verifier selection (alknet ADR-034 §3)

Exactly three outcomes, driven by remote_identity:

remote_identity Remote cert Verifier Outcome
Some(fingerprint) any FingerprintPinVerifier pin match required
None X.509 WebPkiServerVerifier (CA) CA verification against the platform root store
None Ed25519 raw key (the CA verifier fails) fail closed at handshake

None is the public-X.509-endpoint state, not "skip verification". An unknown raw-key remote fails at handshake — it must never silently downgrade to CA verification. Verifier selection happens at config construction; the fail-closed manifests at handshake time (the config carries the CA verifier; the raw-key remote simply cannot satisfy it) — this is the ADR-002 scope boundary in action.

Fail-closed is structural: known peer + fingerprint → pin; unknown + X.509 → CA; unknown + raw key → fail. No fourth path.

FingerprintPinVerifier

The known-peer path. The fingerprint IS the trust anchor: for ed25519:<hex> remotes the raw Ed25519 key is extracted from the presented cert (SPKI) and matched; for SHA256:<hex> remotes the full cert DER is hashed and matched. No CA verification, no name verification — only the pin.

Handshake signatures are still verified (TLS 1.2/1.3, aws-lc-rs algorithms; Ed25519 SPKI certs route through verify_tls13_signature_with_raw_key): a stolen fingerprint cannot be replayed with a forged signature — the presenter must prove possession of the corresponding private key.

The root-store fallback (alknet ADR-088 §5)

The None + X.509 CA path loads the platform's native root certs (rustls-native-certs). If the platform store is empty — a containerized deployment with no system CA bundle — the built-in webpki-roots are merged in so the store is never empty. Native-certs load errors are logged, not returned; the fallback guarantees non-emptiness regardless. This makes NoRootAnchors unreachable in practice (the VerifierBuild variant exists for the builder API's completeness, not a reachable path — ADR-002).

The root store is built unconditionally for the CA path — not feature-gated on tcp or noq — because any client dialing a public X.509 endpoint needs it regardless of transport.

What the client side does NOT do

  • No dial: the dial seam (AlknetClient in alknet; the rewrite's dial crate) consumes the config via for_noq() / into_rustls_config() and owns TcpStream::connect, Endpoint::connect, SNI, and SOCKS5 proxying (alknet ADR-089; SOCKS5 proxy seam per alknet ADR-090). Handshake failures are the dial's errors (ClientDialError), not TlsError.
  • No iroh: the iroh dial takes the 32-byte Ed25519 key directly (key-not-config; ADR-003). iroh's built-in verifier (NodeId check) is iroh's own application of the same fail-closed rule.
  • No per-request auth: a call-protocol auth_token is a call-layer concept; it never reaches TlsClientConfig.

References

  • overview.md — the index; server.md — the server side
  • alknet crates/tls/README.md §Client side — the full client-side spec this doc mirrors
  • alknet ADR-034 (verifier selection), ADR-088 §5 (root-store fallback), ADR-091 (ConnectionCredentials)
  • ADR-002 (TlsError), ADR-004 (accessors), ADR-005 (credential types), ADR-006 (tests — the verifier-selection matrix)