chie_crypto/
zeroizing.rs

1//! Zeroizing wrappers for sensitive cryptographic material.
2//!
3//! This module provides wrappers that automatically zero out memory
4//! when dropped, preventing sensitive data from lingering in memory.
5
6use zeroize::{Zeroize, Zeroizing};
7
8/// A zeroizing wrapper for secret keys.
9///
10/// When this value is dropped, the underlying bytes are securely zeroed.
11pub type ZeroizingKey<const N: usize> = Zeroizing<[u8; N]>;
12
13/// Create a zeroizing 32-byte key.
14pub fn zeroizing_key_32() -> ZeroizingKey<32> {
15    Zeroizing::new([0u8; 32])
16}
17
18/// Create a zeroizing 12-byte nonce.
19pub fn zeroizing_nonce() -> ZeroizingKey<12> {
20    Zeroizing::new([0u8; 12])
21}
22
23/// Securely zero a byte slice.
24///
25/// This uses compiler barriers to prevent the zeroing from being optimized away.
26#[inline]
27pub fn secure_zero(data: &mut [u8]) {
28    data.zeroize();
29}
30
31/// Securely copy and then zero the source.
32#[inline]
33pub fn secure_move(dest: &mut [u8], src: &mut [u8]) {
34    assert_eq!(
35        dest.len(),
36        src.len(),
37        "dest and src must have the same length"
38    );
39    dest.copy_from_slice(src);
40    src.zeroize();
41}
42
43/// A buffer that zeros itself on drop.
44#[derive(Clone, Zeroize)]
45#[zeroize(drop)]
46pub struct SecureBuffer {
47    data: Vec<u8>,
48}
49
50impl SecureBuffer {
51    /// Create a new secure buffer with the given capacity.
52    pub fn new(capacity: usize) -> Self {
53        Self {
54            data: Vec::with_capacity(capacity),
55        }
56    }
57
58    /// Create a secure buffer from bytes.
59    pub fn from_bytes(bytes: Vec<u8>) -> Self {
60        Self { data: bytes }
61    }
62
63    /// Create a secure buffer from a slice.
64    pub fn from_slice(slice: &[u8]) -> Self {
65        Self {
66            data: slice.to_vec(),
67        }
68    }
69
70    /// Get a reference to the inner bytes.
71    pub fn as_bytes(&self) -> &[u8] {
72        &self.data
73    }
74
75    /// Get a mutable reference to the inner bytes.
76    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
77        &mut self.data
78    }
79
80    /// Get the length of the buffer.
81    pub fn len(&self) -> usize {
82        self.data.len()
83    }
84
85    /// Check if the buffer is empty.
86    pub fn is_empty(&self) -> bool {
87        self.data.is_empty()
88    }
89
90    /// Push a byte to the buffer.
91    pub fn push(&mut self, byte: u8) {
92        self.data.push(byte);
93    }
94
95    /// Extend the buffer with a slice.
96    pub fn extend_from_slice(&mut self, slice: &[u8]) {
97        self.data.extend_from_slice(slice);
98    }
99
100    /// Clear the buffer (zeroizes first).
101    pub fn clear(&mut self) {
102        self.data.zeroize();
103        self.data.clear();
104    }
105
106    /// Consume the buffer and return the inner Vec (without zeroing).
107    ///
108    /// Use with caution - the returned Vec will not be zeroized on drop.
109    pub fn into_inner(mut self) -> Vec<u8> {
110        // Take ownership and prevent drop
111        let mut data = Vec::new();
112        std::mem::swap(&mut data, &mut self.data);
113        std::mem::forget(self);
114        data
115    }
116
117    /// Resize the buffer to the new length, filling with zeros.
118    pub fn resize(&mut self, new_len: usize) {
119        self.data.resize(new_len, 0);
120    }
121}
122
123impl std::fmt::Debug for SecureBuffer {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(f, "SecureBuffer([REDACTED; {} bytes])", self.data.len())
126    }
127}
128
129impl From<Vec<u8>> for SecureBuffer {
130    fn from(data: Vec<u8>) -> Self {
131        Self { data }
132    }
133}
134
135impl From<&[u8]> for SecureBuffer {
136    fn from(data: &[u8]) -> Self {
137        Self::from_slice(data)
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_secure_zero() {
147        let mut data = vec![1, 2, 3, 4, 5];
148        secure_zero(&mut data);
149        assert_eq!(data, vec![0, 0, 0, 0, 0]);
150    }
151
152    #[test]
153    fn test_secure_move() {
154        let mut src = vec![1, 2, 3, 4];
155        let mut dest = vec![0, 0, 0, 0];
156
157        secure_move(&mut dest, &mut src);
158
159        assert_eq!(dest, vec![1, 2, 3, 4]);
160        assert_eq!(src, vec![0, 0, 0, 0]);
161    }
162
163    #[test]
164    fn test_secure_buffer() {
165        let mut buffer = SecureBuffer::from_slice(&[1, 2, 3, 4]);
166
167        assert_eq!(buffer.len(), 4);
168        assert!(!buffer.is_empty());
169        assert_eq!(buffer.as_bytes(), &[1, 2, 3, 4]);
170
171        buffer.push(5);
172        assert_eq!(buffer.len(), 5);
173
174        buffer.clear();
175        assert_eq!(buffer.len(), 0);
176        assert!(buffer.is_empty());
177    }
178
179    #[test]
180    fn test_secure_buffer_debug() {
181        let buffer = SecureBuffer::from_slice(&[1, 2, 3, 4]);
182        let debug = format!("{:?}", buffer);
183        assert!(debug.contains("REDACTED"));
184        assert!(!debug.contains("1, 2, 3, 4"));
185    }
186
187    #[test]
188    fn test_zeroizing_key() {
189        let key = zeroizing_key_32();
190        assert_eq!(key.len(), 32);
191
192        let nonce = zeroizing_nonce();
193        assert_eq!(nonce.len(), 12);
194    }
195
196    #[test]
197    fn test_secure_buffer_drop_zeroizes() {
198        let data = vec![1u8, 2, 3, 4];
199
200        {
201            let _buffer = SecureBuffer::from_bytes(data);
202            // Buffer gets dropped here
203        }
204
205        // We can't directly verify memory was zeroed without unsafe code,
206        // but we trust the Zeroize crate's implementation
207    }
208}