Rust · 12056 bytes Raw Blame History
1 //! Secure memory handling for ZephyrFS cryptographic operations
2 //!
3 //! Provides zeroizing containers for sensitive data like keys, passwords, and plaintext.
4 //! Ensures memory is securely cleared when data is no longer needed.
5
6 use zeroize::{Zeroize, ZeroizeOnDrop};
7 use std::ops::{Deref, DerefMut};
8 use std::fmt;
9
10 /// Secure byte buffer that automatically zeros memory on drop
11 #[derive(Clone, ZeroizeOnDrop)]
12 pub struct SecureBytes {
13 data: Vec<u8>,
14 }
15
16 impl SecureBytes {
17 /// Create new secure buffer with specified size
18 pub fn new(size: usize) -> Self {
19 Self {
20 data: vec![0u8; size],
21 }
22 }
23
24 /// Create from existing data (data will be zeroized in source)
25 pub fn from_vec(mut data: Vec<u8>) -> Self {
26 let secure = Self { data: data.clone() };
27 data.zeroize(); // Clear original
28 secure
29 }
30
31 /// Create from slice (slice data will be copied)
32 pub fn from_slice(data: &[u8]) -> Self {
33 Self {
34 data: data.to_vec(),
35 }
36 }
37
38 /// Get length of secure buffer
39 pub fn len(&self) -> usize {
40 self.data.len()
41 }
42
43 /// Check if buffer is empty
44 pub fn is_empty(&self) -> bool {
45 self.data.is_empty()
46 }
47
48 /// Get immutable reference to bytes
49 pub fn as_bytes(&self) -> &[u8] {
50 &self.data
51 }
52
53 /// Get mutable reference to bytes
54 pub fn as_mut(&mut self) -> &mut [u8] {
55 &mut self.data
56 }
57
58 /// Copy data from slice into this buffer
59 pub fn copy_from_slice(&mut self, src: &[u8]) {
60 if src.len() != self.data.len() {
61 panic!("Source length {} doesn't match buffer length {}", src.len(), self.data.len());
62 }
63 self.data.copy_from_slice(src);
64 }
65
66 /// Resize buffer (new space will be zero-filled)
67 pub fn resize(&mut self, new_size: usize) {
68 self.data.resize(new_size, 0);
69 }
70
71 /// Clear buffer contents (set all bytes to zero)
72 pub fn clear(&mut self) {
73 self.data.zeroize();
74 }
75
76 /// Split buffer at index, returning (left, right)
77 pub fn split_at(mut self, mid: usize) -> (SecureBytes, SecureBytes) {
78 let right = self.data.split_off(mid);
79 let left_data = std::mem::take(&mut self.data);
80 let left = SecureBytes { data: left_data };
81 let right = SecureBytes { data: right };
82 self.data.zeroize(); // Clear original (now empty)
83 (left, right)
84 }
85
86 /// Extend buffer with data from slice
87 pub fn extend_from_slice(&mut self, data: &[u8]) {
88 self.data.extend_from_slice(data);
89 }
90
91 /// Convert to Vec<u8> (consumes self and zeros original)
92 pub fn into_vec(mut self) -> Vec<u8> {
93 let data = std::mem::take(&mut self.data);
94 self.data.zeroize();
95 data
96 }
97 }
98
99 impl Deref for SecureBytes {
100 type Target = [u8];
101
102 fn deref(&self) -> &Self::Target {
103 &self.data
104 }
105 }
106
107 impl DerefMut for SecureBytes {
108 fn deref_mut(&mut self) -> &mut Self::Target {
109 &mut self.data
110 }
111 }
112
113 impl AsRef<[u8]> for SecureBytes {
114 fn as_ref(&self) -> &[u8] {
115 &self.data
116 }
117 }
118
119 impl AsMut<[u8]> for SecureBytes {
120 fn as_mut(&mut self) -> &mut [u8] {
121 &mut self.data
122 }
123 }
124
125 // Implement Debug but don't show actual data
126 impl fmt::Debug for SecureBytes {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 f.debug_struct("SecureBytes")
129 .field("len", &self.data.len())
130 .field("data", &"[REDACTED]")
131 .finish()
132 }
133 }
134
135 // Implement PartialEq for testing, but use constant-time comparison
136 impl PartialEq for SecureBytes {
137 fn eq(&self, other: &Self) -> bool {
138 if self.data.len() != other.data.len() {
139 return false;
140 }
141 constant_time_eq::constant_time_eq(&self.data, &other.data)
142 }
143 }
144
145 impl Eq for SecureBytes {}
146
147 impl Zeroize for SecureBytes {
148 fn zeroize(&mut self) {
149 self.data.zeroize();
150 }
151 }
152
153 /// Secure string that automatically zeros memory on drop
154 #[derive(Clone, ZeroizeOnDrop)]
155 pub struct SecureString {
156 data: String,
157 }
158
159 impl SecureString {
160 /// Create new empty secure string
161 pub fn new() -> Self {
162 Self {
163 data: String::new(),
164 }
165 }
166
167 /// Create from existing string (string will be zeroized in source)
168 pub fn from_string(mut data: String) -> Self {
169 let secure = Self { data: data.clone() };
170 data.zeroize();
171 secure
172 }
173
174 /// Create from str (data will be copied)
175 pub fn from_str(data: &str) -> Self {
176 Self {
177 data: data.to_string(),
178 }
179 }
180
181 /// Get length of string
182 pub fn len(&self) -> usize {
183 self.data.len()
184 }
185
186 /// Check if string is empty
187 pub fn is_empty(&self) -> bool {
188 self.data.is_empty()
189 }
190
191 /// Get str reference
192 pub fn as_str(&self) -> &str {
193 &self.data
194 }
195
196 /// Push character to string
197 pub fn push(&mut self, ch: char) {
198 self.data.push(ch);
199 }
200
201 /// Push str to string
202 pub fn push_str(&mut self, string: &str) {
203 self.data.push_str(string);
204 }
205
206 /// Clear string contents
207 pub fn clear(&mut self) {
208 self.data.zeroize();
209 self.data.clear();
210 }
211
212 /// Convert to String (consumes self and zeros original)
213 pub fn into_string(mut self) -> String {
214 let data = std::mem::take(&mut self.data);
215 self.data.zeroize();
216 data
217 }
218
219 /// Get bytes of the UTF-8 string
220 pub fn as_bytes(&self) -> &[u8] {
221 self.data.as_bytes()
222 }
223 }
224
225 impl Default for SecureString {
226 fn default() -> Self {
227 Self::new()
228 }
229 }
230
231 impl Deref for SecureString {
232 type Target = str;
233
234 fn deref(&self) -> &Self::Target {
235 &self.data
236 }
237 }
238
239 impl AsRef<str> for SecureString {
240 fn as_ref(&self) -> &str {
241 &self.data
242 }
243 }
244
245 impl fmt::Debug for SecureString {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 f.debug_struct("SecureString")
248 .field("len", &self.data.len())
249 .field("data", &"[REDACTED]")
250 .finish()
251 }
252 }
253
254 impl PartialEq for SecureString {
255 fn eq(&self, other: &Self) -> bool {
256 constant_time_eq::constant_time_eq(self.data.as_bytes(), other.data.as_bytes())
257 }
258 }
259
260 impl Eq for SecureString {}
261
262 impl Zeroize for SecureString {
263 fn zeroize(&mut self) {
264 self.data.zeroize();
265 }
266 }
267
268 /// Utility for securely reading passwords from console
269 pub struct SecureInput;
270
271 impl SecureInput {
272 /// Read password from stdin without echoing (requires rpassword crate)
273 #[cfg(feature = "password-input")]
274 pub fn read_password(prompt: &str) -> std::io::Result<SecureString> {
275 use rpassword::read_password_from_tty;
276 print!("{}", prompt);
277 std::io::flush(std::io::stdout())?;
278 let password = read_password_from_tty(None)?;
279 Ok(SecureString::from_string(password))
280 }
281
282 /// Read password from stdin (fallback - echoes input, for testing only)
283 #[cfg(not(feature = "password-input"))]
284 pub fn read_password(prompt: &str) -> std::io::Result<SecureString> {
285 use std::io::{self, Write};
286 print!("{}", prompt);
287 io::stdout().flush()?;
288
289 let mut input = String::new();
290 io::stdin().read_line(&mut input)?;
291
292 // Remove trailing newline
293 if input.ends_with('\n') {
294 input.pop();
295 }
296
297 Ok(SecureString::from_string(input))
298 }
299 }
300
301 /// Helper trait for creating secure versions of sensitive data
302 pub trait IntoSecure {
303 type Output;
304 fn into_secure(self) -> Self::Output;
305 }
306
307 impl IntoSecure for Vec<u8> {
308 type Output = SecureBytes;
309
310 fn into_secure(self) -> Self::Output {
311 SecureBytes::from_vec(self)
312 }
313 }
314
315 impl IntoSecure for String {
316 type Output = SecureString;
317
318 fn into_secure(self) -> Self::Output {
319 SecureString::from_string(self)
320 }
321 }
322
323 impl IntoSecure for &[u8] {
324 type Output = SecureBytes;
325
326 fn into_secure(self) -> Self::Output {
327 SecureBytes::from_slice(self)
328 }
329 }
330
331 impl IntoSecure for &str {
332 type Output = SecureString;
333
334 fn into_secure(self) -> Self::Output {
335 SecureString::from_str(self)
336 }
337 }
338
339 #[cfg(test)]
340 mod tests {
341 use super::*;
342
343 #[test]
344 fn test_secure_bytes_basic_operations() {
345 let mut secure = SecureBytes::new(32);
346 assert_eq!(secure.len(), 32);
347 assert!(!secure.is_empty());
348
349 // Fill with test data
350 for (i, byte) in secure.as_mut().iter_mut().enumerate() {
351 *byte = (i % 256) as u8;
352 }
353
354 // Check data
355 for (i, &byte) in secure.as_bytes().iter().enumerate() {
356 assert_eq!(byte, (i % 256) as u8);
357 }
358
359 // Clear should zero all data
360 secure.clear();
361 for &byte in secure.as_bytes() {
362 assert_eq!(byte, 0);
363 }
364 }
365
366 #[test]
367 fn test_secure_bytes_from_slice() {
368 let data = b"Hello, ZephyrFS!";
369 let secure = SecureBytes::from_slice(data);
370
371 assert_eq!(secure.len(), data.len());
372 assert_eq!(secure.as_bytes(), data);
373 }
374
375 #[test]
376 fn test_secure_bytes_split() {
377 let mut secure = SecureBytes::new(10);
378 for (i, byte) in secure.as_mut().iter_mut().enumerate() {
379 *byte = i as u8;
380 }
381
382 let (left, right) = secure.split_at(4);
383 assert_eq!(left.len(), 4);
384 assert_eq!(right.len(), 6);
385 assert_eq!(left.as_bytes(), &[0, 1, 2, 3]);
386 assert_eq!(right.as_bytes(), &[4, 5, 6, 7, 8, 9]);
387 }
388
389 #[test]
390 fn test_secure_string_operations() {
391 let mut secure = SecureString::from_str("Hello");
392 assert_eq!(secure.len(), 5);
393 assert_eq!(secure.as_str(), "Hello");
394
395 secure.push_str(", World!");
396 assert_eq!(secure.as_str(), "Hello, World!");
397
398 secure.clear();
399 assert!(secure.is_empty());
400 }
401
402 #[test]
403 fn test_secure_memory_equality() {
404 let secure1 = SecureBytes::from_slice(b"test data");
405 let secure2 = SecureBytes::from_slice(b"test data");
406 let secure3 = SecureBytes::from_slice(b"different");
407
408 assert_eq!(secure1, secure2);
409 assert_ne!(secure1, secure3);
410
411 let str1 = SecureString::from_str("password");
412 let str2 = SecureString::from_str("password");
413 let str3 = SecureString::from_str("different");
414
415 assert_eq!(str1, str2);
416 assert_ne!(str1, str3);
417 }
418
419 #[test]
420 fn test_into_secure_trait() {
421 let vec = vec![1, 2, 3, 4, 5];
422 let secure_bytes = vec.into_secure();
423 assert_eq!(secure_bytes.as_bytes(), &[1, 2, 3, 4, 5]);
424
425 let string = "test string".to_string();
426 let secure_string = string.into_secure();
427 assert_eq!(secure_string.as_str(), "test string");
428
429 let slice: &[u8] = &[10, 20, 30];
430 let secure_bytes = slice.into_secure();
431 assert_eq!(secure_bytes.as_bytes(), &[10, 20, 30]);
432
433 let str_ref: &str = "reference string";
434 let secure_string = str_ref.into_secure();
435 assert_eq!(secure_string.as_str(), "reference string");
436 }
437
438 #[test]
439 fn test_debug_redaction() {
440 let secure_bytes = SecureBytes::from_slice(b"secret data");
441 let debug_str = format!("{:?}", secure_bytes);
442 assert!(debug_str.contains("[REDACTED]"));
443 assert!(!debug_str.contains("secret"));
444
445 let secure_string = SecureString::from_str("secret password");
446 let debug_str = format!("{:?}", secure_string);
447 assert!(debug_str.contains("[REDACTED]"));
448 assert!(!debug_str.contains("password"));
449 }
450
451 #[test]
452 fn test_zeroize_on_drop() {
453 let mut data = vec![1, 2, 3, 4, 5];
454 {
455 let _secure = SecureBytes::from_vec(data.clone());
456 // secure goes out of scope here and should zero its memory
457 }
458 // Original data should be zeroed
459 assert_eq!(data, vec![0, 0, 0, 0, 0]);
460 }
461 }