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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
//! Envelope encryption, and the erasure it makes provable.
//!
//! # Why deleting is not erasing
//!
//! [`BlobStore::expire`](crate::blob::BlobStore::expire) drops a payload's bytes
//! and leaves a tombstone, and the hash chain still verifies because it only
//! ever committed to a digest. That is the right shape, and it is not enough:
//! it erases the bytes *in the live store*. Every backup taken before the
//! request, every replica, every snapshot an operator forgot about still holds
//! them, and an erasure obligation is not discharged by deleting one copy of
//! something that exists in six places.
//!
//! Chasing the copies does not work either. Backups are the point of backups —
//! they are offline, offsite, and frequently immutable by design, because that
//! is what makes them survive the incident they exist for. A retention story
//! that requires rewriting immutable backups has two guarantees in direct
//! conflict, and whichever one loses, loses silently.
//!
//! # What this does instead
//!
//! Payload bytes are sealed under a **data key**, and the data key is wrapped by
//! a key this crate never holds — a KMS, an HSM, whatever the deployment trusts.
//! Erasure destroys the data key. Every copy of the ciphertext becomes
//! unreadable at the same instant, including the ones nobody can reach, because
//! the thing that was destroyed was never in them.
//!
//! This is the standard answer to the immutable-log-versus-erasure problem, and
//! it is the only one that survives contact with a backup regime.
//!
//! Three operations fall out of the same structure, which is the argument for it:
//!
//! * **Erasure** — destroy a data key. Its scope's bytes are gone, everywhere.
//! * **Rotation** — re-wrap data keys under a new wrapping key. Bulk data is
//! never rewritten, so rotating is cheap enough to actually do on a schedule
//! rather than a plan.
//! * **Revocation** — destroy a wrapping key. Everything wrapped under it is
//! unreadable, which is the blast radius a compromised key should have.
//!
//! # What is deliberately absent
//!
//! No key material is generated by, or stored in, this crate beyond the process
//! that is using it. [`KeyRing`] is a seam: a deployment points it at the thing
//! that already holds its keys. There is a `MemoryKeyRing` for tests, and it
//! lives behind the `testkit` feature rather than here: a key ring in the same
//! process as the data it protects protects it from nobody, so the gate is the
//! guarantee instead of a warning in a doc comment.
use Debug;
use async_trait;
use ;
use ;
use crate;
/// A key that seals payload bytes.
///
/// Zeroized on drop. Not `Clone`, not `Debug`-printable, and it does not
/// serialize: a data key that reaches a log line or a journal record is a data
/// key whose destruction no longer erases anything.
;
// Written by hand so the key cannot reach a log through a derived `Debug`.
/// A data key as it may safely be stored: sealed by a key this crate never has.
///
/// Safe to journal, back up, and replicate — which is the point, because it
/// travels *with* the payload it sealed. A backup therefore contains everything
/// needed to restore and nothing needed to read: the wrapping key stayed in the
/// service, and destroying it is what makes the backup unreadable.
/// Why a key operation did not happen.
/// Where data keys are made, wrapped, and destroyed.
///
/// The wrapping key never leaves the implementation. That is the whole seam: a
/// deployment can put it in a KMS and this crate is still only ever holding a
/// data key it was handed, for as long as it takes to seal or open one payload.
/// The erasure scope for a unit within a tenant.
///
/// Derived here and nowhere else. The write path and the erasure path must
/// agree byte-for-byte about which key seals a payload, and the first version of
/// this had them building the string separately: writes sealed under
/// `tenant/case` while erasure destroyed `case`, so every erasure reported
/// success and destroyed nothing. Two places deriving one fact is how that
/// happens, so there is one place.
///
/// [`TenantId`](crate::core::TenantId) refuses `/`, which is what stops a tenant
/// named `acme/prod` from colliding with tenant `acme`, unit `prod`.
pub use SealedCases;
pub use SealedEvents;
pub use SealedTasks;
pub use SealedJournal;
pub use EncryptedBlobs;
pub use EncryptedMemoryStore;
pub use VaultTransit;