AGENTS/tasks/2026-06-19-keyid-4-route-encryption-sites.md
Date: 2026-06-19
Plan: AGENTS/plans/2_20/encryption-key-rotation.md — "Target design (phase 2)"
Status: ✅ Done
Depends on: Task 2 (keyset), Task 3 (envelope)
Size: M
Make every encryption site stamp the active key id on write and look the key up by id on read, with a legacy fallback for un-prefixed ciphertext. This removes trial-decryption.
util/keyring.go — new ConfigType methods replacing the phase-1 keyring ones:
EncryptAccessSecret(pt) → encodeEnvelope(activeAccessID, EncryptAESGCM(pt, accessMaterial)).DecryptAccessSecret(stored):
id, ct, hasID := parseEnvelope(stored).hasID: mat, ok := keyByID(id); !ok ⇒ loud error "key <id> not found" (do NOT silently try other keys); else DecryptAESGCM(ct, mat).!hasID: legacy path — decrypt with the flat access_key_encryption (and,
if still configured, the phase-1 secondaries) — see Task 6.EncryptOption/DecryptOption using activeOptionID, with the
legacy/option→access fallback preserved for un-prefixed JWT options.services/server/access_key_serializer_local.go:
SerializeSecret → EncryptAccessSecret.deserializeSecretWithKeys → replaced by the id-lookup DecryptAccessSecret;
keep DeserializeSecret2(key, explicitKey) for the --old-key CLI path.util/jwt.go: encryptJWTKey/decryptJWTKey route through the option keyset."<activeID>:<b64>"; decrypt round-trips.keys:).activeOptionID; pre-split / legacy JWT option still loads.