secure_string/secure_types/
vec.rs

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