envelope_cli/crypto/
secure_memory.rs

1//! Secure memory handling for sensitive data
2//!
3//! Provides types that securely zero memory on drop to prevent
4//! sensitive data from lingering in memory.
5
6use std::fmt;
7use std::ops::Deref;
8
9/// A string type that zeros its contents on drop
10///
11/// Use this for passphrases and other sensitive string data.
12pub struct SecureString {
13    inner: String,
14}
15
16impl SecureString {
17    /// Create a new SecureString
18    pub fn new(s: impl Into<String>) -> Self {
19        Self { inner: s.into() }
20    }
21
22    /// Get the string contents
23    pub fn as_str(&self) -> &str {
24        &self.inner
25    }
26
27    /// Get the length
28    pub fn len(&self) -> usize {
29        self.inner.len()
30    }
31
32    /// Check if empty
33    pub fn is_empty(&self) -> bool {
34        self.inner.is_empty()
35    }
36}
37
38impl Drop for SecureString {
39    fn drop(&mut self) {
40        // Zero out the string's memory
41        // SAFETY: We're modifying the bytes in place before the String is dropped
42        // The string might be on the heap, so we need to zero those bytes
43        unsafe {
44            let bytes = self.inner.as_bytes_mut();
45            for byte in bytes.iter_mut() {
46                std::ptr::write_volatile(byte, 0);
47            }
48        }
49        // Clear the string (this might reallocate, but the important thing
50        // is we've zeroed the original memory)
51        self.inner.clear();
52    }
53}
54
55impl Deref for SecureString {
56    type Target = str;
57
58    fn deref(&self) -> &Self::Target {
59        &self.inner
60    }
61}
62
63impl AsRef<str> for SecureString {
64    fn as_ref(&self) -> &str {
65        &self.inner
66    }
67}
68
69impl From<String> for SecureString {
70    fn from(s: String) -> Self {
71        Self::new(s)
72    }
73}
74
75impl From<&str> for SecureString {
76    fn from(s: &str) -> Self {
77        Self::new(s)
78    }
79}
80
81// Don't print the contents in Debug output
82impl fmt::Debug for SecureString {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.debug_struct("SecureString")
85            .field("len", &self.inner.len())
86            .finish()
87    }
88}
89
90// Don't print the contents in Display output
91impl fmt::Display for SecureString {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "[REDACTED {} bytes]", self.inner.len())
94    }
95}
96
97/// A byte vector that zeros its contents on drop
98///
99/// Use this for encryption keys and other sensitive binary data.
100pub struct SecureBytes {
101    inner: Vec<u8>,
102}
103
104impl SecureBytes {
105    /// Create new SecureBytes
106    pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
107        Self {
108            inner: bytes.into(),
109        }
110    }
111
112    /// Create SecureBytes with a specific capacity
113    pub fn with_capacity(capacity: usize) -> Self {
114        Self {
115            inner: Vec::with_capacity(capacity),
116        }
117    }
118
119    /// Get the bytes
120    pub fn as_bytes(&self) -> &[u8] {
121        &self.inner
122    }
123
124    /// Get mutable bytes
125    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
126        &mut self.inner
127    }
128
129    /// Get the length
130    pub fn len(&self) -> usize {
131        self.inner.len()
132    }
133
134    /// Check if empty
135    pub fn is_empty(&self) -> bool {
136        self.inner.is_empty()
137    }
138}
139
140impl Drop for SecureBytes {
141    fn drop(&mut self) {
142        // Zero out the memory
143        for byte in self.inner.iter_mut() {
144            unsafe {
145                std::ptr::write_volatile(byte, 0);
146            }
147        }
148        self.inner.clear();
149    }
150}
151
152impl Deref for SecureBytes {
153    type Target = [u8];
154
155    fn deref(&self) -> &Self::Target {
156        &self.inner
157    }
158}
159
160impl AsRef<[u8]> for SecureBytes {
161    fn as_ref(&self) -> &[u8] {
162        &self.inner
163    }
164}
165
166impl From<Vec<u8>> for SecureBytes {
167    fn from(bytes: Vec<u8>) -> Self {
168        Self::new(bytes)
169    }
170}
171
172impl From<&[u8]> for SecureBytes {
173    fn from(bytes: &[u8]) -> Self {
174        Self::new(bytes.to_vec())
175    }
176}
177
178// Don't print the contents in Debug output
179impl fmt::Debug for SecureBytes {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        f.debug_struct("SecureBytes")
182            .field("len", &self.inner.len())
183            .finish()
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_secure_string_creation() {
193        let s = SecureString::new("test");
194        assert_eq!(s.as_str(), "test");
195        assert_eq!(s.len(), 4);
196    }
197
198    #[test]
199    fn test_secure_string_from_string() {
200        let s: SecureString = String::from("test").into();
201        assert_eq!(s.as_str(), "test");
202    }
203
204    #[test]
205    fn test_secure_string_from_str() {
206        let s: SecureString = "test".into();
207        assert_eq!(s.as_str(), "test");
208    }
209
210    #[test]
211    fn test_secure_string_deref() {
212        let s = SecureString::new("test");
213        let len = s.len(); // Uses Deref to &str
214        assert_eq!(len, 4);
215    }
216
217    #[test]
218    fn test_secure_string_debug() {
219        let s = SecureString::new("secret");
220        let debug = format!("{:?}", s);
221        assert!(!debug.contains("secret"));
222        assert!(debug.contains("SecureString"));
223    }
224
225    #[test]
226    fn test_secure_string_display() {
227        let s = SecureString::new("secret");
228        let display = format!("{}", s);
229        assert!(!display.contains("secret"));
230        assert!(display.contains("REDACTED"));
231    }
232
233    #[test]
234    fn test_secure_bytes_creation() {
235        let b = SecureBytes::new(vec![1, 2, 3]);
236        assert_eq!(b.as_bytes(), &[1, 2, 3]);
237        assert_eq!(b.len(), 3);
238    }
239
240    #[test]
241    fn test_secure_bytes_from_vec() {
242        let b: SecureBytes = vec![1, 2, 3].into();
243        assert_eq!(b.as_bytes(), &[1, 2, 3]);
244    }
245
246    #[test]
247    fn test_secure_bytes_from_slice() {
248        let b: SecureBytes = (&[1u8, 2, 3][..]).into();
249        assert_eq!(b.as_bytes(), &[1, 2, 3]);
250    }
251
252    #[test]
253    fn test_secure_bytes_debug() {
254        let b = SecureBytes::new(vec![1, 2, 3, 4, 5]);
255        let debug = format!("{:?}", b);
256        assert!(debug.contains("SecureBytes"));
257        assert!(debug.contains("5")); // length
258    }
259}