refactor: deepen keyring seam, transit domain, and audit signing#127
Merged
Conversation
… DEK/KEK errors internally
ADR-0013 promised that feature modules would not need to import
crypto/domain. That was partially true: Algorithm was re-exported, but
ErrDecryptionFailed, ErrDekNotFound, ErrKekNotFound, and Zero were not.
- keyring.go: add ErrDecryptionFailed and Zero re-exports
- impl.go: map ErrDekNotFound/ErrKekNotFound → ErrDecryptionFailed at
the Decrypt and openCipher boundaries; KEK/DEK vocabulary stays inside
the module
- secrets, transit, tokenization usecases and HTTP handlers: replace all
cryptoDomain.{Algorithm,Zero,ErrDecryptionFailed} with keyring.* equivalents
- secrets usecase: remove dead errors.Is check that both branches already
returned ErrDecryptionFailed anyway
- tests: update assertions to reflect the new contract (ErrDekNotFound
and ErrKekNotFound no longer propagate through the seam)
crypto/domain now has zero imports in feature production code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ADR-0013 flagged the hardcoded nonceSize = 12 in transit/usecase as a known negative: a future AEAD algorithm with a different nonce length would require a code search to find the buried constant. - domain/const.go: add AEADNonceSize = 12 with an explanatory comment tying it to the ADR-0002 wire format contract - domain/encrypted_blob.go: add NewFramedBlob(version, nonce, ct) and EncryptedBlob.SplitNonce() so framing is a domain concern, not usecase implementation detail - domain/encrypted_blob_test.go: add round-trip and error-path tests for both new functions - transit_key_usecase.go: remove const nonceSize and the manual append/slice framing; delegate to NewFramedBlob and SplitNonce Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The audit log use case previously held a raw *cryptoDomain.KekChain, exposing key material outside the keyring module. Replace kekChain + AuditSigner with a new keyring.KeySigner interface whose implementation keeps HKDF key derivation and HMAC-SHA256 signing entirely inside the keyring package. Canonicalization logic moves from auth/service to auth/domain.AuditLog.Canonical() — a domain method — so the byte representation that gets signed stays with the type it describes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Align test/integration/audit_log_signature_test.go with the NewAuditLogUseCase refactor that replaced (AuditSigner, *KekChain) with a single keyring.KeySigner parameter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three architectural deepening refactors targeting modules identified as shallow in an internal review. Each commit is self-contained and passes the full test suite.
Commit 1 — Complete the Keyring seam (
40098d8)internal/keyringwas introduced in #124 as a single module for envelope encryption, but callers were still importinginternal/crypto/domaindirectly to accessErrDecryptionFailed,Zero, andAlgorithm. This commit closes that gap:ErrDecryptionFailed,Zero,ErrKekNotFoundfromkeyringso callers need no crypto/domain importErrDekNotFound/ErrKekNotFoundtoErrDecryptionFailedinternally inDecryptandopenCipher— errors no longer leak across the seamcrypto/domainCommit 2 — Move transit nonce framing into
transit/domain(9ca5342)The usecase held a hardcoded
const nonceSize = 12and manually slicednonce ‖ ciphertext. This is domain knowledge (the AEAD wire format) living in the wrong layer:AEADNonceSize = 12totransit/domainconstantsNewFramedBlob(version, nonce, ciphertext)andSplitNonce()toEncryptedBlobEncryptandDecryptbecome single-line domain callsNewFramedBlobandSplitNonceCommit 3 — Move audit signing behind a
KeySignerseam (9b3b117)auditLogUseCaseheld a*cryptoDomain.KekChain— raw key material passing through the usecase layer. TheAuditSignerservice also tookkekKey []bytedirectly. This commit keeps key bytes insidekeyring:KeySignerinterface tokeyring:SignWithKey(data []byte) (sig, kekID)andVerifyWithKey(kekID, data, sig)keyringimplementsKeySignervia HKDF-SHA256 + HMAC-SHA256 (same algorithm, now encapsulated)auth/service.auditSignertoauth/domain.AuditLog.Canonical()— the domain type owns its wire formatauditLogUseCasedropskekChainandauditSigner; constructor is nowNewAuditLogUseCase(repo, keySigner)keyring.FakeimplementsKeySignerwith a deterministic zero-key HMAC, making it a real second adapterTest plan
go test ./...— 1541 tests pass, no failureskeyring.Fakesatisfies the newKeySignerinterface, keeping feature tests decoupled from the database🤖 Generated with Claude Code