Skip to main content

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> {}