Skip to main content

commonware_cryptography/
secret.rs

1//! A wrapper type for secret values that prevents accidental leakage.
2//!
3//! `Secret<T>` provides the following guarantees:
4//! - Debug and Display always show `[REDACTED]` instead of the actual value
5//! - The inner value is zeroized on drop
6//! - Access to the inner value requires an explicit `expose()` call
7//! - Comparisons use constant-time operations to prevent timing attacks
8//!
9//! # Type Constraints
10//!
11//! **Important**: `Secret<T>` is designed for flat data types without pointers
12//! (e.g. `[u8; N]`). It does NOT provide full protection for types with
13//! indirection. Types like `Vec<T>`, `String`, or `Box<T>` will only have their
14//! metadata (pointer, length, capacity) zeroized, the referenced data remains
15//! intact. Do not use `Secret` with types that contain pointers.
16
17use core::{
18    fmt::{Debug, Display, Formatter},
19    mem::ManuallyDrop,
20};
21use ctutils::CtEq;
22use zeroize::{Zeroize, ZeroizeOnDrop};
23
24/// Zeroize memory at the given pointer using volatile writes.
25///
26/// # Safety
27///
28/// `ptr` must point to allocated, writable memory of at least `size_of::<T>()` bytes.
29#[inline]
30unsafe fn zeroize_ptr<T>(ptr: *mut T) {
31    // SAFETY: The caller guarantees that `ptr` is valid for writes of `size_of::<T>()` bytes.
32    unsafe {
33        let slice = core::slice::from_raw_parts_mut(ptr as *mut u8, core::mem::size_of::<T>());
34        slice.zeroize();
35    }
36}
37
38/// A wrapper for secret values that prevents accidental leakage.
39///
40/// - Debug and Display show `[REDACTED]`
41/// - Zeroized on drop
42/// - Access requires explicit `expose()` call
43///
44/// # Type Constraints
45///
46/// Only use with flat data types that have no pointers (e.g. `[u8; N]`).
47/// See [module-level documentation](self) for details.
48pub struct Secret<T>(ManuallyDrop<T>);
49
50impl<T> Secret<T> {
51    /// Creates a new `Secret` wrapping the given value.
52    #[inline]
53    pub const fn new(value: T) -> Self {
54        Self(ManuallyDrop::new(value))
55    }
56
57    /// Exposes the secret value for read-only access within a closure.
58    ///
59    /// # Note
60    ///
61    /// The closure uses a higher-ranked trait bound (`for<'a>`) to prevent
62    /// the returned value from containing references to the secret data.
63    /// This ensures the reference cannot escape the closure scope. However,
64    /// this does not prevent copying or cloning the secret value within
65    /// the closure (e.g., `secret.expose(|s| s.clone())`). Callers should
66    /// avoid leaking secrets through such patterns.
67    ///
68    /// Additionally, any temporaries derived from the secret (e.g.
69    /// `s.as_slice()`) may leave secret data on the stack that will not be
70    /// automatically zeroized. Callers should wrap such temporaries in
71    /// [`zeroize::Zeroizing`] if they contain sensitive data.
72    #[inline]
73    pub fn expose<R>(&self, f: impl for<'a> FnOnce(&'a T) -> R) -> R {
74        f(&self.0)
75    }
76
77    /// Consumes the [Secret] and returns the inner value, zeroizing the original
78    /// memory location.
79    ///
80    /// Use this when you need to transfer ownership of the secret value (e.g.,
81    /// for APIs that consume the value).
82    #[inline]
83    pub fn expose_unwrap(mut self) -> T {
84        let ptr = &raw mut *self.0;
85        // SAFETY:
86        // Pointer obtained while self.0 is still initialized,
87        // self.0 is initialized and we have exclusive access
88        let value = unsafe { ManuallyDrop::take(&mut self.0) };
89
90        // Prevent Secret::drop from running (would double-zeroize or double-free on panic)
91        core::mem::forget(self);
92
93        // SAFETY: uses raw pointer (not reference) to zero memory after drop
94        unsafe { zeroize_ptr(ptr) };
95
96        value
97    }
98}
99
100impl<T> Drop for Secret<T> {
101    fn drop(&mut self) {
102        let ptr = &raw mut *self.0;
103        // SAFETY:
104        // - Pointer obtained while self.0 is still initialized
105        // - ManuallyDrop::drop: self.0 is initialized and we have exclusive access
106        // - zeroize_ptr: uses raw pointer (not reference) to zero memory after drop
107        unsafe {
108            ManuallyDrop::drop(&mut self.0);
109            zeroize_ptr(ptr);
110        }
111    }
112}
113
114impl<T> Debug for Secret<T> {
115    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
116        f.write_str("Secret([REDACTED])")
117    }
118}
119
120impl<T> Display for Secret<T> {
121    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
122        f.write_str("[REDACTED]")
123    }
124}
125
126impl<T> ZeroizeOnDrop for Secret<T> {}
127
128impl<T: Clone> Clone for Secret<T> {
129    fn clone(&self) -> Self {
130        self.expose(|v| Self::new(v.clone()))
131    }
132}
133
134impl<T: CtEq> PartialEq for Secret<T> {
135    fn eq(&self, other: &Self) -> bool {
136        self.expose(|a| other.expose(|b| a.ct_eq(b).into()))
137    }
138}
139
140impl<T: CtEq> Eq for Secret<T> {}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_debug_redacted() {
148        let secret = Secret::new([1u8, 2, 3, 4]);
149        assert_eq!(format!("{:?}", secret), "Secret([REDACTED])");
150    }
151
152    #[test]
153    fn test_display_redacted() {
154        let secret = Secret::new([1u8, 2, 3, 4]);
155        assert_eq!(format!("{}", secret), "[REDACTED]");
156    }
157
158    #[test]
159    fn test_expose() {
160        let secret = Secret::new([1u8, 2, 3, 4]);
161        secret.expose(|v| {
162            assert_eq!(v, &[1u8, 2, 3, 4]);
163        });
164    }
165
166    #[test]
167    fn test_expose_unwrap() {
168        let secret = Secret::new([1u8, 2, 3, 4]);
169        let value = secret.expose_unwrap();
170        assert_eq!(value, [1u8, 2, 3, 4]);
171    }
172
173    #[test]
174    fn test_clone() {
175        let secret = Secret::new([1u8, 2, 3, 4]);
176        let cloned = secret.clone();
177        secret.expose(|a| {
178            cloned.expose(|b| {
179                assert_eq!(a, b);
180            });
181        });
182    }
183
184    #[test]
185    fn test_equality() {
186        let s1 = Secret::new([1u8, 2, 3, 4]);
187        let s2 = Secret::new([1u8, 2, 3, 4]);
188        let s3 = Secret::new([5u8, 6, 7, 8]);
189        assert_eq!(s1, s2);
190        assert_ne!(s1, s3);
191    }
192
193    #[test]
194    fn test_multiple_expose() {
195        let secret = Secret::new([42u8; 32]);
196
197        // First expose
198        secret.expose(|v| {
199            assert_eq!(v[0], 42);
200        });
201
202        // Second expose
203        secret.expose(|v| {
204            assert_eq!(v[31], 42);
205        });
206    }
207}