Skip to content

Carries the resolved key in the write partition - #125

Merged
johnnyt merged 1 commit into
mainfrom
enc-jwkn-cache-partition-carries-key
Sep 30, 2026
Merged

johnnyt merged 1 commit into
mainfrom
enc-jwkn-cache-partition-carries-key

Conversation

@johnnyt

@johnnyt johnnyt commented Sep 30, 2026

Copy link
Copy Markdown
Member

What

The engine's encryption cache id hashes the partition id, the suite and the context, never the key that wrapped the entry's data key. With the partition id built from the vault and the selector only (ADR-0001 decision 7), a write after a key version was minted found the warm entry from before the mint and wrapped its data key under the old version until the entry expired or the cache was dropped.

This change folds the resolved key into the write side's partition id. An encrypt and a rekey's write half now derive the id from the vault, the selector and the key encryption_key/2 resolved to (the namespace and name of an Encryptor.Key.Aes, the key id of an Encryptor.Key.Kms). The pre-image is length-prefixed, and the digest and 16-byte truncation are unchanged. A new version therefore gets a cold partition, and the write after a mint wraps under it at once, with no drain. The read side keeps decision 7's formula. Ruled by the operator, 2026-09-30.

  • Encryptor.Vault.Partition.encryption_id/3 (@doc false) computes the write-side id; Partition.id/2 is unchanged.
  • Encryptor.Vault.Encrypt.stack/4 and client/4 build the write-side stack; stack/3 and client/3 remain the read side's (Encryptor.Vault.Decrypt, a rekey's read half).
  • Encryptor.Vault.Rekey's write half calls client/4.
  • No public function or option is added. No wire spelling changes, and the ciphertext shape is unchanged. The partition id lives only in the in-memory cache table: its callers are Encryptor.Vault.Encrypt's partition_id/2 and the tests.

Records

  • ADR-0001 Amendment B (proposed) records the write-side formula (B1), why the pre-image cannot collide (B2), why the read side is unchanged (B3), and the one-time cold cache on deploy (B4). It is appended after the last heading and removes zero lines. The decision touches a hash pre-image the cache keys on, so it is held for the operator's reading.
  • ADR-0005 Note (2026-09-30) says what Amendment B's P2 step 1 drain still covers: a vault on a version without this change (including nodes mid-rolling-deploy), and the provider's own answer. It no longer covers a warm entry on a node running this change. Zero removed lines.
  • changelog.d/enc-jwkn.md (Fixed) names the one-time cold cache on upgrade.

Tests

  • shred_drain_test.exs, "P2 step 1, the mint, on a warm encryption cache". The existing test is flipped: it proves the cache is warm (the second write before the mint reuses the first's encrypted data key), then asserts that the write after the mint names version 2 with no drain. The new test "a rekey's write half rewrites under the new version at once, with no drain" warms the rekey's own write partition with two rewrites before the mint, then asserts that the rewrite after it names version 2.
  • partition_test.exs: two versions give distinct ids; a moved field boundary gives a distinct id (length prefix); AES and KMS keys are tagged apart; the vault and the selector still partition; the derivation is pinned. The two pinned hex values were computed by an independent implementation outside Elixir.
  • encrypt_test.exs: stack/4's partition id is encryption_id/3 and differs across versions and from stack/3's.
  • Each new or flipped test carries a one-line sabotage note. Every note was run and turned the named test red on an assertion.

Gate

mix quality green on the committed tree (684 of 684 tests, Credo, Dialyzer, Docs, doc links).

Review tier

This is a contract-tier PR: it changes what the write side's caching CMM keys on (a behaviour ADR-0001 decision 7 on main cites), and it carries a record Amendment.

Provenance

  • Engineering choice: the write-side id lives beside decision 7's in Encryptor.Vault.Partition, as a @doc false function, so the module stays the only place a partition id is computed without adding a public function. The read and write stacks are two arities of the internal stack/client rather than one tagged argument, so Encryptor.Vault.Decrypt needs no edit.
  • The rotation runbook's P2 step 1 drain text is left as written. It follows ADR-0005 Amendment B, which is still proposed, and the drain remains safe to run.

Refs: enc-jwkn

The engine's encryption cache id never hashes the key, so a write after
a mint found the warm entry from before it and wrapped its data key
under the old version. The write side's partition id (an encrypt and a
rekey's write half) now also carries the resolved key's identity, in a
length-prefixed pre-image; the read side keeps decision 7's formula.

ADR-0001 Amendment B (proposed) records the formula, why the pre-image
cannot collide and the one-time cold cache on deploy; a Note on ADR-0005
says what the P2 step 1 drain still covers. Ruled by the operator,
2026-09-30.

Refs: enc-jwkn
@johnnyt
johnnyt merged commit 749be99 into main Sep 30, 2026
1 check passed
@johnnyt
johnnyt deleted the enc-jwkn-cache-partition-carries-key branch September 30, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant