Skip to main content

securer_string/secure_types/
boxed.rs

1use core::fmt;
2use std::borrow::{Borrow, BorrowMut};
3use std::mem::MaybeUninit;
4
5use subtle::ConstantTimeEq;
6use zeroize::Zeroize;
7
8use crate::secure_utils::memlock;
9
10/// A data type suitable for storing sensitive information such as passwords and
11/// private keys in memory, that implements:
12///
13/// - Automatic zeroing in `Drop`
14/// - Constant time comparison in `PartialEq` (does not short circuit on the
15///   first different character; but terminates instantly if strings have
16///   different length)
17/// - Outputting `***SECRET***` to prevent leaking secrets into logs in
18///   `fmt::Debug` and `fmt::Display`
19/// - Automatic `mlock` to protect against leaking into swap (any unix)
20/// - Automatic `madvise(MADV_NOCORE/MADV_DONTDUMP)` to protect against leaking
21///   into core dumps (FreeBSD, DragonflyBSD, Linux)
22pub struct SecureBox<T>
23where
24    T: Copy,
25{
26    // This is an `Option` to avoid UB in the destructor, outside the destructor, it is always
27    // `Some(_)`
28    content: Option<Box<T>>,
29    /// Whether `content` is currently `mlock`ed. If `mlock` failed, `munlock`
30    /// must be skipped.
31    is_locked: bool,
32}
33
34impl<T> SecureBox<T>
35where
36    T: Copy,
37{
38    #[must_use]
39    pub fn new(mut cont: Box<T>) -> Self {
40        let is_locked = memlock::mlock(&raw mut *cont, 1).is_ok();
41        SecureBox {
42            content: Some(cont),
43            is_locked,
44        }
45    }
46
47    /// Borrow the contents of the string.
48    ///
49    /// # Panics
50    ///
51    /// Panics if the content has already been dropped.
52    #[must_use]
53    pub fn unsecure(&self) -> &T {
54        self.content
55            .as_deref()
56            .expect("SecureBox content accessed after drop")
57    }
58
59    /// Mutably borrow the contents of the string.
60    ///
61    /// # Panics
62    ///
63    /// Panics if the content has already been dropped.
64    #[must_use]
65    pub fn unsecure_mut(&mut self) -> &mut T {
66        self.content
67            .as_deref_mut()
68            .expect("SecureBox content accessed after drop")
69    }
70}
71
72impl<T: Copy> Clone for SecureBox<T> {
73    fn clone(&self) -> Self {
74        Self::new(Box::new(*self.unsecure()))
75    }
76}
77
78impl<T: Copy + ConstantTimeEq> ConstantTimeEq for SecureBox<T> {
79    fn ct_eq(&self, other: &Self) -> subtle::Choice {
80        self.unsecure().ct_eq(other.unsecure())
81    }
82}
83
84impl<T: Copy + ConstantTimeEq> PartialEq for SecureBox<T> {
85    fn eq(&self, other: &Self) -> bool {
86        self.ct_eq(other).into()
87    }
88}
89
90impl<T: Copy + ConstantTimeEq> Eq for SecureBox<T> {}
91
92// Delegate indexing
93impl<T, U> std::ops::Index<U> for SecureBox<T>
94where
95    T: std::ops::Index<U> + Copy,
96{
97    type Output = <T as std::ops::Index<U>>::Output;
98
99    fn index(&self, index: U) -> &Self::Output {
100        std::ops::Index::index(self.unsecure(), index)
101    }
102}
103
104// Borrowing
105impl<T> Borrow<T> for SecureBox<T>
106where
107    T: Copy,
108{
109    fn borrow(&self) -> &T {
110        self.unsecure()
111    }
112}
113impl<T> BorrowMut<T> for SecureBox<T>
114where
115    T: Copy,
116{
117    fn borrow_mut(&mut self) -> &mut T {
118        self.unsecure_mut()
119    }
120}
121
122// Overwrite memory with zeros when we're done
123impl<T> Drop for SecureBox<T>
124where
125    T: Copy,
126{
127    fn drop(&mut self) {
128        // Make sure that the box does not need to be dropped after this function,
129        // because it may see an invalid type, if `T` does not support an
130        // all-zero byte-pattern Instead we manually destruct the box and only
131        // handle the potentially invalid values behind the pointer
132        let ptr = Box::into_raw(self.content.take().expect("SecureBox dropped twice"));
133
134        // There is no need to worry about dropping the contents, because `T: Copy` and
135        // `Copy` types cannot implement `Drop`
136
137        // SAFETY: `ptr` was just obtained from `Box::into_raw` so it is valid, aligned,
138        // and points to `size_of::<T>()` allocated bytes. Writing
139        // `MaybeUninit<u8>` zeros is always valid regardless of `T`'s
140        // invariants.
141        unsafe {
142            std::slice::from_raw_parts_mut::<MaybeUninit<u8>>(
143                ptr.cast::<MaybeUninit<u8>>(),
144                std::mem::size_of::<T>(),
145            )
146            .zeroize();
147        }
148
149        if self.is_locked {
150            memlock::munlock(ptr, 1);
151        }
152
153        // Deallocate only non-zero-sized types, because otherwise it's UB
154        if std::mem::size_of::<T>() != 0 {
155            // SAFETY: This way to manually deallocate is advertised in the documentation of
156            // `Box::into_raw`. The box was allocated with the global allocator and a layout
157            // of `T` and is thus deallocated using the same allocator and
158            // layout here.
159            unsafe { std::alloc::dealloc(ptr.cast::<u8>(), std::alloc::Layout::new::<T>()) };
160        }
161    }
162}
163
164// Make sure sensitive information is not logged accidentally
165impl<T> fmt::Debug for SecureBox<T>
166where
167    T: Copy,
168{
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        f.debug_struct("SecureBox").finish_non_exhaustive()
171    }
172}
173
174impl<T> fmt::Display for SecureBox<T>
175where
176    T: Copy,
177{
178    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
179        f.write_str("***SECRET***").map_err(|_| fmt::Error)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use std::mem::MaybeUninit;
186
187    use zeroize::Zeroize;
188
189    use super::SecureBox;
190    use crate::test_utils::{PRIVATE_KEY_1, PRIVATE_KEY_2, Packed, Padded};
191
192    /// Overwrite the contents with zeros.
193    ///
194    /// # Safety
195    /// An all-zero byte-pattern must be a valid value of `T` in order for this
196    /// function call to not be undefined behavior.
197    unsafe fn zero_out_secure_box<T>(secure_box: &mut SecureBox<T>)
198    where
199        T: Copy,
200    {
201        unsafe {
202            // SAFETY: The pointer is derived from a live `Box<T>` via mutable reference, so
203            // it is valid and aligned for `size_of::<T>()` bytes. The caller
204            // guarantees that an all-zero byte-pattern is a valid value of `T`.
205            std::slice::from_raw_parts_mut::<MaybeUninit<u8>>(
206                std::ptr::from_mut::<T>(secure_box.unsecure_mut()).cast::<MaybeUninit<u8>>(),
207                std::mem::size_of::<T>(),
208            )
209            .zeroize();
210        }
211    }
212
213    #[test]
214    fn test_secure_box() {
215        let key_1 = SecureBox::new(Box::new(PRIVATE_KEY_1));
216        let key_2 = SecureBox::new(Box::new(PRIVATE_KEY_2));
217        let key_3 = SecureBox::new(Box::new(PRIVATE_KEY_1));
218        assert_eq!(key_1, key_1);
219        assert_ne!(key_1, key_2);
220        assert_ne!(key_2, key_3);
221        assert_eq!(key_1, key_3);
222
223        let mut final_key = key_1.clone();
224        unsafe {
225            zero_out_secure_box(&mut final_key);
226        }
227        assert_eq!(final_key.unsecure().0, [0; 32]);
228    }
229
230    #[test]
231    fn test_repr_c_with_padding() {
232        assert_eq!(std::mem::size_of::<Padded>(), 4); // 1 + 1 (pad) + 2
233
234        let sec_a = SecureBox::new(Box::new(Padded { x: 1, y: 2 }));
235        let sec_b = SecureBox::new(Box::new(Padded { x: 1, y: 2 }));
236        assert_eq!(sec_a, sec_b);
237
238        let sec_c = SecureBox::new(Box::new(Padded { x: 1, y: 3 }));
239        assert_ne!(sec_a, sec_c);
240
241        let sec_d = SecureBox::new(Box::new(Padded { x: 2, y: 2 }));
242        assert_ne!(sec_a, sec_d);
243    }
244
245    #[test]
246    fn test_repr_c_packed() {
247        assert_eq!(std::mem::size_of::<Packed>(), 3);
248
249        let sec_a = SecureBox::new(Box::new(Packed { x: 42, y: 1000 }));
250        let sec_b = SecureBox::new(Box::new(Packed { x: 42, y: 1000 }));
251        let sec_c = SecureBox::new(Box::new(Packed { x: 42, y: 1001 }));
252        let sec_d = SecureBox::new(Box::new(Packed { x: 43, y: 1000 }));
253
254        assert_eq!(sec_a, sec_b);
255        assert_ne!(sec_a, sec_c);
256        assert_ne!(sec_a, sec_d);
257    }
258}