Compare commits

...

16 Commits

Author SHA1 Message Date
Karolin Varner
47b556e317 chore(doc): Docs for rosenpass::{config, cli} 2024-12-18 20:48:12 +01:00
Paul Spooren
53168dc62d Add documentation, doc-tests and examples to the secret-memory crate. (#531) 2024-12-18 16:18:11 +01:00
David Niehues
2cfe703118 docu(secret-memeory): improve comment in example for Secret 2024-12-18 16:15:35 +01:00
David Niehues
a2d7c3aaa6 chore(secret-memory): fix typos 2024-12-18 16:15:35 +01:00
David Niehues
1aa111570e style(secret-memory): improve style in doc-tests around using the the ?-operator 2024-12-18 16:15:35 +01:00
David Niehues
a91d61f9f0 docs(secret-memory): fix warnings when generating the documentation 2024-12-18 16:15:35 +01:00
David Niehues
ff7827c24e test(fix-doctest): fix doctests where a function si wrapped around a doctest but the function is never called 2024-12-18 16:15:35 +01:00
David Niehues
255e377d29 test(coverage): add unit tests to improve coverage in public.rs and secret.rs 2024-12-18 16:15:35 +01:00
David Niehues
50505d81cc test: fix doctest in alloc/mod.rs to make it work on macos 2024-12-18 16:15:35 +01:00
David Niehues
10484cc6d4 docs(doctests+coverage): add documentation and doctests for all modules of secret-memory except for alloc 2024-12-18 16:15:35 +01:00
David Niehues
d27e602f43 docu(doctest+coverage): add documentation, doc-tests and examples to the alloc module 2024-12-18 16:15:35 +01:00
Paul Spooren
a279dfc0b1 docs+doctest(to): Add tests, examples and documentation to the to-crate (#546) 2024-12-18 14:30:38 +01:00
Karolin Varner
caf2f6bfec chore: Remove unused warning in api integration test 2024-12-18 14:28:51 +01:00
Karolin Varner
d398ad369e fix: Disable asserts that rely on timing characteristics during coverage testing 2024-12-18 14:28:35 +01:00
Karolin Varner
00696321ff chore: Final improvements on the to crate API doc 2024-12-18 14:28:24 +01:00
David Niehues
0353c82729 docs+doctest(to): Add tests, examples and documentation to the to-crate 2024-12-16 17:47:44 +01:00
33 changed files with 1395 additions and 167 deletions

View File

@@ -20,3 +20,6 @@ memsec = { workspace = true }
[dev-dependencies]
rand = "0.8.5"
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -113,9 +113,10 @@ mod tests {
// Pearson correlation
let correlation = cv / (sd_x * sd_y);
println!("correlation: {:.6?}", correlation);
#[cfg(not(coverage))]
assert!(
correlation.abs() < 0.01,
"execution time correlates with result"
)
);
}
}

View File

