{
 "slug": "fail-closed-cryptographic-key-management-implementing-dual-key",
 "topic_id": "TOPIC-092",
 "cluster": "Cryptography & Standards",
 "tier": "Tier B",
 "title": "Rotating signing keys without breaking verification",
 "summary": "Key rotation causes outages when verifiers reject signatures made with a key they have not yet seen. The fix is an overlap window and a discipline about what gets retired.",
 "lede": "Rotation is a security requirement and an operational hazard. The failure is always the same shape: a signer moves to a new key faster than verifiers learn about it, and everything in between fails.",
 "date": "2024-11-02",
 "category": "Compliance",
 "author_id": "constance-ibe-whitmore",
 "tags": [
  "key rotation",
  "key management",
  "JWKS",
  "availability",
  "fail closed",
  "operations"
 ],
 "image_title": "Rotating Keys Without Breaking",
 "schema": "Article",
 "key_takeaways": [
  "Signing and verification have asymmetric lifetimes: a key stops signing long before it stops needing to verify.",
  "An overlap window where the new key is published before it signs removes the propagation race entirely.",
  "Retiring a verification key invalidates every historical signature it made, which is why audit keys are never retired."
 ],
 "body": [
  {
   "type": "h2",
   "text": "The two lifetimes"
  },
  {
   "type": "diagram",
   "kind": "chain",
   "alt": "Three phases, and why the first one matters",
   "caption": "Publishing before signing removes the race rather than shortening it.",
   "nodes": [
    {
     "label": "Phase 1: publish B, sign with A",
     "sub": "overlap",
     "note": "longer than any cache",
     "good": true
    },
    {
     "label": "Phase 2: cut over to B",
     "sub": "every verifier knows B",
     "note": "no race",
     "good": true
    },
    {
     "label": "Phase 3: keep A for verification",
     "sub": "retention period",
     "note": "never removed",
     "good": true
    }
   ]
  },
  {
   "type": "p",
   "html": "The mistake underneath most rotation outages is treating a key as having one lifetime. It has two."
  },
  {
   "type": "table",
   "head": [
    "Phase",
    "Duration",
    "What it means"
   ],
   "rows": [
    [
     "Signing lifetime",
     "Weeks to months",
     "The key is used to produce new signatures"
    ],
    [
     "Verification lifetime",
     "<strong style=\"font-weight:600\">The full retention period</strong>",
     "The key must remain available to check old signatures"
    ]
   ]
  },
  {
   "type": "p",
   "html": "For session tokens the second can be short, because nobody verifies a token from last year. For audit records it is the retention period, which may be a decade."
  },
  {
   "type": "h2",
   "text": "Why rotation breaks things"
  },
  {
   "type": "code",
   "text": "# The race\n  t0   issuer rotates to key B, begins signing with it\n  t0   key set published with A and B\n  t0+  verifiers have a cached key set containing only A\n       → signatures from B fail until the cache expires\n\n# Cache TTLs vary. Some verifiers refresh on failure,\n# many do not. Some are offline. Some are other companies."
  },
  {
   "type": "p",
   "html": "The window is bounded by the longest cache TTL among verifiers, which the issuer does not control and often does not know."
  },
  {
   "type": "h2",
   "text": "The overlap window"
  },
  {
   "type": "p",
   "html": "Publish before signing. The sequence removes the race rather than shortening it."
  },
  {
   "type": "code",
   "text": "# Phase 1  — publish, do not sign\n  key set: [A (active), B (published, not signing)]\n  duration: longer than the longest verifier cache TTL\n\n# Phase 2  — cut over\n  key set: [A (retired for signing), B (active)]\n  new signatures use B; every verifier already knows B\n\n# Phase 3  — keep A for verification\n  key set: [A (verify only), B (active)]\n  duration: the retention period. A is never removed."
  },
  {
   "type": "p",
   "html": "Phase one duration is the design decision. It must exceed the slowest verifier's refresh interval, and if you do not know what that is, the answer is longer than you think."
  },
  {
   "type": "h2",
   "text": "What the published key set should carry"
  },
  {
   "type": "code",
   "text": "{\n  \"keys\": [\n    { \"kid\": \"2026-03-a\", \"kty\": \"OKP\", \"crv\": \"Ed25519\",\n      \"x\": \"...\", \"use\": \"sig\",\n      \"status\": \"verify_only\",\n      \"signing_from\": \"2025-09-01\",\n      \"signing_until\": \"2026-03-01\" },\n    { \"kid\": \"2026-03-b\", \"kty\": \"OKP\", \"crv\": \"Ed25519\",\n      \"x\": \"...\", \"use\": \"sig\",\n      \"status\": \"active\",\n      \"signing_from\": \"2026-03-01\" }\n  ]\n}"
  },
  {
   "type": "p",
   "html": "The signing window fields let a verifier check that a signature was made while its key was active. A signature dated outside its key's window is suspicious independently of whether it verifies."
  },
  {
   "type": "h2",
   "text": "Failing closed without failing open in practice"
  },
  {
   "type": "p",
   "html": "A verifier that cannot fetch the key set has a choice, and the usual implementation quietly picks wrong."
  },
  {
   "type": "table",
   "head": [
    "Behaviour",
    "Consequence"
   ],
   "rows": [
    [
     "Accept unverified",
     "The control is bypassed by making the key endpoint unreachable"
    ],
    [
     "Reject everything",
     "An availability incident for a network problem"
    ],
    [
     "Use the cached set, reject unknown key ids",
     "<strong style=\"font-weight:600\">Correct</strong> — known keys still work, unknown ones fail"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The third row requires a durable cache rather than an in-memory one, so a restart during an outage does not empty it. That detail is where implementations usually fail."
  },
  {
   "type": "h2",
   "text": "Compromise is different from rotation"
  },
  {
   "type": "p",
   "html": "Planned rotation is graceful. Compromise is not, and conflating them produces a bad design."
  },
  {
   "type": "ol",
   "items": [
    "A compromised key must stop being accepted immediately — there is no overlap window.",
    "Signatures it made before the compromise may still be valid, and distinguishing them requires knowing when the compromise began.",
    "That is what a signed revocation entry with an effective timestamp provides: signatures before this moment remain valid, after it do not.",
    "Without that distinction, a compromise invalidates every historical signature the key made, which for an audit key means losing the evidence."
   ]
  },
  {
   "type": "p",
   "html": "Point four is the argument for planning compromise handling before it happens. An organisation that revokes an audit signing key without an effective-time boundary has destroyed its own records."
  },
  {
   "type": "h2",
   "text": "Two lifetimes, not one"
  },
  {
   "type": "table",
   "caption": "Signing and verification are different clocks",
   "head": [
    "Phase",
    "Duration",
    "Meaning"
   ],
   "rows": [
    [
     "Signing lifetime",
     "Weeks to months",
     "The key produces new signatures"
    ],
    [
     "<strong style=\"font-weight:600\">Verification lifetime</strong>",
     "<strong style=\"font-weight:600\">The full retention period</strong>",
     "<strong style=\"font-weight:600\">The key must remain available</strong>"
    ]
   ]
  },
  {
   "type": "p",
   "html": "Treating a key as having one lifetime is the mistake underneath most rotation outages, and underneath the worse failure of retiring a key that still needs to verify a decade of records."
  },
  {
   "type": "h2",
   "text": "Objections and honest limits"
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“Our verifiers refresh on failure.”</strong> Some do. Some are offline, some are other companies, and some cache for a day. The overlap window has to accommodate the slowest one you do not know about."
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“Compromise is just faster rotation.”</strong> It is not. Compromise has no overlap window, and it needs an effective-time boundary so signatures made before it remain valid. Without that, revoking an audit key destroys your own records."
  }
 ],
 "faq": [
  {
   "q": "Why do rotation outages happen?",
   "a": "The signer moves to a new key faster than verifiers refresh their cached key set. The window is bounded by the slowest verifier's TTL, which the issuer does not control."
  },
  {
   "q": "How long should the overlap window be?",
   "a": "Longer than the longest verifier cache TTL. If you do not know what that is, longer than you think."
  },
  {
   "q": "When can a verification key be removed?",
   "a": "For audit records, never within the retention period. Removing it invalidates every historical signature it made."
  },
  {
   "q": "How is compromise different from rotation?",
   "a": "There is no overlap window. You need a signed revocation with an effective timestamp so signatures before the compromise remain valid."
  }
 ],
 "sources": [
  {
   "t": "RFC 7517 — JSON Web Key",
   "u": "https://www.rfc-editor.org/rfc/rfc7517"
  },
  {
   "t": "NIST SP 800-57 Part 1 Rev. 5 — Key Management",
   "u": "https://csrc.nist.gov/pubs/sp/800/57/pt1/r5/final"
  },
  {
   "t": "RFC 8037 — CFRG elliptic curve signatures for JOSE",
   "u": "https://www.rfc-editor.org/rfc/rfc8037"
  },
  {
   "t": "Published operational guidance on key rotation in distributed systems."
  }
 ],
 "related": [
  {
   "slug": "signed-revocation-lists-vs-ocsp-building-fast-offline",
   "title": "Signed revocation lists versus OCSP",
   "category": "Comparison"
  },
  {
   "slug": "cryptographic-receipt-primitive-rfc8785-ed25519-vs-jwt",
   "title": "A session token is not an audit record",
   "category": "Compliance"
  },
  {
   "slug": "post-quantum-cryptography-pqc-roadmap-migrating-ed25519",
   "title": "The post-quantum migration path",
   "category": "Compliance"
  }
 ],
 "image": "https://cdn.twc.sh/images/igcache/Rotating%20Keys%20Without%20Breaking/1500_900/blog.jpg",
 "wordcount": 843,
 "url": "/blog/fail-closed-cryptographic-key-management-implementing-dual-key.html",
 "reading_time": "4 min read",
 "meta_description": "Key rotation causes outages when verifiers reject signatures made with a key they have not yet seen.",
 "hub": {
  "slug": "topics/receipt-cryptography",
  "title": "Receipt cryptography and standards"
 },
 "answer": "Publish before you sign. Rotation outages happen because the signer moves to a new key faster than verifiers refresh their cached key set, and the window is bounded by the slowest verifier's cache — which the issuer does not control and usually does not know.",
 "answer_q": "How do you rotate a signing key without breaking verification?",
 "glossary": [
  {
   "term": "Overlap window",
   "def": "The period a new key is published but not yet signing, which removes the propagation race."
  },
  {
   "term": "Signing window",
   "def": "The interval during which a key was active, carried in the key set so out-of-window signatures are detectable."
  },
  {
   "term": "Effective-time revocation",
   "def": "Revoking from a specific moment, so earlier signatures survive a compromise."
  }
 ],
 "checklist": {
  "title": "Rotating safely",
  "id": "rotate",
  "desc": "Four steps.",
  "steps": [
   {
    "name": "Publish the new key before signing with it.",
    "text": "For longer than the slowest verifier's cache."
   },
   {
    "name": "Carry signing windows in the key set.",
    "text": "So a signature dated outside its key's window is detectable."
   },
   {
    "name": "Never remove a verification key inside the retention period.",
    "text": "Removing it invalidates every signature it made."
   },
   {
    "name": "Use a durable cache and reject unknown key ids.",
    "text": "So a restart during an outage does not empty it."
   }
  ]
 },
 "cta": {
  "title": "Where this fits in Manav",
  "html": "Manav's receipt is deliberately small: RFC 8785 canonicalisation, one signature algorithm, keys published at a well-known URL, and a verifier short enough to audit or reimplement.",
  "href": "../architecture.html",
  "label": "Read the architecture"
 }
}