1#[cfg(not(feature = "use_os"))]
2use alloc::{Layout, alloc, dealloc};
3
4use super::{Error, SecureVec};
5use core::{marker::PhantomData, mem, ptr::NonNull};
6use zeroize::Zeroize;
7
8#[cfg(feature = "use_os")]
9use super::{alloc, free};
10#[cfg(feature = "use_os")]
11use memsec::Prot;
12
13struct UnlockGuard<'a, T: Zeroize, const LENGTH: usize> {
16 array: &'a SecureArray<T, LENGTH>,
17}
18
19impl<'a, T: Zeroize, const LENGTH: usize> UnlockGuard<'a, T, LENGTH> {
20 fn new(array: &'a SecureArray<T, LENGTH>) -> Self {
21 let ok = array.unlock_memory();
22 debug_assert!(ok, "UnlockGuard::new: unlock_memory failed");
23 UnlockGuard { array }
24 }
25}
26
27impl<'a, T: Zeroize, const LENGTH: usize> Drop for UnlockGuard<'a, T, LENGTH> {
28 fn drop(&mut self) {
29 let ok = self.array.lock_memory();
30 debug_assert!(ok, "UnlockGuard::drop: lock_memory failed");
31 }
32}
33
34pub struct SecureArray<T, const LENGTH: usize>
78where
79 T: Zeroize,
80{
81 ptr: NonNull<T>,
82 _marker: PhantomData<T>,
83}
84
85unsafe impl<T: Zeroize + Send, const LENGTH: usize> Send for SecureArray<T, LENGTH> {}
86unsafe impl<T: Zeroize + Send + Sync, const LENGTH: usize> Sync for SecureArray<T, LENGTH> {}
87
88impl<T, const LENGTH: usize> SecureArray<T, LENGTH>
89where
90 T: Zeroize,
91{
92 pub fn empty() -> Result<Self, Error> {
96 let size = LENGTH * mem::size_of::<T>();
97 if size == 0 {
98 return Err(Error::LengthCannotBeZero);
100 }
101
102 let ptr = unsafe { alloc::<T>(size)? };
103
104 let secure_array = SecureArray {
105 ptr,
106 _marker: PhantomData,
107 };
108
109 let locked = secure_array.lock_memory();
110
111 #[cfg(feature = "use_os")]
112 if !locked {
113 return Err(Error::LockFailed);
114 }
115
116 Ok(secure_array)
117 }
118
119 pub fn from_slice_mut(content: &mut [T; LENGTH]) -> Result<Self, Error> {
123 let secure_array = match Self::empty() {
124 Ok(secure_array) => secure_array,
125 Err(e) => {
126 content.zeroize();
127 return Err(e);
128 }
129 };
130
131 let unlocked = secure_array.unlock_memory();
132
133 if !unlocked {
134 content.zeroize();
135 return Err(Error::UnlockFailed);
136 }
137
138 unsafe {
139 core::ptr::copy_nonoverlapping(
141 content.as_ptr(),
142 secure_array.ptr.as_ptr(),
143 LENGTH,
144 );
145 }
146
147 content.zeroize();
148
149 let locked = secure_array.lock_memory();
150
151 #[cfg(feature = "use_os")]
152 if !locked {
153 return Err(Error::LockFailed);
154 }
155
156 Ok(secure_array)
157 }
158
159 pub fn from_slice(content: &[T; LENGTH]) -> Result<Self, Error> {
163 let secure_array = Self::empty()?;
164
165 let unlocked = secure_array.unlock_memory();
166
167 if !unlocked {
168 return Err(Error::UnlockFailed);
169 }
170
171 unsafe {
172 core::ptr::copy_nonoverlapping(
174 content.as_ptr(),
175 secure_array.ptr.as_ptr(),
176 LENGTH,
177 );
178 }
179
180 let locked = secure_array.lock_memory();
181
182 #[cfg(feature = "use_os")]
183 if !locked {
184 return Err(Error::LockFailed);
185 }
186
187 Ok(secure_array)
188 }
189
190 pub fn len(&self) -> usize {
191 LENGTH
192 }
193
194 pub fn is_empty(&self) -> bool {
195 self.len() == 0
196 }
197
198 pub(crate) fn lock_memory(&self) -> bool {
199 #[cfg(feature = "use_os")]
200 {
201 #[cfg(windows)]
202 {
203 super::mprotect(self.ptr, Prot::NoAccess)
204 }
205 #[cfg(unix)]
206 {
207 super::mprotect(self.ptr, Prot::NoAccess)
208 }
209 }
210 #[cfg(not(feature = "use_os"))]
211 {
212 true }
214 }
215
216 pub(crate) fn unlock_memory(&self) -> bool {
217 #[cfg(feature = "use_os")]
218 {
219 #[cfg(windows)]
220 {
221 super::mprotect(self.ptr, Prot::ReadWrite)
222 }
223 #[cfg(unix)]
224 {
225 super::mprotect(self.ptr, Prot::ReadWrite)
226 }
227 }
228
229 #[cfg(not(feature = "use_os"))]
230 {
231 true }
233 }
234
235 pub fn unlock<F, R>(&self, f: F) -> R
237 where
238 F: FnOnce(&[T]) -> R,
239 {
240 let _guard = UnlockGuard::new(self);
241 let slice = unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), LENGTH) };
242 let result = f(slice);
243 result
244 }
245
246 pub fn unlock_mut<F, R>(&mut self, f: F) -> R
248 where
249 F: FnOnce(&mut [T]) -> R,
250 {
251 let _guard = UnlockGuard::new(self);
252 let slice = unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), LENGTH) };
253 let result = f(slice);
254 result
255 }
256
257 pub fn erase(&mut self) {
259 self.unlock_mut(|slice| {
260 for element in slice.iter_mut() {
261 element.zeroize();
262 }
263 });
264 }
265
266 pub(crate) fn init_from_clone(&mut self, src: &[T])
269 where
270 T: Clone,
271 {
272 debug_assert_eq!(src.len(), LENGTH);
273
274 let ok = self.unlock_memory();
275 debug_assert!(
276 ok,
277 "SecureArray::init_from_clone: unlock_memory failed"
278 );
279
280 unsafe {
281 let dst = self.ptr.as_ptr();
282 for (i, item) in src.iter().enumerate() {
283 core::ptr::write(dst.add(i), item.clone());
284 }
285 }
286 let ok = self.lock_memory();
287 debug_assert!(
288 ok,
289 "SecureArray::init_from_clone: lock_memory failed"
290 );
291 }
292}
293
294impl<T: Zeroize, const LENGTH: usize> core::ops::Index<usize> for SecureArray<T, LENGTH> {
295 type Output = T;
296 fn index(&self, index: usize) -> &Self::Output {
297 assert!(index < self.len(), "Index out of bounds");
298 unsafe {
299 let ptr = self.ptr.as_ptr().add(index);
300 &*ptr
301 }
302 }
303}
304
305impl<T: Zeroize, const LENGTH: usize> core::ops::IndexMut<usize> for SecureArray<T, LENGTH> {
306 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
307 assert!(index < self.len(), "Index out of bounds");
308 unsafe {
309 let ptr = self.ptr.as_ptr().add(index);
310 &mut *ptr
311 }
312 }
313}
314
315impl<T: Zeroize, const LENGTH: usize> Drop for SecureArray<T, LENGTH> {
316 fn drop(&mut self) {
317 self.erase();
318 let ok = self.unlock_memory();
319 debug_assert!(ok, "SecureArray::drop: unlock_memory failed");
320
321 let size = LENGTH * mem::size_of::<T>();
322 if size == 0 {
323 return;
324 }
325
326 #[cfg(feature = "use_os")]
327 free(self.ptr);
328
329 #[cfg(not(feature = "use_os"))]
330 {
331 let layout = Layout::from_size_align_unchecked(size, mem::align_of::<T>());
332 dealloc(self.ptr.as_ptr() as *mut u8, layout);
333 }
334 }
335}
336
337impl<T: Clone + Zeroize, const LENGTH: usize> Clone for SecureArray<T, LENGTH> {
338 fn clone(&self) -> Self {
339 let mut new_array = Self::empty().unwrap();
340 self.unlock(|src_slice| {
341 new_array.init_from_clone(src_slice);
342 });
343 new_array
344 }
345}
346
347impl<const LENGTH: usize> TryFrom<SecureVec<u8>> for SecureArray<u8, LENGTH> {
348 type Error = Error;
349
350 fn try_from(vec: SecureVec<u8>) -> Result<Self, Self::Error> {
356 if vec.len() != LENGTH {
357 return Err(Error::LengthMismatch);
358 }
359
360 let mut new_array = Self::empty()?;
361
362 vec.unlock_slice(|vec_slice| {
363 new_array.init_from_clone(vec_slice);
364 });
365
366 Ok(new_array)
367 }
368}
369
370#[cfg(feature = "serde")]
371impl<const LENGTH: usize> serde::Serialize for SecureArray<u8, LENGTH> {
372 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
373 where
374 S: serde::Serializer,
375 {
376 self.unlock(|slice| serializer.collect_seq(slice.iter()))
377 }
378}
379
380#[cfg(feature = "serde")]
381impl<'de, const LENGTH: usize> serde::Deserialize<'de> for SecureArray<u8, LENGTH> {
382 fn deserialize<D>(deserializer: D) -> Result<SecureArray<u8, LENGTH>, D::Error>
383 where
384 D: serde::Deserializer<'de>,
385 {
386 struct SecureArrayVisitor<const L: usize>;
387
388 impl<'de, const L: usize> serde::de::Visitor<'de> for SecureArrayVisitor<L> {
389 type Value = SecureArray<u8, L>;
390
391 fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
392 write!(formatter, "a byte array of length {}", L)
393 }
394
395 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
396 where
397 A: serde::de::SeqAccess<'de>,
398 {
399 let mut data: SecureVec<u8> =
400 SecureVec::new_with_capacity(L).map_err(serde::de::Error::custom)?;
401 while let Some(byte) = seq.next_element()? {
402 data.push(byte);
403 }
404
405 if data.len() != L {
407 return Err(serde::de::Error::invalid_length(
408 data.len(),
409 &self,
410 ));
411 }
412
413 SecureArray::try_from(data).map_err(serde::de::Error::custom)
414 }
415 }
416
417 deserializer.deserialize_bytes(SecureArrayVisitor::<LENGTH>)
418 }
419}
420
421#[cfg(all(test, feature = "use_os"))]
422mod tests {
423 use super::*;
424 use std::process::{Command, Stdio};
425 use std::sync::{Arc, Mutex};
426
427 #[test]
428 fn test_creation() {
429 let exposed_mut = &mut [1, 2, 3];
430 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed_mut).unwrap();
431 assert_eq!(array.len(), 3);
432
433 array.unlock(|slice| {
434 assert_eq!(slice, &[1, 2, 3]);
435 });
436
437 assert_eq!(exposed_mut, &[0u8; 3]);
438
439 let exposed = &[1, 2, 3];
440 let array: SecureArray<u8, 3> = SecureArray::from_slice(exposed).unwrap();
441 assert_eq!(array.len(), 3);
442
443 array.unlock(|slice| {
444 assert_eq!(slice, &[1, 2, 3]);
445 });
446
447 assert_eq!(exposed, &[1, 2, 3]);
448 }
449
450 #[test]
451 fn test_from_secure_vec() {
452 let vec: SecureVec<u8> = SecureVec::from_slice(&[1, 2, 3]).unwrap();
453 let array: SecureArray<u8, 3> = vec.try_into().unwrap();
454 assert_eq!(array.len(), 3);
455 array.unlock(|slice| {
456 assert_eq!(slice, &[1, 2, 3]);
457 });
458 }
459
460 #[test]
461 fn test_erase() {
462 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
463 let mut array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
464 array.erase();
465 array.unlock(|slice| {
466 assert_eq!(slice, &[0u8; 3]);
467 });
468 }
469
470 #[test]
471 #[should_panic]
472 fn test_length_cannot_be_zero() {
473 let secure_vec = SecureVec::new().unwrap();
474 let _secure_array: SecureArray<u8, 0> = SecureArray::try_from(secure_vec).unwrap();
475 }
476
477 #[test]
478 fn lock_unlock() {
479 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
480 let secure: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
481
482 let unlocked = secure.unlock_memory();
483 assert!(unlocked);
484
485 let locked = secure.lock_memory();
486 assert!(locked);
487 }
488
489 #[test]
490 fn test_clone() {
491 let mut array1: SecureArray<u8, 3> = SecureArray::empty().unwrap();
492 array1.unlock_mut(|slice| {
493 slice[0] = 1;
494 slice[1] = 2;
495 slice[2] = 3;
496 });
497
498 let array2 = array1.clone();
499
500 array2.unlock(|slice| {
501 assert_eq!(slice, &[1, 2, 3]);
502 });
503
504 array1.unlock(|slice| {
505 assert_eq!(slice, &[1, 2, 3]);
506 });
507 }
508
509 #[test]
510 fn test_thread_safety() {
511 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
512 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
513 let arc_array = Arc::new(Mutex::new(array));
514 let mut handles = Vec::new();
515
516 for _ in 0..5u8 {
517 let array_clone = Arc::clone(&arc_array);
518 let handle = std::thread::spawn(move || {
519 let mut guard = array_clone.lock().unwrap();
520 guard.unlock_mut(|slice| {
521 slice[0] += 1;
522 });
523 });
524 handles.push(handle);
525 }
526
527 for handle in handles {
528 handle.join().unwrap();
529 }
530
531 let final_array = arc_array.lock().unwrap();
532 final_array.unlock(|slice| {
533 assert_eq!(slice[0], 6);
534 assert_eq!(slice[1], 2);
535 assert_eq!(slice[2], 3);
536 });
537 }
538
539 #[test]
540 fn test_index_should_fail_when_locked() {
541 let arg = "CRASH_TEST_ARRAY_LOCKED";
542
543 if std::env::args().any(|a| a == arg) {
544 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
545 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
546 let _value = core::hint::black_box(array[0]);
547
548 std::process::exit(1);
549 }
550
551 let child = Command::new(std::env::current_exe().unwrap())
552 .arg("array::tests::test_index_should_fail_when_locked")
553 .arg(arg)
554 .arg("--nocapture")
555 .stdout(Stdio::piped())
556 .stderr(Stdio::piped())
557 .spawn()
558 .expect("Failed to spawn child process");
559
560 let output = child.wait_with_output().expect("Failed to wait on child");
561 let status = output.status;
562
563 assert!(
564 !status.success(),
565 "Process exited successfully with code {:?}, but it should have crashed.",
566 status.code()
567 );
568
569 #[cfg(unix)]
570 {
571 use std::os::unix::process::ExitStatusExt;
572 let signal = status
573 .signal()
574 .expect("Process was not terminated by a signal on Unix.");
575 assert!(
576 signal == libc::SIGSEGV || signal == libc::SIGBUS,
577 "Process terminated with unexpected signal: {}",
578 signal
579 );
580 println!(
581 "Test passed: Process correctly terminated with signal {}.",
582 signal
583 );
584 }
585
586 #[cfg(windows)]
587 {
588 const STATUS_ACCESS_VIOLATION: i32 = 0xC0000005_u32 as i32;
589 assert_eq!(
590 status.code(),
591 Some(STATUS_ACCESS_VIOLATION),
592 "Process exited with unexpected code: {:x?}. Expected STATUS_ACCESS_VIOLATION.",
593 status.code()
594 );
595 eprintln!("Test passed: Process correctly terminated with STATUS_ACCESS_VIOLATION.");
596 }
597 }
598
599 #[test]
600 fn test_unlock_mut() {
601 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
602 let mut array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
603
604 array.unlock_mut(|slice| {
605 slice[1] = 100;
606 });
607
608 array.unlock(|slice| {
609 assert_eq!(slice, &[1, 100, 3]);
610 });
611 }
612
613 #[cfg(feature = "serde")]
614 #[test]
615 fn test_serde() {
616 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
617 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
618 let json_string = serde_json::to_string(&array).expect("Serialization failed");
619 let json_bytes = serde_json::to_vec(&array).expect("Serialization failed");
620
621 let deserialized_string: SecureArray<u8, 3> =
622 serde_json::from_str(&json_string).expect("Deserialization failed");
623
624 let deserialized_bytes: SecureArray<u8, 3> =
625 serde_json::from_slice(&json_bytes).expect("Deserialization failed");
626
627 deserialized_string.unlock(|slice| {
628 assert_eq!(slice, &[1, 2, 3]);
629 });
630
631 deserialized_bytes.unlock(|slice| {
632 assert_eq!(slice, &[1, 2, 3]);
633 });
634 }
635}