# TLS in quicproquo

quicproquo uses QUIC (RFC 9000) for all client-server communication. QUIC mandates TLS 1.3, so every connection is encrypted and authenticated at the transport layer — there is no plaintext mode.

## How it works

The server holds a TLS certificate and private key (DER format). On startup it either loads existing files or, in development mode, generates a self-signed certificate automatically. The client authenticates the server by verifying its certificate against a trusted root provided via `--ca-cert` (or `QPQ_CA_CERT`).

The TLS handshake negotiates the ALPN protocol `capnp`, after which the QUIC bi-directional stream carries Cap'n Proto RPC traffic.

## Certificate pinning with `--ca-cert`

By default the client trusts exactly the certificate (or CA) in the file given by `--ca-cert`:

```bash
qpq --ca-cert data/server-cert.der --server-name localhost health --server 127.0.0.1:7000
```

This is a form of **certificate pinning**: the client will only connect to a server whose certificate chains to the provided root. For single-server deployments, pass the server's own self-signed certificate. For CA-issued certificates, pass the CA's root certificate instead.

| Flag / Env var | Purpose |
|---|---|
| `--ca-cert` / `QPQ_CA_CERT` | Path to trusted root certificate (DER) |
| `--server-name` / `QPQ_SERVER_NAME` | Expected TLS server name (must match certificate SAN) |

## The `--danger-accept-invalid-certs` flag

For local development and testing you can skip certificate verification entirely:

```bash
qpq --danger-accept-invalid-certs health --server 127.0.0.1:7000
```

Or via the environment:

```bash
export QPQ_DANGER_ACCEPT_INVALID_CERTS=true
qpq health --server 127.0.0.1:7000
```

When active, the client prints a warning to stderr:

```
WARNING: TLS verification disabled — insecure mode
```

**Never use this flag in production.** It disables all certificate checks, making the connection vulnerable to man-in-the-middle attacks. It exists solely so that developers can iterate without managing certificates during local testing.

## Generating self-signed certificates for development

### Using rcgen (Rust)

The server generates a self-signed certificate automatically when the cert/key files are missing (unless `QPQ_PRODUCTION=1` is set). The generated files are written to:

- `data/server-cert.der` — DER-encoded certificate
- `data/server-key.der` — DER-encoded PKCS#8 private key

### Using openssl

To generate a self-signed certificate manually:

```bash
# Generate a private key and self-signed certificate (PEM)
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
  -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

# Convert to DER format (required by quicproquo)
openssl x509 -in cert.pem -outform DER -out data/server-cert.der
openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out data/server-key.der -nocrypt
```

Point the server at the DER files:

```bash
export QPQ_TLS_CERT=data/server-cert.der
export QPQ_TLS_KEY=data/server-key.der
cargo run -p quicproquo-server
```

And the client at the certificate:

```bash
qpq --ca-cert data/server-cert.der --server-name localhost repl
```

## CA-issued certificates

For production deployments with a public CA (e.g. Let's Encrypt):

1. Obtain the certificate and key (e.g. via certbot).
2. Convert to DER format as shown above.
3. Configure the client to trust the CA root rather than the server certificate directly:

```bash
qpq --ca-cert /etc/ssl/certs/isrg-root-x1.der --server-name chat.example.com repl
```

See [Certificate Lifecycle and CA-Signed TLS](certificate-lifecycle.md) for rotation, OCSP, and operational details.
