Rename all project references from quicproquo/qpq to quicprochat/qpc across documentation, Docker configuration, CI workflows, packaging scripts, operational configs, and build tooling. - Docker: crate paths, binary names, user/group, data dirs, env vars - CI: workflow crate references, binary names, artifact names - Docs: all markdown files under docs/, SDK READMEs, book.toml - Packaging: OpenWrt Makefile, init script, UCI config (file renames) - Scripts: justfile, dev-shell, screenshot, cross-compile, ai_team - Operations: Prometheus config, alert rules, Grafana dashboard - Config: .env.example (QPQ_* → QPC_*), CODEOWNERS paths - Top-level: README, CONTRIBUTING, ROADMAP, CLAUDE.md
8.4 KiB
QUIC + TLS 1.3
quicprochat 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.
- Per-call concurrency: The v2 RPC framework opens one bidirectional stream per RPC call. Multiple calls run concurrently without blocking each other.
- Push streams: Server-to-client push events use QUIC uni-directional streams, avoiding any request-response overhead.
Crate integration
quicprochat uses the following crates for QUIC and TLS:
quinn 0.11-- The async QUIC implementation for Tokio. ProvidesEndpoint,Connection, and bidirectional/uni-directional stream types.quinn-proto 0.11-- The protocol-level types, includingQuicServerConfigandQuicClientConfigwrappers that bridgerustlsintoquinn.rustls 0.23-- The TLS implementation. quicprochat 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 with:
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"qpc".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 quicprochat 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 OPAQUE password authentication and Ed25519 identity keys. This is a deliberate design choice -- OPAQUE provides stronger authentication properties than TLS client certificates without requiring PKI infrastructure. -
ALPN negotiation: The Application-Layer Protocol Negotiation extension is set to
b"qpc", advertising that this endpoint speaks the quicprochat v2 Protobuf framing protocol. 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 mut tls = rustls::ClientConfig::builder_with_protocol_versions(&[&TLS13])
.with_root_certificates(roots)
.with_no_client_auth();
tls.alpn_protocols = vec![b"qpc".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
The v2 server accepts connections and handles streams concurrently:
// Accept a QUIC connection
let connection = endpoint.accept().await?;
// For each incoming bidirectional stream (one per RPC call):
let (send, recv) = connection.accept_bi().await?;
// Read RequestFrame, dispatch, write ResponseFrame
tokio::spawn(handle_rpc(send, recv, server_state));
// For server-initiated push events:
let send = connection.open_uni().await?;
// Write PushFrame
tokio::spawn(send_push(send, event));
Unlike the v1 Cap'n Proto RPC (which required tokio::task::LocalSet due to
!Send internals), the v2 framework uses Arc-based shared state and
tokio::spawn for full multi-threaded concurrency.
Certificate trust model
quicprochat 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 orQPC_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 OPAQUE password authentication (methods 100-103) and Ed25519 identity keys at the application layer. See Service Architecture.
- End-to-end encryption: TLS terminates at the server. The server can read the Protobuf 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.
Configuration reference
Server
| Environment Variable | CLI Flag | Default | Description |
|---|---|---|---|
QPC_LISTEN |
--listen |
0.0.0.0:5001 |
QUIC listen address |
QPC_TLS_CERT |
--tls-cert |
data/server-cert.der |
TLS certificate path |
QPC_TLS_KEY |
--tls-key |
data/server-key.der |
TLS private key path |
QPC_DATA_DIR |
--data-dir |
data |
Persistent storage directory |
Client
| Environment Variable | CLI Flag | Default | Description |
|---|---|---|---|
QPC_CA_CERT |
--ca-cert |
data/server-cert.der |
Server certificate to trust |
QPC_SERVER_NAME |
--server-name |
localhost |
Expected TLS server name (must match certificate SAN) |
QPC_SERVER |
--server |
127.0.0.1:5001 |
Server address (per-subcommand) |
Further reading
- Protobuf Framing -- The RPC framing layer that runs on top of QUIC streams.
- Service Architecture -- How the server binds to the QUIC endpoint and dispatches 44 RPC methods.