Compare commits

..

4 Commits

Author SHA1 Message Date
Karolin Varner
caf91c84f0 chore: Remove unused warning in api integration test 2024-12-18 13:45:38 +01:00
Karolin Varner
f5b4c17011 fix: Disable asserts that rely on timing characteristics during coverage testing 2024-12-18 13:45:38 +01:00
Karolin Varner
12506e5f95 chore: Final improvements on the to crate API doc 2024-12-18 13:45:38 +01:00
David Niehues
965600212d docs+doctest(to): Add tests, examples and documentation to the to-crate 2024-12-18 13:45:38 +01:00
19 changed files with 116 additions and 984 deletions

View File

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct ApiConfig {
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
/// connections on

View File

@@ -1,8 +1,3 @@
//! Contains the code used to parse command line parameters for rosenpass.
//!
//! [CliArgs::run] is called by the rosenpass main function and contains the
//! bulk of our boostrapping code while the main function just sets up the basic environment
use anyhow::{bail, ensure, Context};
use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
@@ -36,25 +31,15 @@ use {
std::thread,
};
/// How to reach a WireGuard PSK Broker
/// enum representing a choice of interface to a WireGuard broker
#[derive(Debug)]
pub enum BrokerInterface {
/// The PSK Broker is listening on a unix socket at the given path
Socket(PathBuf),
/// The PSK Broker broker is already connected to this process; a
/// unix socket stream can be reached at the given file descriptor.
///
/// This is generally used with file descriptor passing.
FileDescriptor(i32),
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
/// and hand one end of the socketpair to the broker process via file
/// descriptor passing to the subprocess
SocketPair,
}
/// Command line arguments to the Rosenpass binary.
///
/// Used for parsing with [clap].
/// struct holding all CLI arguments for `clap` crate to parse
#[derive(Parser, Debug)]
#[command(author, version, about, long_about, arg_required_else_help = true)]
pub struct CliArgs {
@@ -95,7 +80,6 @@ pub struct CliArgs {
#[arg(short, long, group = "psk-broker-specs")]
psk_broker_spawn: bool,
/// The subcommand to be invoked
#[command(subcommand)]
pub command: Option<CliCommand>,
@@ -114,10 +98,6 @@ pub struct CliArgs {
}
impl CliArgs {
/// Apply the command line parameters to the Rosenpass configuration struct
///
/// Generally the flow of control here is that all the command line parameters
/// are merged into the configuration file to avoid much code duplication.
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
self.api.apply_to_config(_cfg)?;
@@ -143,11 +123,9 @@ impl CliArgs {
None
}
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(feature = "experiment_api")]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
if let Some(path_ref) = self.psk_broker_path.as_ref() {
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
@@ -160,10 +138,9 @@ impl CliArgs {
}
}
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(not(feature = "experiment_api"))]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
None
}
@@ -267,17 +244,15 @@ pub enum CliCommand {
}
impl CliArgs {
/// Run Rosenpass with the given command line parameters
/// Runs the command specified via CLI
///
/// This contains the bulk of our startup logic with
/// the main function just setting up the basic environment
/// and then calling this function.
/// ## TODO
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
pub fn run(
self,
broker_interface: Option<BrokerInterface>,
test_helpers: Option<AppServerTest>,
) -> anyhow::Result<()> {
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
use CliCommand::*;
match &self.command {
Some(GenConfig { config_file, force }) => {
@@ -428,7 +403,6 @@ impl CliArgs {
Ok(())
}
/// Used by [Self::run] to start the Rosenpass key exchange server
fn event_loop(
config: config::Rosenpass,
broker_interface: Option<BrokerInterface>,
@@ -496,19 +470,6 @@ impl CliArgs {
srv.event_loop()
}
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(feature = "experiment_api")]
fn create_broker(
broker_interface: Option<BrokerInterface>,
@@ -524,19 +485,6 @@ impl CliArgs {
}
}
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(not(feature = "experiment_api"))]
fn create_broker(
_broker_interface: Option<BrokerInterface>,
@@ -544,10 +492,6 @@ impl CliArgs {
Ok(Box::new(NativeUnixBroker::new()))
}
/// Used by [Self::create_broker] if the `experiment_api` is configured
/// to set up the connection with the PSK broker process as configured
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
#[cfg(feature = "experiment_api")]
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
// Connect to the psk broker unix socket if one was specified
@@ -605,7 +549,7 @@ impl CliArgs {
}
/// generate secret and public keys, store in files according to the paths passed as arguments
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;

View File

@@ -4,9 +4,8 @@
//! [`Rosenpass`] which holds such a configuration.
//!
//! ## TODO
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
use crate::protocol::{SPk, SSk};
use rosenpass_util::file::LoadValue;
use std::{
@@ -32,10 +31,7 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
}
}
/// Configuration for the Rosenpass key exchange
///
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize)]
pub struct Rosenpass {
// TODO: Raise error if secret key or public key alone is set during deserialization
// SEE: https://github.com/serde-rs/serde/issues/2793
@@ -50,10 +46,7 @@ pub struct Rosenpass {
/// list of [`SocketAddr`] to listen on
///
/// Examples:
///
/// - `0.0.0.0:123` Listen on any interface using IPv4, port 123
/// - `[::1]:1234` Listen on IPv6 localhost, port 1234
/// - `[::]:4476` Listen on any IPv4 or IPv6 interface, port 4476
/// - `0.0.0.0:123`
pub listen: Vec<SocketAddr>,
/// log verbosity
@@ -75,7 +68,6 @@ pub struct Rosenpass {
pub config_file_path: PathBuf,
}
/// Public key and secret key locations.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct Keypair {
/// path to the public key file
@@ -86,7 +78,6 @@ pub struct Keypair {
}
impl Keypair {
/// Construct a keypair from its fields
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
let public_key = public_key.as_ref().to_path_buf();
let secret_key = secret_key.as_ref().to_path_buf();
@@ -97,72 +88,62 @@ impl Keypair {
}
}
/// Level of verbosity for [crate::app_server::AppServer]
///
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
/// for details.
///
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
/// ## TODO
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum Verbosity {
Quiet,
Verbose,
}
/// Configuration data for a single Rosenpass peer
/// ## TODO
/// - examples
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RosenpassPeer {
/// path to the public key of the peer
pub public_key: PathBuf,
/// The hostname and port to connect to
///
/// Can be a
///
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
/// ## TODO
/// - documentation
pub endpoint: Option<String>,
/// path to the pre-shared key shared with the peer
/// path to the pre-shared key with the peer
///
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
pub pre_shared_key: Option<PathBuf>,
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
/// to the given file and write a notification to standard out to let the calling application
/// know that a new key was exchanged
/// ## TODO
/// - documentation
#[serde(default)]
pub key_out: Option<PathBuf>,
/// Information for supplying exchanged keys directly to WireGuard
/// ## TODO
/// - documentation
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
#[serde(flatten)]
pub wg: Option<WireGuard>,
}
/// Information for supplying exchanged keys directly to WireGuard
/// ## TODO
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireGuard {
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
/// key exchange
/// ## TODO
/// - documentation
pub device: String,
/// WireGuard public key of the peer to supply with pre-shared keys
/// ## TODO
/// - documentation
pub peer: String,
/// Extra parameters passed to the `wg` command
/// ## TODO
/// - documentation
#[serde(default)]
pub extra_params: Vec<String>,
}
impl Default for Rosenpass {
/// Generate an empty configuration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
fn default() -> Self {
Self::empty()
}
@@ -175,15 +156,8 @@ impl Rosenpass {
/// checked whether they even exist.
///
/// ## TODO
///
/// - consider using a different algorithm to determine home directory the below one may
/// behave unexpectedly on Windows
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
// read file and deserialize
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
@@ -211,13 +185,7 @@ impl Rosenpass {
Ok(config)
}
/// Encode a configuration object as toml and write it to a file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
/// Write a config to a file
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
let serialized_config =
toml::to_string_pretty(&self).expect("unable to serialize the default config");
@@ -226,12 +194,6 @@ impl Rosenpass {
}
/// Commit the configuration to where it came from, overwriting the original file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn commit(&self) -> anyhow::Result<()> {
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
@@ -239,21 +201,13 @@ impl Rosenpass {
self.store(&self.config_file_path)
}
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
self.api.apply_to_app_server(_srv)?;
Ok(())
}
/// Check that the configuration is sound, ensuring
/// for instance that the referenced files exist
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
/// Validate a configuration
pub fn validate(&self) -> anyhow::Result<()> {
if let Some(ref keypair) = self.keypair {
// check the public key file exists
@@ -330,21 +284,6 @@ impl Rosenpass {
Ok(())
}
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
/// and the configuration values.
///
/// This was introduced when we introduced a unix-socket API feature allowing the server
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
/// enabled in the configuration.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
pub fn check_usefullness(&self) -> anyhow::Result<()> {
#[cfg(not(feature = "experiment_api"))]
ensure!(self.keypair.is_some(), "Server keypair missing.");
@@ -360,38 +299,15 @@ impl Rosenpass {
Ok(())
}
/// Produce an empty confuguration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn empty() -> Self {
Self::new(None)
}
/// Produce configuration from the keypair
///
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
Self::new(Some(Keypair::new(pk, sk)))
}
/// Initialize a minimal configuration with the [Self::keypair] field supplied
/// as a parameter
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
/// Creates a new configuration
pub fn new(keypair: Option<Keypair>) -> Self {
Self {
keypair,
@@ -405,14 +321,6 @@ impl Rosenpass {
}
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
///
/// I.e. listen on any interface.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
#[doc = "```"]
pub fn add_if_any(&mut self, port: u16) {
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
@@ -425,20 +333,8 @@ impl Rosenpass {
self.listen.push(ipv6_any);
}
/// Parser for the old, IP style grammar.
///
/// See out manual page rosenpass-exchange(1) on details about the grammar.
///
/// This function parses the grammar and turns it into an instance of the configuration
/// struct.
///
/// TODO: the grammar is undecidable, what do we do here?
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
#[doc = "```"]
/// from chaotic args
/// Quest: the grammar is undecideable, what do we do here?
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
let mut config = Self::new(Some(Keypair::new("", "")));
@@ -629,13 +525,11 @@ impl Rosenpass {
}
impl Default for Verbosity {
/// Self::Quiet
fn default() -> Self {
Self::Quiet
}
}
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
secret_key = "/path/to/rp-secret-key"
listen = []
@@ -659,7 +553,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
mod test {
use super::*;
use std::borrow::Borrow;
use std::{borrow::Borrow, net::IpAddr};
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
toml::from_str(s.borrow())
@@ -770,6 +664,37 @@ mod test {
Ok(())
}
#[test]
fn test_simple_cli_parse() {
let args = split_str(
"public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out",
);
let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
)
}
#[test]
fn test_cli_parse_multiple_peers() {
let args = split_str(

View File

@@ -1,10 +0,0 @@
use rosenpass::config::Rosenpass;
#[test]
fn config_Rosenpass_add_if_any_example() {
let mut v = Rosenpass::empty();
v.add_if_any(4000);
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
}

View File

@@ -1,18 +0,0 @@
use rosenpass::config::{Keypair, Rosenpass};
#[test]
fn example_config_rosenpass_new() {
let (sk, pk) = ("./example.sk", "./example.pk");
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
assert_eq!(Rosenpass::empty(), Rosenpass::default());
assert_eq!(
Rosenpass::from_sk_pk(sk, pk),
Rosenpass::new(Some(Keypair::new(pk, sk)))
);
let mut v = Rosenpass::empty();
v.keypair = Some(Keypair::new(pk, sk));
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
}

View File

@@ -1,36 +0,0 @@
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
};
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
#[test]
fn parse_simple() {
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out";
let argv = argv.split(' ').map(|s| s.to_string()).collect();
let config = Rosenpass::parse_args(argv).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
);
}

View File

@@ -1,42 +0,0 @@
use std::path::PathBuf;
use rosenpass::config::{Rosenpass, Verbosity};
#[test]
fn example_config_rosenpass_store() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = tmpdir.path().join("config.toml");
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
// Can not commit config, path not known
assert!(c.commit().is_err());
// We can store it to an explicit path though
c.store(&cfg)?;
// Storing does not set commitment path
assert!(c.commit().is_err());
// We can reload the config now and the configurations
// are equal if we adjust the commitment path
let mut c2 = Rosenpass::load(&cfg)?;
c.config_file_path = PathBuf::from(&cfg);
assert_eq!(c, c2);
// And this loaded config can now be committed
c2.verbosity = Verbosity::Verbose;
c2.commit()?;
// And the changes actually made it to disk
let c3 = Rosenpass::load(cfg)?;
assert_eq!(c2, c3);
assert_ne!(c, c3);
Ok(())
}

View File

@@ -1,37 +0,0 @@
use std::fs;
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
#[test]
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
// Empty validates OK
assert!(Rosenpass::empty().validate().is_ok());
// Missing secret key does not pass usefulness
assert!(Rosenpass::empty().check_usefullness().is_err());
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
// Missing secret key does not validate
assert!(cfg.validate().is_err());
// But passes usefulness (the configuration is useful but invalid)
assert!(cfg.check_usefullness().is_ok());
// Providing empty key files does not help
fs::write(&sk, b"")?;
fs::write(&pk, b"")?;
assert!(cfg.validate().is_err());
// But after providing proper key files, the configuration validates
generate_and_save_keypair(sk, pk)?;
assert!(cfg.validate().is_ok());
Ok(())
}

View File

@@ -1,9 +1,3 @@
//! This module provides a wrapper [MallocAllocator] around the memsec allocator in
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memsec allocator
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
//!
//! The module also provides the [MallocVec] and [MallocBox] types.
use std::fmt;
use std::ptr::NonNull;
@@ -12,78 +6,31 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MallocAllocatorContents;
/// A wrapper around the memsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
/// Memory allocation using using the memsec crate
#[derive(Copy, Clone, Default)]
pub struct MallocAllocator {
_dummy_private_data: MallocAllocatorContents,
}
/// A [allocator_api2::boxed::Box] backed by the memsec allocator
/// from the [memsec] crate.
/// A box backed by the memsec allocator
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
/// A [allocator_api2::vec::Vec] backed by the memsec allocator
/// from the [memsec] crate.
/// A vector backed by the memsec allocator
pub type MallocVec<T> = allocator_api2::vec::Vec<T, MallocAllocator>;
/// Try to allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box_try, MallocBox};
/// let data: u8 = 42;
/// let malloc_box: MallocBox<u8> = malloc_box_try(data)?;
/// # assert_eq!(*malloc_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
}
/// Allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box, MallocBox};
/// let data: u8 = 42;
/// let malloc_box: MallocBox<u8> = malloc_box(data);
/// # assert_eq!(*malloc_box, 42u8);
/// ```
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
MallocBox::<T>::new_in(x, MallocAllocator::new())
}
/// Allocate a [MallocVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_vec, MallocVec};
/// let mut malloc_vec: MallocVec<u8> = malloc_vec();
/// malloc_vec.push(0u8);
/// malloc_vec.push(1u8);
/// malloc_vec.push(2u8);
/// # let mut element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn malloc_vec<T>() -> MallocVec<T> {
MallocVec::<T>::new_in(MallocAllocator::new())
}
impl MallocAllocator {
/// Creates a new [MallocAllocator].
pub fn new() -> Self {
Self {
_dummy_private_data: MallocAllocatorContents,
@@ -147,9 +94,6 @@ mod test {
malloc_allocation_impl::<8>(&alloc);
malloc_allocation_impl::<64>(&alloc);
malloc_allocation_impl::<999>(&alloc);
// Also test the debug-print for good measure
let _ = format!("{:?}", alloc);
}
fn malloc_allocation_impl<const N: usize>(alloc: &MallocAllocator) {

View File

@@ -1,9 +1,3 @@
//! This module provides a wrapper [MemfdSecAllocator] around the memfdsec allocator in
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memfdsec allocator
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
//!
//! The module also provides the [MemfdSecVec] and [MemfdSecBox] types.
#![cfg(target_os = "linux")]
use std::fmt;
use std::ptr::NonNull;
@@ -13,78 +7,31 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MemfdSecAllocatorContents;
/// A wrapper around the memfdsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
/// Memory allocation using using the memsec crate
#[derive(Copy, Clone, Default)]
pub struct MemfdSecAllocator {
_dummy_private_data: MemfdSecAllocatorContents,
}
/// A [allocator_api2::boxed::Box] backed by the memfdsec allocator
/// from the [memsec] crate.
/// A box backed by the memsec allocator
pub type MemfdSecBox<T> = allocator_api2::boxed::Box<T, MemfdSecAllocator>;
/// A [allocator_api2::vec::Vec] backed by the memfdsec allocator
/// from the [memsec] crate.
/// A vector backed by the memsec allocator
pub type MemfdSecVec<T> = allocator_api2::vec::Vec<T, MemfdSecAllocator>;
/// Try to allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box_try, MemfdSecBox};
/// let data: u8 = 42;
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box_try(data)?;
/// # assert_eq!(*memfdsec_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn memfdsec_box_try<T>(x: T) -> Result<MemfdSecBox<T>, AllocError> {
MemfdSecBox::<T>::try_new_in(x, MemfdSecAllocator::new())
}
/// Allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box, MemfdSecBox};
/// let data: u8 = 42;
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box(data);
/// # assert_eq!(*memfdsec_box, 42u8);
/// ```
pub fn memfdsec_box<T>(x: T) -> MemfdSecBox<T> {
MemfdSecBox::<T>::new_in(x, MemfdSecAllocator::new())
}
/// Allocate a [MemfdSecVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_vec, MemfdSecVec};
/// let mut memfdsec_vec: MemfdSecVec<u8> = memfdsec_vec();
/// memfdsec_vec.push(0u8);
/// memfdsec_vec.push(1u8);
/// memfdsec_vec.push(2u8);
/// # let mut element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn memfdsec_vec<T>() -> MemfdSecVec<T> {
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
}
impl MemfdSecAllocator {
/// Create a new [MemfdSecAllocator].
pub fn new() -> Self {
Self {
_dummy_private_data: MemfdSecAllocatorContents,
@@ -148,9 +95,6 @@ mod test {
memfdsec_allocation_impl::<8>(&alloc);
memfdsec_allocation_impl::<64>(&alloc);
memfdsec_allocation_impl::<999>(&alloc);
// Also test the debug-print for good measure
let _ = format!("{:?}", alloc);
}
fn memfdsec_allocation_impl<const N: usize>(alloc: &MemfdSecAllocator) {

View File

@@ -1,6 +1,2 @@
//! This module provides wrappers around the memfdsec and the memsec allocators from the
//! [memsec] crate. The wrappers implement the [Allocator](allocator_api2::alloc::Allocator) trait
//! and can thus be used as a drop in replacement wherever ever this trait is required.
pub mod malloc;
pub mod memfdsec;

View File

@@ -1,12 +1,3 @@
//! This module provides a [SecretAllocator](SecretAlloc) that allocates memory with extra
//! protections that make it more difficult for threat actors to access secrets that they aren't
//! authorized to access. At the moment the `memsec` and `memfdsec` allocators from the
//! [memsec] crate are supported for this purpose.
//!
//! [SecretAlloc] implements the [Allocator] trait and can thus be used as a drop in replacement
//! wherever ever this trait is required.
//!
//! The module also provides the [SecretVec] and [SecretBox] types.
pub mod memsec;
use std::sync::OnceLock;
@@ -17,50 +8,15 @@ use memsec::malloc::MallocAllocator;
#[cfg(target_os = "linux")]
use memsec::memfdsec::MemfdSecAllocator;
/// Globally configures which [SecretAllocType] to use as default for
/// [SecretAllocators](SecretAlloc).
static ALLOC_TYPE: OnceLock<SecretAllocType> = OnceLock::new();
/// Sets the secret allocation type to use by default for [SecretAllocators](SecretAlloc).
/// It is intended that this function is called at startup before a secret allocation
/// takes place.
///
/// # Example
/// ```rust
/// # use std::alloc::Layout;
/// # use allocator_api2::alloc::Allocator;
/// # use rosenpass_secret_memory::alloc::{set_secret_alloc_type, SecretAlloc, SecretAllocType};
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// let secret_alloc = SecretAlloc::default();
/// unsafe {
/// let memory = secret_alloc.allocate(Layout::from_size_align_unchecked(128, 32))?;
/// }
/// # Ok::<(), anyhow::Error>(())
/// ```
/// ```
/// Sets the secret allocation type to use.
/// Intended usage at startup before secret allocation
/// takes place
pub fn set_secret_alloc_type(alloc_type: SecretAllocType) {
ALLOC_TYPE.set(alloc_type).unwrap();
}
/// Initializes type of allocator to be sued with `alloc_type` if it is not initialized yet. Returns
/// the current [SecretAllocType] afterward.
///
/// # Example
/// ```rust
/// # use std::alloc::Layout;
/// # use allocator_api2::alloc::Allocator;
/// # use rosenpass_secret_memory::alloc::{get_or_init_secret_alloc_type, set_secret_alloc_type,
/// # SecretAlloc, SecretAllocType};
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// #[cfg(target_os = "linux")] {
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMemfdSec);
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
/// }
/// #[cfg(not(target_os = "linux"))] {
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
/// }
///```
pub fn get_or_init_secret_alloc_type(alloc_type: SecretAllocType) -> SecretAllocType {
*ALLOC_TYPE.get_or_init(|| alloc_type)
}
@@ -72,7 +28,6 @@ pub enum SecretAllocType {
MemsecMemfdSec,
}
/// An [Allocator] that uses a [SecretAllocType] for allocation.
pub struct SecretAlloc {
alloc_type: SecretAllocType,
}
@@ -113,72 +68,19 @@ unsafe impl Allocator for SecretAlloc {
}
}
/// A [allocator_api2::boxed::Box] that is backed by [SecretAlloc].
pub type SecretBox<T> = allocator_api2::boxed::Box<T, SecretAlloc>;
/// A [allocator_api2::vec::Vec] that is backed by [SecretAlloc].
/// A vector backed by the memsec allocator
pub type SecretVec<T> = allocator_api2::vec::Vec<T, SecretAlloc>;
/// Try to allocate a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_box_try, SecretBox};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let data: u8 = 42;
/// let secret_box: SecretBox<u8> = secret_box_try(data)?;
/// # assert_eq!(*secret_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn secret_box_try<T>(x: T) -> Result<SecretBox<T>, AllocError> {
SecretBox::<T>::try_new_in(x, SecretAlloc::default())
}
/// Allocates a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_box, SecretBox};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let data: u8 = 42;
/// let secret_box: SecretBox<u8> = secret_box(data);
/// # assert_eq!(*secret_box, 42u8);
/// ```
pub fn secret_box<T>(x: T) -> SecretBox<T> {
SecretBox::<T>::new_in(x, SecretAlloc::default())
}
/// Allocate a [SecretVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_vec, SecretVec};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let mut secret_vec: SecretVec<u8> = secret_vec();
/// secret_vec.push(0u8);
/// secret_vec.push(1u8);
/// secret_vec.push(2u8);
/// # let mut element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn secret_vec<T>() -> SecretVec<T> {
SecretVec::<T>::new_in(SecretAlloc::default())
}

View File

@@ -1,31 +1,6 @@
//! 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].
/// # 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");
/// ```
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[{}]=")?;
if v.len() > 64 {

View File

@@ -1,46 +1,8 @@
//! 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,38 +1,3 @@
//! 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 [mod@file] module provides functionality to store [Secrets](crate::Secret)
//! and [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,12 +1,3 @@
//! 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")]
@@ -31,8 +22,6 @@ 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;
@@ -45,7 +34,6 @@ 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,21 +15,7 @@ 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.
///
/// # 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");
/// ```
/// public
// TODO: We should get rid of the Public type; just use a normal value
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
@@ -38,84 +24,75 @@ 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
}
@@ -124,7 +101,6 @@ 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)?;
@@ -135,7 +111,6 @@ 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(())
@@ -143,10 +118,8 @@ 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,
@@ -169,7 +142,6 @@ 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];
@@ -183,10 +155,8 @@ 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,
@@ -202,117 +172,89 @@ 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())?;
@@ -321,26 +263,22 @@ 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.
// No extra documentation here because the Trait already provides a good documentation.
// This is implemented separately from Public to avoid allocating too much stack memory
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();
@@ -357,20 +295,16 @@ 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,
@@ -386,7 +320,6 @@ mod tests {
#[allow(clippy::module_inception)]
mod tests {
use crate::{Public, PublicBox};
use rand::Fill;
use rosenpass_util::{
b64::b64_encode,
file::{
@@ -516,112 +449,5 @@ mod tests {
fn test_public_box_load_store_base64() {
run_base64_load_store_test::<PublicBox<N>>();
}
/// Test the debug print function for [Public]
#[test]
fn test_debug_public() {
let p: Public<32> = Public::zero();
let _ = format!("{:?}", p);
}
/// Test that [Public] is correctly borrowed to a u8 array.
#[test]
fn test_borrow_public_sized() {
let p: Public<32> = Public::zero();
let borrowed: &[u8; 32] = &p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 array.
#[test]
fn test_borrow_public_sized_mut() {
let mut p: Public<32> = Public::zero();
let borrowed: &mut [u8; 32] = &mut p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a u8 slice.
#[test]
fn test_borrow_public_unsized() {
use std::borrow::Borrow;
let p: Public<32> = Public::zero();
let borrowed: &[u8] = p.borrow();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
#[test]
fn test_borrow_public_unsized_mut() {
use std::borrow::BorrowMut;
let mut p: Public<32> = Public::zero();
let borrowed: &mut [u8] = p.borrow_mut();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly created from a slice.
#[test]
fn test_public_box_from_slice() {
let my_slice: &[u8; 32] = &[0; 32];
let p: PublicBox<32> = PublicBox::from_slice(my_slice);
assert_eq!(p.deref(), my_slice);
}
/// Test that [PublicBox] can correctly be created with its [PublicBox::new] function.
#[test]
fn test_public_box_new() {
let pb = PublicBox::new([42; 32]);
assert_eq!(pb.deref(), &[42; 32]);
}
/// Test the randomize functionality of [PublicBox].
#[test]
fn test_public_box_randomize() {
let mut pb: PublicBox<32> = PublicBox::zero();
pb.randomize();
pb.try_fill(&mut crate::rand::rng()).unwrap();
// Can't really assert anything here until we have can predict the randomness
// by derandomizing the RNG for tests.
}
/// Test the [Debug] print of [PublicBox]
#[test]
fn test_public_box_debug() {
let pb: PublicBox<32> = PublicBox::new([42; 32]);
let _ = format!("{:?}", pb);
}
/// Test that [PublicBox] is correctly borrowed to a u8 array.
#[test]
fn test_borrow_public_box_sized() {
let p: PublicBox<32> = PublicBox::zero();
let borrowed: &[u8; 32] = &p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly borrowed to a mutable u8 array.
#[test]
fn test_borrow_public_box_sized_mut() {
let mut p: PublicBox<32> = PublicBox::zero();
let borrowed: &mut [u8; 32] = &mut p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly borrowed to a u8 slice.
#[test]
fn test_borrow_public_box_unsized() {
use std::borrow::Borrow;
let p: PublicBox<32> = PublicBox::zero();
let borrowed: &[u8] = p.borrow();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
#[test]
fn test_borrow_public_box_unsized_mut() {
use std::borrow::BorrowMut;
let mut p: PublicBox<32> = PublicBox::zero();
let borrowed: &mut [u8] = p.borrow_mut();
assert_eq!(borrowed, &[0; 32]);
}
}
}

View File

@@ -1,9 +1,5 @@
//! 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,7 +27,6 @@ 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,
@@ -48,47 +47,37 @@ 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] 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> {
/// Releases 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()
}
@@ -97,32 +86,29 @@ 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 {
@@ -130,7 +116,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();
@@ -148,7 +134,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() {
@@ -159,50 +145,22 @@ impl SecretMemoryPool {
}
}
/// [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>");
/// // If you need to, you can zeroize a [Secret] at any time it's necessary:
/// my_secret.zeroize();
/// ```
/// Storage for secret data
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]))
@@ -211,30 +169,28 @@ 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 of an existing [Secret] to random bytes.
/// Sets all data 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.
@@ -244,10 +200,8 @@ 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()
@@ -255,14 +209,7 @@ 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> {
/// Release 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()) {
@@ -293,7 +240,6 @@ 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();
@@ -307,7 +253,6 @@ 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();
@@ -327,7 +272,6 @@ 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();
@@ -347,7 +291,6 @@ 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())
@@ -364,13 +307,11 @@ 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(())
@@ -379,10 +320,7 @@ impl<const N: usize> StoreSecret for Secret<N> {
#[cfg(test)]
mod test {
use crate::{
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
test_spawn_process_provided_policies,
};
use crate::test_spawn_process_provided_policies;
use super::*;
use std::{fs, os::unix::fs::PermissionsExt};
@@ -390,7 +328,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!({
@@ -401,7 +339,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!({
@@ -413,7 +351,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!({
@@ -434,7 +372,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!({
@@ -471,8 +409,7 @@ 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!({
@@ -526,33 +463,4 @@ mod test {
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
});
}
/// Test the creation of a [ZeroizingSecretBox] using its [new](ZeroizingSecretBox::new)
/// function.
#[test]
fn test_zeroizing_secret_box_new() {
struct DummyZeroizing {
inner_dummy: [u8; 32],
}
impl Zeroize for DummyZeroizing {
fn zeroize(&mut self) {
self.inner_dummy = [0; 32];
}
}
let dummy = DummyZeroizing {
inner_dummy: [42; 32],
};
secret_policy_use_only_malloc_secrets();
let mut zsb: ZeroizingSecretBox<DummyZeroizing> = ZeroizingSecretBox::new(dummy);
zsb.zeroize();
assert_eq!(zsb.inner_dummy, [0; 32]);
}
/// Test the debug print of [Secret].
#[test]
fn test_debug_secret() {
secret_policy_use_only_malloc_secrets();
let my_secret: Secret<32> = Secret::zero();
assert_eq!(format!("{:?}", my_secret), "<SECRET>");
}
}