Delete 8 Noise-specific documentation pages (noise-xx.md,
transport-keys.md, adr-001/003/006, framing-codec.md) and update
~30 remaining wiki pages to reflect QUIC+TLS as the sole transport.
Remove obsolete Noise-based integration tests (auth_service.rs,
mls_group.rs). Code-side Noise removal was done in f334ed3.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.5 KiB
QUIC + TLS 1.3
quicnprotochat uses QUIC (RFC 9000) with mandatory TLS 1.3 (RFC 9001) as its transport layer. This page explains how the quinn and rustls crates are integrated and what security properties the transport provides.
Why QUIC
QUIC provides several advantages over traditional TCP-based transports:
- Multiplexed streams: Native bidirectional streams; each RPC call gets its own stream without head-of-line blocking.
- 0-RTT resumption: Returning clients can send data in the first flight, reducing connection setup latency.
- Integrated encryption: TLS 1.3 is integral to the QUIC handshake; no extra round-trips for transport security.
- NAT traversal: UDP-based; connection migration survives NAT rebinding.
- Ecosystem support:
capnp-rpccan use QUIC bidirectional streams directly via thetokio-utilcompat layer.
Crate integration
quicnprotochat uses the following crates for QUIC and TLS:
quinn 0.11-- The async QUIC implementation for Tokio. ProvidesEndpoint,Connection, and bidirectional stream types.quinn-proto 0.11-- The protocol-level types, includingQuicServerConfigandQuicClientConfigwrappers that bridgerustlsintoquinn.rustls 0.23-- The TLS implementation. quicnprotochat uses it in strict TLS 1.3 mode with no fallback to TLS 1.2.rcgen 0.13-- Self-signed certificate generation for development and testing.
Server configuration
The server builds its QUIC endpoint configuration in build_server_config() (in quicnprotochat-server/src/main.rs):
let mut tls = rustls::ServerConfig::builder_with_protocol_versions(&[&TLS13])
.with_no_client_auth()
.with_single_cert(cert_chain, key)?;
tls.alpn_protocols = vec![b"capnp".to_vec()];
let crypto = QuicServerConfig::try_from(tls)?;
Ok(ServerConfig::with_crypto(Arc::new(crypto)))
Key points:
-
TLS 1.3 strict mode:
builder_with_protocol_versions(&[&TLS13])ensures no TLS 1.2 fallback. This is a hard requirement: TLS 1.2 lacks the 0-RTT and full forward secrecy guarantees that quicnprotochat relies on. -
No client certificate authentication:
with_no_client_auth()means the server does not verify client certificates at the TLS layer. Client authentication is handled at the application layer via Ed25519 identity keys and MLS credentials. This is a deliberate design choice -- MLS provides stronger authentication properties than TLS client certificates. -
ALPN negotiation: The Application-Layer Protocol Negotiation extension is set to
b"capnp", advertising that this endpoint speaks Cap'n Proto RPC. Both client and server must agree on this protocol identifier or the TLS handshake fails. -
QuicServerConfigbridge: Thequinn-protocrate providesQuicServerConfig::try_from(tls)to adapt therustls::ServerConfigfor use with QUIC. This handles the QUIC-specific TLS parameters (transport parameters, QUIC header protection keys) automatically.
Client configuration
The client performs the mirror operation. It loads the server's DER-encoded certificate from a local file and constructs a rustls::ClientConfig:
let mut roots = rustls::RootCertStore::empty();
roots.add(CertificateDer::from(cert_bytes))?;
let tls = rustls::ClientConfig::builder_with_protocol_versions(&[&TLS13])
.with_root_certificates(roots)
.with_no_client_auth();
tls.alpn_protocols = vec![b"capnp".to_vec()];
let crypto = QuicClientConfig::try_from(tls)?;
The client trusts exactly one certificate: the server's self-signed cert loaded from disk. There is no system trust store involved, which simplifies the trust model but requires out-of-band distribution of the server certificate.
Per-connection handling
Each accepted QUIC connection spawns a handler task:
let (send, recv) = connection.accept_bi().await?;
let (reader, writer) = (recv.compat(), send.compat_write());
let network = twoparty::VatNetwork::new(reader, writer, Side::Server, Default::default());
let service: node_service::Client = capnp_rpc::new_client(NodeServiceImpl { store, waiters });
RpcSystem::new(Box::new(network), Some(service.client)).await?;
The tokio-util compat layer (compat() and compat_write()) converts Quinn's RecvStream and SendStream into types that implement futures::AsyncRead and futures::AsyncWrite, which capnp-rpc's VatNetwork requires. The entire Cap'n Proto RPC system then runs over this single QUIC bidirectional stream.
Because capnp-rpc uses Rc<RefCell<>> internally (making it !Send), all RPC tasks run on a tokio::task::LocalSet. The server spawns each connection handler via tokio::task::spawn_local.
Certificate trust model
quicnprotochat currently uses a trust-on-first-use (TOFU) model with self-signed certificates:
- On first start, the server generates a self-signed certificate using
rcgen::generate_simple_self_signedwith SANs forlocalhost,127.0.0.1, and::1. - The certificate and private key are persisted to disk as DER files (default:
data/server-cert.deranddata/server-key.der). - Clients must obtain the server's certificate file out-of-band and reference it via the
--ca-certflag orQUICNPROTOCHAT_CA_CERTenvironment variable.
This model is adequate for development and single-server deployments. The roadmap includes:
- ACME integration (Let's Encrypt) for production deployments with publicly-routable servers.
- Certificate pinning to detect MITM attacks even when a CA is compromised.
- Certificate transparency log monitoring for detecting misissued certificates.
Self-signed certificate generation
The server's generate_self_signed() function:
let subject_alt_names = vec![
"localhost".to_string(),
"127.0.0.1".to_string(),
"::1".to_string(),
];
let issued = generate_simple_self_signed(subject_alt_names)?;
fs::write(cert_path, issued.cert.der())?;
fs::write(key_path, &issued.key_pair.serialize_der())?;
The generated certificate includes both DNS and IP SANs so that clients can connect using either localhost or an IP address. The client specifies the expected server name via --server-name (default: localhost), which must match one of the certificate's SANs.
Security properties
The QUIC + TLS 1.3 layer provides:
| Property | Mechanism |
|---|---|
| Transport confidentiality | All application data is encrypted with AES-128-GCM or ChaCha20-Poly1305 (negotiated during the TLS handshake) |
| Server authentication | The client verifies the server's certificate against the locally-trusted DER file |
| Forward secrecy | TLS 1.3 exclusively uses ephemeral Diffie-Hellman key exchange; session keys are not derivable from the server's long-term key |
| Replay protection | QUIC packet numbers and TLS 1.3's anti-replay mechanism prevent replay attacks |
| Connection migration | QUIC connection IDs allow the client to change IP addresses without re-handshaking |
What TLS does not provide
- Client authentication: Handled by MLS identity credentials at the application layer. See MLS (RFC 9420).
- End-to-end encryption: TLS terminates at the server. The server can read the Cap'n Proto RPC framing and message routing metadata. Payload confidentiality is provided by MLS. See MLS (RFC 9420).
- Post-quantum resistance: TLS 1.3 key exchange uses classical ECDHE. Post-quantum protection of application data is provided by the Hybrid KEM layer (M5 milestone).
Configuration reference
Server
| Environment Variable | CLI Flag | Default | Description |
|---|---|---|---|
QUICNPROTOCHAT_LISTEN |
--listen |
0.0.0.0:7000 |
QUIC listen address |
QUICNPROTOCHAT_TLS_CERT |
--tls-cert |
data/server-cert.der |
TLS certificate path |
QUICNPROTOCHAT_TLS_KEY |
--tls-key |
data/server-key.der |
TLS private key path |
QUICNPROTOCHAT_DATA_DIR |
--data-dir |
data |
Persistent storage directory |
Client
| Environment Variable | CLI Flag | Default | Description |
|---|---|---|---|
QUICNPROTOCHAT_CA_CERT |
--ca-cert |
data/server-cert.der |
Server certificate to trust |
QUICNPROTOCHAT_SERVER_NAME |
--server-name |
localhost |
Expected TLS server name (must match certificate SAN) |
QUICNPROTOCHAT_SERVER |
--server |
127.0.0.1:7000 |
Server address (per-subcommand) |
Further reading
- Cap'n Proto Serialisation and RPC -- The RPC layer that runs on top of QUIC streams.
- Service Architecture -- How the server's
NodeServiceImplbinds to the QUIC endpoint.