secure_types/
lib.rs

1//! # Secure Types
2//!
3//! This crate provides heap-allocated data structures (`SecureVec`, `SecureArray`, `SecureString`)
4//! designed to handle sensitive information in memory with enhanced security.
5//!
6//! ## Core Security Guarantees
7//!
8//! The primary goal is to protect secret data (like passwords, private keys, or credentials)
9//! from being exposed through common vulnerabilities.
10//!
11//! 1.  **Zeroization on Drop**: All secure types implement the `Zeroize` trait, ensuring their
12//!     memory is securely overwritten with zeros when they are dropped. This prevents stale
13//!     data from being recoverable in deallocated memory.
14//!
15//! 2.  **Memory Locking (`std` only)**: When compiled with the `std` feature (the default),
16//!     the crate uses OS-level primitives to lock memory pages, preventing them from being
17//!     swapped to disk.
18//!     - On Windows: `VirtualLock` and `VirtualProtect`.
19//!     - On Unix: `mlock` and `mprotect`.
20//!
21//! 3.  **Memory Encryption (`std` on Windows only)**: On Windows, memory is also encrypted
22//!     in place using `CryptProtectMemory`, providing an additional layer of protection
23//!     against memory inspection.
24//!
25//! 4.  **Scoped Access**: Data is protected by default. To access it, you must use scoped
26//!     methods like `.unlocked_scope(|slice| { ... })`, which temporarily makes the data
27//!     accessible and automatically re-locks it afterward.
28//!
29//! ## Usage Example
30//!
31//! Here's a quick example of how to use `SecureString`:
32//!
33//! ```rust
34//! use secure_types::SecureString;
35//!
36//! // Create a string from a sensitive literal.
37//! // The original data is securely zeroized after being copied.
38//! let mut secret = SecureString::from("my_super_secret_password");
39//!
40//! // The memory is locked and protected here. Direct access is not possible.
41//!
42//! // Use a scope to safely access the content as a &str.
43//! secret.str_scope(|unlocked_str| {
44//!     assert_eq!(unlocked_str, "my_super_secret_password");
45//!     println!("The secret is: {}", unlocked_str);
46//! });
47//!
48//! // The memory is automatically locked again when the scope ends.
49//!
50//! // When `secret` goes out of scope, its memory will be securely zeroized.
51//! ```
52//!
53//! ## Feature Flags
54//!
55//! - `std` (default): Enables all OS-level security features like memory locking and encryption.
56//! - `serde`: Enables serialization and deserialization for `SecureString` and `SecureBytes` via the Serde framework.
57//! - `no_std`: Compiles the crate in a `no_std` environment. In this mode, only the **Zeroize on Drop**
58//!   guarantee is provided. This is useful for embedded systems or WebAssembly.
59
60#![cfg_attr(feature = "no_std", no_std)]
61
62#[cfg(feature = "no_std")]
63extern crate alloc;
64
65pub mod array;
66pub mod string;
67pub mod vec;
68
69pub use array::SecureArray;
70pub use string::SecureString;
71pub use vec::{SecureBytes, SecureVec};
72
73use core::ptr::NonNull;
74pub use zeroize::Zeroize;
75
76#[cfg(feature = "std")]
77pub use memsec;
78#[cfg(feature = "std")]
79use memsec::Prot;
80
81use thiserror::Error as ThisError;
82
83#[cfg(feature = "std")]
84#[derive(ThisError, Debug)]
85#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
86pub enum Error {
87   #[error("Failed to allocate secure memory")]
88   AllocationFailed,
89   #[error("Allocated Ptr is null")]
90   NullAllocation,
91   #[error("CryptProtectMemory failed")]
92   CryptProtectMemoryFailed,
93   #[error("CryptUnprotectMemory failed")]
94   CryptUnprotectMemoryFailed,
95   #[error("Failed to lock memory")]
96   LockFailed,
97   #[error("Failed to unlock memory")]
98   UnlockFailed,
99   #[error("Source length does not match the fixed size of the destination array")]
100   LengthMismatch,
101}
102
103#[cfg(not(feature = "std"))]
104#[derive(Debug)]
105pub enum Error {
106   AllocationFailed,
107   NullAllocation,
108}
109
110#[cfg(all(feature = "std", test, windows))]
111use windows_sys::Win32::Foundation::GetLastError;
112#[cfg(all(feature = "std", windows))]
113use windows_sys::Win32::Security::Cryptography::{
114   CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptProtectMemory,
115   CryptUnprotectMemory,
116};
117#[cfg(all(feature = "std", windows))]
118use windows_sys::Win32::System::SystemInformation::GetSystemInfo;
119
120#[cfg(feature = "std")]
121pub fn page_size() -> usize {
122   #[cfg(unix)]
123   {
124      unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
125   }
126
127   #[cfg(windows)]
128   {
129      let mut si = core::mem::MaybeUninit::uninit();
130      unsafe {
131         GetSystemInfo(si.as_mut_ptr());
132         (*si.as_ptr()).dwPageSize as usize
133      }
134   }
135}
136
137#[cfg(feature = "std")]
138pub fn mprotect<T>(ptr: NonNull<T>, prot: Prot::Ty) -> bool {
139   let success = unsafe { memsec::mprotect(ptr, prot) };
140   if !success {
141      #[cfg(test)]
142      eprintln!("mprotect failed");
143   }
144   success
145}
146
147#[cfg(all(feature = "std", windows))]
148pub fn crypt_protect_memory(ptr: *mut u8, size_in_bytes: usize) -> bool {
149   if size_in_bytes == 0 {
150      return true; // Nothing to encrypt
151   }
152
153   if size_in_bytes % (CRYPTPROTECTMEMORY_BLOCK_SIZE as usize) != 0 {
154      // not a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE
155      return false;
156   }
157
158   if size_in_bytes > u32::MAX as usize {
159      return false;
160   }
161
162   let result = unsafe {
163      CryptProtectMemory(
164         ptr as *mut core::ffi::c_void,
165         size_in_bytes as u32,
166         CRYPTPROTECTMEMORY_SAME_PROCESS,
167      )
168   };
169
170   if result == 0 {
171      #[cfg(test)]
172      {
173         let error_code = unsafe { GetLastError() };
174         eprintln!(
175            "CryptProtectMemory failed with error code: {}",
176            error_code
177         );
178      }
179      return false;
180   } else {
181      true
182   }
183}
184
185#[cfg(all(feature = "std", windows))]
186pub fn crypt_unprotect_memory(ptr: *mut u8, size_in_bytes: usize) -> bool {
187   if size_in_bytes == 0 {
188      return true;
189   }
190
191   if size_in_bytes % (CRYPTPROTECTMEMORY_BLOCK_SIZE as usize) != 0 {
192      return false;
193   }
194
195   if size_in_bytes > u32::MAX as usize {
196      return false;
197   }
198
199   let result = unsafe {
200      CryptUnprotectMemory(
201         ptr as *mut core::ffi::c_void,
202         size_in_bytes as u32,
203         CRYPTPROTECTMEMORY_SAME_PROCESS,
204      )
205   };
206
207   if result == 0 {
208      #[cfg(test)]
209      {
210         let error_code = unsafe { GetLastError() };
211         eprintln!(
212            "CryptUnprotectMemory failed with error code: {}",
213            error_code
214         );
215      }
216      return false;
217   } else {
218      true
219   }
220}