Skip to main content

securer_string/secure_types/
vec.rs

1use core::fmt;
2use std::borrow::{Borrow, BorrowMut};
3use std::str::FromStr;
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)
22///
23/// `PartialEq` and `Eq` are only implemented when `T: ConstantTimeEq`. The
24/// safety of comparisons with respect to padding bytes depends on the
25/// `ConstantTimeEq` implementation of `T`.
26///
27/// Be careful with `SecureBytes::from`: if you have a borrowed string, it will
28/// be copied. Use `SecureBytes::new` if you have a `Vec<u8>`.
29pub struct SecureVec<T>
30where
31    T: Copy + Zeroize,
32{
33    pub(crate) content: Vec<T>,
34    /// Whether `content` is currently `mlock`ed. If `mlock` failed, `munlock`
35    /// must be skipped.
36    pub(crate) is_locked: bool,
37}
38
39/// Type alias for a vector that stores just bytes
40pub type SecureBytes = SecureVec<u8>;
41
42impl<T> SecureVec<T>
43where
44    T: Copy + Zeroize,
45{
46    #[must_use]
47    pub fn new(mut cont: Vec<T>) -> Self {
48        let is_locked = memlock::mlock(cont.as_mut_ptr(), cont.capacity()).is_ok();
49        SecureVec {
50            content: cont,
51            is_locked,
52        }
53    }
54
55    /// Borrow the contents of the string.
56    #[must_use]
57    pub fn unsecure(&self) -> &[T] {
58        self.borrow()
59    }
60
61    /// Mutably borrow the contents of the string.
62    #[must_use]
63    pub fn unsecure_mut(&mut self) -> &mut [T] {
64        self.borrow_mut()
65    }
66
67    /// Resizes the `SecureVec` in-place so that len is equal to `new_len`.
68    ///
69    /// If `new_len` is smaller the inner vector is truncated.
70    /// If `new_len` is larger the inner vector will grow, placing `value` in
71    /// all new cells.
72    ///
73    /// This ensures that the new memory region is secured if reallocation
74    /// occurs.
75    ///
76    /// Similar to [`Vec::resize`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.resize)
77    pub fn resize(&mut self, new_len: usize, value: T) {
78        // Trucnate if shorter or same length
79        if new_len <= self.content.len() {
80            self.content.truncate(new_len);
81            return;
82        }
83
84        // Allocate new vector, copy old data into it
85        let mut new_vec = vec![value; new_len];
86        let new_is_locked = memlock::mlock(new_vec.as_mut_ptr(), new_vec.capacity()).is_ok();
87        new_vec[0..self.content.len()].copy_from_slice(&self.content);
88
89        // Securely clear old vector, replace with new vector
90        self.zero_out();
91        if self.is_locked {
92            memlock::munlock(self.content.as_mut_ptr(), self.content.capacity());
93        }
94        self.content = new_vec;
95        self.is_locked = new_is_locked;
96    }
97
98    /// Overwrite the string with zeros. This is automatically called in the
99    /// destructor.
100    ///
101    /// This also sets the length to `0`.
102    pub fn zero_out(&mut self) {
103        self.content.zeroize();
104    }
105}
106
107impl<T: Copy + Zeroize> Clone for SecureVec<T> {
108    fn clone(&self) -> Self {
109        Self::new(self.content.clone())
110    }
111}
112
113impl<T: Copy + Zeroize + ConstantTimeEq> ConstantTimeEq for SecureVec<T> {
114    fn ct_eq(&self, other: &Self) -> subtle::Choice {
115        self.content.ct_eq(&other.content)
116    }
117}
118
119impl<T: Copy + Zeroize + ConstantTimeEq> PartialEq for SecureVec<T> {
120    fn eq(&self, other: &Self) -> bool {
121        self.ct_eq(other).into()
122    }
123}
124
125impl<T: Copy + Zeroize + ConstantTimeEq> Eq for SecureVec<T> {}
126
127// Creation
128impl<T, U> From<U> for SecureVec<T>
129where
130    U: Into<Vec<T>>,
131    T: Copy + Zeroize,
132{
133    fn from(s: U) -> SecureVec<T> {
134        SecureVec::new(s.into())
135    }
136}
137
138impl FromStr for SecureVec<u8> {
139    type Err = std::convert::Infallible;
140
141    fn from_str(s: &str) -> Result<Self, Self::Err> {
142        Ok(SecureVec::new(s.into()))
143    }
144}
145
146// Vec item indexing
147impl<T, U> std::ops::Index<U> for SecureVec<T>
148where
149    T: Copy + Zeroize,
150    Vec<T>: std::ops::Index<U>,
151{
152    type Output = <Vec<T> as std::ops::Index<U>>::Output;
153
154    fn index(&self, index: U) -> &Self::Output {
155        std::ops::Index::index(&self.content, index)
156    }
157}
158
159// Borrowing
160impl<T> Borrow<[T]> for SecureVec<T>
161where
162    T: Copy + Zeroize,
163{
164    fn borrow(&self) -> &[T] {
165        self.content.borrow()
166    }
167}
168
169impl<T> BorrowMut<[T]> for SecureVec<T>
170where
171    T: Copy + Zeroize,
172{
173    fn borrow_mut(&mut self) -> &mut [T] {
174        self.content.borrow_mut()
175    }
176}
177
178// Overwrite memory with zeros when we're done
179impl<T> Drop for SecureVec<T>
180where
181    T: Copy + Zeroize,
182{
183    fn drop(&mut self) {
184        self.zero_out();
185        if self.is_locked {
186            memlock::munlock(self.content.as_mut_ptr(), self.content.capacity());
187        }
188    }
189}
190
191// Make sure sensitive information is not logged accidentally
192impl<T> fmt::Debug for SecureVec<T>
193where
194    T: Copy + Zeroize,
195{
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197        f.debug_struct("SecureVec").finish_non_exhaustive()
198    }
199}
200
201impl<T> fmt::Display for SecureVec<T>
202where
203    T: Copy + Zeroize,
204{
205    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206        f.write_str("***SECRET***").map_err(|_| fmt::Error)
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::{SecureBytes, SecureVec};
213
214    #[test]
215    fn test_basic() {
216        let my_sec = SecureBytes::from("hello");
217        assert_eq!(my_sec, SecureBytes::from("hello".to_string()));
218        assert_eq!(my_sec.unsecure(), b"hello");
219    }
220
221    #[test]
222    fn test_zero_out() {
223        let mut my_sec = SecureBytes::from("hello");
224        my_sec.zero_out();
225        // `zero_out` sets the `len` to 0, here we reset it to check that the bytes were
226        // zeroed
227        unsafe {
228            my_sec.content.set_len(5);
229        }
230        assert_eq!(my_sec.unsecure(), b"\x00\x00\x00\x00\x00");
231    }
232
233    #[test]
234    fn test_resize() {
235        let mut my_sec = SecureVec::from([0, 1]);
236        assert_eq!(my_sec.unsecure().len(), 2);
237        my_sec.resize(1, 0);
238        assert_eq!(my_sec.unsecure().len(), 1);
239        my_sec.resize(16, 2);
240        assert_eq!(
241            my_sec.unsecure(),
242            &[0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
243        );
244    }
245
246    #[test]
247    fn test_comparison() {
248        assert_eq!(SecureBytes::from("hello"), SecureBytes::from("hello"));
249        assert_ne!(SecureBytes::from("hello"), SecureBytes::from("yolo"));
250        assert_ne!(SecureBytes::from("hello"), SecureBytes::from("olleh"));
251        assert_ne!(SecureBytes::from("hello"), SecureBytes::from("helloworld"));
252        assert_ne!(SecureBytes::from("hello"), SecureBytes::from(""));
253    }
254
255    #[test]
256    fn test_indexing() {
257        let string = SecureBytes::from("hello");
258        assert_eq!(string[0], b'h');
259        assert_eq!(&string[3..5], "lo".as_bytes());
260    }
261
262    #[test]
263    fn test_show() {
264        assert_eq!(
265            format!("{:?}", SecureBytes::from("hello")),
266            "SecureVec { .. }".to_string()
267        );
268        assert_eq!(
269            format!("{}", SecureBytes::from("hello")),
270            "***SECRET***".to_string()
271        );
272    }
273
274    #[test]
275    fn test_comparison_zero_out_mb() {
276        let mbstring1 = SecureVec::from(vec![
277            'H' as u32,
278            'a' as u32,
279            'l' as u32,
280            'l' as u32,
281            'o' as u32,
282            ' ' as u32,
283            '🦄' as u32,
284            '!' as u32,
285        ]);
286        let mbstring2 = SecureVec::from(vec![
287            'H' as u32,
288            'a' as u32,
289            'l' as u32,
290            'l' as u32,
291            'o' as u32,
292            ' ' as u32,
293            '🦄' as u32,
294            '!' as u32,
295        ]);
296        let mbstring3 = SecureVec::from(vec![
297            '!' as u32,
298            '🦄' as u32,
299            ' ' as u32,
300            'o' as u32,
301            'l' as u32,
302            'l' as u32,
303            'a' as u32,
304            'H' as u32,
305        ]);
306        assert_eq!(mbstring1, mbstring2);
307        assert_ne!(mbstring1, mbstring3);
308
309        let mut mbstring = mbstring1.clone();
310        mbstring.zero_out();
311        // `zero_out` sets the `len` to 0, here we reset it to check that the bytes were
312        // zeroed
313        unsafe {
314            mbstring.content.set_len(8);
315        }
316        assert_eq!(mbstring.unsecure(), &[0u32; 8]);
317    }
318}