Security
Key Rotation Guide
This page tracks two distinct key lifecycles:
- Disclosure key: the GPG key reporters use to encrypt vulnerability reports to the maintainer. Single key, public, valid for three years and rotated before expiry or on compromise. Source of truth for the active fingerprint is
SECURITY.md. - Secrets encryption keys: the Age and SOPS keys that protect the encrypted files in your chezmoi source. Multiple keys, per-machine, rotated annually or on personnel / device change.
Both sections live here so a single audit can confirm the project's posture across encrypted-disclosure-in and encrypted-secrets-at-rest.
Disclosure Key (GPG)
Closes the rotation half of #870.
Active key
| Field | Value |
|---|---|
| Identity | security@sebastienrousseau.com |
| Fingerprint | 55AFAD364FD9DB3819E61F0C8D688FAFA9144693 |
| Algorithm | ED25519 primary (sign, certify) + CV25519 encryption subkey |
| Created | 2026-05-16 |
| Expires | 2029-05-15 (3 years from creation; the encryption subkey has no expiry of its own) |
| In-repo copy | docs/security/security-pubkey.asc |
| WKD URL | https://sebastienrousseau.com/.well-known/openpgpkey/hu/t5s8ztdbon8yzntexy6oz5y48etqsnbb |
| Keyserver | Not published to keys.openpgp.org; use the in-repo copy or WKD. |
| Consistency check | .github/workflows/verify-gpg-wkd.yml compares the in-repo key, the WKD key and the pinned fingerprint. This key does not sign commits; commit signing uses separate SSH keys (KEYS.asc). |
Rotation triggers
| Trigger | Response time | Action |
|---|---|---|
| Expiry | Within 30 days of expiry | Generate new key, publish, sign transition statement with the old key, archive old key below. |
| Suspected compromise | Same day | Revoke immediately, publish revocation cert to WKD, replay in-flight encrypted disclosures with the new key. |
| Algorithm deprecation | Within 90 days of advisory | Generate replacement on a stronger algorithm; follow the expiry steps. |
| Maintainer change | Same day as handover | Old maintainer signs a transition statement; old key revoked 30 days after handover. |
Rotation procedure
-
Generate the new key (air-gapped where possible), with the same shape as the active one: an ED25519 primary plus a CV25519 encryption subkey (reporters encrypt to the subkey):
-
Sign a transition statement with the OLD key:
-
Publish the new key to:
sebastienrousseau.com/.well-known/openpgpkey/hu/(WKD)docs/security/security-pubkey.ascin this repository- the maintainer's keyoxide profile if applicable
Then update the fingerprint pinned in
SECURITY.mdand in.github/workflows/verify-gpg-wkd.yml, which fails when the three disagree. -
Update the "Active key" table above with the new fingerprint and move the previous row into the "Historical disclosure keys" table below with the actual revocation date.
-
Commit, signing with both old and new keys during the overlap:
-
After the 30-day overlap, revoke the old key and publish the revocation cert to the same WKD endpoint.
Historical disclosure keys
| From | Until | Fingerprint | Reason for rotation |
|---|---|---|---|
| (none yet — first key) | — | — | — |
Age Key Rotation
When to Rotate
- Annually: As a preventive measure
- After compromise: If you suspect key exposure
- Personnel change: When team members leave
- Device loss: If a device with the key is lost/stolen
Rotation Procedure
1. Generate New Key
# Create new age key
# Note the public key (starts with "age1...")
|
2. Re-encrypt All Secrets
# List all encrypted files
# For each encrypted file, decrypt with old key and re-encrypt with new
for; do
# Decrypt with old key
# Re-encrypt with new key (update chezmoi config first)
# See step 3
done
3. Update Chezmoi Configuration
Edit ~/.config/chezmoi/chezmoi.toml:
[age]
identity = "~/.config/chezmoi/key-new.txt"
recipient = "age1your_new_public_key_here"
4. Re-add Encrypted Files
# Re-add each secret with new encryption
5. Verify and Clean Up
# Verify decryption works
# Archive old key (store securely offline)
# Rename new key
# Point [age] identity back at the renamed file in chezmoi.toml
# (step 3 set it to key-new.txt, which no longer exists)
# identity = "~/.config/chezmoi/key.txt"
&&
# Commit changes
Emergency Rotation (Compromised Key)
# 1. Immediately generate new key
# 2. Re-encrypt ALL secrets (prioritize most sensitive)
# 3. Revoke/rotate any API keys, tokens that were encrypted
# 4. Update any shared secrets with team members
# 5. Document the incident
SOPS Key Rotation
Overview
SOPS supports multiple key types. This covers Age keys (recommended) and GPG keys.
SOPS with Age Keys
Generate New Key
Update .sops.yaml
creation_rules:
- path_regex: \.env$
age: >-
age1new_key_here,
age1old_key_here # Keep old key temporarily for decryption
Re-encrypt Files
# Rotate key for a single file
# Or re-encrypt entirely
|
Remove Old Key
After confirming all files are re-encrypted:
- Remove old key from
.sops.yaml - Archive old key securely
- Commit changes
SOPS with GPG Keys
# Generate new GPG key
# Export fingerprint
# Update .sops.yaml with new fingerprint
# Re-encrypt files
Automation Scripts
Check Key Age
#!/usr/bin/env bash
# check-key-age.sh - Alert if keys are older than 365 days
KEY_FILE="/.config/chezmoi/key.txt"
MAX_AGE_DAYS=365
if ; then
key_age=
if ; then
else
fi
fi
Automated Backup Before Rotation
#!/usr/bin/env bash
# backup-before-rotation.sh
BACKUP_DIR="/.local/share/dotfiles/key-backups"
# One timestamp for every step: calling date repeatedly can cross a
# second boundary and point the later steps at a file that doesn't exist
ts=""
plain="/key-.txt"
# Backup current key
# Encrypt backup with passphrase (writes "$plain.gpg")
# Remove unencrypted backup
Best Practices
- Never commit unencrypted keys - Use
.gitignoreproperly - Store backups offline - USB drive in secure location
- Use hardware keys when possible - YubiKey with age-plugin-yubikey
- Document rotation dates - Keep a log of when keys were rotated
- Test decryption - Always verify after rotation
- Notify team members - If using shared secrets
Recovery Procedures
If you lose access to your key:
- Check backups - Offline storage, password manager
- Check other machines - Key may exist on another device
- Re-create secrets - As last resort, regenerate API keys, tokens, etc.
Related Documentation
- SECRETS.md - Secrets management overview
- SECURITY.md - Security hardening guide
- Age documentation
- SOPS documentation