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
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 |