secure_string/secure_types/
boxed.rs

1use core::fmt;
2use std::{
3    borrow::{Borrow, BorrowMut},
4    mem::MaybeUninit,
5};
6
7use zeroize::Zeroize;
8
9use crate::secure_utils::memlock;
10
11/// A data type suitable for storing sensitive information such as passwords and private keys in memory, that implements:
12///
13/// - Automatic zeroing in `Drop`
14/// - Constant time comparison in `PartialEq` (does not short circuit on the first different character; but terminates instantly if strings have different length)
15/// - Outputting `***SECRET***` to prevent leaking secrets into logs in `fmt::Debug` and `fmt::Display`
16/// - Automatic `mlock` to protect against leaking into swap (any unix)
17/// - Automatic `madvise(MADV_NOCORE/MADV_DONTDUMP)` to protect against leaking into core dumps (FreeBSD, DragonflyBSD, Linux)
18///
19/// Comparisons using the `PartialEq` implementation are undefined behavior (and most likely wrong) if `T` has any padding bytes.
20#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
21pub struct SecureBox<T>
22where
23    T: Copy,
24{
25    // This is an `Option` to avoid UB in the destructor, outside the destructor, it is always
26    // `Some(_)`
27    content: Option<Box<T>>,
28}
29
30impl<T> SecureBox<T>
31where
32    T: Copy,
33{
34    pub fn new(mut cont: Box<T>) -> Self {
35        memlock::mlock(&mut cont, 1);
36        SecureBox { content: Some(cont) }
37    }
38
39    /// Borrow the contents of the string.
40    pub fn unsecure(&self) -> &T {
41        self.content.as_ref().unwrap()
42    }
43
44    /// Mutably borrow the contents of the string.
45    pub fn unsecure_mut(&mut self) -> &mut T {
46        self.content.as_mut().unwrap()
47    }
48}
49
50impl<T: Copy> Clone for SecureBox<T> {
51    fn clone(&self) -> Self {
52        Self::new(self.content.clone().unwrap())
53    }
54}
55
56// Delegate indexing
57impl<T, U> std::ops::Index<U> for SecureBox<T>
58where
59    T: std::ops::Index<U> + Copy,
60{
61    type Output = <T as std::ops::Index<U>>::Output;
62
63    fn index(&self, index: U) -> &Self::Output {
64        std::ops::Index::index(self.content.as_ref().unwrap().as_ref(), index)
65    }
66}
67
68// Borrowing
69impl<T> Borrow<T> for SecureBox<T>
70where
71    T: Copy,
72{
73    fn borrow(&self) -> &T {
74        self.content.as_ref().unwrap()
75    }
76}
77impl<T> BorrowMut<T> for SecureBox<T>
78where
79    T: Copy,
80{
81    fn borrow_mut(&mut self) -> &mut T {
82        self.content.as_mut().unwrap()
83    }
84}
85
86// Overwrite memory with zeros when we're done
87impl<T> Drop for SecureBox<T>
88where
89    T: Copy,
90{
91    #[cfg_attr(feature = "pre", pre::pre)]
92    fn drop(&mut self) {
93        // Make sure that the box does not need to be dropped after this function, because it may
94        // see an invalid type, if `T` does not support an all-zero byte-pattern
95        // Instead we manually destruct the box and only handle the potentially invalid values
96        // behind the pointer
97        let ptr = Box::into_raw(self.content.take().unwrap());
98
99        // There is no need to worry about dropping the contents, because `T: Copy` and `Copy`
100        // types cannot implement `Drop`
101
102        unsafe {
103            std::slice::from_raw_parts_mut::<MaybeUninit<u8>>(ptr as *mut MaybeUninit<u8>, std::mem::size_of::<T>()).zeroize();
104        }
105
106        memlock::munlock(ptr, 1);
107
108        // Deallocate only non-zero-sized types, because otherwise it's UB
109        if std::mem::size_of::<T>() != 0 {
110            // Safety:
111            // This way to manually deallocate is advertised in the documentation of `Box::into_raw`.
112            // The box was allocated with the global allocator and a layout of `T` and is thus
113            // deallocated using the same allocator and layout here.
114            unsafe { std::alloc::dealloc(ptr as *mut u8, std::alloc::Layout::new::<T>()) };
115        }
116    }
117}
118
119// Make sure sensitive information is not logged accidentally
120impl<T> fmt::Debug for SecureBox<T>
121where
122    T: Copy,
123{
124    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125        f.write_str("***SECRET***").map_err(|_| fmt::Error)
126    }
127}
128
129impl<T> fmt::Display for SecureBox<T>
130where
131    T: Copy,
132{
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        f.write_str("***SECRET***").map_err(|_| fmt::Error)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use std::mem::MaybeUninit;
141
142    use zeroize::Zeroize;
143
144    use super::SecureBox;
145
146    const PRIVATE_KEY_1: [u8; 32] = [
147        0xb0, 0x3b, 0x34, 0xc3, 0x3a, 0x1c, 0x44, 0xf2, 0x25, 0xb6, 0x62, 0xd2, 0xbf, 0x48, 0x59, 0xb8, 0x13, 0x54, 0x11, 0xfa,
148        0x7b, 0x03, 0x86, 0xd4, 0x5f, 0xb7, 0x5d, 0xc5, 0xb9, 0x1b, 0x44, 0x66,
149    ];
150
151    const PRIVATE_KEY_2: [u8; 32] = [
152        0xc8, 0x06, 0x43, 0x9d, 0xc9, 0xd2, 0xc4, 0x76, 0xff, 0xed, 0x8f, 0x25, 0x80, 0xc0, 0x88, 0x8d, 0x58, 0xab, 0x40, 0x6b,
153        0xf7, 0xae, 0x36, 0x98, 0x87, 0x90, 0x21, 0xb9, 0x6b, 0xb4, 0xbf, 0x59,
154    ];
155
156    /// Overwrite the contents with zeros. This is automatically done in the destructor.
157    ///
158    /// # Safety
159    /// An all-zero byte-pattern must be a valid value of `T` in order for this function call to not be
160    /// undefined behavior.
161    #[cfg_attr(feature = "pre", pre::pre("an all-zero byte-pattern is a valid value of `T`"))]
162    pub(crate) unsafe fn zero_out_secure_box<T>(secure_box: &mut SecureBox<T>)
163    where
164        T: Copy,
165    {
166        std::slice::from_raw_parts_mut::<MaybeUninit<u8>>(
167            &mut **secure_box.content.as_mut().unwrap() as *mut T as *mut MaybeUninit<u8>,
168            std::mem::size_of::<T>(),
169        )
170        .zeroize();
171    }
172
173    #[test]
174    #[cfg_attr(feature = "pre", pre::pre)]
175    fn test_secure_box() {
176        let key_1 = SecureBox::new(Box::new(PRIVATE_KEY_1));
177        let key_2 = SecureBox::new(Box::new(PRIVATE_KEY_2));
178        let key_3 = SecureBox::new(Box::new(PRIVATE_KEY_1));
179        assert!(key_1 == key_1);
180        assert!(key_1 != key_2);
181        assert!(key_2 != key_3);
182        assert!(key_1 == key_3);
183
184        let mut final_key = key_1.clone();
185        #[cfg_attr(
186            feature = "pre",
187            assure(
188                "an all-zero byte-pattern is a valid value of `T`",
189                reason = "`T` is `i32`, for which an all-zero byte-pattern is valid"
190            )
191        )]
192        unsafe {
193            zero_out_secure_box(&mut final_key)
194        };
195        assert_eq!(final_key.unsecure(), &[0; 32]);
196    }
197}