ADR-028: Verification root-key rotation and signed-material re-enveloping

Status

Accepted — 2026-06-10 (operator-approved; the four open questions below are resolved)

Context

Recent work (commits 7a4f7921e2 backend, 7446116190 Console UI) made signature-verification material admin-settable per regional crypto profile (world|fips|gost|sm|kcmvp|eidas) via Platform EnvironmentSettings, under the reserved namespace Verification:<profile>:<service>:..., consumed by services at boot through PlatformVerificationSettingsConfigurationSource (a fail-closed boot-time pull from GET /platform/verification-settings/{service}). Today the in-scope material is:

Those keys are currently seeded with test values for the world profile. Production needs:

  1. Operators to set real keys from both UI and CLI.
  2. A change to a verification root key to require an explicit “this has consequences” acknowledgment.
  3. A defined procedure to re-envelope existing signed material under the next key and then invalidate the old material.

Removing a verification root key makes every artifact signed under it stop verifying. A naive hard cutover would instantly break verification of all in-flight signed material (packs, DSSE attestations, VEX envelopes, evidence bundles). We must not do that.

Existing primitives we build on (verified in src/):

Decision

Adopt a phased, dual-trust rotation with explicit re-enveloping and a consequences-gated invalidation — never a hard cutover. Three phases, applied per region profile and per material type:

  1. Introduce (dual-trust). The operator adds the NEW verification root alongside the current one; both verify during a grace window. New material is signed with the new key; old material continues to verify under the old root. The new root is stored with status pending/active; the old becomes retiring. A warning is shown that a rotation has begun. No old material is invalidated yet.

  2. Re-envelope. A resumable, offline-capable re-enveloping job re-signs existing signed material under the new key:

    • Packs → bulk RotateSignatureAsync with the new key (iterates the pack corpus; idempotent; emits signature.rotated).
    • DSSE attestations / evidence bundles → add a new-keyId signature to each envelope (multi-sig) — or re-issue where re-signing is impossible.
    • VEX → re-sign / re-ingest under the new trust root. Progress is tracked per material type (total / re-enveloped / failed / skipped), is resumable, and runs without external services (air-gap-safe).
  3. Invalidate (consequences-gated). Once re-enveloping is complete (or the operator explicitly accepts the residual), the operator removes the OLD root. From this point, old signatures fail verification by design. This step requires an explicit typed consequences acknowledgment (UI confirmation dialog requiring the operator to type a confirmation phrase; CLI --confirm plus an --i-understand-old-material-will-be-invalidated flag) and is fully audited.

This applies to all roots — the env_settings verification roots and the regional crypto signing root (UseCryptoRegistry + KeyId) that attestor/evidence use for capsule/verdict signing. Signing-root rotation publishes both the old and new public keys in the JWKS during the grace window, re-envelopes (re-signs/re-seals) material under the new KeyId, then drops the old KeyId from the active JWKS — retaining the old public key as an archived root (see below).

Cross-cutting rules:

Consequences

Alternatives considered

Resolved decisions (2026-06-10, operator)

  1. Secret storage — secrets are never exposed in env_settings. Non-secret verification material (public keys, fingerprints) lives in env_settings; secret material (VexHub HMAC trust roots) is sealed in the platform encrypted credential store (ConnectorCredentials AEAD / ICredentialStore, regional-crypto-backed, ConnectorCredentialReSealStore for rotation). Vault is NOT required (optional integration only).
  2. Immutable / transparency-anchored material — verify it via a retained archivedroot (verify-only). Do NOT re-issue.
  3. Scope = all roots — rotate both the env_settings verification roots and the regional crypto signing root (UseCryptoRegistry/KeyId, JWKS-published) for attestor/evidence.
  4. Grace deadline with re-envelope incompletewarn-and-allow: invalidation proceeds with a residual-count warning; it is not blocked.

References