@@ -91,3 +91,6 @@ experiment_api = [
internal_signal_handling_for_coverage_reports = ["signal-hook"]
internal_testing = []
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
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,3 +1,8 @@
//! 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;
@@ -31,15 +36,25 @@ use {
std::thread,
};
/// enum representing a choice of interface to a WireGuard broker
/// How to reach a WireGuard PSK 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,
}
/// struct holding all CLI arguments for `clap` crate to parse
/// Command line arguments to the Rosenpass binary.
///
/// Used for parsing with [clap].
#[derive(Parser, Debug)]
#[command(author, version, about, long_about, arg_required_else_help = true)]
pub struct CliArgs {
@@ -80,6 +95,7 @@ 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>,
@@ -98,6 +114,10 @@ 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)?;
@@ -123,9 +143,11 @@ 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()))
@@ -138,9 +160,10 @@ 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
}
@@ -244,15 +267,17 @@ pub enum CliCommand {
}
impl CliArgs {
/// Runs the command specified via CLI
/// Run Rosenpass with the given command line parameters
///
/// ## TODO
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
/// This contains the bulk of our startup logic with
/// the main function just setting up the basic environment
/// and then calling this function.
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 }) => {
@@ -403,6 +428,7 @@ impl CliArgs {
Ok(())
}
/// Used by [Self::run] to start the Rosenpass key exchange server
fn event_loop(
config: config::Rosenpass,
broker_interface: Option<BrokerInterface>,
@@ -470,6 +496,19 @@ 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>,
@@ -485,6 +524,19 @@ 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>,
@@ -492,6 +544,10 @@ 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
@@ -549,7 +605,7 @@ impl CliArgs {
}
/// generate secret and public keys, store in files according to the paths passed as arguments
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
pub 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,8 +4,9 @@
//! [`Rosenpass`] which holds such a configuration.
//!
//! ## TODO
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
//! - 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>
use crate::protocol::{SPk, SSk};
use rosenpass_util::file::LoadValue;
use std::{
@@ -31,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
}
}
#[derive(Debug, Serialize, Deserialize)]
/// Configuration for the Rosenpass key exchange
///
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
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
@@ -46,7 +50,10 @@ pub struct Rosenpass {
/// list of [`SocketAddr`] to listen on
///
/// Examples:
/// - `0.0.0.0:123`
///
/// - `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
pub listen: Vec<SocketAddr>,
/// log verbosity
@@ -68,6 +75,7 @@ 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
@@ -78,6 +86,7 @@ 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();
@@ -88,62 +97,72 @@ impl Keypair {
}
}
/// ## TODO
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
/// 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>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum Verbosity {
Quiet,
Verbose,
}
/// ## TODO
/// - examples
/// - documentation
/// Configuration data for a single Rosenpass peer
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RosenpassPeer {
/// path to the public key of the peer
pub public_key: PathBuf,
/// ## TODO
/// - documentation
/// 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`
pub endpoint: Option<String>,
/// path to the pre-shared key with the peer
/// path to the pre-shared key shared 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>,
/// ## TODO
/// - documentation
/// 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
#[serde(default)]
pub key_out: Option<PathBuf>,
/// ## TODO
/// - documentation
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
/// Information for supplying exchanged keys directly to WireGuard
#[serde(flatten)]
pub wg: Option<WireGuard>,
}
/// ## TODO
/// - documentation
/// Information for supplying exchanged keys directly to WireGuard
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireGuard {
/// ## TODO
/// - documentation
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
/// key exchange
pub device: String,
/// ## TODO
/// - documentation
/// WireGuard public key of the peer to supply with pre-shared keys
pub peer: String,
/// ## TODO
/// - documentation
/// Extra parameters passed to the `wg` command
#[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()
}
@@ -156,8 +175,15 @@ 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)?)?;
@@ -185,7 +211,13 @@ impl Rosenpass {
Ok(config)
}
/// Write a config to a file
/// Encode a configuration object as toml and write it to a file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
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");
@@ -194,6 +226,12 @@ 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())?;
@@ -201,13 +239,21 @@ 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(())
}
/// Validate a configuration
/// 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 = "```"]
pub fn validate(&self) -> anyhow::Result<()> {
if let Some(ref keypair) = self.keypair {
// check the public key file exists
@@ -284,6 +330,21 @@ 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.");
@@ -299,15 +360,38 @@ 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)))
}
/// Creates a new configuration
/// 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 = "```"]
pub fn new(keypair: Option<Keypair>) -> Self {
Self {
keypair,
@@ -321,6 +405,14 @@ 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(
@@ -333,8 +425,20 @@ impl Rosenpass {
self.listen.push(ipv6_any);
}
/// from chaotic args
/// Quest: the grammar is undecideable, what do we do here?
/// 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 = "```"]
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
let mut config = Self::new(Some(Keypair::new("", "")));
@@ -525,11 +629,13 @@ 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 = []
@@ -553,7 +659,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
mod test {
use super::*;
use std::{borrow::Borrow, net::IpAddr};
use std::borrow::Borrow;
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
toml::from_str(s.borrow())
@@ -664,37 +770,6 @@ 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

@@ -39,7 +39,7 @@ impl Drop for KillChild {
// system is a bit broken; there is probably a few functions that just restart on EINTR
// so the signal is absorbed
loop {
rustix::process::kill_process(pid, Term).discard_result();
kill_process(pid, Term).discard_result();
if self.0.try_wait().unwrap().is_some() {
break;
}

View File

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,36 @@
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

@@ -0,0 +1,42 @@
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

@@ -0,0 +1,37 @@
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

@@ -2,11 +2,9 @@
use std::{
borrow::{Borrow, BorrowMut},
collections::VecDeque,
fmt::{Debug, Write},
ops::{DerefMut, RangeBounds},
ops::DerefMut,
};
use rand::distributions::uniform::SampleBorrow;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_util::result::OkExt;
@@ -28,48 +26,53 @@ fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
sim.poll_loop(150)?; // Poll 75 times
let transcript = sim.transcript;
let completions: Vec<_> = transcript
let _completions: Vec<_> = transcript
.iter()
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
.collect();
#[cfg(not(coverage))]
assert!(
!completions.is_empty(),
!_completions.is_empty(),
"\
Should have performed a successful key exchanged!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions[0].0 < 20.0,
_completions[0].0 < 20.0,
"\
First key exchange should happen in under twenty seconds!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions.len() >= 3,
_completions.len() >= 3,
"\
Should have at least two renegotiations!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
(110.0..175.0).contains(&completions[1].0),
(110.0..175.0).contains(&_completions[1].0),
"\
First renegotiation should happen in between two and three minutes!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
#[cfg(not(coverage))]
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
");
Ok(())
@@ -106,48 +109,53 @@ fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
}
let transcript = sim.transcript;
let completions: Vec<_> = transcript
let _completions: Vec<_> = transcript
.iter()
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
.collect();
#[cfg(not(coverage))]
assert!(
!completions.is_empty(),
!_completions.is_empty(),
"\
Should have performed a successful key exchanged!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions[0].0 < 10.0,
_completions[0].0 < 10.0,
"\
First key exchange should happen in under twenty seconds!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions.len() >= 3,
_completions.len() >= 3,
"\
Should have at least two renegotiations!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
(110.0..175.0).contains(&completions[1].0),
(110.0..175.0).contains(&_completions[1].0),
"\
First renegotiation should happen in between two and three minutes!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
#[cfg(not(coverage))]
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
");
Ok(())

View File

@@ -1,3 +1,9 @@
//! 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;
@@ -6,31 +12,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MallocAllocatorContents;
/// Memory allocation using using the memsec crate
/// A wrapper around the memsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
#[derive(Copy, Clone, Default)]
pub struct MallocAllocator {
_dummy_private_data: MallocAllocatorContents,
}
/// A box backed by the memsec allocator
/// A [allocator_api2::boxed::Box] backed by the memsec allocator
/// from the [memsec] crate.
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
/// A vector backed by the memsec allocator
/// A [allocator_api2::vec::Vec] backed by the memsec allocator
/// from the [memsec] crate.
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,
@@ -94,6 +147,9 @@ 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,3 +1,9 @@
//! 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;
@@ -7,31 +13,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MemfdSecAllocatorContents;
/// Memory allocation using using the memsec crate
/// A wrapper around the memfdsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
#[derive(Copy, Clone, Default)]
pub struct MemfdSecAllocator {
_dummy_private_data: MemfdSecAllocatorContents,
}
/// A box backed by the memsec allocator
/// A [allocator_api2::boxed::Box] backed by the memfdsec allocator
/// from the [memsec] crate.
pub type MemfdSecBox<T> = allocator_api2::boxed::Box<T, MemfdSecAllocator>;
/// A vector backed by the memsec allocator
/// A [allocator_api2::vec::Vec] backed by the memfdsec allocator
/// from the [memsec] crate.
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,
@@ -95,6 +148,9 @@ 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,2 +1,6 @@
//! 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,3 +1,12 @@
//! 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;
@@ -8,15 +17,50 @@ 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.
/// Intended usage at startup before secret allocation
/// takes place
/// 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>(())
/// ```
/// ```
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)
}
@@ -28,6 +72,7 @@ pub enum SecretAllocType {
MemsecMemfdSec,
}
/// An [Allocator] that uses a [SecretAllocType] for allocation.
pub struct SecretAlloc {
alloc_type: SecretAllocType,
}
@@ -68,19 +113,72 @@ 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 vector backed by the memsec allocator
/// A [allocator_api2::vec::Vec] that is backed by [SecretAlloc].
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,6 +1,31 @@
//! This module provides a helper for creating debug prints for byte slices.
//! See [debug_crypto_array] for more details.
use std::fmt;
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter].
/// # Example
///
/// ```rust
/// use std::fmt::{Debug, Formatter};
/// use rosenpass_secret_memory::debug::debug_crypto_array;
///
/// struct U8Wrapper {
/// pub u_eigt: Vec<u8>
/// }
/// impl Debug for U8Wrapper {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
/// // let dead_beef: [u8; 11] = [3, 3, 6, 5, 3, 3, 3, 7, 3, 5, 7];
/// debug_crypto_array(self.u_eigt.as_slice(), f)
/// }
/// }
/// // Short byte slices are printed completely.
/// let cafe = U8Wrapper {u_eigt: vec![1, 4, 5, 3, 7, 6]};
/// assert_eq!(format!("{:?}", cafe), "[{}]=145376");
/// // For longer byte slices, only the first 32 and last 32 bytes are printed.
/// let all_u8 = U8Wrapper {u_eigt: (0..256).map(|i| i as u8).collect()};
/// assert_eq!(format!("{:?}", all_u8), "[{}]=0123456789abcdef101112131415161718191a1b1c1d1e1f…e0e\
/// 1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
/// ```
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[{}]=")?;
if v.len() > 64 {

View File

@@ -1,8 +1,46 @@
//! Objects that implement this Trait provide a way to store their data in way that respects the
//! confidentiality of its data. Specifically, an object implementing this Trait guarantees
//! if its data with [store_secret](StoreSecret::store_secret) are saved in the file with visibility
//! equivalent to [rosenpass_util::file::Visibility::Secret].
use std::path::Path;
/// Objects that implement this Trait provide a standard method to be stored securely. The trait can
/// be implemented as follows for example:
/// # Example
/// ```rust
/// use std::io::Write;
/// use std::path::Path;
/// use rosenpass_secret_memory::file::StoreSecret;
///
/// use rosenpass_util::file::{fopen_w, Visibility};
///
/// struct MyWeirdI32 {
/// _priv_i32: [u8; 4],
/// }
///
/// impl StoreSecret for MyWeirdI32 {
/// type Error = std::io::Error;
///
/// fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?;
/// Ok(())
/// }
///
/// fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
/// fopen_w(path, Visibility::Public)?.write_all(&self._priv_i32)?;
/// Ok(())
/// }
/// }
/// ```
pub trait StoreSecret {
type Error;
/// Stores the object securely. In particular, it ensures that the visibility is equivalent to
/// [rosenpass_util::file::Visibility::Secret].
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
/// Stores the object. No requirement on the visibility is given, but it is common to store
/// the data with visibility equivalent to [rosenpass_util::file::Visibility::Public].
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}

View File

@@ -1,3 +1,38 @@
//! This library provides functionality for working with secret data and protecting it in
//! memory from illegitimate access.
//!
//! Specifically, the [alloc] module provides wrappers around the `memsec` and `memfdsec` allocators
//! from the [memsec] crate that implement the [Allocator](allocator_api2::alloc::Allocator) Trait.
//! We refer to the documentation of these modules for more details on their appropriate usage.
//!
//! The [policy] module then provides functionality for specifying which of the allocators from
//! the [alloc] module should be used.
//!
//! Once this configuration is made [Secret] can be used to store sensitive data in memory
//! allocated by the configured allocator. [Secret] is implemented such that memory is *aloways*
//! zeroized before it is released. Because allocations of the protected memory are expensive to do,
//! [Secret] is build to reuse once allocated memory. A simple use of [Secret] looks as follows:
//! # Exmaple
//! ```rust
//! use zeroize::Zeroize;
//! use rosenpass_secret_memory::{secret_policy_try_use_memfd_secrets, Secret};
//! secret_policy_try_use_memfd_secrets();
//! let mut my_secret: Secret<32> = Secret::random();
//! my_secret.zeroize();
//! ```
//!
//! # Futher functionality
//! In addition to this core functionality, this library provides some more smaller tools.
//!
//! 1. [Public] and [PublicBox] provide byte array storage for public data in a manner analogous to
//! that of [Secret].
//! 2. The [debug] module provides functionality to easily create debug output for objects that are
//! backed by byte arrays or slices, like for example [Secret].
//! 3. The [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,3 +1,12 @@
//! This crates the `memsec` and `memfdsec` allocators from the [memsec] crate to be used for
//! allocations of memory on which [Secrects](crate::Secret) are stored. This, however, requires
//! that an allocator is chosen before [Secret](crate::Secret) is used the first time.
//! This module provides functionality for just that.
/// This function sets the `memfdsec` allocator as the default in case it is supported by
/// the target and uses the `memsec` allocator otherwise.
///
/// At the time of writing, the `memfdsec` allocator is just supported on linux targets.
pub fn secret_policy_try_use_memfd_secrets() {
let alloc_type = {
#[cfg(target_os = "linux")]
@@ -22,6 +31,8 @@ pub fn secret_policy_try_use_memfd_secrets() {
log::info!("Secrets will be allocated using {:?}", alloc_type);
}
/// This functions sets the `memfdsec` allocator as the default. At the time of writing
/// this is only supported on Linux targets.
#[cfg(target_os = "linux")]
pub fn secret_policy_use_only_memfd_secrets() {
let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec;
@@ -34,6 +45,7 @@ pub fn secret_policy_use_only_memfd_secrets() {
log::info!("Secrets will be allocated using {:?}", alloc_type);
}
/// This function sets the `memsec` allocator as the default. It is supported on all targets.
pub fn secret_policy_use_only_malloc_secrets() {
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
assert_eq!(

View File

@@ -15,7 +15,21 @@ use std::ops::{Deref, DerefMut};
use std::path::Path;
/// Contains information in the form of a byte array that may be known to the
/// public
/// public.
///
/// # Example
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{Public};
///
/// let mut my_public_data: Public<32> = Public::random();
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_public_data.randomize();
/// // A Public can be overwritten with zeros.
/// my_public_data.zeroize();
/// // If a Public is printed as Debug, its content is printed byte for byte.
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
/// ```
// TODO: We should get rid of the Public type; just use a normal value
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
@@ -24,75 +38,84 @@ pub struct Public<const N: usize> {
}
impl<const N: usize> Public<N> {
/// Create a new [Public] from a byte slice
/// Create a new [Public] from a byte slice.
pub fn from_slice(value: &[u8]) -> Self {
copy_slice(value).to_this(Self::zero)
}
/// Create a new [Public] from a byte array
/// Create a new [Public] from a byte array.
pub fn new(value: [u8; N]) -> Self {
Self { value }
}
/// Create a zero initialized [Public]
/// Create a zero initialized [Public].
pub fn zero() -> Self {
Self { value: [0u8; N] }
}
/// Create a random initialized [Public]
/// Create a random initialized [Public].
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Randomize all bytes in an existing [Public]
/// Randomize all bytes in an existing [Public].
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
}
impl<const N: usize> Randomize for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.value.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&self.value, fmt)
}
}
impl<const N: usize> Deref for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Target = [u8; N];
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> DerefMut for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8] {
&mut self.value
}
@@ -101,6 +124,7 @@ impl<const N: usize> BorrowMut<[u8]> for Public<N> {
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
@@ -111,6 +135,7 @@ impl<const N: usize> LoadValue for Public<N> {
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
@@ -118,8 +143,10 @@ impl<const N: usize> StoreValue for Public<N> {
}
impl<const N: usize> LoadValueB64 for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
@@ -142,6 +169,7 @@ impl<const N: usize> LoadValueB64 for Public<N> {
impl<const N: usize> StoreValueB64 for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let p = path.as_ref();
let mut f = [0u8; F];
@@ -155,8 +183,10 @@ impl<const N: usize> StoreValueB64 for Public<N> {
}
impl<const N: usize> StoreValueB64Writer for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: std::io::Write>(
&self,
mut writer: W,
@@ -172,89 +202,117 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
}
}
/// A [Box] around a [Public] so that the latter one can be allocated on the heap.
///
/// # Example
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{Public, PublicBox};
///
/// let mut my_public_data: Public<32> = Public::random();
/// let mut my_bbox: PublicBox<32> = PublicBox{ inner: Box::new(my_public_data)};
///
/// // Now we can practically handle it just as we would handle the Public itself:
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_public_data.randomize();
/// // A Public can be overwritten with zeros.
/// my_public_data.zeroize();
/// // If a Public is printed as Debug, its content is printed byte for byte.
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
/// ```
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct PublicBox<const N: usize> {
/// The inner [Box] around the [Public].
pub inner: Box<Public<N>>,
}
impl<const N: usize> PublicBox<N> {
/// Create a new [PublicBox] from a byte slice
/// Create a new [PublicBox] from a byte slice.
pub fn from_slice(value: &[u8]) -> Self {
Self {
inner: Box::new(Public::from_slice(value)),
}
}
/// Create a new [PublicBox] from a byte array
/// Create a new [PublicBox] from a byte array.
pub fn new(value: [u8; N]) -> Self {
Self {
inner: Box::new(Public::new(value)),
}
}
/// Create a zero initialized [PublicBox]
/// Create a zero initialized [PublicBox].
pub fn zero() -> Self {
Self {
inner: Box::new(Public::zero()),
}
}
/// Create a random initialized [PublicBox]
/// Create a random initialized [PublicBox].
pub fn random() -> Self {
Self {
inner: Box::new(Public::random()),
}
}
/// Randomize all bytes in an existing [PublicBox]
/// Randomize all bytes in an existing [PublicBox].
pub fn randomize(&mut self) {
self.inner.randomize()
}
}
impl<const N: usize> Randomize for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.inner.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&**self, fmt)
}
}
impl<const N: usize> Deref for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Target = [u8; N];
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &[u8; N] {
self.inner.deref()
}
}
impl<const N: usize> DerefMut for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut [u8; N] {
self.inner.deref_mut()
}
}
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
self.deref()
}
}
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8] {
self.deref_mut()
}
}
impl<const N: usize> LoadValue for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut p = Self::random();
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
@@ -263,22 +321,26 @@ impl<const N: usize> LoadValue for PublicBox<N> {
}
impl<const N: usize> StoreValue for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store(path)
}
}
impl<const N: usize> LoadValueB64 for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory
// This is implemented separately from Public to avoid allocating too much stack memory.
// No extra documentation here because the Trait already provides a good documentation.
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
{
// A vector is used here to ensure heap allocation without copy from stack
// A vector is used here to ensure heap allocation without copy from stack.
let mut f = vec![0u8; F];
let mut v = PublicBox::zero();
let p = path.as_ref();
@@ -295,16 +357,20 @@ impl<const N: usize> LoadValueB64 for PublicBox<N> {
}
impl<const N: usize> StoreValueB64 for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store_b64::<F, P>(path)
}
}
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: std::io::Write>(
&self,
writer: W,
@@ -320,6 +386,7 @@ mod tests {
#[allow(clippy::module_inception)]
mod tests {
use crate::{Public, PublicBox};
use rand::Fill;
use rosenpass_util::{
b64::b64_encode,
file::{
@@ -449,5 +516,112 @@ 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,5 +1,9 @@
//! This module provides functionality for generating random numbers using the [rand] crate.
/// We use the [ThreadRng](rand::rngs::ThreadRng) for randomness in this crate.
pub type Rng = rand::rngs::ThreadRng;
/// Get the default [Rng].
pub fn rng() -> Rng {
rand::thread_rng()
}

View File

@@ -27,6 +27,7 @@ thread_local! {
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
}
/// Executes the given function `f` with the [SECRET_CACHE] as a parameter.
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
where
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
@@ -47,37 +48,47 @@ where
.unwrap_or_else(|| f(None))
}
// Wrapper around SecretBox that applies automatic zeroization
/// Wrapper around SecretBox that applies automatic zeroization.
#[derive(Debug)]
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
impl<T: Zeroize> ZeroizingSecretBox<T> {
/// Creates a new [ZeroizingSecretBox] around `boxed` for the type `T`, where `T` must implement
/// [Zeroize].
fn new(boxed: T) -> Self {
ZeroizingSecretBox(Some(secret_box(boxed)))
}
}
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
/// Creates a new [ZeroizingSecretBox] from a [SecretBox] 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()
}
@@ -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>");
/// // If you need to, you can zeroize a [Secret] at any time it's necessary:
/// 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> {
/// 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()) {
@@ -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(())
@@ -320,7 +379,10 @@ impl<const N: usize> StoreSecret for Secret<N> {
#[cfg(test)]
mod test {
use crate::test_spawn_process_provided_policies;
use crate::{
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
test_spawn_process_provided_policies,
};
use super::*;
use std::{fs, os::unix::fs::PermissionsExt};
@@ -328,7 +390,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 +401,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 +413,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 +434,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 +471,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!({
@@ -463,4 +526,33 @@ 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>");
}
}

View File

@@ -17,13 +17,14 @@ use rosenpass_to::{to, with_destination, To};
use std::ops::BitXorAssign;
// Destination functions return some value that implements the To trait.
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
// Unfortunately dealing with lifetimes is a bit more finicky than it would
// be without destination parameters
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
where
T: BitXorAssign + Clone,
{
// Custom implementations of the to trait can be created, but the easiest
// way to create them is to use the provided helper functions like with_destination.
with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) {

View File

@@ -1,4 +1,5 @@
//! Functions with destination copying data between slices and arrays.
//! Functions that make it easy to copy data between arrays and slices using functions with
//! destinations. See the specific functions for examples and more explanations.
use crate::{with_destination, To};
@@ -8,6 +9,17 @@ use crate::{with_destination, To};
/// # Panics
///
/// This function will panic if the two slices have different lengths.
///
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::copy_slice;
/// let to_function = copy_slice(&[0; 16]);
/// let mut dst = [255; 16];
/// to_function.to(&mut dst);
/// // After the operation `dst` will hold the same data as the original slice.
/// assert!(dst.iter().all(|b| *b == 0));
/// ```
pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
where
T: Copy,
@@ -23,6 +35,19 @@ where
/// # Panics
///
/// This function will panic if destination is shorter than origin.
///
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::copy_slice_least_src;
/// let to_function = copy_slice_least_src(&[0; 16]);
/// let mut dst = [255; 32];
/// to_function.to(&mut dst);
/// // After the operation the first half of `dst` will hold the same data as the original slice.
/// assert!(dst[0..16].iter().all(|b| *b == 0));
/// // The second half will have remained the same
/// assert!(dst[16..32].iter().all(|b| *b == 255));
/// ```
pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
where
T: Copy,
@@ -34,6 +59,18 @@ where
/// destination.
///
/// Copies as much data as is present in the shorter slice.
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::copy_slice_least;
/// let to_function = copy_slice_least(&[0; 16]);
/// let mut dst = [255; 32];
/// to_function.to(&mut dst);
/// // After the operation the first half of `dst` will hold the same data as the original slice.
/// assert!(dst[0..16].iter().all(|b| *b == 0));
/// // The second half will have remained the same.
/// assert!(dst[16..32].iter().all(|b| *b == 255));
/// ```
pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
where
T: Copy,
@@ -47,6 +84,24 @@ where
/// Function with destination that attempts to copy data from origin into the destination.
///
/// Will return None if the slices are of different lengths.
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::try_copy_slice;
/// let to_function = try_copy_slice(&[0; 16]);
/// let mut dst = [255; 32];
/// let result = to_function.to(&mut dst);
/// // This will return None because the slices do not have the same length.
/// assert!(result.is_none());
///
/// let to_function = try_copy_slice(&[0; 16]);
/// let mut dst = [255; 16];
/// let result = to_function.to(&mut dst);
/// // This time it works:
/// assert!(result.is_some());
/// // After the operation `dst` will hold the same data as the original slice.
/// assert!(dst.iter().all(|b| *b == 0));
/// ```
pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where
T: Copy,
@@ -62,6 +117,26 @@ where
/// Destination may be longer than origin.
///
/// Will return None if the destination is shorter than origin.
/// # Example
/// ```rust
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::try_copy_slice_least_src;
/// let to_function = try_copy_slice_least_src(&[0; 16]);
/// let mut dst = [255; 15];
/// let result = to_function.to(&mut dst);
/// // This will return None because the destination is to short.
/// assert!(result.is_none());
///
/// let to_function = try_copy_slice_least_src(&[0; 16]);
/// let mut dst = [255; 32];
/// let result = to_function.to(&mut dst);
/// // This time it works:
/// assert!(result.is_some());
/// // After the operation, the first half of `dst` will hold the same data as the original slice.
/// assert!(dst[0..16].iter().all(|b| *b == 0));
/// // The second half will have remained the same.
/// assert!(dst[16..32].iter().all(|b| *b == 255));
/// ```
pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where
T: Copy,
@@ -72,6 +147,18 @@ where
}
/// Function with destination that copies all data between two array references.
///
/// # Example
/// ```rust
/// use rosenpass_to::ops::copy_array;
/// use rosenpass_to::To;
/// let my_arr: [u8; 32] = [0; 32];
/// let to_function = copy_array(&my_arr);
/// let mut dst = [255; 32];
/// to_function.to(&mut dst);
/// // After the operation `dst` will hold the same data as the original slice.
/// assert!(dst.iter().all(|b| *b == 0));
/// ```
pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
where
T: Copy,

View File

@@ -1,6 +1,11 @@
//! This module provides the [Beside] struct. In the context of functions with targets,
//! [Beside] structures the destination value and the return value unmistakably and offers useful
//! helper functions to work with them.
use crate::CondenseBeside;
/// Named tuple holding the return value and the output from a function with destinations.
/// Named tuple holding the return value and the destination from a function with destinations.
/// See the respective functions for usage examples.
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
pub struct Beside<Val, Ret>(pub Val, pub Ret);
@@ -59,7 +64,7 @@ impl<Val, Ret> Beside<Val, Ret> {
&mut self.1
}
/// Perform beside condensation. See [CondenseBeside]
/// Perform beside condensation. See [CondenseBeside] for more details.
///
/// # Example
/// ```
@@ -90,3 +95,25 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
(val, ret)
}
}
#[cfg(test)]
mod tests {
use crate::Beside;
#[test]
fn from_tuple() {
let tuple = (21u8, 42u16);
let beside: Beside<u8, u16> = Beside::from(tuple);
assert_eq!(beside.dest(), &21u8);
assert_eq!(beside.ret(), &42u16);
}
#[test]
fn from_beside() {
let beside: Beside<u8, u16> = Beside(21u8, 42u16);
type U8u16 = (u8, u16);
let tuple = U8u16::from(beside);
assert_eq!(tuple.0, 21u8);
assert_eq!(tuple.1, 42u16);
}
}

View File

@@ -1,4 +1,11 @@
/// Beside condensation.
//! This module provides condensation for values that stand side by side,
//! which is often useful when working with destination parameters. See [CondenseBeside]
//! for more details.
/// Condenses two values that stand beside each other into one value.
/// For example, a blanked implementation for [Result<(), Error>](Result) is provided. If
/// `condense(val)` is called on such an object, a [Result<Val, Error>](Result) will
/// be returned, if `val` is of type `Val`.
///
/// This trait can be used to enable the use of [to_this(|| ...)](crate::To::to_this),
/// [to_value()](crate::To::to_value), and [collect::<...>()](crate::To::collect) with custom
@@ -6,6 +13,19 @@
///
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
/// condense trait.
///
/// # Example
/// As an example implementation, we take a look at the blanket implementation for [Option]
/// ```ignore
/// impl<Val> CondenseBeside<Val> for Option<()> {
/// type Condensed = Option<Val>;
///
/// /// Replaces the empty tuple inside this [Option] with `ret`.
/// fn condense(self, ret: Val) -> Option<Val> {
/// self.map(|()| ret)
/// }
/// }
/// ```
pub trait CondenseBeside<Val> {
/// The type that results from condensation.
type Condensed;
@@ -17,6 +37,7 @@ pub trait CondenseBeside<Val> {
impl<Val> CondenseBeside<Val> for () {
type Condensed = Val;
/// Replaces this empty tuple with `ret`.
fn condense(self, ret: Val) -> Val {
ret
}
@@ -25,6 +46,7 @@ impl<Val> CondenseBeside<Val> for () {
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
type Condensed = Result<Val, Error>;
/// Replaces the empty tuple inside this [Result] with `ret`.
fn condense(self, ret: Val) -> Result<Val, Error> {
self.map(|()| ret)
}
@@ -33,6 +55,7 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
impl<Val> CondenseBeside<Val> for Option<()> {
type Condensed = Option<Val>;
/// Replaces the empty tuple inside this [Option] with `ret`.
fn condense(self, ret: Val) -> Option<Val> {
self.map(|()| ret)
}

View File

@@ -1,5 +1,28 @@
/// Helper performing explicit unsized coercion.
/// Used by the [to](crate::to()) function.
//! This module provides explicit type coercion from [Sized] types to [?Sized][core::marker::Sized]
//! types. See [DstCoercion] for more details.
/// Helper Trait for performing explicit coercion from [Sized] types to
/// [?Sized][core::marker::Sized] types. It's used by the [to](crate::to()) function.
///
/// We provide blanket implementations for any [Sized] type and for any array of [Sized] types.
///
/// # Example
/// It can be used as follows:
/// ```
/// # use rosenpass_to::DstCoercion;
/// // Consider a sized type like this example:
/// struct SizedStruct {
/// x: u32
/// }
/// // Then we can coerce it to be unsized:
/// let mut sized = SizedStruct { x: 42 };
/// assert_eq!(42, sized.coerce_dest().x);
///
/// // Analogously, we can coerce arrays to slices:
/// let mut sized_array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
/// let un_sized: &[i32] = sized_array.coerce_dest();
/// assert_eq!(un_sized, un_sized);
/// ```
pub trait DstCoercion<Dst: ?Sized> {
/// Performs an explicit coercion to the destination type.
fn coerce_dest(&mut self) -> &mut Dst;

View File

@@ -6,11 +6,16 @@
//! - `Dst: ?Sized`; (e.g. [u8]) The target to write to
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) A reference to the target to write to
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) Some value that
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or
//! some sized variant of
//! `Dst` (e.g. `[u8; 64]`).
//! - `Ret: Sized`; (anything) must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) Some owned storage that can be borrowed as `Dst`
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
//! - `Ret: Sized`; (anything) must be `CondenseBeside<_>` if condensing is to be applied. The
//! ordinary return value of a function with an output
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) Some owned storage that can be borrowed as
//! `Dst`
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>)
//! The combiation of Val and Ret after condensing was applied
//! (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
pub mod beside;
pub mod condense;

View File

@@ -1,9 +1,23 @@
//! This module provides the [To::to] function which allows to use functions with destination in
//! a manner akin to that of a variable assignment. See [To::to] for more details.
use crate::{DstCoercion, To};
/// Alias for [To::to] moving the destination to the left.
///
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
/// the variable to assign to on the left and the generating function on the right.
///
/// # Example
/// ```rust
/// // Using the to function to have data flowing from the right to the left,
/// // performing something akin to a variable assignment.
/// use rosenpass_to::ops::copy_slice_least;
/// # use rosenpass_to::to;
/// let mut dst = b" ".to_vec();
/// to(&mut dst[..], copy_slice_least(b"Hello World"));
/// assert_eq!(&dst[..], b"Hello World");
/// ```
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
where
Coercable: ?Sized + DstCoercion<Dst>,

View File

@@ -1,12 +1,46 @@
//! Module that contains the [To] crate which is the container used to
//! implement the core functionality of this crate.
use crate::{Beside, CondenseBeside};
use std::borrow::BorrowMut;
/// The To trait is the core of the to crate; most functions with destinations will either return
/// an object that is an instance of this trait or they will return `-> impl To<Destination,
/// Return_value`.
/// an object that is an instance of this trait, or they will return `-> impl To<Destination,
/// Return_value>`.
///
/// A quick way to implement a function with destination is to use the
/// [with_destination(|param: &mut Type| ...)] higher order function.
/// [with_destination(|param: &mut Type| ...)](crate::with_destination) higher order function.
///
/// # Example
/// Below, we provide a very simple example for how the Trait can be implemented. More examples for
/// how this Trait is best implemented can be found in the overall [crate documentation](crate).
/// ```
/// use rosenpass_to::To;
///
/// // This is a simple wrapper around a String that can be written into a byte array using to.
/// struct StringToBytes {
/// inner: String
/// }
///
/// impl To<[u8], Result<(), String>> for StringToBytes {
/// fn to(self, out: &mut [u8]) -> Result<(), String> {
/// let bytes = self.inner.as_bytes();
/// if bytes.len() > out.len() {
/// return Err("out is to short".to_string());
/// }
/// for i in 0..bytes.len() {
/// (*out)[i] = bytes[i];
/// }
/// Ok(())
/// }
/// }
///
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
/// let mut buffer: [u8; 10] = [0; 10];
/// let result = string_to_bytes.to(&mut buffer);
/// assert_eq!(buffer, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
/// assert!(result.is_ok());
/// ```
pub trait To<Dst: ?Sized, Ret>: Sized {
/// Writes self to the destination `out` and returns a value of type `Ret`.
///
@@ -19,6 +53,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return
/// value.
///
/// # Example
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
/// [self.to_this_beside]. We refer to the overall [crate documentation](crate)
/// for more examples and general explanations.
/// ```
/// # use rosenpass_to::To;
/// use rosenpass_to::Beside;
/// # struct StringToBytes {
/// # inner: String
/// # }
///
/// # impl To<[u8], Result<(), String>> for StringToBytes {
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
/// # let bytes = self.inner.as_bytes();
/// # if bytes.len() > out.len() {
/// # return Err("out is to short".to_string());
/// # }
/// # for i in 0..bytes.len() {
/// # (*out)[i] = bytes[i];
/// # }
/// # Ok(())
/// # }
/// # }
/// // StringToBytes is taken from the overall Trait example.
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
/// let Beside(dst, result) = string_to_bytes.to_this_beside(|| [0; 10]);
/// assert_eq!(dst, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
/// assert!(result.is_ok());
/// ```
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
where
Val: BorrowMut<Dst>,
@@ -31,10 +95,21 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// Generate a destination on the fly using default.
///
/// Uses [Default] to create a value,
/// calls [crate::to()] to evaluate the function and finally
/// Uses [Default] to create a value, calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return
/// value.
///
/// # Example
/// Below, we provide a simple example for the usage of [to_value_beside](To::to_value_beside).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
/// let Beside(dst, ret) = copy_array(&[42u8; 16]).to_value_beside();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn to_value_beside(self) -> Beside<Dst, Ret>
where
Dst: Sized + Default,
@@ -53,6 +128,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// when the Destination is unsized.
///
/// This could be the case when the destination is an `[u8]` for instance.
///
/// # Example
/// Below, we provide a simple example for the usage of [collect_beside](To::collect_beside).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
///
/// let Beside(dst, ret) = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn collect_beside<Val>(self) -> Beside<Val, Ret>
where
Val: Default + BorrowMut<Dst>,
@@ -64,6 +152,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one.
///
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
/// # Example
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
/// [Self::to_this]. We refer to the overall [crate documentation](crate)
/// for more examples and general explanations.
/// ```
/// # use rosenpass_to::To;
/// use rosenpass_to::Beside;
/// # struct StringToBytes {
/// # inner: String
/// # }
///
/// # impl To<[u8], Result<(), String>> for StringToBytes {
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
/// # let bytes = self.inner.as_bytes();
/// # if bytes.len() > out.len() {
/// # return Err("out is to short".to_string());
/// # }
/// # for i in 0..bytes.len() {
/// # (*out)[i] = bytes[i];
/// # }
/// # Ok(())
/// # }
/// # }
/// // StringToBytes is taken from the overall Trait example.
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
/// let result = string_to_bytes.to_this_beside(|| [0; 10]).condense();
/// assert!(result.is_ok());
/// assert_eq!(result.unwrap(), [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
///
/// ```
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
where
Ret: CondenseBeside<Val>,
@@ -77,6 +195,18 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one.
///
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
///
/// # Example
/// Below, we provide a simple example for the usage of [to_value](To::to_value).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
/// let dst = copy_array(&[42u8; 16]).to_value_beside().condense();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
where
Dst: Sized + Default,
@@ -89,6 +219,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one.
///
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
///
/// # Example
/// Below, we provide a simple example for the usage of [collect](To::collect).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
///
/// let dst = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>().condense();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
where
Val: Default + BorrowMut<Dst>,

View File

@@ -1,15 +1,19 @@
//! The module provides the [with_destination] function, which makes it easy to create
//! a [To] from a lambda function. See [with_destination] and the [crate documentation](crate)
//! for more details and examples.
use crate::To;
use std::marker::PhantomData;
/// A struct that wraps a closure and implements the `To` trait
/// A struct that wraps a closure and implements the `To` trait.
///
/// This allows passing closures that operate on a destination type `Dst`
/// and return `Ret`.
/// and return `Ret`. It is only internally used to implement [with_destination].
///
/// # Type Parameters
/// * `Dst` - The destination type the closure operates on
/// * `Ret` - The return type of the closure
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
/// * `Dst` - The destination type the closure operates on.
/// * `Ret` - The return type of the closure.
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`.
struct ToClosure<Dst, Ret, Fun>
where
Dst: ?Sized,
@@ -17,11 +21,11 @@ where
{
/// The function to call.
fun: Fun,
/// Phantom data to hold the destination type
/// Phantom data to hold the destination type.
_val: PhantomData<Box<Dst>>,
}
/// Implementation of the `To` trait for ToClosure
/// Implementation of the `To` trait for ToClosure.
///
/// This enables calling the wrapped closure with a destination reference.
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
@@ -33,6 +37,7 @@ where
///
/// # Arguments
/// * `out` - Mutable reference to the destination
/// See the tutorial in [readme.md] for examples and more explanations.
fn to(self, out: &mut Dst) -> Ret {
(self.fun)(out)
}
@@ -48,7 +53,24 @@ where
/// * `Ret` - The return type of the closure
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
///
/// See the tutorial in [readme.me]..
/// See the tutorial in the [crate documentation](crate) for more examples and more explanations.
/// # Example
/// ```
/// # use rosenpass_to::with_destination;
/// use crate::rosenpass_to::To;
/// let my_origin_data: [u8; 16]= [2; 16];
/// let times_two = with_destination( move |dst: &mut [u8; 16]| {
/// for (dst, org) in dst.iter_mut().zip(my_origin_data.iter()) {
/// *dst = dst.clone() * org;
/// }
/// });
/// let mut dst: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
/// times_two.to(&mut dst);
/// for i in 0..16 {
/// assert_eq!(dst[i], (2 * i) as u8);
/// }
///
/// ```
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
where
Dst: ?Sized,