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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
use std::any::Any;
use std::borrow::Cow;
use vitaminc_protected::{Controlled, Protected};
use crate::{Encrypt, IntoAad};
/// An error that provides **no information** about the failure.
/// It is crucial when returning an error from a cipher operation
/// that does not reveal any details about the failure as this can lead to side channel attacks.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unspecified;
impl std::fmt::Display for Unspecified {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unspecified error")
}
}
impl std::error::Error for Unspecified {}
/// A driver of an encryption operation, analogous to serde's `Serializer`.
///
/// `Cipher` defines the entry points an [`Encrypt`](crate::Encrypt) implementation
/// can use to encrypt itself. Concrete implementations (e.g. an AES-256-GCM
/// cipher) decide how plaintext is sealed and what ciphertext container is
/// produced via [`Ok`](Cipher::Ok).
///
/// The trait supports three modes:
/// - raw byte plaintext, via [`encrypt_bytes_vec`](Cipher::encrypt_bytes_vec) /
/// [`encrypt_bytes_array`](Cipher::encrypt_bytes_array);
/// - structured sequences, via [`encrypt_seq`](Cipher::encrypt_seq) returning
/// a [`SeqCipher`];
/// - structured maps, via [`encrypt_map`](Cipher::encrypt_map) returning a
/// [`MapCipher`].
pub trait Cipher: Sized {
/// The encrypted output produced by this cipher (e.g. a structured
/// ciphertext container holding nonce + ciphertext + tag).
type Ok;
/// The error type returned on cipher failure. Implementations should keep
/// this opaque — see [`Unspecified`].
type Error;
/// The payload type carried by [`passthrough`](Cipher::passthrough)
/// values.
///
/// Rust-native ciphers typically use `Box<dyn Any + Send + 'static>`:
/// callers box on the way in and downcast on the way out. Ciphers
/// targeting an FFI boundary instead use an owned host-value type (e.g. a
/// converted JS value), carried value-in/value-out without the cipher ever
/// inspecting it.
type Passthrough;
/// The sub-cipher returned by [`encrypt_seq`](Cipher::encrypt_seq).
type SeqCipher: SeqCipher<Ok = Self::Ok, Error = Self::Error, Passthrough = Self::Passthrough>;
/// The sub-cipher returned by [`encrypt_map`](Cipher::encrypt_map).
type MapCipher: MapCipher<Ok = Self::Ok, Error = Self::Error, Passthrough = Self::Passthrough>;
/// Encrypt the given byte vector with the supplied associated data.
///
/// The plaintext is taken as `Protected<Vec<u8>>` so the chain of custody
/// — and the zeroize-on-drop guarantee — survives the trait boundary.
/// Implementations should keep the value inside `Protected` for the
/// duration of the encryption and let it drop (and wipe) at end of scope.
fn encrypt_bytes_vec<'a, A>(
self,
data: Protected<Vec<u8>>,
aad: A,
) -> Result<Self::Ok, Self::Error>
where
A: IntoAad<'a>;
/// Encrypt a fixed-size byte array. The default implementation forwards to
/// [`encrypt_bytes_vec`](Cipher::encrypt_bytes_vec).
///
/// As with [`encrypt_bytes_vec`](Cipher::encrypt_bytes_vec), the plaintext
/// is taken inside `Protected` so the stack array is wiped on drop after
/// `to_vec` has copied its contents onto the heap.
fn encrypt_bytes_array<'a, const N: usize, A>(
self,
data: Protected<[u8; N]>,
aad: A,
) -> Result<Self::Ok, Self::Error>
where
A: IntoAad<'a>,
{
// Borrow the protected array so it stays owned by `data` and is
// wiped by ZeroizeOnDrop at end of scope. A `risky_unwrap` here
// would partially-move out of `data`, skipping its Drop and
// leaving the bare `[u8; N]` on the stack — see issue #170.
let copy = Protected::new(data.risky_ref().to_vec());
self.encrypt_bytes_vec(copy, aad)
}
/// Begin encrypting a sequence of values under `aad`. `size_hint` lets the
/// implementation preallocate when known.
///
/// The AAD is captured **once**, here, and applies to every element and to
/// the empty marker. Sub-cipher methods therefore take no AAD of their
/// own: there is no second place to supply it, so an element and its
/// container can never end up sealed under different AAD.
fn encrypt_seq<'a, A>(self, size_hint: Option<usize>, aad: A) -> Self::SeqCipher
where
A: IntoAad<'a>;
/// Begin encrypting a map of key/value pairs under `aad`, captured once —
/// see [`encrypt_seq`](Cipher::encrypt_seq).
fn encrypt_map<'a, A>(self, aad: A) -> Self::MapCipher
where
A: IntoAad<'a>;
/// Encrypt a present optional value. Default forwards to the inner value's
/// [`Encrypt`] impl — the `Some` discriminator is implicit in the structural
/// shape of the ciphertext (any non-`None` variant means `Some`).
fn encrypt_some<'a, T, A>(self, value: T, aad: A) -> Result<Self::Ok, Self::Error>
where
T: Encrypt,
A: IntoAad<'a>,
{
value.encrypt_with_aad(self, aad)
}
/// Encrypt the absent case of an optional value. Must produce a
/// cryptographically authenticated marker — distinguishable from any
/// `Some(_)` ciphertext and bound to `aad` so it cannot be forged.
///
/// Implementations must seal the marker against
/// [`Aad::for_none`](crate::Aad::for_none) of the caller's AAD (and the
/// decrypt side must verify the sealed plaintext is empty under the same
/// derivation), so a `Some(_)` leaf sealed under the bare AAD can never
/// be re-tagged as an authenticated absence.
fn encrypt_none<'a, A>(self, aad: A) -> Result<Self::Ok, Self::Error>
where
A: IntoAad<'a>;
/// Pass a value through the cipher's output container **without**
/// encrypting it. Intended for fields that need to survive the encryption
/// envelope in the clear (e.g. a schema version tag).
///
/// The value has the cipher's [`Passthrough`](Cipher::Passthrough) type,
/// stored opaquely and returned as-is by the corresponding
/// [`Decipher::decrypt_passthrough`](crate::Decipher::decrypt_passthrough).
///
/// # ⚠️ Non-sensitive data only
///
/// Passthrough values travel **in the clear** alongside the ciphertext,
/// and — unlike map keys — are not authenticated either. Do not use this
/// for secret-bearing data such as keys, plaintexts, or credentials —
/// there is no encryption, no zeroize discipline applied to the payload,
/// and no guarantee about when the storage is freed. For secrets, use
/// [`encrypt_with_aad`](crate::Encrypt::encrypt_with_aad) (via the
/// [`Encrypt`](crate::Encrypt) trait) or wrap in
/// [`vitaminc_protected::Protected`].
fn passthrough(self, value: Self::Passthrough) -> Result<Self::Ok, Self::Error>;
/// Pass a **type-erased** value through the output container without
/// encrypting it — the entry point for self-describing
/// [`Encrypt`](crate::Encrypt) implementations that cannot name this
/// cipher's [`Passthrough`](Cipher::Passthrough) type at the call site.
///
/// A tree-shaped, dynamically typed value (e.g. `FfiValue`) implements
/// `Encrypt` generically over *every* cipher, so its impl has no way to
/// construct a specific cipher's payload type — the trait method signature
/// forbids the extra bound (`impl has stricter requirements than trait`).
/// This method closes that gap: the value is delivered as
/// `Box<dyn Any + Send>`, and each cipher decides how to absorb it. A
/// Rust-native cipher whose payload type *is* `Box<dyn Any + Send>` stores
/// the box directly; a cipher with an owned payload type downcasts it
/// (returning an error for a foreign payload type). The decrypt-side counterpart is
/// [`DecipherVisitor::visit_passthrough`](crate::DecipherVisitor::visit_passthrough).
///
/// # ⚠️ Non-sensitive data only
///
/// Identical contract to [`passthrough`](Cipher::passthrough): the value
/// travels **in the clear** and is **not authenticated**. Never route
/// secret-bearing data through it.
fn passthrough_boxed(
self,
value: Box<dyn Any + Send + 'static>,
) -> Result<Self::Ok, Self::Error>;
}
/// Sub-cipher driving the encryption of a sequence of values.
///
/// Obtained from [`Cipher::encrypt_seq`], which fixes the AAD for the whole
/// sequence. Each element is encrypted with
/// [`encrypt_next`](SeqCipher::encrypt_next); the caller finalises the sequence
/// with [`end`](SeqCipher::end) to produce the cipher's `Ok` output.
pub trait SeqCipher: Sized {
/// The final encrypted output produced by [`end`](SeqCipher::end).
type Ok;
/// The error type for sequence operations.
type Error;
/// The passthrough payload type — matches the parent
/// [`Cipher::Passthrough`].
type Passthrough;
/// Encrypt the next element in the sequence, returning the updated cipher.
///
/// The element is sealed against the AAD supplied to
/// [`Cipher::encrypt_seq`].
fn encrypt_next<T>(self, data: T) -> Result<Self, Self::Error>
where
T: Encrypt;
/// Append a passthrough (unencrypted) element to the sequence.
///
/// See [`Cipher::passthrough`] — passthrough values are non-sensitive by
/// design and must not carry secret data.
fn passthrough_next(self, value: Self::Passthrough) -> Result<Self, Self::Error>;
/// Finalise the sequence and return the produced ciphertext container.
///
/// Implementations must authenticate the sequence's AAD even when no
/// encrypted elements were appended. Otherwise an empty sequence used as a
/// map value would not authenticate the map entry's derived AAD, allowing
/// its cleartext key to be renamed without detection. The empty marker
/// must be sealed against
/// [`Aad::for_empty_sequence`](crate::Aad::for_empty_sequence) of that AAD.
///
/// A sequence whose elements are all passthrough must be **rejected**:
/// passthrough elements authenticate nothing, so such a container would
/// carry no tag binding the AAD at all — decrypt implementations must
/// likewise refuse to open one.
fn end(self) -> Result<Self::Ok, Self::Error>;
}
/// Sub-cipher driving the encryption of a map of key/value pairs.
///
/// Obtained from [`Cipher::encrypt_map`], which fixes the AAD for the whole
/// map. Keys are not encrypted; values are. The expected call order is
/// key → value → key → value → … → `end`, or use the
/// [`encrypt_entry`](MapCipher::encrypt_entry) convenience method.
///
/// Keys may be `&'static str` or owned `String`s (anything
/// `Into<Cow<'static, str>>`), so maps with runtime-derived keys — e.g. values
/// crossing an FFI boundary — can be encrypted as well as decrypted.
///
/// # Key authentication
///
/// Keys travel in the clear, but they are **not** unauthenticated:
/// implementations must bind each entry's key into the AAD its value is sealed
/// against, via [`Aad::for_map_entry`](crate::Aad::for_map_entry). Swapping or
/// renaming keys in a stored ciphertext therefore causes the affected values to
/// fail decryption. [`MapAccess`](crate::MapAccess) implementations perform the
/// symmetric binding on decrypt.
///
/// The exception is [`passthrough_entry`](MapCipher::passthrough_entry): a
/// passthrough value is neither encrypted nor authenticated, so nothing seals
/// its key either.
pub trait MapCipher: Sized {
/// The final encrypted output produced by [`end`](MapCipher::end).
type Ok;
/// The error type for map operations.
type Error;
/// The passthrough payload type — matches the parent
/// [`Cipher::Passthrough`].
type Passthrough;
/// Record the next key. Keys are stored in the clear — only values are
/// encrypted — but each key is bound into its value's AAD (see the
/// trait-level *Key authentication* notes).
///
/// Must be followed by exactly one [`encrypt_value`](MapCipher::encrypt_value)
/// before the next `encrypt_key` or [`end`](MapCipher::end). Calling
/// `encrypt_key` twice with no intervening `encrypt_value` is a trait-contract
/// violation; implementations should return an error rather than silently
/// dropping the first key.
fn encrypt_key<K>(self, key: K) -> Result<Self, Self::Error>
where
K: Into<Cow<'static, str>>;
/// Encrypt the value associated with the most recently supplied key.
///
/// Implementations **must not** seal the value against the map's AAD
/// directly: they must bind the pending key alongside it via
/// [`Aad::for_map_entry`](crate::Aad::for_map_entry), so that key and value
/// are cryptographically inseparable in the stored ciphertext.
fn encrypt_value<T>(self, value: T) -> Result<Self, Self::Error>
where
T: Encrypt;
/// Convenience for [`encrypt_key`](MapCipher::encrypt_key) followed by
/// [`encrypt_value`](MapCipher::encrypt_value).
fn encrypt_entry<K, T>(self, key: K, value: T) -> Result<Self, Self::Error>
where
K: Into<Cow<'static, str>>,
T: Encrypt,
Self: Sized,
{
self.encrypt_key(key).and_then(|mc| mc.encrypt_value(value))
}
/// Insert a passthrough (unencrypted) entry under `key`. Equivalent to
/// [`encrypt_key`](MapCipher::encrypt_key) followed by storing the value
/// without AEAD treatment.
///
/// See [`Cipher::passthrough`] — passthrough values are non-sensitive by
/// design and must not carry secret data. Neither the value nor its key is
/// authenticated.
fn passthrough_entry<K>(self, key: K, value: Self::Passthrough) -> Result<Self, Self::Error>
where
K: Into<Cow<'static, str>>;
/// Insert a **type-erased** passthrough entry under `key` — the entry point
/// for callers that cannot name this cipher's
/// [`Passthrough`](Cipher::Passthrough) type at the call site.
///
/// Stands to [`passthrough_entry`](MapCipher::passthrough_entry) exactly as
/// [`Cipher::passthrough_boxed`] stands to [`Cipher::passthrough`], and for
/// the same reason: code generic over *every* cipher — a derived
/// `Encrypt` impl, a self-describing tree value — has no way to construct a
/// specific cipher's payload type. The value arrives as
/// `Box<dyn Any + Send>` and each cipher decides how to absorb it: one whose
/// payload type *is* `Box<dyn Any + Send>` stores it directly, one with an
/// owned payload type downcasts (erroring on a foreign payload type).
///
/// # ⚠️ Non-sensitive data only
///
/// Identical contract to [`passthrough_entry`](MapCipher::passthrough_entry):
/// neither the value nor its key is encrypted or authenticated.
fn passthrough_entry_boxed<K>(
self,
key: K,
value: Box<dyn Any + Send + 'static>,
) -> Result<Self, Self::Error>
where
K: Into<Cow<'static, str>>;
/// Finalise the map and return the produced ciphertext container.
///
/// Implementations must authenticate the map's AAD even when no encrypted
/// entries were appended. Otherwise an empty map used as a map value would
/// not authenticate the outer entry's derived AAD, allowing its cleartext
/// key to be renamed without detection. The empty marker must be sealed
/// against [`Aad::for_empty_map`](crate::Aad::for_empty_map) of that AAD.
///
/// A map whose entries are all passthrough must be **rejected** — see
/// [`SeqCipher::end`].
fn end(self) -> Result<Self::Ok, Self::Error>;
}
#[cfg(test)]
mod tests {
use crate::test_util::MockDefaultCipher;
use crate::Encrypt;
// Drives the trait-default `encrypt_bytes_array` body: a cipher with no
// override must forward the array's bytes through `encrypt_bytes_vec`.
// `MockCipher` and the real ciphers all override the method, so this is
// the only test in the crate exercising the default. It pins the
// forwarding only — an unwrap-then-`to_vec` rewrite of the body would
// forward the same bytes and still pass, so the issue-#170 custody
// discipline (borrow, never `risky_unwrap`) is held by the comment in
// the default body and by review, not by this test.
#[test]
fn default_encrypt_bytes_array_forwards_through_the_vec_entry_point() {
let cipher = MockDefaultCipher::new();
let ct = [1u8, 2, 3].encrypt(&cipher).expect("encrypt");
assert_eq!(ct, vec![1, 2, 3]);
}
}