mirror of
https://github.com/rosenpass/rosenpass.git
synced 2026-03-01 06:53:10 -08:00
docs(doctests+coverage): add documentation and doctests for all modules of secret-memory except for alloc
This commit is contained in:
committed by
David Niehues
parent
d27e602f43
commit
10484cc6d4
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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!({
|
||||
|
||||
Reference in New Issue
Block a user