# QUIC + TLS 1.3 quicproquo 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-rpc` can use QUIC bidirectional streams directly via the `tokio-util` compat layer. ## Crate integration quicproquo uses the following crates for QUIC and TLS: - **`quinn 0.11`** -- The async QUIC implementation for Tokio. Provides `Endpoint`, `Connection`, and bidirectional stream types. - **`quinn-proto 0.11`** -- The protocol-level types, including `QuicServerConfig` and `QuicClientConfig` wrappers that bridge `rustls` into `quinn`. - **`rustls 0.23`** -- The TLS implementation. quicproquo 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 `quicproquo-server/src/main.rs`): ```rust 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: 1. **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 quicproquo relies on. 2. **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. 3. **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. 4. **`QuicServerConfig` bridge**: The `quinn-proto` crate provides `QuicServerConfig::try_from(tls)` to adapt the `rustls::ServerConfig` for 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`: ```rust 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: ```rust 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>` 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 quicproquo currently uses a **trust-on-first-use (TOFU)** model with self-signed certificates: 1. On first start, the server generates a self-signed certificate using `rcgen::generate_simple_self_signed` with SANs for `localhost`, `127.0.0.1`, and `::1`. 2. The certificate and private key are persisted to disk as DER files (default: `data/server-cert.der` and `data/server-key.der`). 3. Clients must obtain the server's certificate file out-of-band and reference it via the `--ca-cert` flag or `QPQ_CA_CERT` environment 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: ```rust 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)](mls.md). - **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)](mls.md). - **Post-quantum resistance**: TLS 1.3 key exchange uses classical ECDHE. Post-quantum protection of application data is provided by the [Hybrid KEM](hybrid-kem.md) layer (M5 milestone). ## Configuration reference ### Server | Environment Variable | CLI Flag | Default | Description | |---|---|---|---| | `QPQ_LISTEN` | `--listen` | `0.0.0.0:7000` | QUIC listen address | | `QPQ_TLS_CERT` | `--tls-cert` | `data/server-cert.der` | TLS certificate path | | `QPQ_TLS_KEY` | `--tls-key` | `data/server-key.der` | TLS private key path | | `QPQ_DATA_DIR` | `--data-dir` | `data` | Persistent storage directory | ### Client | Environment Variable | CLI Flag | Default | Description | |---|---|---|---| | `QPQ_CA_CERT` | `--ca-cert` | `data/server-cert.der` | Server certificate to trust | | `QPQ_SERVER_NAME` | `--server-name` | `localhost` | Expected TLS server name (must match certificate SAN) | | `QPQ_SERVER` | `--server` | `127.0.0.1:7000` | Server address (per-subcommand) | ## Further reading - [Cap'n Proto Serialisation and RPC](capn-proto.md) -- The RPC layer that runs on top of QUIC streams. - [Service Architecture](../architecture/service-architecture.md) -- How the server's `NodeServiceImpl` binds to the QUIC endpoint.