Skip to content

feat(pairwise): let the reversible deriver's keys stay in an HSM or KMS - #653

Merged
osanderson merged 3 commits into
mainfrom
feat/pairwise-reversible-ops
Oct 13, 2026
Merged

osanderson merged 3 commits into
mainfrom
feat/pairwise-reversible-ops

Conversation

@osanderson

Copy link
Copy Markdown
Collaborator

pairwise.NewReversibleWith(ops ReversibleOps, opts...) builds the reversible pairwise deriver on two embedder-implemented keyed operations, so the MAC and encryption keys can stay inside an HSM or KMS that never releases them:

  • MAC(ctx, data): HMAC-SHA-256 under the MAC key, full 32-byte output (PKCS#11 CKM_SHA256_HMAC).
  • EncryptCTR(ctx, iv, data): AES-256-CTR under the encryption key, with the 16-byte IV as the whole initial counter block incremented as one 128-bit big-endian integer (PKCS#11 CKM_AES_CTR with ulCounterBits 128). Decryption is the same operation.

For the same keys it derives exactly the subs NewReversible does, so a deployment can move between envelope-wrapped keys and HSM/KMS-resident ones without changing any user's sub. NewReversible is now NewReversibleWith over in-memory operations, so both share one code path (the existing weak-key checks stay in NewReversible).

Fail closed: every operation result is checked (a 32-byte MAC, a ciphertext as long as its input). An error or wrong-sized result fails the derivation or lookup and is never ErrSubjectNotFound, so it surfaces as server_error, not an unknown subject. The caller's context reaches each operation. Custody is declared through opts, as for NewReversible.

Docs: GETTING_STARTED's pairwise section gets a compatibility table (PKCS#11 HSMs, AWS CloudHSM, Google Cloud KMS, AWS KMS, Azure Key Vault Managed HSM, Azure Cloud HSM, HashiCorp Vault Transit) with the exact mechanisms or APIs for each operation, caveats and sources, plus guidance on choosing between envelope-wrapped and resident keys (two key-service calls per derivation and per resource-server lookup with JWT access tokens).

Tests:

  • NewReversibleWith over a standard-library stand-in for an HSM reproduces all 11 published vectors in both directions.
  • An implementation with a 32-bit counter fails the carry vector (IV ending 0xfffffffd).
  • Operation errors and wrong-sized results fail closed, not ErrSubjectNotFound.
  • The caller's context reaches every operation; custody declarations work; the deriver contract passes.
  • New fuzz target FuzzReversibleWithRoundTrip checks byte-identical bodies against NewReversible and round-trips (registered in the scheduled fuzz workflow).

Non-breaking.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

osanderson and others added 3 commits October 13, 2026 10:41
NewReversibleWith builds the reversible deriver on a ReversibleOps: the
two keyed operations the construction needs (HMAC-SHA-256, and AES-256-CTR
with a caller-supplied IV as a full 128-bit counter), implemented by the
embedder against an HSM or KMS that never releases the keys. For the
same keys it derives exactly the subs NewReversible does, so a
deployment can move between them without changing any user's sub.
NewReversible is now NewReversibleWith over in-memory operations, one
code path for both.

Every result is checked (a 32-byte MAC, a ciphertext as long as its
input), and a failed or wrong-sized operation means the derivation or
lookup couldn't be answered, never ErrSubjectNotFound. Tests cover the
published vectors through ops, a 32-bit-counter implementation failing
the carry vector, operation failures, the caller's context, custody and
the deriver contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A compatibility table in GETTING_STARTED's pairwise section: for PKCS#11
HSMs, AWS CloudHSM, Google Cloud KMS, AWS KMS, Azure Key Vault Managed
HSM, Azure Cloud HSM and HashiCorp Vault Transit, whether both keys can
stay inside, the exact mechanisms or APIs for each operation, and
caveats (the 128-bit counter, roles, unverified support), with sources.
Plus how to choose between envelope-wrapped keys and HSM- or KMS-resident
ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@osanderson
osanderson force-pushed the feat/pairwise-reversible-ops branch from 7d88dec to 3a2f59d Compare October 13, 2026 02:42
@sonarqubecloud

Copy link
Copy Markdown

@osanderson
osanderson merged commit b58a170 into main Oct 13, 2026
19 checks passed
@osanderson
osanderson deleted the feat/pairwise-reversible-ops branch October 13, 2026 03:56
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