alktls
Shared TLS setup types for rustls: server and client config
construction, cert resolvers, verifiers, and ACME state-machine wiring —
transport-agnostic and shareable across transports.
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 the deployment runs (QUIC, TCP+TLS, or
your own wrapper). It does not dial, accept, dispatch, or resolve peer
identities — those stay in the transport and auth layers.
Not tied to any particular stack: compose it with any transport that
consumes a rustls config.
Quick start
Server — one identity, N transports
use Arc;
use ;
// Any of: X509 { cert, key }, RawKey(Ed25519SecretKey),
// SelfSigned, or Acme { .. } (behind the `acme` feature).
let identity = RawKey;
let alpn = vec!;
let tls = new;
// The same config feeds every transport — share it via Arc, never
// re-build per transport (and never spawn a second ACME machine):
let _rustls = tls.rustls_config; // any custom wrapper
let _acceptor = tls.for_tcp_tls; // tokio-rustls TlsAcceptor
let _quic = tls.for_noq?; // noq (QUIC) ServerConfig
On the ACME path the config spawns a background renewal task and
appends acme-tls/1 to the ALPN list for you (idempotently); the
handle lives in the config, which is why TlsServerConfig is not
Clone — share via Arc.
Client — pin, CA, or fail closed
use ;
// Known peer: pin its fingerprint (the fingerprint IS the trust
// anchor). Pins are format-exact: `ed25519:<hex>` for raw-key
// remotes, `SHA256:<hex>` for X.509 remotes.
let creds = new
.with_remote_identity;
let config = new?;
let _rustls = config.into_rustls_config; // for a TCP+TLS dial
// Unknown X.509 endpoint: `remote_identity: None` → CA verification
// against the platform root store (falls back to webpki-roots when
// the platform bundle is missing, e.g. containers).
let public = new;
let _config = new?;
Verifier selection follows the identity: known peer + fingerprint → pin; unknown + X.509 → CA; unknown + raw key → fail closed at the handshake. Unknown raw-key remotes are never downgraded to CA verification — a raw-key remote has no CA, so it is always a known peer.
Identity model
TlsIdentity
is the one identity type on both paths:
| Identity | Server presents | Client auth presents |
|---|---|---|
X509 { cert, key } |
cert chain from PEM files | cert chain from PEM files |
RawKey(Ed25519SecretKey) |
RFC 7250 raw public key (SPKI) | RFC 7250 raw public key |
SelfSigned |
generated in-memory dev cert (no SANs, never expires; pair with fingerprint pinning) | nothing (NoClientCertResolver) |
Acme { .. } (acme feature) |
ACME-managed cert, auto-renewed | config error — server-only |
Client-auth presentation follows the local identity: raw key and X.509
present their cert, SelfSigned/None present nothing.
Behavior-preservation invariants
Some settings look optional but are load-bearing — the crate sets them on every path so callers cannot forget them:
max_early_data_size = u32::MAXon server configs (0-RTT works out of the box),enable_early_data = trueon the client.aws-lc-rsas the crypto provider everywhere.acme-tls/1ALPN append for the ACME path only, done by the crate.- Non-empty root store — the client CA path merges
webpki-rootswhen the platform store is empty. - Fail closed — verifier selection never silently downgrades; a wrong pin fails the handshake, not the verification posture.
- Proof-of-possession by default — the default server verifier
verifies the client's CertificateVerify signature against the
presented cert (raw-key and X.509 paths), so a copied cert is not
usable by a party that lacks the matching private key. The
no-possession escape hatch (
AcceptAnyCertVerifier) exists but is never the default.
Features
| Feature | Contents |
|---|---|
| (default) | config construction, identity/credential/fingerprint types, PEM loading, verifiers, resolvers |
tcp |
for_tcp_tls() — the tokio-rustls acceptor wrapper |
noq |
for_noq() — QUIC config wrapping via noq |
acme |
the ACME path — rustls-acme state machine, background renewal, acme-tls/1 |
The default crate is deliberately lean (rustls + cert material + tokio spawn); the transport-specific dependencies are opt-in.
Scope boundary
This crate is the cert/config provider, not the accept loop. It
does not bind sockets, dial, or dispatch — accept loops and dial seams
live in the consumers. Handshake-time outcomes (a rejected cert, a
mismatched pin) surface through the transport's connector, not through
TlsError,
which covers config construction only. ACME state-machine runtime
events are logged in the spawned renewal task.
Documentation
- Architecture docs — the authoritative
spec: overview, the server and client API surfaces, and ADRs 001–008
(extraction baseline,
TlsErrorshape,noq, accessors, config-type ownership, module layout, RFC 7250 negotiation, possession verification). - API docs — full crate documentation on docs.rs.
Verification
License
MIT OR Apache-2.0