secure_types/
lib.rs

1#![doc = include_str!("../readme.md")]
2#![cfg_attr(feature = "no_os", no_os)]
3
4#[cfg(feature = "no_os")]
5extern crate alloc;
6
7pub mod array;
8pub mod string;
9pub mod vec;
10
11pub use array::SecureArray;
12pub use string::SecureString;
13pub use vec::{SecureBytes, SecureVec};
14
15use core::ptr::NonNull;
16pub use zeroize::Zeroize;
17
18#[cfg(feature = "use_os")]
19pub use memsec;
20#[cfg(feature = "use_os")]
21use memsec::Prot;
22#[cfg(feature = "use_os")]
23use std::sync::Once;
24
25use thiserror::Error as ThisError;
26
27#[cfg(feature = "use_os")]
28#[derive(ThisError, Debug)]
29#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
30pub enum Error {
31   #[error("Failed to allocate secure memory")]
32   AllocationFailed,
33   #[error("Length cannot be zero")]
34   LengthCannotBeZero,
35   #[error("Allocated Ptr is null")]
36   NullAllocation,
37   #[error("CryptProtectMemory failed")]
38   CryptProtectMemoryFailed,
39   #[error("CryptUnprotectMemory failed")]
40   CryptUnprotectMemoryFailed,
41   #[error("Failed to lock memory")]
42   LockFailed,
43   #[error("Failed to unlock memory")]
44   UnlockFailed,
45   #[error("Source length does not match the fixed size of the destination array")]
46   LengthMismatch,
47}
48
49#[cfg(not(feature = "use_os"))]
50#[derive(Debug)]
51pub enum Error {
52   AlignmentFailed,
53   AllocationFailed,
54   NullAllocation,
55}
56
57#[cfg(all(feature = "use_os", test, windows))]
58use windows_sys::Win32::Foundation::GetLastError;
59#[cfg(all(feature = "use_os", windows))]
60use windows_sys::Win32::Security::Cryptography::{
61   CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptProtectMemory,
62   CryptUnprotectMemory,
63};
64#[cfg(all(feature = "use_os", windows))]
65use windows_sys::Win32::System::SystemInformation::GetSystemInfo;
66
67static PAGE_SIZE_INIT: Once = Once::new();
68static mut PAGE_SIZE: usize = 0;
69
70#[cfg(feature = "use_os")]
71/// Returns the page size depending on the OS
72unsafe fn page_size_init() {
73   #[cfg(unix)]
74   unsafe {
75      PAGE_SIZE = libc::sysconf(libc::_SC_PAGESIZE) as usize;
76   }
77
78   #[cfg(windows)]
79   {
80      let mut si = core::mem::MaybeUninit::uninit();
81      unsafe {
82         GetSystemInfo(si.as_mut_ptr());
83         PAGE_SIZE = (*si.as_ptr()).dwPageSize as usize;
84      }
85   }
86}
87
88#[cfg(feature = "use_os")]
89/// Returns the page aligned size of a given size
90pub(crate) unsafe fn page_aligned_size(size: usize) -> usize {
91   PAGE_SIZE_INIT.call_once(|| unsafe { page_size_init() });
92   unsafe { (size + PAGE_SIZE - 1) & !(PAGE_SIZE - 1) }
93}
94
95/// Allocate memory
96///
97/// Size is page aligned if the target is an OS
98pub(crate) fn alloc_aligned<T>(size: usize) -> Result<NonNull<T>, Error> {
99   #[cfg(feature = "use_os")]
100   unsafe {
101      let aligned_size = page_aligned_size(size);
102      let allocated_ptr = memsec::malloc_sized(aligned_size);
103      let non_null = allocated_ptr.ok_or(Error::AllocationFailed)?;
104      let ptr = non_null.as_ptr() as *mut T;
105      NonNull::new(ptr).ok_or(Error::NullAllocation)
106   }
107
108   #[cfg(not(feature = "use_os"))]
109   {
110      let layout =
111         Layout::from_size_align(size, mem::align_of::<T>()).map_err(|_| Error::AlignmentFailed)?;
112      let ptr = unsafe { alloc::alloc(layout) as *mut T };
113      if ptr.is_null() {
114         return Err(Error::NullAllocation);
115      }
116      ptr
117   }
118}
119
120#[cfg(feature = "use_os")]
121pub(crate) fn mprotect<T>(ptr: NonNull<T>, prot: Prot::Ty) -> bool {
122   let success = unsafe { memsec::mprotect(ptr, prot) };
123   #[cfg(test)]
124   {
125      if !success {
126         eprintln!("mprotect failed");
127      }
128   }
129   success
130}
131
132#[cfg(all(feature = "use_os", windows))]
133pub(crate) fn crypt_protect_memory(ptr: *mut u8, aligned_size: usize) -> bool {
134   if aligned_size == 0 {
135      return true; // Nothing to encrypt
136   }
137
138   if aligned_size % (CRYPTPROTECTMEMORY_BLOCK_SIZE as usize) != 0 {
139      // not a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE
140      return false;
141   }
142
143   if aligned_size > u32::MAX as usize {
144      return false;
145   }
146
147   let result = unsafe {
148      CryptProtectMemory(
149         ptr as *mut core::ffi::c_void,
150         aligned_size as u32,
151         CRYPTPROTECTMEMORY_SAME_PROCESS,
152      )
153   };
154
155   if result == 0 {
156      #[cfg(test)]
157      {
158         let error_code = unsafe { GetLastError() };
159         eprintln!(
160            "CryptProtectMemory failed with error code: {}",
161            error_code
162         );
163      }
164      false
165   } else {
166      true
167   }
168}
169
170#[cfg(all(feature = "use_os", windows))]
171pub(crate) fn crypt_unprotect_memory(ptr: *mut u8, size_in_bytes: usize) -> bool {
172   if size_in_bytes == 0 {
173      return true;
174   }
175
176   if size_in_bytes % (CRYPTPROTECTMEMORY_BLOCK_SIZE as usize) != 0 {
177      return false;
178   }
179
180   if size_in_bytes > u32::MAX as usize {
181      return false;
182   }
183
184   let result = unsafe {
185      CryptUnprotectMemory(
186         ptr as *mut core::ffi::c_void,
187         size_in_bytes as u32,
188         CRYPTPROTECTMEMORY_SAME_PROCESS,
189      )
190   };
191
192   if result == 0 {
193      #[cfg(test)]
194      {
195         let error_code = unsafe { GetLastError() };
196         eprintln!(
197            "CryptUnprotectMemory failed with error code: {}",
198            error_code
199         );
200      }
201      false
202   } else {
203      true
204   }
205}
206
207#[cfg(test)]
208mod tests {
209   #[cfg(feature = "serde")]
210   #[test]
211   fn test_array_and_secure_vec_serde_compatibility() {
212      use super::*;
213      let exposed_array: &mut [u8; 3] = &mut [1, 2, 3];
214      let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed_array).unwrap();
215      let vec: SecureVec<u8> = array.clone().into();
216
217      let array_json_string = serde_json::to_string(&array).unwrap();
218      let array_json_bytes = serde_json::to_vec(&array).unwrap();
219      let vec_json_string = serde_json::to_string(&vec).unwrap();
220      let vec_json_bytes = serde_json::to_vec(&vec).unwrap();
221
222      assert_eq!(array_json_string, vec_json_string);
223      assert_eq!(array_json_bytes, vec_json_bytes);
224
225      let deserialized_array_from_string: SecureArray<u8, 3> =
226         serde_json::from_str(&array_json_string).unwrap();
227
228      let deserialized_array_from_bytes: SecureArray<u8, 3> =
229         serde_json::from_slice(&array_json_bytes).unwrap();
230
231      let deserialized_vec_from_string: SecureVec<u8> =
232         serde_json::from_str(&vec_json_string).unwrap();
233
234      let deserialized_vec_from_bytes: SecureVec<u8> =
235         serde_json::from_slice(&vec_json_bytes).unwrap();
236
237      deserialized_array_from_string.unlock(|slice| {
238         deserialized_vec_from_string.unlock_slice(|slice2| {
239            assert_eq!(slice, slice2);
240         });
241      });
242
243      deserialized_array_from_bytes.unlock(|slice| {
244         deserialized_vec_from_bytes.unlock_slice(|slice2| {
245            assert_eq!(slice, slice2);
246         });
247      });
248   }
249}