mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 13:24:38 +03:00
Compare commits
16 Commits
docu-tests
...
dev/karo/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47b556e317 | ||
|
|
53168dc62d | ||
|
|
2cfe703118 | ||
|
|
a2d7c3aaa6 | ||
|
|
1aa111570e | ||
|
|
a91d61f9f0 | ||
|
|
ff7827c24e | ||
|
|
255e377d29 | ||
|
|
50505d81cc | ||
|
|
10484cc6d4 | ||
|
|
d27e602f43 | ||
|
|
a279dfc0b1 | ||
|
|
caf2f6bfec | ||
|
|
d398ad369e | ||
|
|
00696321ff | ||
|
|
0353c82729 |
@@ -20,3 +20,6 @@ memsec = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)'] }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal 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"));
|
||||
}
|
||||
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
18
rosenpass/tests/config_Rosenpass_new.rs
Normal 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);
|
||||
}
|
||||
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal 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()
|
||||
}]
|
||||
);
|
||||
}
|
||||
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
42
rosenpass/tests/config_Rosenpass_store.rs
Normal 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(())
|
||||
}
|
||||
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal 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(())
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
//! This module provides a helper for creating debug prints for byte slices.
|
||||
//! See [debug_crypto_array] for more details.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter].
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::fmt::{Debug, Formatter};
|
||||
/// use rosenpass_secret_memory::debug::debug_crypto_array;
|
||||
///
|
||||
/// struct U8Wrapper {
|
||||
/// pub u_eigt: Vec<u8>
|
||||
/// }
|
||||
/// impl Debug for U8Wrapper {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
/// // let dead_beef: [u8; 11] = [3, 3, 6, 5, 3, 3, 3, 7, 3, 5, 7];
|
||||
/// debug_crypto_array(self.u_eigt.as_slice(), f)
|
||||
/// }
|
||||
/// }
|
||||
/// // Short byte slices are printed completely.
|
||||
/// let cafe = U8Wrapper {u_eigt: vec![1, 4, 5, 3, 7, 6]};
|
||||
/// assert_eq!(format!("{:?}", cafe), "[{}]=145376");
|
||||
/// // For longer byte slices, only the first 32 and last 32 bytes are printed.
|
||||
/// let all_u8 = U8Wrapper {u_eigt: (0..256).map(|i| i as u8).collect()};
|
||||
/// assert_eq!(format!("{:?}", all_u8), "[{}]=0123456789abcdef101112131415161718191a1b1c1d1e1f…e0e\
|
||||
/// 1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
|
||||
/// ```
|
||||
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("[{}]=")?;
|
||||
if v.len() > 64 {
|
||||
|
||||
@@ -1,8 +1,46 @@
|
||||
//! Objects that implement this Trait provide a way to store their data in way that respects the
|
||||
//! confidentiality of its data. Specifically, an object implementing this Trait guarantees
|
||||
//! if its data with [store_secret](StoreSecret::store_secret) are saved in the file with visibility
|
||||
//! equivalent to [rosenpass_util::file::Visibility::Secret].
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// Objects that implement this Trait provide a standard method to be stored securely. The trait can
|
||||
/// be implemented as follows for example:
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use std::io::Write;
|
||||
/// use std::path::Path;
|
||||
/// use rosenpass_secret_memory::file::StoreSecret;
|
||||
///
|
||||
/// use rosenpass_util::file::{fopen_w, Visibility};
|
||||
///
|
||||
/// struct MyWeirdI32 {
|
||||
/// _priv_i32: [u8; 4],
|
||||
/// }
|
||||
///
|
||||
/// impl StoreSecret for MyWeirdI32 {
|
||||
/// type Error = std::io::Error;
|
||||
///
|
||||
/// fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||
/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||
/// fopen_w(path, Visibility::Public)?.write_all(&self._priv_i32)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait StoreSecret {
|
||||
type Error;
|
||||
|
||||
/// Stores the object securely. In particular, it ensures that the visibility is equivalent to
|
||||
/// [rosenpass_util::file::Visibility::Secret].
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
|
||||
/// Stores the object. No requirement on the visibility is given, but it is common to store
|
||||
/// the data with visibility equivalent to [rosenpass_util::file::Visibility::Public].
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
//! This library provides functionality for working with secret data and protecting it in
|
||||
//! memory from illegitimate access.
|
||||
//!
|
||||
//! Specifically, the [alloc] module provides wrappers around the `memsec` and `memfdsec` allocators
|
||||
//! from the [memsec] crate that implement the [Allocator](allocator_api2::alloc::Allocator) Trait.
|
||||
//! We refer to the documentation of these modules for more details on their appropriate usage.
|
||||
//!
|
||||
//! The [policy] module then provides functionality for specifying which of the allocators from
|
||||
//! the [alloc] module should be used.
|
||||
//!
|
||||
//! Once this configuration is made [Secret] can be used to store sensitive data in memory
|
||||
//! allocated by the configured allocator. [Secret] is implemented such that memory is *aloways*
|
||||
//! zeroized before it is released. Because allocations of the protected memory are expensive to do,
|
||||
//! [Secret] is build to reuse once allocated memory. A simple use of [Secret] looks as follows:
|
||||
//! # Exmaple
|
||||
//! ```rust
|
||||
//! use zeroize::Zeroize;
|
||||
//! use rosenpass_secret_memory::{secret_policy_try_use_memfd_secrets, Secret};
|
||||
//! secret_policy_try_use_memfd_secrets();
|
||||
//! let mut my_secret: Secret<32> = Secret::random();
|
||||
//! my_secret.zeroize();
|
||||
//! ```
|
||||
//!
|
||||
//! # Futher functionality
|
||||
//! In addition to this core functionality, this library provides some more smaller tools.
|
||||
//!
|
||||
//! 1. [Public] and [PublicBox] provide byte array storage for public data in a manner analogous to
|
||||
//! that of [Secret].
|
||||
//! 2. The [debug] module provides functionality to easily create debug output for objects that are
|
||||
//! backed by byte arrays or slices, like for example [Secret].
|
||||
//! 3. The [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;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! This crates the `memsec` and `memfdsec` allocators from the [memsec] crate to be used for
|
||||
//! allocations of memory on which [Secrects](crate::Secret) are stored. This, however, requires
|
||||
//! that an allocator is chosen before [Secret](crate::Secret) is used the first time.
|
||||
//! This module provides functionality for just that.
|
||||
|
||||
/// This function sets the `memfdsec` allocator as the default in case it is supported by
|
||||
/// the target and uses the `memsec` allocator otherwise.
|
||||
///
|
||||
/// At the time of writing, the `memfdsec` allocator is just supported on linux targets.
|
||||
pub fn secret_policy_try_use_memfd_secrets() {
|
||||
let alloc_type = {
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -22,6 +31,8 @@ pub fn secret_policy_try_use_memfd_secrets() {
|
||||
log::info!("Secrets will be allocated using {:?}", alloc_type);
|
||||
}
|
||||
|
||||
/// This functions sets the `memfdsec` allocator as the default. At the time of writing
|
||||
/// this is only supported on Linux targets.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn secret_policy_use_only_memfd_secrets() {
|
||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec;
|
||||
@@ -34,6 +45,7 @@ pub fn secret_policy_use_only_memfd_secrets() {
|
||||
log::info!("Secrets will be allocated using {:?}", alloc_type);
|
||||
}
|
||||
|
||||
/// This function sets the `memsec` allocator as the default. It is supported on all targets.
|
||||
pub fn secret_policy_use_only_malloc_secrets() {
|
||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
|
||||
assert_eq!(
|
||||
|
||||
@@ -15,7 +15,21 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::path::Path;
|
||||
|
||||
/// Contains information in the form of a byte array that may be known to the
|
||||
/// public
|
||||
/// public.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{Public};
|
||||
///
|
||||
/// let mut my_public_data: Public<32> = Public::random();
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_public_data.randomize();
|
||||
/// // A Public can be overwritten with zeros.
|
||||
/// my_public_data.zeroize();
|
||||
/// // If a Public is printed as Debug, its content is printed byte for byte.
|
||||
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
|
||||
/// ```
|
||||
// TODO: We should get rid of the Public type; just use a normal value
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
@@ -24,75 +38,84 @@ pub struct Public<const N: usize> {
|
||||
}
|
||||
|
||||
impl<const N: usize> Public<N> {
|
||||
/// Create a new [Public] from a byte slice
|
||||
/// Create a new [Public] from a byte slice.
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
copy_slice(value).to_this(Self::zero)
|
||||
}
|
||||
|
||||
/// Create a new [Public] from a byte array
|
||||
/// Create a new [Public] from a byte array.
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// Create a zero initialized [Public]
|
||||
/// Create a zero initialized [Public].
|
||||
pub fn zero() -> Self {
|
||||
Self { value: [0u8; N] }
|
||||
}
|
||||
|
||||
/// Create a random initialized [Public]
|
||||
/// Create a random initialized [Public].
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [Public]
|
||||
/// Randomize all bytes in an existing [Public].
|
||||
pub fn randomize(&mut self) {
|
||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
self.value.try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&self.value, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Target = [u8; N];
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.value
|
||||
}
|
||||
@@ -101,6 +124,7 @@ impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
||||
impl<const N: usize> LoadValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut v = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
||||
@@ -111,6 +135,7 @@ impl<const N: usize> LoadValue for Public<N> {
|
||||
impl<const N: usize> StoreValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
std::fs::write(path, **self)?;
|
||||
Ok(())
|
||||
@@ -118,8 +143,10 @@ impl<const N: usize> StoreValue for Public<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -142,6 +169,7 @@ impl<const N: usize> LoadValueB64 for Public<N> {
|
||||
impl<const N: usize> StoreValueB64 for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
let p = path.as_ref();
|
||||
let mut f = [0u8; F];
|
||||
@@ -155,8 +183,10 @@ impl<const N: usize> StoreValueB64 for Public<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||
&self,
|
||||
mut writer: W,
|
||||
@@ -172,89 +202,117 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Box] around a [Public] so that the latter one can be allocated on the heap.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{Public, PublicBox};
|
||||
///
|
||||
/// let mut my_public_data: Public<32> = Public::random();
|
||||
/// let mut my_bbox: PublicBox<32> = PublicBox{ inner: Box::new(my_public_data)};
|
||||
///
|
||||
/// // Now we can practically handle it just as we would handle the Public itself:
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_public_data.randomize();
|
||||
/// // A Public can be overwritten with zeros.
|
||||
/// my_public_data.zeroize();
|
||||
/// // If a Public is printed as Debug, its content is printed byte for byte.
|
||||
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
|
||||
/// ```
|
||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct PublicBox<const N: usize> {
|
||||
/// The inner [Box] around the [Public].
|
||||
pub inner: Box<Public<N>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> PublicBox<N> {
|
||||
/// Create a new [PublicBox] from a byte slice
|
||||
/// Create a new [PublicBox] from a byte slice.
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::from_slice(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [PublicBox] from a byte array
|
||||
/// Create a new [PublicBox] from a byte array.
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a zero initialized [PublicBox]
|
||||
/// Create a zero initialized [PublicBox].
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a random initialized [PublicBox]
|
||||
/// Create a random initialized [PublicBox].
|
||||
pub fn random() -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::random()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [PublicBox]
|
||||
/// Randomize all bytes in an existing [PublicBox].
|
||||
pub fn randomize(&mut self) {
|
||||
self.inner.randomize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
self.inner.try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&**self, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Target = [u8; N];
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
self.inner.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8] {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
self.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValue for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut p = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
|
||||
@@ -263,22 +321,26 @@ impl<const N: usize> LoadValue for PublicBox<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValue for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
self.inner.store(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory.
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// A vector is used here to ensure heap allocation without copy from stack
|
||||
// A vector is used here to ensure heap allocation without copy from stack.
|
||||
let mut f = vec![0u8; F];
|
||||
let mut v = PublicBox::zero();
|
||||
let p = path.as_ref();
|
||||
@@ -295,16 +357,20 @@ impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64 for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
self.inner.store_b64::<F, P>(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||
&self,
|
||||
writer: W,
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! This module provides functionality for generating random numbers using the [rand] crate.
|
||||
|
||||
/// We use the [ThreadRng](rand::rngs::ThreadRng) for randomness in this crate.
|
||||
pub type Rng = rand::rngs::ThreadRng;
|
||||
|
||||
/// Get the default [Rng].
|
||||
pub fn rng() -> Rng {
|
||||
rand::thread_rng()
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ thread_local! {
|
||||
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
|
||||
}
|
||||
|
||||
/// Executes the given function `f` with the [SECRET_CACHE] as a parameter.
|
||||
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
|
||||
where
|
||||
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
|
||||
@@ -47,37 +48,47 @@ where
|
||||
.unwrap_or_else(|| f(None))
|
||||
}
|
||||
|
||||
// Wrapper around SecretBox that applies automatic zeroization
|
||||
/// Wrapper around SecretBox that applies automatic zeroization.
|
||||
#[derive(Debug)]
|
||||
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
|
||||
|
||||
impl<T: Zeroize> ZeroizingSecretBox<T> {
|
||||
/// Creates a new [ZeroizingSecretBox] around `boxed` for the type `T`, where `T` must implement
|
||||
/// [Zeroize].
|
||||
fn new(boxed: T) -> Self {
|
||||
ZeroizingSecretBox(Some(secret_box(boxed)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
|
||||
/// Creates a new [ZeroizingSecretBox] from a [SecretBox] 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>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user