Files
quicproquo/docs/operations/key-rotation.md
Christian Nennemann 2e081ead8e chore: rename quicproquo → quicprochat in docs, Docker, CI, and packaging
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
2026-03-21 19:14:06 +01:00

7.5 KiB

Key Rotation Procedures

This document provides step-by-step procedures for rotating all cryptographic material in a quicprochat deployment.

Auth Token Rotation

The auth token (QPC_AUTH_TOKEN) is used for bearer-token authentication (auth version 1). OPAQUE-authenticated sessions are not affected by token rotation.

Procedure

# 1. Generate a new token (minimum 16 characters for production)
NEW_TOKEN=$(openssl rand -base64 32)
echo "New token: $NEW_TOKEN"

# 2. Update the config file or environment
#    Option A: TOML config file
sed -i "s/^auth_token = .*/auth_token = \"$NEW_TOKEN\"/" qpc-server.toml

#    Option B: Environment variable (systemd)
systemctl edit qpc-server --force
# Add: Environment=QPC_AUTH_TOKEN=<new-token>

#    Option C: Docker Compose
# Update QPC_AUTH_TOKEN in docker-compose.prod.yml or .env file

# 3. Restart the server
systemctl restart qpc-server
# or: docker compose restart server

# 4. Update all clients with the new token
# Clients using OPAQUE auth are unaffected.
# Clients using bearer-token auth must update their QPC_ACCESS_TOKEN.

Impact

  • Active bearer-token sessions continue until they expire (sessions are in-memory).
  • New bearer-token connections must use the new token.
  • OPAQUE-authenticated clients are not affected.

TLS Certificate Rotation

The server uses DER-encoded X.509 certificates for QUIC TLS 1.3. The server validates certificates at startup and warns if expiry is within 30 days.

Procedure

# 1. Obtain a new certificate (example with Let's Encrypt / certbot)
certbot certonly --standalone -d chat.example.com

# 2. Convert PEM to DER format (qpc-server expects DER)
openssl x509 -in /etc/letsencrypt/live/chat.example.com/fullchain.pem \
  -outform DER -out /tmp/server-cert.der

openssl pkey -in /etc/letsencrypt/live/chat.example.com/privkey.pem \
  -outform DER -out /tmp/server-key.der

# 3. Set restrictive permissions on the private key
chmod 600 /tmp/server-key.der

# 4. Back up the current certificates
cp data/server-cert.der data/server-cert.der.bak
cp data/server-key.der data/server-key.der.bak

# 5. Replace certificates
cp /tmp/server-cert.der data/server-cert.der
cp /tmp/server-key.der data/server-key.der

# 6. Verify the new certificate
openssl x509 -inform DER -in data/server-cert.der -noout -text | head -20

# 7. Restart the server (QUIC requires restart for new TLS config)
systemctl restart qpc-server

# 8. Verify the server started with the new certificate
journalctl -u qpc-server --since "1 min ago" | grep -i tls

Self-Signed Certificate (Development)

In non-production mode, the server auto-generates a self-signed certificate if none exists. To force regeneration:

rm data/server-cert.der data/server-key.der
systemctl restart qpc-server
# Server will generate a new self-signed cert for localhost/127.0.0.1/::1

Automated Renewal with Certbot

#!/bin/bash
# /opt/qpc/scripts/renew-cert.sh
set -euo pipefail

DOMAIN="chat.example.com"
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
QPC_DATA="/var/lib/quicprochat"

certbot renew --quiet

openssl x509 -in "$CERT_DIR/fullchain.pem" -outform DER -out "$QPC_DATA/server-cert.der"
openssl pkey -in "$CERT_DIR/privkey.pem" -outform DER -out "$QPC_DATA/server-key.der"
chmod 600 "$QPC_DATA/server-key.der"
chown qpc:qpc "$QPC_DATA/server-cert.der" "$QPC_DATA/server-key.der"

systemctl restart qpc-server
# Run cert renewal check twice daily
0 3,15 * * * /opt/qpc/scripts/renew-cert.sh >> /var/log/qpc-cert-renew.log 2>&1

Federation Certificate Rotation

Federation uses mutual TLS (mTLS) with a shared CA for server-to-server authentication.

Procedure

