docs(doctests+coverage): add documentation and doctests for all modules of secret-memory except for alloc

This commit is contained in:
David Niehues
2024-12-12 21:13:18 +01:00
committed by David Niehues
parent d27e602f43
commit 10484cc6d4
9 changed files with 272 additions and 35 deletions

View File

@@ -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)]

View File

@@ -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;

View File

@@ -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<u8>
/// }
/// 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 {

View File

@@ -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<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?;
/// Ok(())
/// }
///
/// fn store<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}

View File

@@ -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;

View File

@@ -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!(

View File

@@ -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<const N: usize> {
}
impl<const N: usize> Public<N> {
/// 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<const N: usize> Randomize for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.value.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for Public<N> {
// 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<const N: usize> Deref for Public<N> {
// 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<const N: usize> DerefMut for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
// 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<const N: usize> BorrowMut<[u8]> for Public<N> {
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
@@ -111,6 +135,7 @@ impl<const N: usize> LoadValue for Public<N> {
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
@@ -118,8 +143,10 @@ impl<const N: usize> StoreValue for Public<N> {
}
impl<const N: usize> LoadValueB64 for Public<N> {
// 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<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
@@ -142,6 +169,7 @@ impl<const N: usize> LoadValueB64 for Public<N> {
impl<const N: usize> StoreValueB64 for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let p = path.as_ref();
let mut f = [0u8; F];
@@ -155,8 +183,10 @@ impl<const N: usize> StoreValueB64 for Public<N> {
}
impl<const N: usize> StoreValueB64Writer for Public<N> {
// 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<const F: usize, W: std::io::Write>(
&self,
mut writer: W,
@@ -172,89 +202,117 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
}
}
/// 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<const N: usize> {
/// The inner [Box] around the [Public].
pub inner: Box<Public<N>>,
}
impl<const N: usize> PublicBox<N> {
/// 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<const N: usize> Randomize for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.inner.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for PublicBox<N> {
// 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<const N: usize> Deref for PublicBox<N> {
// 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<const N: usize> DerefMut for PublicBox<N> {
// 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<const N: usize> Borrow<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
self.deref()
}
}
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8] {
self.deref_mut()
}
}
impl<const N: usize> LoadValue for PublicBox<N> {
// 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<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut p = Self::random();
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
@@ -263,22 +321,26 @@ impl<const N: usize> LoadValue for PublicBox<N> {
}
impl<const N: usize> StoreValue for PublicBox<N> {
// 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<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store(path)
}
}
impl<const N: usize> LoadValueB64 for PublicBox<N> {
// 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<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
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<const N: usize> LoadValueB64 for PublicBox<N> {
}
impl<const N: usize> StoreValueB64 for PublicBox<N> {
// 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<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store_b64::<F, P>(path)
}
}
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
// 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<const F: usize, W: std::io::Write>(
&self,
writer: W,

View File

@@ -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()
}

View File

@@ -27,6 +27,7 @@ thread_local! {
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
}
/// Executes the given function `f` with the [SECRET_CACHE] as a parameter.
fn with_secret_memory_pool<Fn, R>(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<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
impl<T: Zeroize> ZeroizingSecretBox<T> {
/// 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<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
/// Creates a new [ZeroizingSecretBox] from a [SecretBox<T>] for the type `T`,
/// which must implement [Zeroize] but does not have to be [Sized].
fn from_secret_box(inner: SecretBox<T>) -> 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<T> {
self.0.take().unwrap()
}
}
// Indicate that a Secret is always zeroized when it is dropped.
impl<T: Zeroize + ?Sized> ZeroizeOnDrop for ZeroizingSecretBox<T> {}
impl<T: Zeroize + ?Sized> Zeroize for ZeroizingSecretBox<T> {
fn zeroize(&mut self) {
if let Some(inner) = &mut self.0 {
let inner: &mut SecretBox<T> = inner; // type annotation
let inner: &mut SecretBox<T> = inner; // Type annotation.
inner.zeroize()
}
}
}
impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
/// 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<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
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<T: Zeroize + ?Sized> DerefMut for ZeroizingSecretBox<T> {
// 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<usize, Vec<ZeroizingSecretBox<[u8]>>>,
}
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<const N: usize>(&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<const N: usize>(&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), "<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<const N: usize> {
storage: Option<ZeroizingSecretBox<[u8; N]>>,
}
impl<const N: usize> Secret<N> {
/// 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<const N: usize> Secret<N> {
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<const N: usize> Randomize for Secret<N> {
/// 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<R: Rng + ?Sized>(&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<const N: usize> Randomize for Secret<N> {
}
}
// Indicate that a Secret is always zeroized when it is dropped.
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
impl<const N: usize> Zeroize for Secret<N> {
// 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<const N: usize> Zeroize for Secret<N> {
}
}
// Our implementation of Drop gives back the allocated secret memory to the secret memory pool.
impl<const N: usize> Drop for Secret<N> {
/// 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<const N: usize> fmt::Debug for Secret<N> {
impl<const N: usize> LoadValue for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
@@ -253,6 +307,7 @@ impl<const N: usize> LoadValue for Secret<N> {
impl<const N: usize> LoadValueB64 for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut f: Secret<F> = Secret::random();
let mut v = Self::random();
@@ -272,6 +327,7 @@ impl<const N: usize> LoadValueB64 for Secret<N> {
impl<const N: usize> StoreValueB64 for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let p = path.as_ref();
@@ -291,6 +347,7 @@ impl<const N: usize> StoreValueB64 for Secret<N> {
impl<const N: usize> StoreValueB64Writer for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: Write>(&self, mut writer: W) -> anyhow::Result<()> {
let mut f: Secret<F> = Secret::random();
let encoded_str = b64_encode(self.secret(), f.secret_mut())
@@ -307,11 +364,13 @@ impl<const N: usize> StoreValueB64Writer for Secret<N> {
impl<const N: usize> StoreSecret for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_secret<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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!({