Commit 008eb81
authored
feat(encryption): foundation package (Stage 0) (#719)
## Summary
Stage 0 of the data-at-rest encryption rollout from PR #707
(`docs/design/2026_04_29_proposed_data_at_rest_encryption.md`). This PR
is library-only — no integration with existing storage / raft / FSM
layers. Those land in Stages 1–9.
New package `internal/encryption/`:
- `cipher.go` — AES-256-GCM `Encrypt` / `Decrypt` primitive over a
`Keystore`. Caller-supplied AAD bytes (no AAD composition baked in —
storage and raft layers compose their own AAD per §4.1 / §4.2 in later
stages). `Decrypt` wraps GCM tag mismatch as `ErrIntegrity` with the
original `Open` error attached as a secondary cause, so callers match
via `errors.Is` and never silently zero or retry.
- `envelope.go` — §4.1 wire format encoder/decoder. Layout: `[version
1B][flag 1B][key_id 4B BE][nonce 12B][ciphertext+tag]`.
`EnvelopeOverhead = 34` bytes. `HeaderAADBytes` helper exposed for
storage callers that compose `AAD = HeaderAADBytes ‖ pebble_key`.
`ReservedKeyID = 0` sentinel per §5.1.
- `keystore.go` — copy-on-write `atomic.Pointer[map[uint32][]byte]` DEK
store. `Get` is a single atomic load (no mutex on the hot path per §10
self-review lens 2). `Set` rejects `ReservedKeyID` and non-32-byte DEKs;
copies input.
- `errors.go` — typed errors: `ErrUnknownKeyID`, `ErrReservedKeyID`,
`ErrBadNonceSize`, `ErrBadKeySize`, `ErrIntegrity`, `ErrEnvelopeShort`,
`ErrEnvelopeVersion`.
- `kek/kek.go` — `Wrapper` interface (`Wrap` / `Unwrap` / `Name`).
KMS-backed providers (AWS KMS, GCP KMS, Vault) come in Stage 9.
- `kek/file.go` — `FileWrapper`: AES-256-GCM under a 32-byte KEK read
from a file. Output `[12B nonce][32B ct][16B tag] = 60 bytes`. Validates
lengths instead of padding/truncating.
Tests:
- `cipher_test.go` — round-trip across plaintext/AAD shapes;
tag/AAD/ciphertext/nonce tamper rejected with `ErrIntegrity`; typed
error checks for reserved/bad-nonce/unknown-key cases; distinct nonces
produce distinct ciphertexts.
- `cipher_prop_test.go` — `pgregory.net/rapid` property tests for
arbitrary plaintext/AAD round-trip and single-bit AAD-flip rejection
(the §4.1 cut-and-paste defence property).
- `envelope_test.go` — encode/decode round-trip; under-length input
rejected; unknown version bytes rejected; decode does not alias input.
- `keystore_test.go` — Set/Get/Delete/IDs/Len; concurrent reader/writer
stress under `-race`.
- `kek/file_test.go` — round-trip; distinct Wrap nonces; bad-length
rejection; tag/nonce tamper rejected; missing file.
## Self-review (per CLAUDE.md 5 lenses)
1. **Data loss** — Pure library, no persistence yet. `ErrIntegrity`
always propagated. Round-trip property test guards envelope-format bugs.
2. **Concurrency** — `atomic.Pointer[map]` for keystore; concurrent
stress test under `-race`.
3. **Performance** — AES-NI via `crypto/aes`. No allocations beyond
per-call ciphertext buffer (sync.Pool optimisation deferred to
integration stages).
4. **Data consistency** — No MVCC/OCC interaction yet.
5. **Test coverage** — 23 unit tests + 2 property tests, `-race` clean,
0 lint issues against project `.golangci.yaml`.
## Test plan
- [x] `go test ./internal/encryption/...` (~14s)
- [x] `go test -race ./internal/encryption/...` (~65s)
- [x] `golangci-lint run ./internal/encryption/...` (0 issues)
- [ ] Reviewer: confirm `Cipher` API does not bake storage/raft AAD
assumptions in (§4.1 vs §4.2 layouts must remain orthogonal).
- [ ] Reviewer: confirm `ReservedKeyID = 0` is rejected at all entry
points (`Cipher.Encrypt`, `Cipher.Decrypt`, `Keystore.Set`).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## New Features
* Added AES-256-GCM encryption support with envelope-based encryption
format
* Implemented key management system for secure encryption key handling
* Added file-based key encryption key (KEK) support for key wrapping and
unwrapping
* Introduced comprehensive error handling for encryption operations
including integrity verification
## Tests
* Added extensive unit, benchmark, and property-based tests for
encryption functionality
<!-- end of auto-generated comment: release notes by coderabbit.ai -->16 files changed
Lines changed: 2251 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
0 commit comments