Export Center KMS Envelope Pattern (age + AES-GCM)

Audience: Export Center service engineers and operators implementing or verifying encrypted bundle payloads. Purpose: Define the deterministic envelope-encryption scheme StellaOps uses for mirror bundle encryption and general export signing, so that bundles encrypt and decrypt identically across reruns and remain verifiable offline.

This pattern covers the worker (write) path, the verification (read) path, and the provenance metadata that ties the two together. It supports two interchangeable wrapping mechanisms — age recipients for fully offline deployments and a KMS envelope for installations with Authority/HSM reachability.

Key hierarchy

Write path (worker)

  1. Generate DEK (32 bytes) per run; zeroize after use.
  2. For each encrypted file, derive AAD = {runId}:{relativePath}; encrypt with AES-256-GCM (nonce per file). Store nonce and ciphertext.
  3. Wrap DEK for all configured recipients:
    • age: age --encrypt --recipient <pub> over DEK bytes → base64.
    • KMS: Encrypt/WrapKey with KeyId=export/envelope and EncryptionContext={runId,tenant} → base64.
  4. Record wrapping metadata in provenance.json under environment.encryption.recipients[] preserving deterministic order (age recipients lexicographically by recipient, then KMS entries by kmsKeyId).
  5. Include encryption.mode (age or aes-gcm+kms), aadFormat, and nonceFormat in provenance for verification tooling.

Read/verification path

  1. Select a recipient entry that matches available keys (age private key or KMS key).
  2. Unwrap DEK:
    • age: age --decrypt → DEK bytes.
    • KMS: Decrypt/UnwrapKey with same encryption context.
  3. For each encrypted file, recompute AAD from {runId}:{relativePath}, decrypt with AES-256-GCM using stored nonce, verify tag.
  4. Recompute SHA-256 of decrypted payload and compare with export.json entries.

Determinism & offline posture

Testing notes

Rollout guidance