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
//! Marker trait asserting that a secret's storage owns no heap allocation.
//!
//! [`Fixed<T>`](crate::Fixed) is documented as having no reallocation surface, which is
//! what lets `SECURITY.md` exempt it from the heap-residue weakness that
//! [`Dynamic<T>`](crate::Dynamic) carries. That exemption was a claim about the *shape*
//! people were expected to use, not something the type system checked: `Fixed<T>` is
//! bounded only by `Zeroize`, so `Fixed<Vec<u8>>` and `Fixed<String>` compiled, and
//! `fixed_newtype!(pub Name, generic Vec<u8>)` was a documented path straight to them.
//! Measured, they leak exactly as much as the `Dynamic` equivalent — and worse, because
//! `Dynamic<Vec<u8>>` has a safe-growth `std::io::Write` impl and `Fixed<Vec<u8>>` has
//! nothing.
//!
//! [`FixedStorage`] is what makes the exemption true. [`Fixed::new`](crate::Fixed::new)
//! requires it, so the compiler now rejects an inner type that owns a heap allocation.
//!
//! # The contract
//!
//! Implementing `FixedStorage` for a type asserts that **the type owns no heap
//! allocation**. Nothing else, and note that this is stricter than "cannot be resized".
//!
//! The first version of this trait asked only for a fixed capacity, and blessed `Box<[T]>`
//! on the reasoning that a boxed slice has its length fixed at construction. That was
//! wrong, and measurably so: the residue never needed a capacity change. Whole-value
//! replacement abandons the allocation just as well, and the wrapper wipes whatever it
//! holds at drop, not what it used to hold. On a `Fixed<Box<[u8]>>` carrying a 1024-byte
//! secret, `with_secret_mut(|slot| *slot = other_boxed_slice)` released the original block
//! with **1024 of 1024** bytes intact, and so did the same assignment through
//! `expose_secret_mut`. The inline contrast, `Fixed<[u8; 1024]>` assigned the same way,
//! freed nothing at all — there is no allocation to abandon. So `Box<[T]>` is no longer
//! implemented, and the predicate is heap ownership rather than resizability.
//!
//! The test to apply to your own type: *does `Self` own a heap allocation?* If yes, do not
//! implement this — reach for [`Dynamic`](crate::Dynamic), which documents the residue.
//!
//! # This is an assertion, not an enforcement
//!
//! Like [`CloneableSecret`](crate::CloneableSecret), the compiler checks that you wrote
//! the impl, not that the claim is true. A type with a `Vec` field that implements
//! `FixedStorage` anyway will compile and will leak, which is measured and deliberate:
//! the alternative is a closed set of blessed types, and that would break the one thing
//! the `generic` arm of [`fixed_newtype!`](crate::fixed_newtype) exists for — holding a
//! custom secret on a target with no allocator.
//!
//! What the marker buys is that the claim is now *written down at a greppable line* in
//! the crate making it, rather than being an unstated assumption. That is the same trade
//! the other opt-in markers in this crate make.
//!
//! # What is implemented for you
//!
//! - the integer, floating-point, `bool`, `char` and `()` primitives;
//! - the twelve `NonZero` integers;
//! - `[T; N]` for any `N`, when `T: FixedStorage` — which covers `[u8; 32]` and the
//! non-byte arrays the `generic` arm exists for, such as `[i16; 256]`;
//! - tuples up to ten elements of `FixedStorage` types, matching `zeroize`'s own ceiling;
//! - `Option<T>` when `T: FixedStorage` — a discriminant adds no heap storage;
//! - `Wrapping<T>` and `MaybeUninit<T>` when `T: FixedStorage` — both are `T`'s own storage;
//! - `zeroize::Zeroizing<T>` when `T: FixedStorage`;
//! - `Fixed<T>` itself when `T: FixedStorage`, so the wrapper nests.
//!
//! # Why that list has to be this long
//!
//! The bound is an allow-list, which means it fails closed: a type is refused both when it
//! owns a heap allocation and when nothing has asserted that it does not. For a type you
//! define, the second case costs one line. For a type you do **not** own it costs nothing,
//! because you cannot pay it — `impl FixedStorage for NonZeroU32` in your crate is E0117,
//! the orphan rule, and no amount of effort downstream changes that.
//!
//! So every heap-free type reachable through `zeroize`'s own `Zeroize` impls has to be
//! blessed here or it is simply unavailable as a `Fixed` inner type, with the caller's only
//! escape being to change their own type. That is the reason for the `NonZero`, `Wrapping`,
//! `MaybeUninit` and `Zeroizing` impls above, and for tuples reaching ten rather than four:
//! each was a shape that compiled before this bound existed and that no downstream crate
//! could have restored. Ten is not arbitrary — `zeroize` implements `Zeroize` for tuples up
//! to ten elements and no further, so an eleventh element fails the `Zeroize` bound before
//! this one is ever consulted.
//!
//! The deny-list alternative — accept everything except a known set of heap owners — was
//! rejected deliberately. It has no false rejections, but it fails **open**: a custom type
//! wrapping a `Vec` would be accepted in silence, which is the exact weakness this trait
//! exists to close.
//!
//! Nothing heap-owning is implemented, deliberately. `Box<[T]>` was and is not: see the
//! contract above for the measurement that removed it. `Box<[u8; N]>` never arises anyway,
//! because `zeroize` does not implement `Zeroize` for a `Box` of a sized type. For a
//! heap-only secret of fixed size, the supported shape is `Dynamic<[u8; N]>`.
//!
//! A custom inner type needs one line:
//!
//! ```rust
//! use secure_gate::{FixedStorage, Fixed, RevealSecret, SentinelValue};
//! use zeroize::Zeroize;
//!
//! struct Poly([i16; 256]);
//!
//! impl Zeroize for Poly {
//! fn zeroize(&mut self) {
//! self.0.zeroize();
//! }
//! }
//! impl SentinelValue for Poly {
//! fn sentinel_value() -> Self {
//! Poly([0i16; 256])
//! }
//! }
//! // Asserted here: `Poly` owns no heap allocation.
//! impl FixedStorage for Poly {}
//!
//! let p = Fixed::new(Poly([7i16; 256]));
//! assert_eq!(p.with_secret(|poly| poly.0[0]), 7);
//! ```
//!
//! # Naming the type still compiles
//!
//! The bound is on [`Fixed::new`](crate::Fixed::new), not on the struct, which keeps the
//! property the zero-size guard already documents: `type Bad = Fixed<Vec<u8>>;` is a
//! legal type expression and no guard in the type could make it otherwise. What the bound
//! removes is every *value* of it.
/// Marker trait asserting that a type owns no heap allocation.
///
/// Required by [`Fixed::new`](crate::Fixed::new). See the
/// [module documentation](self) for the contract, for what is already implemented,
/// and for why this is an assertion rather than an enforcement.
///
/// No methods — its only purpose is to gate construction of a
/// [`Fixed<T>`](crate::Fixed).
__sg_fixed_storage_prims!;
// The twelve `NonZero` integers `zeroize` implements `Zeroize` for. Each is a single integer
// with a niche, so there is no allocation to abandon.
//
// These are foreign types, so a downstream crate cannot assert this itself: `impl
// FixedStorage for NonZeroU32` is E0117. That is why the impls live here — without them,
// `Fixed<NonZeroU32>` would be refused with no fix available to the caller.
__sg_fixed_storage_prims!;
/// An array never reallocates, whatever its length, so it is `FixedStorage` exactly when
/// its element type is. This is the impl that covers `[u8; N]` and the non-byte arrays the
/// `generic` arm of [`fixed_newtype!`](crate::fixed_newtype) exists for.
/// `Wrapping` is a transparent arithmetic wrapper: same storage as its payload.
/// `MaybeUninit<T>` is `T`'s storage, initialised or not. No allocation either way.
/// `zeroize::Zeroizing<T>` holds `T` inline and wipes it on drop, so it qualifies exactly
/// when `T` does.
///
/// This one matters out of proportion to its size. `Zeroizing` is the idiomatic "wipe this
/// on drop" wrapper from this crate's only non-optional dependency, so
/// `Fixed<Zeroizing<[u8; 32]>>` is a shape people reach for — and because both the trait and
/// the type are foreign to the caller, the orphan rule leaves them no way to assert it. It
/// has to be here or it is unavailable.
/// A `Fixed<T>` holds `T` inline, so the wrapper nests: `Fixed<Fixed<[u8; 32]>>` is legal
/// exactly when the payload qualifies. Without this impl the crate's own wrapper could not
/// be used as its own inner type.
;
}
__sg_fixed_storage_tuples!
/// An `Option` adds a discriminant and no heap storage, so it qualifies exactly when its
/// payload does. `Option<[u8; 32]>` is a legitimate fixed-size secret; `Option<Vec<u8>>`
/// and `Option<Box<[u8]>>` are both rejected, which is the point.