| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::fmt::{self, Debug, Display, Formatter}; |
| use std::mem::swap; |
| |
| use zeroize::Zeroize; |
| |
| /// A container intended for handling sensitive data without leaving copies |
| /// behind after it is dropped (inspired by libbrillo::SecureBlob). |
| #[derive(Clone, Default, Zeroize)] |
| #[zeroize(drop)] |
| pub struct SecureBlob { |
| data: Vec<u8>, |
| } |
| |
| impl SecureBlob { |
| /// Creates a SecureBlob that can be extended to `capacity` before a |
| /// reallocation is needed. |
| pub fn with_capacity(capacity: usize) -> Self { |
| SecureBlob { |
| data: Vec::with_capacity(capacity), |
| } |
| } |
| |
| /// Appends the contents of the slice to the SecureBlob taking care of |
| /// zeroing the data if a reallocation is needed. |
| pub fn extend_from_slice(&mut self, other: &[u8]) { |
| let total_len = self.len() + other.len(); |
| if total_len > self.capacity() { |
| let mut temp = Vec::with_capacity(total_len); |
| temp.extend_from_slice(self.as_ref()); |
| self.data.zeroize(); |
| swap(&mut self.data, &mut temp) |
| } |
| self.data.extend_from_slice(other.as_ref()); |
| } |
| |
| /// How much data can be in the SecureBlob without a reallocation. |
| pub fn capacity(&self) -> usize { |
| self.data.capacity() |
| } |
| |
| /// Returns `true` if the length of SecureBob is zero. |
| pub fn is_empty(&self) -> bool { |
| self.data.is_empty() |
| } |
| |
| /// Returns the number of bytes in the SecureBlob. |
| pub fn len(&self) -> usize { |
| self.data.len() |
| } |
| |
| /// Securely erase the contents of the SecureBlob. |
| pub fn clear(&mut self) { |
| self.data.zeroize(); |
| self.data.clear(); |
| } |
| } |
| |
| impl AsRef<[u8]> for SecureBlob { |
| fn as_ref(&self) -> &[u8] { |
| self.data.as_slice() |
| } |
| } |
| |
| impl AsMut<[u8]> for SecureBlob { |
| fn as_mut(&mut self) -> &mut [u8] { |
| self.data.as_mut_slice() |
| } |
| } |
| |
| impl Debug for SecureBlob { |
| fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| write!(f, "<SecureBlob[{}]>", self.data.len()) |
| } |
| } |
| |
| impl Display for SecureBlob { |
| fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| write!(f, "<SecureBlob[{}]>", self.data.len()) |
| } |
| } |
| |
| /// Takes ownership of the Vec. |
| impl From<Vec<u8>> for SecureBlob { |
| fn from(v: Vec<u8>) -> Self { |
| SecureBlob { data: v } |
| } |
| } |
| |
| /// Zeroizes the source after it is copied into the SecureBlob. |
| impl From<&mut [u8]> for SecureBlob { |
| fn from(v: &mut [u8]) -> Self { |
| let mut sec = SecureBlob { data: Vec::new() }; |
| sec.data.reserve_exact(v.len()); |
| sec.data.extend(v.iter().copied()); |
| v.zeroize(); |
| sec |
| } |
| } |
| |
| #[cfg(test)] |
| pub(crate) mod tests { |
| use super::*; |
| |
| use std::mem::take; |
| |
| const TEST_DATA: &[u8; 6] = b"secret"; |
| |
| #[test] |
| fn from_vec() { |
| let test = TEST_DATA.to_vec(); |
| let ptr_start = test.as_ptr(); |
| let secret = SecureBlob::from(test); |
| |
| // Make sure the data wasn't copied. |
| assert_eq!(secret.data.as_ptr(), ptr_start) |
| } |
| |
| #[test] |
| fn from_slice() { |
| let mut test = TEST_DATA.to_vec(); |
| let ptr_start = test.as_ptr(); |
| let secret = SecureBlob::from(AsMut::<[u8]>::as_mut(&mut test)); |
| |
| // Make sure the data was copied. |
| assert_ne!(secret.data.as_ptr(), ptr_start); |
| // Make sure the original was zeroed. |
| assert_eq!(test, [0; TEST_DATA.len()]); |
| } |
| |
| #[test] |
| fn clone() { |
| let secret = SecureBlob::from(TEST_DATA.to_vec()); |
| let cloned_secret = secret.clone(); |
| |
| drop(secret); |
| |
| // Make sure the clone wasn't zeroed. |
| assert_eq!(cloned_secret.as_ref(), TEST_DATA); |
| } |
| |
| #[test] |
| fn extendfromslice_norealloc() { |
| let test = TEST_DATA.to_vec(); |
| let mut secret = SecureBlob::with_capacity(TEST_DATA.len()); |
| |
| assert!(secret.is_empty()); |
| assert_eq!(secret.capacity(), TEST_DATA.len()); |
| |
| let middle_index = 3; |
| secret.extend_from_slice(&test[0..middle_index]); |
| assert_eq!(secret.len(), middle_index); |
| |
| secret.extend_from_slice(&test[middle_index..]); |
| assert_eq!(secret.as_ref(), test); |
| |
| // Make sure there wasn't a realloc. |
| assert_eq!(secret.capacity(), TEST_DATA.len()); |
| } |
| |
| #[test] |
| fn extendfromslice_realloc() { |
| let test = TEST_DATA.to_vec(); |
| let mut secret = SecureBlob::with_capacity(0); |
| |
| assert!(secret.is_empty()); |
| // Make sure there is a realloc. |
| assert_eq!(secret.capacity(), 0); |
| |
| secret.extend_from_slice(&test); |
| assert_eq!(secret.as_ref(), test); |
| |
| assert!(secret.capacity() >= TEST_DATA.len()); |
| } |
| |
| #[test] |
| fn check_stack_move() { |
| let mut secret = SecureBlob::from(TEST_DATA.to_vec()); |
| let before_ptr = secret.data.as_ptr(); |
| |
| let moved = take(&mut secret); |
| |
| assert_eq!(moved.data.as_ptr(), before_ptr); |
| assert_ne!(secret.data.as_ptr(), before_ptr); |
| } |
| |
| #[test] |
| fn check_heap_move() { |
| let mut secret = Box::new(SecureBlob::from(TEST_DATA.to_vec())); |
| let before_ptr = secret.data.as_ptr(); |
| |
| let moved = take(secret.as_mut()); |
| |
| assert_eq!(moved.data.as_ptr(), before_ptr); |
| assert_ne!(secret.data.as_ptr(), before_ptr); |
| } |
| } |