bouncycastle_utils/secret.rs
1//! A transparent wrapper type, [`Secret`], which is a wrapper that holds a secret value and
2//! guarantees it is securely zeroized on drop, and protected from accidintal logging by implementing
3//! redacting `fmt::Debug` and `fmt::Display`.
4//!
5//! # Why write_volatile
6//!
7//! Plain writes such as `slice.fill(0)` are ordinary, non-observable memory accesses. Under
8//! optimization the compiler may prove that a buffer is never read again after it is scrubbed --
9//! precisely the situation just before a drop -- and elide the scrub as a dead store. To prevent
10//! that, [`Secret`] erases through [`core::ptr::write_volatile`], whose accesses are defined by the
11//! language as observable side effects and therefore may not be elided or coalesced. Each scrub is
12//! followed by a [`compiler_fence`] with [SeqCst](Ordering::SeqCst) ordering so the volatile
13//! writes are not reordered with respect to later memory operations.
14//!
15//! # Why Sized?
16//!
17//! The [`ZeroizablePrimitive`] is bounded on [`Sized`], which explicitly forbids
18//! instantiating `Secret<T>` over something like `Vec<T>` whose size is not known at compile time.
19//!
20//! The reason is that an implementation of `.zeroize()` that is guaranteed not be optimized away
21//! by the compiler requires the use of `unsafe{ write_volatile() }` to directly write the `T::ZEROED`
22//! byte pattern over top of the provided memory block. With a `Sized` type, this is a single line of
23//! unsafe code and it is easy to prove that it is writing the number of bytes that it should be.
24//! For a dynamically-sized value such as `Vec<T>`, this is substantially trickier.
25//! Taking `Vec` as an example, it is not a flat piece of memory that can be trivially over-written
26//! with a static value; `Vec` is actually a stack of structs that implement a smart-pointer that
27//! tracks both `length` and `capacity` of the memory referenced by the pointer.
28//! Properly zeroizing this means following the pointer, filling the referenced memory with `0x00` up
29//! to the `capacity`, then setting `length=0` without changing `capacity` or the pointer.
30//! Zeroizing a `Vec` would require a substantial amount of unsafe code that is Vec-specific and
31//! tricky to prove the correctness of.
32//! Doing this for arbitrary heap-allocated objects (which may contain nested heap-allocated objects)
33//! sounds like a whole research project.
34//!
35//! This is not to say that bc-rust will never attempt this challenge, but since bc-rust is a `[no_std]`
36//! library that keeps all of its secrets in stack-allocated variables, this is not a problem that
37//! needs to be solved for internal library use.
38
39use crate::ct;
40use core::any::type_name;
41use core::fmt;
42use core::mem::size_of;
43use core::ops::{Deref, DerefMut};
44use core::ptr;
45use core::sync::atomic::{Ordering, compiler_fence};
46
47/// A `Copy` type whose all-zero value is meaningful and valid, so that a [`Secret`] of it can be
48/// default-constructed in a zeroed state.
49///
50/// This is used instead of [`Default`] for two reasons: it lets [`Secret::new`] produce a zeroed
51/// value at compile time via an associated `const`, and -- crucially -- it works for arrays of *any*
52/// length, whereas `[T; N]: Default` is only implemented for `N <= 32`.
53// Dev note: the `Copy` bound is load-bearing, but only in preventing impl'ng this for additional types
54// that will turn out to be problematic.
55// Specifically: we're using `Copy` to mean 'no Drop' which works because 'Copy' and `Drop` are
56// mutually exclusive, and the byte-scrub semantics here go squirrely if you instantiate a
57// Secret over a type that impls Drop. So we don't actually care about the underlying type
58// impl'ing Copy; we only care that it doesn't impl Drop, and the `Copy` bound is a
59// convenient way to catch that if we in the future do impl_zero_init!(T) for a T that has Drop.
60pub trait ZeroizablePrimitive: Copy + Sized {
61 /// The zeroed value of this type.
62 /// This needs to be a valid static instance of Self that [`Secret::zeroize`] will internally
63 /// convert into a byte array and use to overwrite the memory location of the given instance of
64 /// the primitive.
65 const ZEROED: Self;
66}
67
68macro_rules! impl_zero_init {
69 ($($t:ty),+ $(,)?) => {$(
70 impl ZeroizablePrimitive for $t {
71 const ZEROED: Self = 0 as $t;
72 }
73 )+};
74}
75
76impl_zero_init!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
77
78impl ZeroizablePrimitive for bool {
79 const ZEROED: Self = false;
80}
81
82impl ZeroizablePrimitive for char {
83 const ZEROED: Self = '\0';
84}
85
86/// Blanket impl for fixed-size arrays of any length.
87/// This is an alternative to `[T; N]: Default` which is capped at `N <= 32`.
88impl<T: ZeroizablePrimitive, const N: usize> ZeroizablePrimitive for [T; N] {
89 const ZEROED: Self = [T::ZEROED; N];
90}
91
92/// A wrapper that holds a secret value of any primitive or fixed-size array type
93/// and guarantees it is securely zeroized on drop, and is
94/// protected from accidental logging by implementing redacting `fmt::Debug` and `fmt::Display`.
95///
96/// A `Secret<T>` is created in a zeroed state with [`Secret::new`] / [`Default`] and populated in
97/// place through [`DerefMut`]; there is intentionally no by-value constructor (see the [module
98/// docs](self)). It behaves transparently as a `&T` / `&mut T` via [`Deref`]/[`DerefMut`], so a
99/// `Secret<[u8; 32]>` can be indexed, sliced, and iterated exactly like the underlying array.
100///
101/// `Secret<T>` deliberately does **not** implement [`Copy`] (it owns a `Drop`), which forces move
102/// semantics and prevents silent, unscrubbed duplication of secrets. It *does* implement [`Clone`]
103/// for the cases where an explicit, intentional copy is required.
104///
105/// Its [`Debug`](fmt::Debug) and [`Display`](fmt::Display) impls are redacting: they never print the
106/// contained bytes, so a secret cannot leak into logs or panic/crash output.
107///
108/// # Usage Examples
109///
110/// `Secret<i32>` and other scalar types
111///
112/// [`Secret`] can wrap any of the following scalar types:
113/// u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, bool, and char.
114///
115/// ```
116/// use bouncycastle_utils::secret::Secret;
117///
118/// let mut nonce: Secret<u64> = Secret::default();
119/// *nonce = 0xDEAD_BEEF;
120/// assert_eq!(*nonce, 0xDEAD_BEEF);
121///
122///
123/// let mut counter: Secret<i32> = Secret::new();
124/// // Here you have to explicitly Deref to get at the underlying type, but it all works.
125/// *counter += 1;
126/// assert_eq!(*counter, 1);
127/// ```
128///
129/// ## `Secret<[u8; 32]>`
130///
131/// `Secret` can wrap arrays of any of the supported scalar types.
132/// There is no bound on how large an array you can make.
133///
134/// Here we'll construct a zeroed `Secret<[u8; 32]>` and fill them *in place* through [`DerefMut`],
135/// so the plaintext is never held in a separate, unprotected variable:
136///
137/// ```
138/// use bouncycastle_utils::secret::Secret;
139///
140/// let mut key: Secret<[u8; 32]> = Secret::new();
141///
142/// // Here, .copy_from_slice may still produce a copy in memory, but it's the best we can do in
143/// // illustrative example code.
144/// // In real code an RNG or KDF or network socket read should be directly handed the mut ref to
145/// // Secret so that it can write directly into it.
146/// key.copy_from_slice(&[0x42u8; 32]);
147///
148/// // `Secret<T>` is (mostly) transparent: you can use it exactly as you would the underlying type T
149/// // (possibly requiring a dereference).
150/// // indexing, slicing, `.len()`, iteration, etc all work automatically via `Deref`.
151/// // Just forget the Secret is there
152/// assert_eq!(key[0], 0x42);
153/// assert_eq!(key.len(), 32);
154/// assert!(key.iter().all(|&b| b == 0x42));
155/// // `key` is volatile-scrubbed to zero when it drops at the end of this scope.
156/// ```
157///
158/// ## On a custom type
159///
160/// Since [`ZeroizablePrimitive`] is a public trait, you can implement it on your own types and then
161/// trivially be able to wrap them in a `Secret<T>`. The only requirement is that there is a
162/// well-defined "ZEROED" value for the type.
163///
164/// Toy Example:
165///
166/// ```
167/// use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive};
168///
169/// /// Holds a system user
170/// #[derive(Clone, Copy)]
171/// struct User {
172/// userid: i32,
173/// name: [u8; 64],
174/// }
175///
176/// /// Provide the const ZEROED value for the type.
177/// impl ZeroizablePrimitive for User {
178/// const ZEROED: Self = Self{ userid: 0i32, name: [0u8; 64] };
179/// }
180///
181/// /// We will tag the admins as Secret<User> to give them extra protections against
182/// /// having their info leaked.
183/// struct AllUsers {
184/// users: Vec<User>,
185/// admins: Vec<Secret<User>>,
186/// }
187/// ```
188///
189/// Note that `Secret<T>` is only defined for statically-sized types -- ie types that satisfy
190/// [`Sized`]. For a justification, see the module docs.
191///
192/// # Redacting Debug and Display
193///
194/// [`Debug`](fmt::Debug) and [`Display`](fmt::Display) are redacting, so a `Secret` can be logged
195/// without leaking its contents:
196///
197/// ```
198/// use bouncycastle_utils::secret::Secret;
199///
200/// let mut secret: Secret<u32> = Secret::new();
201/// *secret = 0x4141_4141; // would render as "AAAA" if leaked
202/// assert_eq!(format!("{secret}"), "<redacted>");
203/// assert_eq!(format!("{secret:?}"), "Secret<u32>(<redacted>)");
204/// ```
205///
206/// This will also work for custom types; let's say the `User` struct from the previous example had
207/// impl'd Display and Debug; mhen you wrap them in `Secret<User>` then `Secret`'s Display and Debug
208/// are invoked instead of `User`'s and you get the `"<redacted>"` output.
209///
210/// # Memory Usage
211///
212/// As a direct wrapper of the type `T`, a `Secret<T>` does not add any memory overhead.
213///
214/// ```
215/// use bouncycastle_utils::secret::Secret;
216///
217/// print!("{}\n", size_of::<i32>()); // 4
218/// print!("{}\n", size_of::<Secret<i32>>()); // also 4
219///
220/// print!("{}\n", size_of::<[u8; 32]>()); // 32
221/// print!("{}\n", size_of::<Secret<[u8; 32]>>()); // also 32
222/// ```
223///
224/// # 🚨 Security 🚨
225///
226/// What this does NOT guarantee:
227///
228/// [`Secret`] only guarantees that the *final* scrub of the wrapped value is emitted. It cannot
229/// recover copies that the compiler or CPU made. To minimize copies of the underlying bytes in memory,
230/// you should be careful with a few things:
231///
232/// * Create a new [`Secret`] instance, then get a mut ref to its internal value via [`Secret::deref_mut`]
233/// and write to that instead of having a copy in an unprotected variable and then copying it into the Secret.
234/// * Avoid copying out of Secret for the same reason.
235pub struct Secret<T: ZeroizablePrimitive>(T);
236
237impl<T: ZeroizablePrimitive> Secret<T> {
238 /// Create a new `Secret` in a zeroed state.
239 ///
240 /// Populate it afterwards in place via [`DerefMut`] (e.g. by having an RNG or KDF write into
241 /// `&mut *secret`), which avoids ever materializing an unprotected copy of the secret.
242 #[inline]
243 pub const fn new() -> Self {
244 Self(T::ZEROED)
245 }
246}
247
248impl<T: ZeroizablePrimitive> Default for Secret<T> {
249 #[inline]
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255impl<T: ZeroizablePrimitive> Secret<T> {
256 /// Securely overwrite the contained value with zeros.
257 /// After this returns, every byte of the wrapped value has been volatile-written to `0`.
258 ///
259 /// This is called automatically on drop; call it directly only if you need to scrub the value
260 /// early, for example before reusing the same `Secret` object.
261 /// Though in many circumstances you are zeroizing because you know you're done with the object before
262 /// it goes out of scope, in which case you would be better served calling `drop(s)` instead
263 /// since this will still call `zeroize()` and also move the object, preventing you from accidentally reusing it.
264 #[inline]
265 pub fn zeroize(&mut self) {
266 // SAFETY: `&mut self.0` is a valid, properly aligned, mutable reference to an initialized
267 // `T`, which is exactly the contract `write_volatile` requires.
268 // `T::ZEROED` is defined above for each supported primitive and primitive-array as the
269 // is the all-zero value of `T`, which is a valid and correctly-sized bit pattern
270 // for the primitive scalar/array being zeroized.
271 //`write_volatile` (rather than a plain store) is what forbids the compiler from eliding
272 // this scrub as a dead write as per its contract:
273 // https://doc.rust-lang.org/std/ptr/fn.write_volatile.html
274
275 // Just to make sure -- this should trigger on any unit tests for any instantiation of
276 // Secret<T> that causes this assumption to be violated.
277 debug_assert_eq!(size_of::<T>(), size_of_val(&T::ZEROED));
278
279 unsafe {
280 ptr::write_volatile(&mut self.0, T::ZEROED);
281 }
282 // Compile-time barrier: keeps the volatile scrub ordered before any later memory ops.
283 // (for example, if the user calls .zeroize() outside of a drop and then continues using
284 // the object by filling it with new data, which is valid usage.)
285 // Emits no machine instructions.
286 compiler_fence(Ordering::SeqCst);
287 }
288}
289
290impl<T: ZeroizablePrimitive> Drop for Secret<T> {
291 #[inline]
292 fn drop(&mut self) {
293 self.zeroize();
294 }
295}
296
297impl<T: ZeroizablePrimitive> Deref for Secret<T> {
298 type Target = T;
299 #[inline]
300 fn deref(&self) -> &T {
301 &self.0
302 }
303}
304
305impl<T: ZeroizablePrimitive> DerefMut for Secret<T> {
306 #[inline]
307 fn deref_mut(&mut self) -> &mut T {
308 &mut self.0
309 }
310}
311
312// Intentionally not `Copy`: a `Copy` type cannot have a `Drop`, and we want move semantics so a
313// secret is never silently duplicated. `Clone` is provided for deliberate copies.
314impl<T: ZeroizablePrimitive> Clone for Secret<T> {
315 #[inline]
316 fn clone(&self) -> Self {
317 Self(self.0)
318 }
319}
320
321/// Redacting: prints the wrapped type but never its contents.
322impl<T: ZeroizablePrimitive> fmt::Debug for Secret<T> {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 write!(f, "Secret<{}>(<redacted>)", type_name::<T>())
325 }
326}
327
328/// Redacting: never prints the contents.
329impl<T: ZeroizablePrimitive> fmt::Display for Secret<T> {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 f.write_str("<redacted>")
332 }
333}
334
335/// Checks for equality of the secret data by casting to bytes and using a constant-time comparison.
336///
337/// Both operands are the same type `T`, so both views are exactly `size_of::<T>()` bytes and the
338/// comparison never short-circuits: it always inspects every byte, avoiding a timing side channel
339/// that would otherwise leak how many leading bytes of two secrets match.
340impl<T: ZeroizablePrimitive> PartialEq for Secret<T> {
341 fn eq(&self, other: &Self) -> bool {
342 let len = size_of::<T>();
343 // SAFETY: `self.0` / `other.0` are live, initialized `T` values, so the `len` bytes starting
344 // at each address lie within that single object. `u8` has alignment 1, so every byte address
345 // is well aligned, and the slices are read-only and used only within this call. `T: Copy`
346 // (via `ZeroizablePrimitive`) means there is no interior mutability or drop glue to worry
347 // about.
348 let a = unsafe { core::slice::from_raw_parts((&self.0 as *const T).cast::<u8>(), len) };
349 let b = unsafe { core::slice::from_raw_parts((&other.0 as *const T).cast::<u8>(), len) };
350 ct::ct_eq_bytes(a, b)
351 }
352}
353impl<T: ZeroizablePrimitive> Eq for Secret<T> {}