# 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 ```bash # 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= # 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 ```bash # 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: ```bash 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 ```bash #!/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 ``` ```cron # 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 ```bash # 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) ```bash # 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 <> .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: ```bash # 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.** ```bash # 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. ```bash # 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 |