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}