# 1. Generate a new federation certificate signed by the federation CA
openssl req -new -nodes -keyout /tmp/federation-key.pem \
  -out /tmp/federation.csr -subj "/CN=chat.example.com"

openssl x509 -req -in /tmp/federation.csr \
  -CA federation-ca.pem -CAkey federation-ca-key.pem \
  -CAcreateserial -days 365 -out /tmp/federation-cert.pem

# 2. Convert to DER
openssl x509 -in /tmp/federation-cert.pem -outform DER -out data/federation-cert.der
openssl pkey -in /tmp/federation-key.pem -outform DER -out data/federation-key.der
chmod 600 data/federation-key.der

# 3. Restart the server
systemctl restart qpc-server

# 4. Coordinate with federation peers: they must trust the same CA

Database Encryption Key Rotation

The SQLCipher database key (QPC_DB_KEY) encrypts all data at rest.

Procedure (SQLCipher PRAGMA rekey)

# 1. Stop the server
systemctl stop qpc-server

# 2. Back up the database
cp data/qpc.db /backups/qpc-pre-rekey-$(date +%Y%m%d).db

# 3. Rekey the database
sqlite3 data/qpc.db <<EOF
PRAGMA key = 'old-encryption-key';
PRAGMA rekey = 'new-encryption-key';
EOF

# 4. Verify the database opens with the new key
sqlite3 data/qpc.db "PRAGMA key = 'new-encryption-key'; PRAGMA integrity_check;"

# 5. Update the environment/config with the new key
#    Option A: systemd
systemctl edit qpc-server --force
# Environment=QPC_DB_KEY=new-encryption-key

#    Option B: Docker Compose .env
echo "QPC_DB_KEY=new-encryption-key" >> .env

# 6. Start the server
systemctl start qpc-server

Full Re-encryption (Alternative)

If PRAGMA rekey is unavailable or you want a fresh database file:

# 1. Stop the server and back up
systemctl stop qpc-server
cp data/qpc.db /backups/qpc-pre-rekey.db

# 2. Export with old key, import with new key
sqlite3 data/qpc.db "PRAGMA key='old-key'; .dump" | \
  sqlite3 data/qpc-new.db "PRAGMA key='new-key'; .read /dev/stdin"

# 3. Replace the database
mv data/qpc-new.db data/qpc.db

# 4. Update config and restart
systemctl start qpc-server

OPAQUE ServerSetup Rotation

The OPAQUE ServerSetup is generated once and persisted. Rotating it invalidates all registered OPAQUE credentials.

WARNING: Rotating the OPAQUE ServerSetup requires all users to re-register. Only do this if the setup is compromised.

# 1. Stop the server
systemctl stop qpc-server

# 2. Back up the database
cp data/qpc.db /backups/qpc-pre-opaque-rotate.db

# 3. Delete the persisted OPAQUE setup
#    For SQL backend:
sqlite3 data/qpc.db "PRAGMA key='${QPC_DB_KEY}'; DELETE FROM server_state WHERE key = 'opaque_setup';"

#    For file backend:
rm data/opaque_setup.bin 2>/dev/null || true

# 4. Start the server (it will generate a new OPAQUE ServerSetup)
systemctl start qpc-server

# 5. All users must re-register (existing OPAQUE credentials are invalid)

Server Signing Key Rotation

The Ed25519 signing key is used for delivery proofs. Rotating it means old delivery proofs cannot be verified against the new key.

# 1. Stop the server
systemctl stop qpc-server

# 2. Back up
cp data/qpc.db /backups/qpc-pre-sigkey-rotate.db

# 3. Delete the persisted signing key seed
#    For SQL backend:
sqlite3 data/qpc.db "PRAGMA key='${QPC_DB_KEY}'; DELETE FROM server_state WHERE key = 'signing_key_seed';"

# 4. Start the server (generates a new Ed25519 signing key)
systemctl start qpc-server

Rotation Schedule

Key Material Rotation Frequency Impact
Auth token Quarterly or on compromise Clients using bearer auth must update
TLS certificate Before expiry (automate with certbot) Server restart required
Federation cert Annually or before expiry Coordinate with peers
DB encryption key Annually or on compromise Server downtime required
OPAQUE ServerSetup Only on compromise All users must re-register
Server signing key Only on compromise Old delivery proofs unverifiable