mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 21:34:37 +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]
|
[dev-dependencies]
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||||
|
|||||||
@@ -113,9 +113,10 @@ mod tests {
|
|||||||
// Pearson correlation
|
// Pearson correlation
|
||||||
let correlation = cv / (sd_x * sd_y);
|
let correlation = cv / (sd_x * sd_y);
|
||||||
println!("correlation: {:.6?}", correlation);
|
println!("correlation: {:.6?}", correlation);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
correlation.abs() < 0.01,
|
correlation.abs() < 0.01,
|
||||||
"execution time correlates with result"
|
"execution time correlates with result"
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,3 +91,6 @@ experiment_api = [
|
|||||||
internal_signal_handling_for_coverage_reports = ["signal-hook"]
|
internal_signal_handling_for_coverage_reports = ["signal-hook"]
|
||||||
internal_testing = []
|
internal_testing = []
|
||||||
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
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;
|
use crate::app_server::AppServer;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct ApiConfig {
|
pub struct ApiConfig {
|
||||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||||
/// connections on
|
/// 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 anyhow::{bail, ensure, Context};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
@@ -31,15 +36,25 @@ use {
|
|||||||
std::thread,
|
std::thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// enum representing a choice of interface to a WireGuard broker
|
/// How to reach a WireGuard PSK Broker
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BrokerInterface {
|
pub enum BrokerInterface {
|
||||||
|
/// The PSK Broker is listening on a unix socket at the given path
|
||||||
Socket(PathBuf),
|
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),
|
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,
|
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)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
@@ -80,6 +95,7 @@ pub struct CliArgs {
|
|||||||
#[arg(short, long, group = "psk-broker-specs")]
|
#[arg(short, long, group = "psk-broker-specs")]
|
||||||
psk_broker_spawn: bool,
|
psk_broker_spawn: bool,
|
||||||
|
|
||||||
|
/// The subcommand to be invoked
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Option<CliCommand>,
|
pub command: Option<CliCommand>,
|
||||||
|
|
||||||
@@ -98,6 +114,10 @@ pub struct CliArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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<()> {
|
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
self.api.apply_to_config(_cfg)?;
|
self.api.apply_to_config(_cfg)?;
|
||||||
@@ -123,9 +143,11 @@ impl CliArgs {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the WireGuard PSK broker interface configured.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[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> {
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
||||||
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
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"))]
|
#[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> {
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -244,15 +267,17 @@ pub enum CliCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
/// Runs the command specified via CLI
|
/// Run Rosenpass with the given command line parameters
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// This contains the bulk of our startup logic with
|
||||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
/// the main function just setting up the basic environment
|
||||||
|
/// and then calling this function.
|
||||||
pub fn run(
|
pub fn run(
|
||||||
self,
|
self,
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
test_helpers: Option<AppServerTest>,
|
test_helpers: Option<AppServerTest>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||||
use CliCommand::*;
|
use CliCommand::*;
|
||||||
match &self.command {
|
match &self.command {
|
||||||
Some(GenConfig { config_file, force }) => {
|
Some(GenConfig { config_file, force }) => {
|
||||||
@@ -403,6 +428,7 @@ impl CliArgs {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by [Self::run] to start the Rosenpass key exchange server
|
||||||
fn event_loop(
|
fn event_loop(
|
||||||
config: config::Rosenpass,
|
config: config::Rosenpass,
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
@@ -470,6 +496,19 @@ impl CliArgs {
|
|||||||
srv.event_loop()
|
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")]
|
#[cfg(feature = "experiment_api")]
|
||||||
fn create_broker(
|
fn create_broker(
|
||||||
broker_interface: Option<BrokerInterface>,
|
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"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
fn create_broker(
|
fn create_broker(
|
||||||
_broker_interface: Option<BrokerInterface>,
|
_broker_interface: Option<BrokerInterface>,
|
||||||
@@ -492,6 +544,10 @@ impl CliArgs {
|
|||||||
Ok(Box::new(NativeUnixBroker::new()))
|
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")]
|
#[cfg(feature = "experiment_api")]
|
||||||
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
||||||
// Connect to the psk broker unix socket if one was specified
|
// 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
|
/// 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 ssk = crate::protocol::SSk::random();
|
||||||
let mut spk = crate::protocol::SPk::random();
|
let mut spk = crate::protocol::SPk::random();
|
||||||
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
//! [`Rosenpass`] which holds such a configuration.
|
//! [`Rosenpass`] which holds such a configuration.
|
||||||
//!
|
//!
|
||||||
//! ## TODO
|
//! ## TODO
|
||||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
//! - 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: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||||
|
|
||||||
use crate::protocol::{SPk, SSk};
|
use crate::protocol::{SPk, SSk};
|
||||||
use rosenpass_util::file::LoadValue;
|
use rosenpass_util::file::LoadValue;
|
||||||
use std::{
|
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 {
|
pub struct Rosenpass {
|
||||||
// TODO: Raise error if secret key or public key alone is set during deserialization
|
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||||
// SEE: https://github.com/serde-rs/serde/issues/2793
|
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||||
@@ -46,7 +50,10 @@ pub struct Rosenpass {
|
|||||||
/// list of [`SocketAddr`] to listen on
|
/// list of [`SocketAddr`] to listen on
|
||||||
///
|
///
|
||||||
/// Examples:
|
/// 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>,
|
pub listen: Vec<SocketAddr>,
|
||||||
|
|
||||||
/// log verbosity
|
/// log verbosity
|
||||||
@@ -68,6 +75,7 @@ pub struct Rosenpass {
|
|||||||
pub config_file_path: PathBuf,
|
pub config_file_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Public key and secret key locations.
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||||
pub struct Keypair {
|
pub struct Keypair {
|
||||||
/// path to the public key file
|
/// path to the public key file
|
||||||
@@ -78,6 +86,7 @@ pub struct Keypair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
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 public_key = public_key.as_ref().to_path_buf();
|
||||||
let secret_key = secret_key.as_ref().to_path_buf();
|
let secret_key = secret_key.as_ref().to_path_buf();
|
||||||
@@ -88,62 +97,72 @@ impl Keypair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// Level of verbosity for [crate::app_server::AppServer]
|
||||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
///
|
||||||
|
/// 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)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
||||||
pub enum Verbosity {
|
pub enum Verbosity {
|
||||||
Quiet,
|
Quiet,
|
||||||
Verbose,
|
Verbose,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// Configuration data for a single Rosenpass peer
|
||||||
/// - examples
|
|
||||||
/// - documentation
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct RosenpassPeer {
|
pub struct RosenpassPeer {
|
||||||
/// path to the public key of the peer
|
/// path to the public key of the peer
|
||||||
pub public_key: PathBuf,
|
pub public_key: PathBuf,
|
||||||
|
|
||||||
/// ## TODO
|
/// The hostname and port to connect to
|
||||||
/// - documentation
|
///
|
||||||
|
/// 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>,
|
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
|
/// 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>,
|
pub pre_shared_key: Option<PathBuf>,
|
||||||
|
|
||||||
/// ## TODO
|
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
|
||||||
/// - documentation
|
/// 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)]
|
#[serde(default)]
|
||||||
pub key_out: Option<PathBuf>,
|
pub key_out: Option<PathBuf>,
|
||||||
|
|
||||||
/// ## TODO
|
/// Information for supplying exchanged keys directly to WireGuard
|
||||||
/// - documentation
|
|
||||||
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub wg: Option<WireGuard>,
|
pub wg: Option<WireGuard>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// Information for supplying exchanged keys directly to WireGuard
|
||||||
/// - documentation
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WireGuard {
|
pub struct WireGuard {
|
||||||
/// ## TODO
|
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
|
||||||
/// - documentation
|
/// key exchange
|
||||||
pub device: String,
|
pub device: String,
|
||||||
|
|
||||||
/// ## TODO
|
/// WireGuard public key of the peer to supply with pre-shared keys
|
||||||
/// - documentation
|
|
||||||
pub peer: String,
|
pub peer: String,
|
||||||
|
|
||||||
/// ## TODO
|
/// Extra parameters passed to the `wg` command
|
||||||
/// - documentation
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub extra_params: Vec<String>,
|
pub extra_params: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Rosenpass {
|
impl Default for Rosenpass {
|
||||||
|
/// Generate an empty configuration
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::empty()
|
Self::empty()
|
||||||
}
|
}
|
||||||
@@ -156,8 +175,15 @@ impl Rosenpass {
|
|||||||
/// checked whether they even exist.
|
/// checked whether they even exist.
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// ## TODO
|
||||||
|
///
|
||||||
/// - consider using a different algorithm to determine home directory – the below one may
|
/// - consider using a different algorithm to determine home directory – the below one may
|
||||||
/// behave unexpectedly on Windows
|
/// 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> {
|
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
||||||
// read file and deserialize
|
// read file and deserialize
|
||||||
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
||||||
@@ -185,7 +211,13 @@ impl Rosenpass {
|
|||||||
Ok(config)
|
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<()> {
|
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
||||||
let serialized_config =
|
let serialized_config =
|
||||||
toml::to_string_pretty(&self).expect("unable to serialize the default 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
|
/// 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<()> {
|
pub fn commit(&self) -> anyhow::Result<()> {
|
||||||
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
||||||
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||||
@@ -201,13 +239,21 @@ impl Rosenpass {
|
|||||||
self.store(&self.config_file_path)
|
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<()> {
|
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
self.api.apply_to_app_server(_srv)?;
|
self.api.apply_to_app_server(_srv)?;
|
||||||
Ok(())
|
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<()> {
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
if let Some(ref keypair) = self.keypair {
|
if let Some(ref keypair) = self.keypair {
|
||||||
// check the public key file exists
|
// check the public key file exists
|
||||||
@@ -284,6 +330,21 @@ impl Rosenpass {
|
|||||||
Ok(())
|
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<()> {
|
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
||||||
#[cfg(not(feature = "experiment_api"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
||||||
@@ -299,15 +360,38 @@ impl Rosenpass {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce an empty confuguration
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self::new(None)
|
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 {
|
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||||
Self::new(Some(Keypair::new(pk, sk)))
|
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 {
|
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
keypair,
|
keypair,
|
||||||
@@ -321,6 +405,14 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
/// 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) {
|
pub fn add_if_any(&mut self, port: u16) {
|
||||||
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||||
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||||
@@ -333,8 +425,20 @@ impl Rosenpass {
|
|||||||
self.listen.push(ipv6_any);
|
self.listen.push(ipv6_any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// from chaotic args
|
/// Parser for the old, IP style grammar.
|
||||||
/// Quest: the grammar is undecideable, what do we do here?
|
///
|
||||||
|
/// 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> {
|
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||||
let mut config = Self::new(Some(Keypair::new("", "")));
|
let mut config = Self::new(Some(Keypair::new("", "")));
|
||||||
|
|
||||||
@@ -525,11 +629,13 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Verbosity {
|
impl Default for Verbosity {
|
||||||
|
/// Self::Quiet
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Quiet
|
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"
|
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
|
||||||
secret_key = "/path/to/rp-secret-key"
|
secret_key = "/path/to/rp-secret-key"
|
||||||
listen = []
|
listen = []
|
||||||
@@ -553,7 +659,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
|
|||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use super::*;
|
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> {
|
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
||||||
toml::from_str(s.borrow())
|
toml::from_str(s.borrow())
|
||||||
@@ -664,37 +770,6 @@ mod test {
|
|||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn test_cli_parse_multiple_peers() {
|
fn test_cli_parse_multiple_peers() {
|
||||||
let args = split_str(
|
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
|
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
||||||
// so the signal is absorbed
|
// so the signal is absorbed
|
||||||
loop {
|
loop {
|
||||||
rustix::process::kill_process(pid, Term).discard_result();
|
kill_process(pid, Term).discard_result();
|
||||||
if self.0.try_wait().unwrap().is_some() {
|
if self.0.try_wait().unwrap().is_some() {
|
||||||
break;
|
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::{
|
use std::{
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt::{Debug, Write},
|
ops::DerefMut,
|
||||||
ops::{DerefMut, RangeBounds},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::distributions::uniform::SampleBorrow;
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::kem::StaticKem;
|
||||||
use rosenpass_util::result::OkExt;
|
use rosenpass_util::result::OkExt;
|
||||||
@@ -28,48 +26,53 @@ fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
|
|||||||
sim.poll_loop(150)?; // Poll 75 times
|
sim.poll_loop(150)?; // Poll 75 times
|
||||||
let transcript = sim.transcript;
|
let transcript = sim.transcript;
|
||||||
|
|
||||||
let completions: Vec<_> = transcript
|
let _completions: Vec<_> = transcript
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
!completions.is_empty(),
|
!_completions.is_empty(),
|
||||||
"\
|
"\
|
||||||
Should have performed a successful key exchanged!\n\
|
Should have performed a successful key exchanged!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions[0].0 < 20.0,
|
_completions[0].0 < 20.0,
|
||||||
"\
|
"\
|
||||||
First key exchange should happen in under twenty seconds!\n\
|
First key exchange should happen in under twenty seconds!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions.len() >= 3,
|
_completions.len() >= 3,
|
||||||
"\
|
"\
|
||||||
Should have at least two renegotiations!\n\
|
Should have at least two renegotiations!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
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\
|
First renegotiation should happen in between two and three minutes!\n\
|
||||||
Transcript: {transcript:?}\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\
|
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
");
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -106,48 +109,53 @@ fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let transcript = sim.transcript;
|
let transcript = sim.transcript;
|
||||||
let completions: Vec<_> = transcript
|
let _completions: Vec<_> = transcript
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
!completions.is_empty(),
|
!_completions.is_empty(),
|
||||||
"\
|
"\
|
||||||
Should have performed a successful key exchanged!\n\
|
Should have performed a successful key exchanged!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions[0].0 < 10.0,
|
_completions[0].0 < 10.0,
|
||||||
"\
|
"\
|
||||||
First key exchange should happen in under twenty seconds!\n\
|
First key exchange should happen in under twenty seconds!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions.len() >= 3,
|
_completions.len() >= 3,
|
||||||
"\
|
"\
|
||||||
Should have at least two renegotiations!\n\
|
Should have at least two renegotiations!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
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\
|
First renegotiation should happen in between two and three minutes!\n\
|
||||||
Transcript: {transcript:?}\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\
|
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
");
|
");
|
||||||
|
|
||||||
Ok(())
|
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::fmt;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
@@ -6,31 +12,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
|||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
struct MallocAllocatorContents;
|
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)]
|
#[derive(Copy, Clone, Default)]
|
||||||
pub struct MallocAllocator {
|
pub struct MallocAllocator {
|
||||||
_dummy_private_data: MallocAllocatorContents,
|
_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>;
|
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>;
|
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> {
|
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
|
||||||
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
|
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> {
|
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
|
||||||
MallocBox::<T>::new_in(x, MallocAllocator::new())
|
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> {
|
pub fn malloc_vec<T>() -> MallocVec<T> {
|
||||||
MallocVec::<T>::new_in(MallocAllocator::new())
|
MallocVec::<T>::new_in(MallocAllocator::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MallocAllocator {
|
impl MallocAllocator {
|
||||||
|
/// Creates a new [MallocAllocator].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_dummy_private_data: MallocAllocatorContents,
|
_dummy_private_data: MallocAllocatorContents,
|
||||||
@@ -94,6 +147,9 @@ mod test {
|
|||||||
malloc_allocation_impl::<8>(&alloc);
|
malloc_allocation_impl::<8>(&alloc);
|
||||||
malloc_allocation_impl::<64>(&alloc);
|
malloc_allocation_impl::<64>(&alloc);
|
||||||
malloc_allocation_impl::<999>(&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) {
|
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")]
|
#![cfg(target_os = "linux")]
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
@@ -7,31 +13,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
|||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
struct MemfdSecAllocatorContents;
|
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)]
|
#[derive(Copy, Clone, Default)]
|
||||||
pub struct MemfdSecAllocator {
|
pub struct MemfdSecAllocator {
|
||||||
_dummy_private_data: MemfdSecAllocatorContents,
|
_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>;
|
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>;
|
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> {
|
pub fn memfdsec_box_try<T>(x: T) -> Result<MemfdSecBox<T>, AllocError> {
|
||||||
MemfdSecBox::<T>::try_new_in(x, MemfdSecAllocator::new())
|
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> {
|
pub fn memfdsec_box<T>(x: T) -> MemfdSecBox<T> {
|
||||||
MemfdSecBox::<T>::new_in(x, MemfdSecAllocator::new())
|
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> {
|
pub fn memfdsec_vec<T>() -> MemfdSecVec<T> {
|
||||||
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
|
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemfdSecAllocator {
|
impl MemfdSecAllocator {
|
||||||
|
/// Create a new [MemfdSecAllocator].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_dummy_private_data: MemfdSecAllocatorContents,
|
_dummy_private_data: MemfdSecAllocatorContents,
|
||||||
@@ -95,6 +148,9 @@ mod test {
|
|||||||
memfdsec_allocation_impl::<8>(&alloc);
|
memfdsec_allocation_impl::<8>(&alloc);
|
||||||
memfdsec_allocation_impl::<64>(&alloc);
|
memfdsec_allocation_impl::<64>(&alloc);
|
||||||
memfdsec_allocation_impl::<999>(&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) {
|
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 malloc;
|
||||||
pub mod memfdsec;
|
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;
|
pub mod memsec;
|
||||||
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
@@ -8,15 +17,50 @@ use memsec::malloc::MallocAllocator;
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use memsec::memfdsec::MemfdSecAllocator;
|
use memsec::memfdsec::MemfdSecAllocator;
|
||||||
|
|
||||||
|
/// Globally configures which [SecretAllocType] to use as default for
|
||||||
|
/// [SecretAllocators](SecretAlloc).
|
||||||
static ALLOC_TYPE: OnceLock<SecretAllocType> = OnceLock::new();
|
static ALLOC_TYPE: OnceLock<SecretAllocType> = OnceLock::new();
|
||||||
|
|
||||||
/// Sets the secret allocation type to use.
|
/// Sets the secret allocation type to use by default for [SecretAllocators](SecretAlloc).
|
||||||
/// Intended usage at startup before secret allocation
|
/// It is intended that this function is called at startup before a secret allocation
|
||||||
/// takes place
|
/// 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) {
|
pub fn set_secret_alloc_type(alloc_type: SecretAllocType) {
|
||||||
ALLOC_TYPE.set(alloc_type).unwrap();
|
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 {
|
pub fn get_or_init_secret_alloc_type(alloc_type: SecretAllocType) -> SecretAllocType {
|
||||||
*ALLOC_TYPE.get_or_init(|| alloc_type)
|
*ALLOC_TYPE.get_or_init(|| alloc_type)
|
||||||
}
|
}
|
||||||
@@ -28,6 +72,7 @@ pub enum SecretAllocType {
|
|||||||
MemsecMemfdSec,
|
MemsecMemfdSec,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An [Allocator] that uses a [SecretAllocType] for allocation.
|
||||||
pub struct SecretAlloc {
|
pub struct SecretAlloc {
|
||||||
alloc_type: SecretAllocType,
|
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>;
|
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>;
|
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> {
|
pub fn secret_box_try<T>(x: T) -> Result<SecretBox<T>, AllocError> {
|
||||||
SecretBox::<T>::try_new_in(x, SecretAlloc::default())
|
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> {
|
pub fn secret_box<T>(x: T) -> SecretBox<T> {
|
||||||
SecretBox::<T>::new_in(x, SecretAlloc::default())
|
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> {
|
pub fn secret_vec<T>() -> SecretVec<T> {
|
||||||
SecretVec::<T>::new_in(SecretAlloc::default())
|
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;
|
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 {
|
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt.write_str("[{}]=")?;
|
fmt.write_str("[{}]=")?;
|
||||||
if v.len() > 64 {
|
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;
|
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 {
|
pub trait StoreSecret {
|
||||||
type Error;
|
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>;
|
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>;
|
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 debug;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod rand;
|
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() {
|
pub fn secret_policy_try_use_memfd_secrets() {
|
||||||
let alloc_type = {
|
let alloc_type = {
|
||||||
#[cfg(target_os = "linux")]
|
#[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);
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn secret_policy_use_only_memfd_secrets() {
|
pub fn secret_policy_use_only_memfd_secrets() {
|
||||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec;
|
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);
|
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() {
|
pub fn secret_policy_use_only_malloc_secrets() {
|
||||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
|
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -15,7 +15,21 @@ use std::ops::{Deref, DerefMut};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
/// Contains information in the form of a byte array that may be known to the
|
/// 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
|
// TODO: We should get rid of the Public type; just use a normal value
|
||||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
@@ -24,75 +38,84 @@ pub struct Public<const N: usize> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Public<N> {
|
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 {
|
pub fn from_slice(value: &[u8]) -> Self {
|
||||||
copy_slice(value).to_this(Self::zero)
|
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 {
|
pub fn new(value: [u8; N]) -> Self {
|
||||||
Self { value }
|
Self { value }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a zero initialized [Public]
|
/// Create a zero initialized [Public].
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self { value: [0u8; N] }
|
Self { value: [0u8; N] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a random initialized [Public]
|
/// Create a random initialized [Public].
|
||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
mutating(Self::zero(), |r| r.randomize())
|
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) {
|
pub fn randomize(&mut self) {
|
||||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Randomize for Public<N> {
|
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> {
|
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||||
self.value.try_fill(rng)
|
self.value.try_fill(rng)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> fmt::Debug for Public<N> {
|
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 {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
debug_crypto_array(&self.value, fmt)
|
debug_crypto_array(&self.value, fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Deref for Public<N> {
|
impl<const N: usize> Deref for Public<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Target = [u8; N];
|
type Target = [u8; N];
|
||||||
|
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
fn deref(&self) -> &[u8; N] {
|
fn deref(&self) -> &[u8; N] {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> DerefMut for Public<N> {
|
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] {
|
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||||
&mut self.value
|
&mut self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
|
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] {
|
fn borrow(&self) -> &[u8; N] {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
|
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] {
|
fn borrow_mut(&mut self) -> &mut [u8; N] {
|
||||||
&mut self.value
|
&mut self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Borrow<[u8]> for Public<N> {
|
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] {
|
fn borrow(&self) -> &[u8] {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
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] {
|
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||||
&mut self.value
|
&mut self.value
|
||||||
}
|
}
|
||||||
@@ -101,6 +124,7 @@ impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
|||||||
impl<const N: usize> LoadValue for Public<N> {
|
impl<const N: usize> LoadValue for Public<N> {
|
||||||
type Error = anyhow::Error;
|
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> {
|
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||||
let mut v = Self::random();
|
let mut v = Self::random();
|
||||||
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
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> {
|
impl<const N: usize> StoreValue for Public<N> {
|
||||||
type Error = anyhow::Error;
|
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<()> {
|
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
std::fs::write(path, **self)?;
|
std::fs::write(path, **self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -118,8 +143,10 @@ impl<const N: usize> StoreValue for Public<N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> LoadValueB64 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;
|
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>
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@@ -142,6 +169,7 @@ impl<const N: usize> LoadValueB64 for Public<N> {
|
|||||||
impl<const N: usize> StoreValueB64 for Public<N> {
|
impl<const N: usize> StoreValueB64 for Public<N> {
|
||||||
type Error = anyhow::Error;
|
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<()> {
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
let p = path.as_ref();
|
let p = path.as_ref();
|
||||||
let mut f = [0u8; F];
|
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> {
|
impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Error = anyhow::Error;
|
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>(
|
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||||
&self,
|
&self,
|
||||||
mut writer: W,
|
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)]
|
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct PublicBox<const N: usize> {
|
pub struct PublicBox<const N: usize> {
|
||||||
|
/// The inner [Box] around the [Public].
|
||||||
pub inner: Box<Public<N>>,
|
pub inner: Box<Public<N>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> PublicBox<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 {
|
pub fn from_slice(value: &[u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Box::new(Public::from_slice(value)),
|
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 {
|
pub fn new(value: [u8; N]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Box::new(Public::new(value)),
|
inner: Box::new(Public::new(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a zero initialized [PublicBox]
|
/// Create a zero initialized [PublicBox].
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Box::new(Public::zero()),
|
inner: Box::new(Public::zero()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a random initialized [PublicBox]
|
/// Create a random initialized [PublicBox].
|
||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Box::new(Public::random()),
|
inner: Box::new(Public::random()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Randomize all bytes in an existing [PublicBox]
|
/// Randomize all bytes in an existing [PublicBox].
|
||||||
pub fn randomize(&mut self) {
|
pub fn randomize(&mut self) {
|
||||||
self.inner.randomize()
|
self.inner.randomize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Randomize for PublicBox<N> {
|
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> {
|
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||||
self.inner.try_fill(rng)
|
self.inner.try_fill(rng)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> fmt::Debug for PublicBox<N> {
|
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 {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
debug_crypto_array(&**self, fmt)
|
debug_crypto_array(&**self, fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Deref for PublicBox<N> {
|
impl<const N: usize> Deref for PublicBox<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Target = [u8; N];
|
type Target = [u8; N];
|
||||||
|
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
fn deref(&self) -> &[u8; N] {
|
fn deref(&self) -> &[u8; N] {
|
||||||
self.inner.deref()
|
self.inner.deref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> DerefMut for PublicBox<N> {
|
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] {
|
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||||
self.inner.deref_mut()
|
self.inner.deref_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
|
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] {
|
fn borrow(&self) -> &[u8] {
|
||||||
self.deref()
|
self.deref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
|
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] {
|
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||||
self.deref_mut()
|
self.deref_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> LoadValue for PublicBox<N> {
|
impl<const N: usize> LoadValue for PublicBox<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Error = anyhow::Error;
|
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<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||||
let mut p = Self::random();
|
let mut p = Self::random();
|
||||||
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
|
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> {
|
impl<const N: usize> StoreValue for PublicBox<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Error = anyhow::Error;
|
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<()> {
|
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
self.inner.store(path)
|
self.inner.store(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Error = anyhow::Error;
|
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>
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
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 f = vec![0u8; F];
|
||||||
let mut v = PublicBox::zero();
|
let mut v = PublicBox::zero();
|
||||||
let p = path.as_ref();
|
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> {
|
impl<const N: usize> StoreValueB64 for PublicBox<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Error = anyhow::Error;
|
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<()> {
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
self.inner.store_b64::<F, P>(path)
|
self.inner.store_b64::<F, P>(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
|
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
type Error = anyhow::Error;
|
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>(
|
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||||
&self,
|
&self,
|
||||||
writer: W,
|
writer: W,
|
||||||
@@ -320,6 +386,7 @@ mod tests {
|
|||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Public, PublicBox};
|
use crate::{Public, PublicBox};
|
||||||
|
use rand::Fill;
|
||||||
use rosenpass_util::{
|
use rosenpass_util::{
|
||||||
b64::b64_encode,
|
b64::b64_encode,
|
||||||
file::{
|
file::{
|
||||||
@@ -449,5 +516,112 @@ mod tests {
|
|||||||
fn test_public_box_load_store_base64() {
|
fn test_public_box_load_store_base64() {
|
||||||
run_base64_load_store_test::<PublicBox<N>>();
|
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;
|
pub type Rng = rand::rngs::ThreadRng;
|
||||||
|
|
||||||
|
/// Get the default [Rng].
|
||||||
pub fn rng() -> Rng {
|
pub fn rng() -> Rng {
|
||||||
rand::thread_rng()
|
rand::thread_rng()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ thread_local! {
|
|||||||
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
|
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
|
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
|
||||||
where
|
where
|
||||||
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
|
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
|
||||||
@@ -47,37 +48,47 @@ where
|
|||||||
.unwrap_or_else(|| f(None))
|
.unwrap_or_else(|| f(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper around SecretBox that applies automatic zeroization
|
/// Wrapper around SecretBox that applies automatic zeroization.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
|
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
|
||||||
|
|
||||||
impl<T: Zeroize> ZeroizingSecretBox<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 {
|
fn new(boxed: T) -> Self {
|
||||||
ZeroizingSecretBox(Some(secret_box(boxed)))
|
ZeroizingSecretBox(Some(secret_box(boxed)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
|
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 {
|
fn from_secret_box(inner: SecretBox<T>) -> Self {
|
||||||
Self(Some(inner))
|
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> {
|
fn take(mut self) -> SecretBox<T> {
|
||||||
self.0.take().unwrap()
|
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> ZeroizeOnDrop for ZeroizingSecretBox<T> {}
|
||||||
impl<T: Zeroize + ?Sized> Zeroize for ZeroizingSecretBox<T> {
|
impl<T: Zeroize + ?Sized> Zeroize for ZeroizingSecretBox<T> {
|
||||||
fn zeroize(&mut self) {
|
fn zeroize(&mut self) {
|
||||||
if let Some(inner) = &mut self.0 {
|
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()
|
inner.zeroize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
|
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) {
|
fn drop(&mut self) {
|
||||||
self.zeroize()
|
self.zeroize()
|
||||||
}
|
}
|
||||||
@@ -86,29 +97,32 @@ impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
|
|||||||
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
|
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
fn deref(&self) -> &T {
|
fn deref(&self) -> &T {
|
||||||
self.0.as_ref().unwrap()
|
self.0.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Zeroize + ?Sized> DerefMut for ZeroizingSecretBox<T> {
|
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 {
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
self.0.as_mut().unwrap()
|
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
|
/// Allocation of secret memory is expensive. Thus, this struct provides a
|
||||||
/// pool of secret memory, readily available to yield protected, slices of
|
/// pool of secret memory, readily available to yield protected, slices of
|
||||||
/// memory.
|
/// memory.
|
||||||
#[derive(Debug)] // TODO check on Debug derive, is that clever
|
#[derive(Debug)] // TODO check on Debug derive, is that clever
|
||||||
struct SecretMemoryPool {
|
struct SecretMemoryPool {
|
||||||
|
/// A pool to reuse secret memory
|
||||||
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
|
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SecretMemoryPool {
|
impl SecretMemoryPool {
|
||||||
/// Create a new [SecretMemoryPool]
|
/// Create a new [SecretMemoryPool].
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
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]>) {
|
pub fn release<const N: usize>(&mut self, mut sec: ZeroizingSecretBox<[u8; N]>) {
|
||||||
sec.zeroize();
|
sec.zeroize();
|
||||||
|
|
||||||
@@ -134,7 +148,7 @@ impl SecretMemoryPool {
|
|||||||
/// Take protected memory from the pool, allocating new one if no suitable
|
/// Take protected memory from the pool, allocating new one if no suitable
|
||||||
/// chunk is found in the inventory.
|
/// 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]> {
|
pub fn take<const N: usize>(&mut self) -> ZeroizingSecretBox<[u8; N]> {
|
||||||
let entry = self.pool.entry(N).or_default();
|
let entry = self.pool.entry(N).or_default();
|
||||||
let inner = match entry.pop() {
|
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> {
|
pub struct Secret<const N: usize> {
|
||||||
storage: Option<ZeroizingSecretBox<[u8; N]>>,
|
storage: Option<ZeroizingSecretBox<[u8; N]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Secret<N> {
|
impl<const N: usize> Secret<N> {
|
||||||
|
/// Create a new [Secret] from a byte-slice.
|
||||||
pub fn from_slice(slice: &[u8]) -> Self {
|
pub fn from_slice(slice: &[u8]) -> Self {
|
||||||
let mut new_self = Self::zero();
|
let mut new_self = Self::zero();
|
||||||
new_self.secret_mut().copy_from_slice(slice);
|
new_self.secret_mut().copy_from_slice(slice);
|
||||||
new_self
|
new_self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new [Secret] that is zero initialized
|
/// Returns a new [Secret] that is zero initialized.
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
// Using [SecretMemoryPool] here because this operation is expensive,
|
// 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| {
|
let buf = with_secret_memory_pool(|pool| {
|
||||||
pool.map(|p| p.take())
|
pool.map(|p| p.take())
|
||||||
.unwrap_or_else(|| ZeroizingSecretBox::new([0u8; N]))
|
.unwrap_or_else(|| ZeroizingSecretBox::new([0u8; N]))
|
||||||
@@ -169,28 +211,30 @@ impl<const N: usize> Secret<N> {
|
|||||||
Self { storage: Some(buf) }
|
Self { storage: Some(buf) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new [Secret] that is randomized
|
/// Returns a new [Secret] that is randomized.
|
||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
mutating(Self::zero(), |r| r.randomize())
|
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) {
|
pub fn randomize(&mut self) {
|
||||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrows the data
|
/// Borrows the data.
|
||||||
pub fn secret(&self) -> &[u8; N] {
|
pub fn secret(&self) -> &[u8; N] {
|
||||||
self.storage.as_ref().unwrap()
|
self.storage.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrows the data mutably
|
/// Borrows the data mutably.
|
||||||
pub fn secret_mut(&mut self) -> &mut [u8; N] {
|
pub fn secret_mut(&mut self) -> &mut [u8; N] {
|
||||||
self.storage.as_mut().unwrap()
|
self.storage.as_mut().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Randomize for Secret<N> {
|
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> {
|
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
|
// Zeroize self first just to make sure the barriers from the zeroize create take
|
||||||
// effect to prevent the compiler from optimizing this away.
|
// 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> ZeroizeOnDrop for Secret<N> {}
|
||||||
impl<const N: usize> Zeroize 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) {
|
fn zeroize(&mut self) {
|
||||||
if let Some(inner) = &mut self.storage {
|
if let Some(inner) = &mut self.storage {
|
||||||
inner.zeroize()
|
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> {
|
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) {
|
fn drop(&mut self) {
|
||||||
with_secret_memory_pool(|pool| {
|
with_secret_memory_pool(|pool| {
|
||||||
if let Some((pool, secret)) = pool.zip(self.storage.take()) {
|
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> {
|
impl<const N: usize> LoadValue for Secret<N> {
|
||||||
type Error = anyhow::Error;
|
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> {
|
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||||
let mut v = Self::random();
|
let mut v = Self::random();
|
||||||
let p = path.as_ref();
|
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> {
|
impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||||
type Error = anyhow::Error;
|
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> {
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||||
let mut f: Secret<F> = Secret::random();
|
let mut f: Secret<F> = Secret::random();
|
||||||
let mut v = Self::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> {
|
impl<const N: usize> StoreValueB64 for Secret<N> {
|
||||||
type Error = anyhow::Error;
|
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<()> {
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
let p = path.as_ref();
|
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> {
|
impl<const N: usize> StoreValueB64Writer for Secret<N> {
|
||||||
type Error = anyhow::Error;
|
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<()> {
|
fn store_b64_writer<const F: usize, W: Write>(&self, mut writer: W) -> anyhow::Result<()> {
|
||||||
let mut f: Secret<F> = Secret::random();
|
let mut f: Secret<F> = Secret::random();
|
||||||
let encoded_str = b64_encode(self.secret(), f.secret_mut())
|
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> {
|
impl<const N: usize> StoreSecret for Secret<N> {
|
||||||
type Error = anyhow::Error;
|
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<()> {
|
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
fopen_w(path, Visibility::Secret)?.write_all(self.secret())?;
|
fopen_w(path, Visibility::Secret)?.write_all(self.secret())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No extra documentation here because the Trait already provides a good documentation.
|
||||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
fopen_w(path, Visibility::Public)?.write_all(self.secret())?;
|
fopen_w(path, Visibility::Public)?.write_all(self.secret())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -320,7 +379,10 @@ impl<const N: usize> StoreSecret for Secret<N> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod 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 super::*;
|
||||||
use std::{fs, os::unix::fs::PermissionsExt};
|
use std::{fs, os::unix::fs::PermissionsExt};
|
||||||
@@ -328,7 +390,7 @@ mod test {
|
|||||||
|
|
||||||
procspawn::enable_test_support!();
|
procspawn::enable_test_support!();
|
||||||
|
|
||||||
/// check that we can alloc using the magic pool
|
/// Check that we can alloc using the magic pool.
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_memory_pool_take() {
|
fn secret_memory_pool_take() {
|
||||||
test_spawn_process_provided_policies!({
|
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]
|
#[test]
|
||||||
fn secret_memory_pool_drop() {
|
fn secret_memory_pool_drop() {
|
||||||
test_spawn_process_provided_policies!({
|
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]
|
#[test]
|
||||||
fn secret_memory_pool_release() {
|
fn secret_memory_pool_release() {
|
||||||
test_spawn_process_provided_policies!({
|
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]
|
#[test]
|
||||||
fn test_secret_load_store() {
|
fn test_secret_load_store() {
|
||||||
test_spawn_process_provided_policies!({
|
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]
|
#[test]
|
||||||
fn test_secret_load_store_base64() {
|
fn test_secret_load_store_base64() {
|
||||||
test_spawn_process_provided_policies!({
|
test_spawn_process_provided_policies!({
|
||||||
@@ -463,4 +526,33 @@ mod test {
|
|||||||
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
|
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;
|
use std::ops::BitXorAssign;
|
||||||
|
|
||||||
// Destination functions return some value that implements the To trait.
|
// 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
|
// be without destination parameters
|
||||||
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
|
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
|
||||||
where
|
where
|
||||||
T: BitXorAssign + Clone,
|
T: BitXorAssign + Clone,
|
||||||
{
|
{
|
||||||
// Custom implementations of the to trait can be created, but the easiest
|
// 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]| {
|
with_destination(move |dst: &mut [T]| {
|
||||||
assert!(src.len() == dst.len());
|
assert!(src.len() == dst.len());
|
||||||
for (d, s) in dst.iter_mut().zip(src.iter()) {
|
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};
|
use crate::{with_destination, To};
|
||||||
|
|
||||||
@@ -8,6 +9,17 @@ use crate::{with_destination, To};
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// This function will panic if the two slices have different lengths.
|
/// 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], ()> + '_
|
pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
@@ -23,6 +35,19 @@ where
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// This function will panic if destination is shorter than origin.
|
/// 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], ()> + '_
|
pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
@@ -34,6 +59,18 @@ where
|
|||||||
/// destination.
|
/// destination.
|
||||||
///
|
///
|
||||||
/// Copies as much data as is present in the shorter slice.
|
/// 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], ()> + '_
|
pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
@@ -47,6 +84,24 @@ where
|
|||||||
/// Function with destination that attempts to copy data from origin into the destination.
|
/// Function with destination that attempts to copy data from origin into the destination.
|
||||||
///
|
///
|
||||||
/// Will return None if the slices are of different lengths.
|
/// 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<()>> + '_
|
pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
@@ -62,6 +117,26 @@ where
|
|||||||
/// Destination may be longer than origin.
|
/// Destination may be longer than origin.
|
||||||
///
|
///
|
||||||
/// Will return None if the destination is shorter 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<()>> + '_
|
pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
@@ -72,6 +147,18 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Function with destination that copies all data between two array references.
|
/// 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], ()> + '_
|
pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
|
||||||
where
|
where
|
||||||
T: Copy,
|
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;
|
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)]
|
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
|
||||||
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
||||||
|
|
||||||
@@ -59,7 +64,7 @@ impl<Val, Ret> Beside<Val, Ret> {
|
|||||||
&mut self.1
|
&mut self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform beside condensation. See [CondenseBeside]
|
/// Perform beside condensation. See [CondenseBeside] for more details.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
@@ -90,3 +95,25 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
|
|||||||
(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),
|
/// 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
|
/// [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
|
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
||||||
/// condense trait.
|
/// 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> {
|
pub trait CondenseBeside<Val> {
|
||||||
/// The type that results from condensation.
|
/// The type that results from condensation.
|
||||||
type Condensed;
|
type Condensed;
|
||||||
@@ -17,6 +37,7 @@ pub trait CondenseBeside<Val> {
|
|||||||
impl<Val> CondenseBeside<Val> for () {
|
impl<Val> CondenseBeside<Val> for () {
|
||||||
type Condensed = Val;
|
type Condensed = Val;
|
||||||
|
|
||||||
|
/// Replaces this empty tuple with `ret`.
|
||||||
fn condense(self, ret: Val) -> Val {
|
fn condense(self, ret: Val) -> Val {
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
@@ -25,6 +46,7 @@ impl<Val> CondenseBeside<Val> for () {
|
|||||||
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||||
type Condensed = Result<Val, Error>;
|
type Condensed = Result<Val, Error>;
|
||||||
|
|
||||||
|
/// Replaces the empty tuple inside this [Result] with `ret`.
|
||||||
fn condense(self, ret: Val) -> Result<Val, Error> {
|
fn condense(self, ret: Val) -> Result<Val, Error> {
|
||||||
self.map(|()| ret)
|
self.map(|()| ret)
|
||||||
}
|
}
|
||||||
@@ -33,6 +55,7 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
|||||||
impl<Val> CondenseBeside<Val> for Option<()> {
|
impl<Val> CondenseBeside<Val> for Option<()> {
|
||||||
type Condensed = Option<Val>;
|
type Condensed = Option<Val>;
|
||||||
|
|
||||||
|
/// Replaces the empty tuple inside this [Option] with `ret`.
|
||||||
fn condense(self, ret: Val) -> Option<Val> {
|
fn condense(self, ret: Val) -> Option<Val> {
|
||||||
self.map(|()| ret)
|
self.map(|()| ret)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
/// Helper performing explicit unsized coercion.
|
//! This module provides explicit type coercion from [Sized] types to [?Sized][core::marker::Sized]
|
||||||
/// Used by the [to](crate::to()) function.
|
//! 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> {
|
pub trait DstCoercion<Dst: ?Sized> {
|
||||||
/// Performs an explicit coercion to the destination type.
|
/// Performs an explicit coercion to the destination type.
|
||||||
fn coerce_dest(&mut self) -> &mut Dst;
|
fn coerce_dest(&mut self) -> &mut Dst;
|
||||||
|
|||||||
@@ -6,11 +6,16 @@
|
|||||||
//! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to
|
//! - `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
|
//! - `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
|
//! - `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]`).
|
//! `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
|
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The
|
||||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst`
|
//! ordinary return value of a function with an output
|
||||||
//! - `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`).
|
//! - `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 beside;
|
||||||
pub mod condense;
|
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};
|
use crate::{DstCoercion, To};
|
||||||
|
|
||||||
/// Alias for [To::to] moving the destination to the left.
|
/// Alias for [To::to] moving the destination to the left.
|
||||||
///
|
///
|
||||||
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
|
/// 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.
|
/// 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
|
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
|
||||||
where
|
where
|
||||||
Coercable: ?Sized + DstCoercion<Dst>,
|
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 crate::{Beside, CondenseBeside};
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
|
|
||||||
/// The To trait is the core of the to crate; most functions with destinations will either return
|
/// 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,
|
/// an object that is an instance of this trait, or they will return `-> impl To<Destination,
|
||||||
/// Return_value`.
|
/// Return_value>`.
|
||||||
///
|
///
|
||||||
/// A quick way to implement a function with destination is to use the
|
/// 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 {
|
pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||||
/// Writes self to the destination `out` and returns a value of type `Ret`.
|
/// 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
|
/// calls [crate::to()] to evaluate the function and finally
|
||||||
/// returns a [Beside] instance containing the generated destination value and the return
|
/// returns a [Beside] instance containing the generated destination value and the return
|
||||||
/// value.
|
/// 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>
|
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
|
||||||
where
|
where
|
||||||
Val: BorrowMut<Dst>,
|
Val: BorrowMut<Dst>,
|
||||||
@@ -31,10 +95,21 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
|||||||
|
|
||||||
/// Generate a destination on the fly using default.
|
/// Generate a destination on the fly using default.
|
||||||
///
|
///
|
||||||
/// Uses [Default] to create a value,
|
/// Uses [Default] to create a value, calls [crate::to()] to evaluate the function and finally
|
||||||
/// calls [crate::to()] to evaluate the function and finally
|
|
||||||
/// returns a [Beside] instance containing the generated destination value and the return
|
/// returns a [Beside] instance containing the generated destination value and the return
|
||||||
/// value.
|
/// 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>
|
fn to_value_beside(self) -> Beside<Dst, Ret>
|
||||||
where
|
where
|
||||||
Dst: Sized + Default,
|
Dst: Sized + Default,
|
||||||
@@ -53,6 +128,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
|||||||
/// when the Destination is unsized.
|
/// when the Destination is unsized.
|
||||||
///
|
///
|
||||||
/// This could be the case when the destination is an `[u8]` for instance.
|
/// 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>
|
fn collect_beside<Val>(self) -> Beside<Val, Ret>
|
||||||
where
|
where
|
||||||
Val: Default + BorrowMut<Dst>,
|
Val: Default + BorrowMut<Dst>,
|
||||||
@@ -64,6 +152,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
|||||||
/// return value into one.
|
/// return value into one.
|
||||||
///
|
///
|
||||||
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
|
/// 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
|
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||||
where
|
where
|
||||||
Ret: CondenseBeside<Val>,
|
Ret: CondenseBeside<Val>,
|
||||||
@@ -77,6 +195,18 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
|||||||
/// return value into one.
|
/// return value into one.
|
||||||
///
|
///
|
||||||
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
|
/// 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
|
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
|
||||||
where
|
where
|
||||||
Dst: Sized + Default,
|
Dst: Sized + Default,
|
||||||
@@ -89,6 +219,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
|||||||
/// return value into one.
|
/// return value into one.
|
||||||
///
|
///
|
||||||
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
|
/// 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
|
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||||
where
|
where
|
||||||
Val: Default + BorrowMut<Dst>,
|
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 crate::To;
|
||||||
use std::marker::PhantomData;
|
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`
|
/// 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
|
/// # Type Parameters
|
||||||
/// * `Dst` - The destination type the closure operates on
|
/// * `Dst` - The destination type the closure operates on.
|
||||||
/// * `Ret` - The return type of the closure
|
/// * `Ret` - The return type of the closure.
|
||||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`.
|
||||||
struct ToClosure<Dst, Ret, Fun>
|
struct ToClosure<Dst, Ret, Fun>
|
||||||
where
|
where
|
||||||
Dst: ?Sized,
|
Dst: ?Sized,
|
||||||
@@ -17,11 +21,11 @@ where
|
|||||||
{
|
{
|
||||||
/// The function to call.
|
/// The function to call.
|
||||||
fun: Fun,
|
fun: Fun,
|
||||||
/// Phantom data to hold the destination type
|
/// Phantom data to hold the destination type.
|
||||||
_val: PhantomData<Box<Dst>>,
|
_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.
|
/// This enables calling the wrapped closure with a destination reference.
|
||||||
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
||||||
@@ -33,6 +37,7 @@ where
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `out` - Mutable reference to the destination
|
/// * `out` - Mutable reference to the destination
|
||||||
|
/// See the tutorial in [readme.md] for examples and more explanations.
|
||||||
fn to(self, out: &mut Dst) -> Ret {
|
fn to(self, out: &mut Dst) -> Ret {
|
||||||
(self.fun)(out)
|
(self.fun)(out)
|
||||||
}
|
}
|
||||||
@@ -48,7 +53,24 @@ where
|
|||||||
/// * `Ret` - The return type of the closure
|
/// * `Ret` - The return type of the closure
|
||||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
/// * `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>
|
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
||||||
where
|
where
|
||||||
Dst: ?Sized,
|
Dst: ?Sized,
|
||||||
|
|||||||
Reference in New Issue
Block a user