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