diff --git a/secret-memory/src/alloc/memsec/malloc.rs b/secret-memory/src/alloc/memsec/malloc.rs index 65b7173..8fb5f9a 100644 --- a/secret-memory/src/alloc/memsec/malloc.rs +++ b/secret-memory/src/alloc/memsec/malloc.rs @@ -7,7 +7,6 @@ use std::fmt; use std::ptr::NonNull; -use crate::alloc::{SecretBox, SecretVec}; use allocator_api2::alloc::{AllocError, Allocator, Layout}; #[derive(Copy, Clone, Default)] diff --git a/secret-memory/src/alloc/mod.rs b/secret-memory/src/alloc/mod.rs index 5bf5bb5..f4cddc2 100644 --- a/secret-memory/src/alloc/mod.rs +++ b/secret-memory/src/alloc/mod.rs @@ -14,8 +14,6 @@ use std::sync::OnceLock; use allocator_api2::alloc::{AllocError, Allocator}; use memsec::malloc::MallocAllocator; -use crate::alloc::memsec::malloc::MallocBox; -use crate::alloc::memsec::memfdsec::MemfdSecVec; #[cfg(target_os = "linux")] use memsec::memfdsec::MemfdSecAllocator; diff --git a/secret-memory/src/debug.rs b/secret-memory/src/debug.rs index ae1b7dd..c2bf818 100644 --- a/secret-memory/src/debug.rs +++ b/secret-memory/src/debug.rs @@ -1,6 +1,31 @@ +//! This module provides a helper for creating debug prints for byte slices. +//! See [debug_crypto_array] for more details. + use std::fmt; -/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter] +/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]. +/// # Example +/// +/// ```rust +/// use std::fmt::{Debug, Formatter}; +/// use rosenpass_secret_memory::debug::debug_crypto_array; +/// +/// struct U8Wrapper { +/// pub u_eigt: Vec +/// } +/// impl Debug for U8Wrapper {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +/// // let dead_beef: [u8; 11] = [3, 3, 6, 5, 3, 3, 3, 7, 3, 5, 7]; +/// debug_crypto_array(self.u_eigt.as_slice(), f) +/// } +/// } +/// // Short byte slices are printed completely. +/// let cafe = U8Wrapper {u_eigt: vec![1, 4, 5, 3, 7, 6]}; +/// assert_eq!(format!("{:?}", cafe), "[{}]=145376"); +/// // For longer byte slices, only the first 32 and last 32 bytes are printed. +/// let all_u8 = U8Wrapper {u_eigt: (0..256).map(|i| i as u8).collect()}; +/// assert_eq!(format!("{:?}", all_u8), "[{}]=0123456789abcdef101112131415161718191a1b1c1d1e1f…e0e\ +/// 1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); +/// ``` pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str("[{}]=")?; if v.len() > 64 { diff --git a/secret-memory/src/file.rs b/secret-memory/src/file.rs index 78d5f22..c10e233 100644 --- a/secret-memory/src/file.rs +++ b/secret-memory/src/file.rs @@ -1,8 +1,46 @@ +//! Objects that implement this Trait provide a way to store their data in way that respects the +//! confidentiality of its data. Specifically, an object implementing this Trait guarantees +//! if its data with [store_secret](StoreSecret::store_secret) are saved in the file with visibility +//! equivalent to [rosenpass_util::file::Visibility::Secret]. + use std::path::Path; +/// Objects that implement this Trait provide a standard method to be stored securely. The trait can +/// be implemented as follows for example: +/// # Example +/// ```rust +/// use std::io::Write; +/// use std::path::Path; +/// use rosenpass_secret_memory::file::StoreSecret; +/// +/// use rosenpass_util::file::{fopen_w, Visibility}; +/// +/// struct MyWeirdI32 { +/// _priv_i32: [u8; 4], +/// } +/// +/// impl StoreSecret for MyWeirdI32 { +/// type Error = std::io::Error; +/// +/// fn store_secret>(&self, path: P) -> Result<(), Self::Error> { +/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?; +/// Ok(()) +/// } +/// +/// fn store>(&self, path: P) -> Result<(), Self::Error> { +/// fopen_w(path, Visibility::Public)?.write_all(&self._priv_i32)?; +/// Ok(()) +/// } +/// } +/// ``` pub trait StoreSecret { type Error; + /// Stores the object securely. In particular, it ensures that the visibility is equivalent to + /// [rosenpass_util::file::Visibility::Secret]. fn store_secret>(&self, path: P) -> Result<(), Self::Error>; + + /// Stores the object. No requirement on the visibility is given, but it is common to store + /// the data with visibility equivalent to [rosenpass_util::file::Visibility::Public]. fn store>(&self, path: P) -> Result<(), Self::Error>; } diff --git a/secret-memory/src/lib.rs b/secret-memory/src/lib.rs index 2ecbb9d..9d12561 100644 --- a/secret-memory/src/lib.rs +++ b/secret-memory/src/lib.rs @@ -1,3 +1,38 @@ +//! This library provides functionality for working with secret data and protecting it in +//! memory from illegitimate access. +//! +//! Specifically, the [alloc] module provides wrappers around the `memsec` and `memfdsec` allocators +//! from the [memsec] crate that implement the [Allocator](allocator_api2::alloc::Allocator) Trait. +//! We refer to the documentation of these modules for more details on their appropriate usage. +//! +//! The [policy] module then provides functionality for specifying which of the allocators from +//! the [alloc] module should be used. +//! +//! Once this configuration is made [Secret] can be used to store sensitive data in memory +//! allocated by the configured allocator. [Secret] is implemented such that memory is *aloways* +//! zeroized before it is released. Because allocations of the protected memory are expensive to do, +//! [Secret] is build to reuse once allocated memory. A simple use of [Secret] looks as follows: +//! # Exmaple +//! ```rust +//! use zeroize::Zeroize; +//! use rosenpass_secret_memory::{secret_policy_try_use_memfd_secrets, Secret}; +//! secret_policy_try_use_memfd_secrets(); +//! let mut my_secret: Secret<32> = Secret::random(); +//! my_secret.zeroize(); +//! ``` +//! +//! # Futher functionality +//! In addition to this core functionality, this library provides some more smaller tools. +//! +//! 1. [Public] and [PublicBox] provide byte array storage for public data in a manner analogous to +//! that of [Secret]. +//! 2. The [debug] module provides functionality to easily create debug output for objects that are +//! backed by byte arrays or slices, like for example [Secret]. +//! 3. The [file] module provides functionality to store [Secrets](crate::Secret) und [Public] +//! in files such that the file's [Visibility](rosenpass_util::file::Visibility) +//! corresponds to the confidentiality of the data. +//! 4. The [rand] module provides a simple way of generating randomness. + pub mod debug; pub mod file; pub mod rand; diff --git a/secret-memory/src/policy.rs b/secret-memory/src/policy.rs index 4aad2b1..d6aede4 100644 --- a/secret-memory/src/policy.rs +++ b/secret-memory/src/policy.rs @@ -1,3 +1,12 @@ +//! This crates the `memsec` and `memfdsec` allocators from the [memsec] crate to be used for +//! allocations of memory on which [Secrects](crate::Secret) are stored. This, however, requires +//! that an allocator is chosen before [Secret](crate::Secret) is used the first time. +//! This module provides functionality for just that. + +/// This function sets the `memfdsec` allocator as the default in case it is supported by +/// the target and uses the `memsec` allocator otherwise. +/// +/// At the time of writing, the `memfdsec` allocator is just supported on linux targets. pub fn secret_policy_try_use_memfd_secrets() { let alloc_type = { #[cfg(target_os = "linux")] @@ -22,6 +31,8 @@ pub fn secret_policy_try_use_memfd_secrets() { log::info!("Secrets will be allocated using {:?}", alloc_type); } +/// This functions sets the `memfdsec` allocator as the default. At the time of writing +/// this is only supported on Linux targets. #[cfg(target_os = "linux")] pub fn secret_policy_use_only_memfd_secrets() { let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec; @@ -34,6 +45,7 @@ pub fn secret_policy_use_only_memfd_secrets() { log::info!("Secrets will be allocated using {:?}", alloc_type); } +/// This function sets the `memsec` allocator as the default. It is supported on all targets. pub fn secret_policy_use_only_malloc_secrets() { let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc; assert_eq!( diff --git a/secret-memory/src/public.rs b/secret-memory/src/public.rs index 88bb785..c86bb5a 100644 --- a/secret-memory/src/public.rs +++ b/secret-memory/src/public.rs @@ -15,7 +15,21 @@ use std::ops::{Deref, DerefMut}; use std::path::Path; /// Contains information in the form of a byte array that may be known to the -/// public +/// public. +/// +/// # Example +/// ```rust +/// # use zeroize::Zeroize; +/// # use rosenpass_secret_memory::{Public}; +/// +/// let mut my_public_data: Public<32> = Public::random(); +/// // Fill with some random data that I can use a cryptographic key later on. +/// my_public_data.randomize(); +/// // A Public can be overwritten with zeros. +/// my_public_data.zeroize(); +/// // If a Public is printed as Debug, its content is printed byte for byte. +/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000"); +/// ``` // TODO: We should get rid of the Public type; just use a normal value #[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] @@ -24,75 +38,84 @@ pub struct Public { } impl Public { - /// Create a new [Public] from a byte slice + /// Create a new [Public] from a byte slice. pub fn from_slice(value: &[u8]) -> Self { copy_slice(value).to_this(Self::zero) } - /// Create a new [Public] from a byte array + /// Create a new [Public] from a byte array. pub fn new(value: [u8; N]) -> Self { Self { value } } - /// Create a zero initialized [Public] + /// Create a zero initialized [Public]. pub fn zero() -> Self { Self { value: [0u8; N] } } - /// Create a random initialized [Public] + /// Create a random initialized [Public]. pub fn random() -> Self { mutating(Self::zero(), |r| r.randomize()) } - /// Randomize all bytes in an existing [Public] + /// Randomize all bytes in an existing [Public]. pub fn randomize(&mut self) { self.try_fill(&mut crate::rand::rng()).unwrap() } } impl Randomize for Public { + // No extra documentation here because the Trait already provides a good documentation. fn try_fill(&mut self, rng: &mut R) -> Result<(), rand::Error> { self.value.try_fill(rng) } } impl fmt::Debug for Public { + // No extra documentation here because the Trait already provides a good documentation. fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { debug_crypto_array(&self.value, fmt) } } impl Deref for Public { + // No extra documentation here because the Trait already provides a good documentation. type Target = [u8; N]; + // No extra documentation here because the Trait already provides a good documentation. fn deref(&self) -> &[u8; N] { &self.value } } impl DerefMut for Public { + // No extra documentation here because the Trait already provides a good documentation. fn deref_mut(&mut self) -> &mut [u8; N] { &mut self.value } } impl Borrow<[u8; N]> for Public { + // No extra documentation here because the Trait already provides a good documentation. fn borrow(&self) -> &[u8; N] { &self.value } } impl BorrowMut<[u8; N]> for Public { + // No extra documentation here because the Trait already provides a good documentation. fn borrow_mut(&mut self) -> &mut [u8; N] { &mut self.value } } impl Borrow<[u8]> for Public { + // No extra documentation here because the Trait already provides a good documentation. fn borrow(&self) -> &[u8] { &self.value } } impl BorrowMut<[u8]> for Public { + // No extra documentation here because the Trait already provides a good documentation. fn borrow_mut(&mut self) -> &mut [u8] { &mut self.value } @@ -101,6 +124,7 @@ impl BorrowMut<[u8]> for Public { impl LoadValue for Public { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn load>(path: P) -> anyhow::Result { let mut v = Self::random(); fopen_r(path)?.read_exact_to_end(&mut *v)?; @@ -111,6 +135,7 @@ impl LoadValue for Public { impl StoreValue for Public { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store>(&self, path: P) -> anyhow::Result<()> { std::fs::write(path, **self)?; Ok(()) @@ -118,8 +143,10 @@ impl StoreValue for Public { } impl LoadValueB64 for Public { + // No extra documentation here because the Trait already provides a good documentation. type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn load_b64>(path: P) -> Result where Self: Sized, @@ -142,6 +169,7 @@ impl LoadValueB64 for Public { impl StoreValueB64 for Public { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store_b64>(&self, path: P) -> anyhow::Result<()> { let p = path.as_ref(); let mut f = [0u8; F]; @@ -155,8 +183,10 @@ impl StoreValueB64 for Public { } impl StoreValueB64Writer for Public { + // No extra documentation here because the Trait already provides a good documentation. type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store_b64_writer( &self, mut writer: W, @@ -172,89 +202,117 @@ impl StoreValueB64Writer for Public { } } +/// A [Box] around a [Public] so that the latter one can be allocated on the heap. +/// +/// # Example +/// ```rust +/// # use zeroize::Zeroize; +/// # use rosenpass_secret_memory::{Public, PublicBox}; +/// +/// let mut my_public_data: Public<32> = Public::random(); +/// let mut my_bbox: PublicBox<32> = PublicBox{ inner: Box::new(my_public_data)}; +/// +/// // Now we can practically handle it just as we would handle the Public itself: +/// // Fill with some random data that I can use a cryptographic key later on. +/// my_public_data.randomize(); +/// // A Public can be overwritten with zeros. +/// my_public_data.zeroize(); +/// // If a Public is printed as Debug, its content is printed byte for byte. +/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000"); +/// ``` #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct PublicBox { + /// The inner [Box] around the [Public]. pub inner: Box>, } impl PublicBox { - /// Create a new [PublicBox] from a byte slice + /// Create a new [PublicBox] from a byte slice. pub fn from_slice(value: &[u8]) -> Self { Self { inner: Box::new(Public::from_slice(value)), } } - /// Create a new [PublicBox] from a byte array + /// Create a new [PublicBox] from a byte array. pub fn new(value: [u8; N]) -> Self { Self { inner: Box::new(Public::new(value)), } } - /// Create a zero initialized [PublicBox] + /// Create a zero initialized [PublicBox]. pub fn zero() -> Self { Self { inner: Box::new(Public::zero()), } } - /// Create a random initialized [PublicBox] + /// Create a random initialized [PublicBox]. pub fn random() -> Self { Self { inner: Box::new(Public::random()), } } - /// Randomize all bytes in an existing [PublicBox] + /// Randomize all bytes in an existing [PublicBox]. pub fn randomize(&mut self) { self.inner.randomize() } } impl Randomize for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. fn try_fill(&mut self, rng: &mut R) -> Result<(), rand::Error> { self.inner.try_fill(rng) } } impl fmt::Debug for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { debug_crypto_array(&**self, fmt) } } impl Deref for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. type Target = [u8; N]; + // No extra documentation here because the Trait already provides a good documentation. fn deref(&self) -> &[u8; N] { self.inner.deref() } } impl DerefMut for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. fn deref_mut(&mut self) -> &mut [u8; N] { self.inner.deref_mut() } } impl Borrow<[u8]> for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. fn borrow(&self) -> &[u8] { self.deref() } } impl BorrowMut<[u8]> for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. fn borrow_mut(&mut self) -> &mut [u8] { self.deref_mut() } } impl LoadValue for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. type Error = anyhow::Error; // This is implemented separately from Public to avoid allocating too much stack memory + // No extra documentation here because the Trait already provides a good documentation. fn load>(path: P) -> anyhow::Result { let mut p = Self::random(); fopen_r(path)?.read_exact_to_end(p.deref_mut())?; @@ -263,22 +321,26 @@ impl LoadValue for PublicBox { } impl StoreValue for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store>(&self, path: P) -> anyhow::Result<()> { self.inner.store(path) } } impl LoadValueB64 for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. type Error = anyhow::Error; - // This is implemented separately from Public to avoid allocating too much stack memory + // This is implemented separately from Public to avoid allocating too much stack memory. + // No extra documentation here because the Trait already provides a good documentation. fn load_b64>(path: P) -> Result where Self: Sized, { - // A vector is used here to ensure heap allocation without copy from stack + // A vector is used here to ensure heap allocation without copy from stack. let mut f = vec![0u8; F]; let mut v = PublicBox::zero(); let p = path.as_ref(); @@ -295,16 +357,20 @@ impl LoadValueB64 for PublicBox { } impl StoreValueB64 for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store_b64>(&self, path: P) -> anyhow::Result<()> { self.inner.store_b64::(path) } } impl StoreValueB64Writer for PublicBox { + // No extra documentation here because the Trait already provides a good documentation. type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store_b64_writer( &self, writer: W, diff --git a/secret-memory/src/rand.rs b/secret-memory/src/rand.rs index a6daa6d..bf1b359 100644 --- a/secret-memory/src/rand.rs +++ b/secret-memory/src/rand.rs @@ -1,5 +1,9 @@ +//! This module provides functionality for generating random numbers using the [rand] crate. + +/// We use the [ThreadRng](rand::rngs::ThreadRng) for randomness in this crate. pub type Rng = rand::rngs::ThreadRng; +/// Get the default [Rng]. pub fn rng() -> Rng { rand::thread_rng() } diff --git a/secret-memory/src/secret.rs b/secret-memory/src/secret.rs index 9f4e50d..27d2ee2 100644 --- a/secret-memory/src/secret.rs +++ b/secret-memory/src/secret.rs @@ -27,6 +27,7 @@ thread_local! { static SECRET_CACHE: RefCell = RefCell::new(SecretMemoryPool::new()); } +/// Executes the given function `f` with the [SECRET_CACHE] as a parameter. fn with_secret_memory_pool(mut f: Fn) -> R where Fn: FnMut(Option<&mut SecretMemoryPool>) -> R, @@ -47,37 +48,47 @@ where .unwrap_or_else(|| f(None)) } -// Wrapper around SecretBox that applies automatic zeroization +/// Wrapper around SecretBox that applies automatic zeroization. #[derive(Debug)] struct ZeroizingSecretBox(Option>); impl ZeroizingSecretBox { + /// Creates a new [ZeroizingSecretBox] around `boxed` for the type `T`, where `T` must implement + /// [Zeroize]. fn new(boxed: T) -> Self { ZeroizingSecretBox(Some(secret_box(boxed))) } } impl ZeroizingSecretBox { + /// Creates a new [ZeroizingSecretBox] from a [SecretBox] for the type `T`, + /// which must implement [Zeroize] but does not have to be [Sized]. fn from_secret_box(inner: SecretBox) -> Self { Self(Some(inner)) } + /// Consumes the [ZeroizingSecretBox] and returns the content in a [SecretBox] for the type `T`, + /// which must implement [Zeroize] but does not have to be [Sized]. fn take(mut self) -> SecretBox { self.0.take().unwrap() } } +// Indicate that a Secret is always zeroized when it is dropped. impl ZeroizeOnDrop for ZeroizingSecretBox {} impl Zeroize for ZeroizingSecretBox { fn zeroize(&mut self) { if let Some(inner) = &mut self.0 { - let inner: &mut SecretBox = inner; // type annotation + let inner: &mut SecretBox = inner; // Type annotation. inner.zeroize() } } } impl Drop for ZeroizingSecretBox { + /// Relaases the memory of this [ZeroizingSecretBox]. In contrast to usual implementations + /// of [Drop], we zeroize the memory before dropping it. This fulfills the promises we make + /// by implementing [ZeroizeOnDrop]. fn drop(&mut self) { self.zeroize() } @@ -86,29 +97,32 @@ impl Drop for ZeroizingSecretBox { impl Deref for ZeroizingSecretBox { type Target = T; + // No extra documentation here because the Trait already provides a good documentation. fn deref(&self) -> &T { self.0.as_ref().unwrap() } } impl DerefMut for ZeroizingSecretBox { + // No extra documentation here because the Trait already provides a good documentation. fn deref_mut(&mut self) -> &mut T { self.0.as_mut().unwrap() } } -/// Pool that stores secret memory allocations +/// Pool that stores secret memory allocations. /// /// Allocation of secret memory is expensive. Thus, this struct provides a /// pool of secret memory, readily available to yield protected, slices of /// memory. #[derive(Debug)] // TODO check on Debug derive, is that clever struct SecretMemoryPool { + /// A pool to reuse secret memory pool: HashMap>>, } impl SecretMemoryPool { - /// Create a new [SecretMemoryPool] + /// Create a new [SecretMemoryPool]. #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { @@ -116,7 +130,7 @@ impl SecretMemoryPool { } } - /// Return secret back to the pool for future re-use + /// Return secret back to the pool for future re-use. pub fn release(&mut self, mut sec: ZeroizingSecretBox<[u8; N]>) { sec.zeroize(); @@ -134,7 +148,7 @@ impl SecretMemoryPool { /// Take protected memory from the pool, allocating new one if no suitable /// chunk is found in the inventory. /// - /// The secret is guaranteed to be full of nullbytes + /// The secret is guaranteed to be full of nullbytes. pub fn take(&mut self) -> ZeroizingSecretBox<[u8; N]> { let entry = self.pool.entry(N).or_default(); let inner = match entry.pop() { @@ -145,22 +159,50 @@ impl SecretMemoryPool { } } -/// Storage for secret data +/// [Secret] stores its memory in a way that makes it difficult for threat actors to access it +/// without permission. This is achieved through the following mechanisms: +/// 1. Data in a [Secret] is stored in memory that is allocated and managed by memory allocators +/// that make it more difficult for threat actors to access the memory. Specifically, the +/// allocators from [memsec] are supported. +/// 2. Memory that is allocated for a [Secret] is zeroized before it is used for anything else. +/// +/// In order to use a [Secret], we have to decide on the secure allocator to use beforehand +/// calling either +/// [secret_policy_use_only_malloc_secrets](crate::secret_policy_use_only_malloc_secrets), +/// [secret_policy_use_only_memfd_secrets](crate::secret_policy_use_only_memfd_secrets) +/// or [secret_policy_try_use_memfd_secrets](crate::secret_policy_try_use_memfd_secrets). +/// +/// You can use a [Secret] as follows: +/// ```rust +/// # use zeroize::Zeroize; +/// # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret}; +/// +/// // We have to define the security policy before using Secrets. +/// secret_policy_use_only_malloc_secrets(); +/// let mut my_secret: Secret<32> = Secret::zero(); +/// // Fill with some random data that I can use a cryptographic key later on. +/// my_secret.randomize(); +/// // In case I accidentally print my secret in a debug, it's still not leaked: +/// assert_eq!(format!("{:?}", my_secret), ""); +/// // The data will be zeroized either way in the end, but for good measure, let's zeroize anymay +/// my_secret.zeroize(); +/// ``` pub struct Secret { storage: Option>, } impl Secret { + /// Create a new [Secret] from a byte-slice. pub fn from_slice(slice: &[u8]) -> Self { let mut new_self = Self::zero(); new_self.secret_mut().copy_from_slice(slice); new_self } - /// Returns a new [Secret] that is zero initialized + /// Returns a new [Secret] that is zero initialized. pub fn zero() -> Self { // Using [SecretMemoryPool] here because this operation is expensive, - // yet it is used in hot loops + // yet it is used in hot loops. let buf = with_secret_memory_pool(|pool| { pool.map(|p| p.take()) .unwrap_or_else(|| ZeroizingSecretBox::new([0u8; N])) @@ -169,28 +211,30 @@ impl Secret { Self { storage: Some(buf) } } - /// Returns a new [Secret] that is randomized + /// Returns a new [Secret] that is randomized. pub fn random() -> Self { mutating(Self::zero(), |r| r.randomize()) } - /// Sets all data an existing secret to random bytes + /// Sets all data of an existing [Secret] to random bytes. pub fn randomize(&mut self) { self.try_fill(&mut crate::rand::rng()).unwrap() } - /// Borrows the data + /// Borrows the data. pub fn secret(&self) -> &[u8; N] { self.storage.as_ref().unwrap() } - /// Borrows the data mutably + /// Borrows the data mutably. pub fn secret_mut(&mut self) -> &mut [u8; N] { self.storage.as_mut().unwrap() } } impl Randomize for Secret { + /// Tries to fill this [Secret] with random data. The [Secret] is first zeroized + /// to make sure that the barriers from the zeroize crate take effect. fn try_fill(&mut self, rng: &mut R) -> Result<(), rand::Error> { // Zeroize self first just to make sure the barriers from the zeroize create take // effect to prevent the compiler from optimizing this away. @@ -200,8 +244,10 @@ impl Randomize for Secret { } } +// Indicate that a Secret is always zeroized when it is dropped. impl ZeroizeOnDrop for Secret {} impl Zeroize for Secret { + // No extra documentation here because the Trait already provides a good documentation. fn zeroize(&mut self) { if let Some(inner) = &mut self.storage { inner.zeroize() @@ -209,7 +255,14 @@ impl Zeroize for Secret { } } +// Our implementation of Drop gives back the allocated secret memory to the secret memory pool. impl Drop for Secret { + /// Relaases the memory of this [Secret]. In contrast to usual implementations to [Drop] we + /// do the following: + /// 1. The memory of this [Secret] gets zeroized as required by [ZeroizeOnDrop]. + /// 2. The memory is returned to a memory pool of specially secure memory to be reused. + /// + /// This behaviour fulfills the promises we make by implementing [ZeroizeOnDrop]. fn drop(&mut self) { with_secret_memory_pool(|pool| { if let Some((pool, secret)) = pool.zip(self.storage.take()) { @@ -240,6 +293,7 @@ impl fmt::Debug for Secret { impl LoadValue for Secret { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn load>(path: P) -> anyhow::Result { let mut v = Self::random(); let p = path.as_ref(); @@ -253,6 +307,7 @@ impl LoadValue for Secret { impl LoadValueB64 for Secret { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn load_b64>(path: P) -> anyhow::Result { let mut f: Secret = Secret::random(); let mut v = Self::random(); @@ -272,6 +327,7 @@ impl LoadValueB64 for Secret { impl StoreValueB64 for Secret { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store_b64>(&self, path: P) -> anyhow::Result<()> { let p = path.as_ref(); @@ -291,6 +347,7 @@ impl StoreValueB64 for Secret { impl StoreValueB64Writer for Secret { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store_b64_writer(&self, mut writer: W) -> anyhow::Result<()> { let mut f: Secret = Secret::random(); let encoded_str = b64_encode(self.secret(), f.secret_mut()) @@ -307,11 +364,13 @@ impl StoreValueB64Writer for Secret { impl StoreSecret for Secret { type Error = anyhow::Error; + // No extra documentation here because the Trait already provides a good documentation. fn store_secret>(&self, path: P) -> anyhow::Result<()> { fopen_w(path, Visibility::Secret)?.write_all(self.secret())?; Ok(()) } + // No extra documentation here because the Trait already provides a good documentation. fn store>(&self, path: P) -> anyhow::Result<()> { fopen_w(path, Visibility::Public)?.write_all(self.secret())?; Ok(()) @@ -328,7 +387,7 @@ mod test { procspawn::enable_test_support!(); - /// check that we can alloc using the magic pool + /// Check that we can alloc using the magic pool. #[test] fn secret_memory_pool_take() { test_spawn_process_provided_policies!({ @@ -339,7 +398,7 @@ mod test { }); } - /// check that a secret lives, even if its [SecretMemoryPool] is deleted + /// Check that a secret lives, even if its [SecretMemoryPool] is deleted. #[test] fn secret_memory_pool_drop() { test_spawn_process_provided_policies!({ @@ -351,7 +410,7 @@ mod test { }); } - /// check that a secret can be reborn, freshly initialized with zero + /// Check that a secret can be reborn, freshly initialized with zero. #[test] fn secret_memory_pool_release() { test_spawn_process_provided_policies!({ @@ -372,7 +431,7 @@ mod test { }); } - /// test loading a secret from an example file, and then storing it again in a different file + /// Test loading a secret from an example file, and then storing it again in a different file. #[test] fn test_secret_load_store() { test_spawn_process_provided_policies!({ @@ -409,7 +468,8 @@ mod test { }); } - /// test loading a base64 encoded secret from an example file, and then storing it again in a different file + /// Test loading a base64 encoded secret from an example file, and then storing it again in + /// a different file. #[test] fn test_secret_load_store_base64() { test_spawn_process_provided_policies!({