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")]
71unsafe 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")]
89pub(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
95pub(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; }
137
138 if aligned_size % (CRYPTPROTECTMEMORY_BLOCK_SIZE as usize) != 0 {
139 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}