Skip to main content

dcrypt_common/security/
secret.rs

1//! Secret data types that invoke zeroization for owned storage
2//!
3//! This module provides type-safe wrappers that invoke explicit clearing for
4//! initialized storage they own. Software zeroization cannot erase caller,
5//! compiler/register, allocator, swap, crash-dump, or already-freed copies.
6
7use core::convert::{AsMut, AsRef};
8use core::fmt;
9use core::ops::{Deref, DerefMut};
10use dcrypt_internal::zeroing::Zeroize;
11
12pub use dcrypt_api::types::SecretBytes as SecretBuffer;
13#[cfg(feature = "alloc")]
14pub use dcrypt_api::types::SecretVec;
15
16/// Trait for types that explicitly clear owned storage and preserve that
17/// behavior when cloned.
18pub trait SecureZeroingType: Zeroize + Clone {
19    /// Create a zeroed instance
20    fn zeroed() -> Self;
21
22    /// Create a clone with the same owned-storage cleanup behavior.
23    ///
24    /// Each clone owns a separate copy and must be cleared independently.
25    fn secure_clone(&self) -> Self {
26        self.clone() // Default implementation uses regular clone
27    }
28}
29
30impl<const N: usize> SecureZeroingType for SecretBuffer<N> {
31    fn zeroed() -> Self {
32        Self::zeroed()
33    }
34
35    fn secure_clone(&self) -> Self {
36        self.clone()
37    }
38}
39
40#[cfg(feature = "alloc")]
41impl SecureZeroingType for SecretVec {
42    fn zeroed() -> Self {
43        Self::empty()
44    }
45
46    fn secure_clone(&self) -> Self {
47        self.clone()
48    }
49}
50
51/// Ephemeral secret that invokes explicit clearing on drop.
52///
53/// This type wraps a value and invokes its [`Zeroize`] implementation on drop.
54/// It is useful for temporary secrets and intermediate cryptographic values,
55/// subject to the module-level limits of best-effort software clearing.
56pub struct EphemeralSecret<T: Zeroize> {
57    inner: Option<T>,
58}
59
60impl<T: Zeroize> EphemeralSecret<T> {
61    /// Create a new ephemeral secret
62    pub fn new(value: T) -> Self {
63        Self { inner: Some(value) }
64    }
65
66    /// Consume the secret and return the inner value
67    ///
68    /// Note: after calling this method, the caller owns the value and is
69    /// responsible for invoking its clearing policy when appropriate.
70    pub fn into_inner(self) -> T {
71        let mut this = self;
72        this.inner
73            .take()
74            .expect("EphemeralSecret value was already consumed")
75    }
76}
77
78// Fixed: Implement actual AsRef and AsMut traits instead of methods
79impl<T: Zeroize> AsRef<T> for EphemeralSecret<T> {
80    fn as_ref(&self) -> &T {
81        self.inner
82            .as_ref()
83            .expect("EphemeralSecret value was already consumed")
84    }
85}
86
87impl<T: Zeroize> AsMut<T> for EphemeralSecret<T> {
88    fn as_mut(&mut self) -> &mut T {
89        self.inner
90            .as_mut()
91            .expect("EphemeralSecret value was already consumed")
92    }
93}
94
95impl<T: Zeroize> Drop for EphemeralSecret<T> {
96    fn drop(&mut self) {
97        if let Some(inner) = self.inner.as_mut() {
98            inner.zeroize();
99        }
100    }
101}
102
103impl<T: Zeroize + Clone> Clone for EphemeralSecret<T> {
104    fn clone(&self) -> Self {
105        Self::new(
106            self.inner
107                .as_ref()
108                .expect("EphemeralSecret value was already consumed")
109                .clone(),
110        )
111    }
112}
113
114impl<T: Zeroize + Default> Default for EphemeralSecret<T> {
115    fn default() -> Self {
116        Self::new(T::default())
117    }
118}
119
120impl<T: Zeroize> Deref for EphemeralSecret<T> {
121    type Target = T;
122
123    fn deref(&self) -> &Self::Target {
124        self.as_ref()
125    }
126}
127
128impl<T: Zeroize> DerefMut for EphemeralSecret<T> {
129    fn deref_mut(&mut self) -> &mut Self::Target {
130        self.as_mut()
131    }
132}
133
134impl<T: Zeroize> fmt::Debug for EphemeralSecret<T> {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(f, "EphemeralSecret([REDACTED])")
137    }
138}
139
140/// Guard type that invokes a value's [`Zeroize`] implementation when dropped.
141///
142/// This is useful for ensuring cleanup happens even in the presence
143/// of early returns or panics.
144pub struct ZeroizeGuard<'a, T: Zeroize> {
145    value: &'a mut T,
146}
147
148impl<'a, T: Zeroize> ZeroizeGuard<'a, T> {
149    /// Create a new zeroize guard for the given value
150    pub fn new(value: &'a mut T) -> Self {
151        Self { value }
152    }
153}
154
155// Fixed: Use lifetime elision instead of explicit lifetimes
156impl<T: Zeroize> Drop for ZeroizeGuard<'_, T> {
157    fn drop(&mut self) {
158        self.value.zeroize();
159    }
160}
161
162impl<T: Zeroize> Deref for ZeroizeGuard<'_, T> {
163    type Target = T;
164
165    fn deref(&self) -> &Self::Target {
166        self.value
167    }
168}
169
170impl<T: Zeroize> DerefMut for ZeroizeGuard<'_, T> {
171    fn deref_mut(&mut self) -> &mut Self::Target {
172        self.value
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_secret_buffer_basic() {
182        let mut buffer = SecretBuffer::<32>::new([42u8; 32]);
183        assert_eq!(buffer.len(), 32);
184        assert_eq!(buffer.as_slice()[0], 42);
185
186        // Test mutation
187        buffer.as_mut_slice()[0] = 1;
188        assert_eq!(buffer.as_slice()[0], 1);
189    }
190
191    #[test]
192    fn test_secret_buffer_secure_clone() {
193        let buffer = SecretBuffer::<16>::new([0xAA; 16]);
194        let cloned = buffer.secure_clone();
195        assert_eq!(cloned.as_slice(), buffer.as_slice());
196    }
197
198    #[test]
199    fn test_secret_buffer_zeroed() {
200        let zeroed = SecretBuffer::<32>::zeroed();
201        assert_eq!(zeroed.as_slice(), &[0u8; 32]);
202    }
203
204    #[cfg(feature = "alloc")]
205    #[test]
206    fn test_secret_vec_operations() {
207        let mut vec = SecretVec::from_slice(&[1, 2, 3, 4]);
208        assert_eq!(vec.len(), 4);
209        assert_eq!(vec.as_slice(), &[1, 2, 3, 4]);
210
211        // Test extend
212        vec.extend_from_slice(&[5, 6]);
213        assert_eq!(vec.as_slice(), &[1, 2, 3, 4, 5, 6]);
214
215        // Test truncate
216        vec.truncate(3);
217        assert_eq!(vec.as_slice(), &[1, 2, 3]);
218
219        // Test resize
220        vec.resize(5, 0xFF);
221        assert_eq!(vec.as_slice(), &[1, 2, 3, 0xFF, 0xFF]);
222    }
223
224    #[cfg(feature = "alloc")]
225    #[test]
226    fn secret_vec_uses_exact_size_storage() {
227        let secret = SecretVec::from_slice(&[0xA5; 4]);
228
229        assert_eq!(secret.as_slice(), &[0xA5; 4]);
230        assert_eq!(secret.capacity(), secret.len());
231    }
232
233    #[cfg(feature = "alloc")]
234    #[test]
235    fn secret_vec_wipes_bytes_removed_by_truncate_resize_clear_and_pop() {
236        let mut secret = SecretVec::from_slice(&[1, 2, 3, 4, 5, 6]);
237
238        secret.truncate(4);
239        assert_eq!(secret.as_slice(), &[1, 2, 3, 4]);
240        assert_eq!(secret.capacity(), secret.len());
241
242        secret.resize(2, 0xFF);
243        assert_eq!(secret.as_slice(), &[1, 2]);
244        assert_eq!(secret.capacity(), secret.len());
245
246        assert_eq!(secret.pop(), Some(2));
247        assert_eq!(secret.as_slice(), &[1]);
248        assert_eq!(secret.capacity(), secret.len());
249
250        secret.clear();
251        assert!(secret.is_empty());
252        assert_eq!(secret.capacity(), secret.len());
253    }
254
255    #[cfg(feature = "alloc")]
256    #[test]
257    fn secret_vec_growth_and_shrink_replace_allocations_securely() {
258        let mut secret = SecretVec::from_slice(&[0x11; 4]);
259        secret.extend_from_slice(&[0x22, 0x33]);
260        assert_eq!(secret.capacity(), 6);
261        assert_eq!(secret.as_slice(), &[0x11, 0x11, 0x11, 0x11, 0x22, 0x33]);
262    }
263
264    #[test]
265    fn test_ephemeral_secret() {
266        #[derive(Clone)]
267        struct TestSecret(u64);
268
269        impl Zeroize for TestSecret {
270            fn zeroize(&mut self) {
271                self.0.zeroize();
272            }
273        }
274
275        let secret = EphemeralSecret::new(TestSecret(42));
276        assert_eq!(secret.0, 42);
277
278        // Test deref
279        let value = secret.0;
280        assert_eq!(value, 42);
281
282        // Test clone
283        let cloned = secret.clone();
284        assert_eq!(cloned.0, 42);
285
286        // Test into_inner
287        let inner = secret.into_inner();
288        assert_eq!(inner.0, 42);
289    }
290
291    #[test]
292    fn test_zeroize_guard() {
293        let mut value = [1u8, 2, 3, 4];
294        {
295            let guard = ZeroizeGuard::new(&mut value);
296            // Simulate work with the value
297            assert_eq!(&*guard, &[1, 2, 3, 4]);
298        }
299        assert_eq!(value, [0; 4]);
300    }
301}