From 47b556e317c1e3d3b45b7d5b28481956082577de Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Wed, 18 Dec 2024 20:35:58 +0100 Subject: [PATCH] chore(doc): Docs for rosenpass::{config, cli} --- rosenpass/src/api/config.rs | 2 +- rosenpass/src/cli.rs | 76 ++++++- rosenpass/src/config.rs | 199 ++++++++++++------ .../tests/config_Rosenpass_add_if_any.rs | 10 + rosenpass/tests/config_Rosenpass_new.rs | 18 ++ .../config_Rosenpass_parse_args_simple.rs | 36 ++++ rosenpass/tests/config_Rosenpass_store.rs | 42 ++++ rosenpass/tests/config_Rosenpass_validate.rs | 37 ++++ 8 files changed, 347 insertions(+), 73 deletions(-) create mode 100644 rosenpass/tests/config_Rosenpass_add_if_any.rs create mode 100644 rosenpass/tests/config_Rosenpass_new.rs create mode 100644 rosenpass/tests/config_Rosenpass_parse_args_simple.rs create mode 100644 rosenpass/tests/config_Rosenpass_store.rs create mode 100644 rosenpass/tests/config_Rosenpass_validate.rs diff --git a/rosenpass/src/api/config.rs b/rosenpass/src/api/config.rs index 2bf1b60..d728bb6 100644 --- a/rosenpass/src/api/config.rs +++ b/rosenpass/src/api/config.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::app_server::AppServer; -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)] pub struct ApiConfig { /// Where in the file-system to create the unix socket the rosenpass API will be listening for /// connections on diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index c3df516..870045f 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -1,3 +1,8 @@ +//! Contains the code used to parse command line parameters for rosenpass. +//! +//! [CliArgs::run] is called by the rosenpass main function and contains the +//! bulk of our boostrapping code while the main function just sets up the basic environment + use anyhow::{bail, ensure, Context}; use clap::{Parser, Subcommand}; use rosenpass_cipher_traits::Kem; @@ -31,15 +36,25 @@ use { std::thread, }; -/// enum representing a choice of interface to a WireGuard broker +/// How to reach a WireGuard PSK Broker #[derive(Debug)] pub enum BrokerInterface { + /// The PSK Broker is listening on a unix socket at the given path Socket(PathBuf), + /// The PSK Broker broker is already connected to this process; a + /// unix socket stream can be reached at the given file descriptor. + /// + /// This is generally used with file descriptor passing. FileDescriptor(i32), + /// Create a socketpair(3p), spawn the PSK broker process from within rosenpass, + /// and hand one end of the socketpair to the broker process via file + /// descriptor passing to the subprocess SocketPair, } -/// struct holding all CLI arguments for `clap` crate to parse +/// Command line arguments to the Rosenpass binary. +/// +/// Used for parsing with [clap]. #[derive(Parser, Debug)] #[command(author, version, about, long_about, arg_required_else_help = true)] pub struct CliArgs { @@ -80,6 +95,7 @@ pub struct CliArgs { #[arg(short, long, group = "psk-broker-specs")] psk_broker_spawn: bool, + /// The subcommand to be invoked #[command(subcommand)] pub command: Option, @@ -98,6 +114,10 @@ pub struct CliArgs { } impl CliArgs { + /// Apply the command line parameters to the Rosenpass configuration struct + /// + /// Generally the flow of control here is that all the command line parameters + /// are merged into the configuration file to avoid much code duplication. pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> { #[cfg(feature = "experiment_api")] self.api.apply_to_config(_cfg)?; @@ -123,9 +143,11 @@ impl CliArgs { None } + /// Return the WireGuard PSK broker interface configured. + /// + /// Returns `None` if the `experiment_api` feature is disabled. + #[cfg(feature = "experiment_api")] - /// returns the broker interface set by CLI args - /// returns `None` if the `experiment_api` feature isn't enabled pub fn get_broker_interface(&self) -> Option { if let Some(path_ref) = self.psk_broker_path.as_ref() { Some(BrokerInterface::Socket(path_ref.to_path_buf())) @@ -138,9 +160,10 @@ impl CliArgs { } } + /// Return the WireGuard PSK broker interface configured. + /// + /// Returns `None` if the `experiment_api` feature is disabled. #[cfg(not(feature = "experiment_api"))] - /// returns the broker interface set by CLI args - /// returns `None` if the `experiment_api` feature isn't enabled pub fn get_broker_interface(&self) -> Option { None } @@ -244,15 +267,17 @@ pub enum CliCommand { } impl CliArgs { - /// Runs the command specified via CLI + /// Run Rosenpass with the given command line parameters /// - /// ## TODO - /// - This method consumes the [`CliCommand`] value. It might be wise to use a reference... + /// This contains the bulk of our startup logic with + /// the main function just setting up the basic environment + /// and then calling this function. pub fn run( self, broker_interface: Option, test_helpers: Option, ) -> anyhow::Result<()> { + // TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference... use CliCommand::*; match &self.command { Some(GenConfig { config_file, force }) => { @@ -403,6 +428,7 @@ impl CliArgs { Ok(()) } + /// Used by [Self::run] to start the Rosenpass key exchange server fn event_loop( config: config::Rosenpass, broker_interface: Option, @@ -470,6 +496,19 @@ impl CliArgs { srv.event_loop() } + /// Create the WireGuard PSK broker to be used by + /// [crate::app_server::AppServer]. + /// + /// If the `experiment_api` + /// feature flag is set, then this communicates with a PSK broker + /// running in a different process as configured via + /// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn` + /// fields. + /// + /// If the `experiment_api` + /// feature flag is not set, then this returns a [NativeUnixBroker], + /// sending pre-shared keys directly to WireGuard from within this + /// process. #[cfg(feature = "experiment_api")] fn create_broker( broker_interface: Option, @@ -485,6 +524,19 @@ impl CliArgs { } } + /// Create the WireGuard PSK broker to be used by + /// [crate::app_server::AppServer]. + /// + /// If the `experiment_api` + /// feature flag is set, then this communicates with a PSK broker + /// running in a different process as configured via + /// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn` + /// fields. + /// + /// If the `experiment_api` + /// feature flag is not set, then this returns a [NativeUnixBroker], + /// sending pre-shared keys directly to WireGuard from within this + /// process. #[cfg(not(feature = "experiment_api"))] fn create_broker( _broker_interface: Option, @@ -492,6 +544,10 @@ impl CliArgs { Ok(Box::new(NativeUnixBroker::new())) } + /// Used by [Self::create_broker] if the `experiment_api` is configured + /// to set up the connection with the PSK broker process as configured + /// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn` + /// fields. #[cfg(feature = "experiment_api")] fn get_broker_socket(broker_interface: BrokerInterface) -> Result { // Connect to the psk broker unix socket if one was specified @@ -549,7 +605,7 @@ impl CliArgs { } /// generate secret and public keys, store in files according to the paths passed as arguments -fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> { +pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> { let mut ssk = crate::protocol::SSk::random(); let mut spk = crate::protocol::SPk::random(); StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?; diff --git a/rosenpass/src/config.rs b/rosenpass/src/config.rs index a16f739..fedddeb 100644 --- a/rosenpass/src/config.rs +++ b/rosenpass/src/config.rs @@ -4,8 +4,9 @@ //! [`Rosenpass`] which holds such a configuration. //! //! ## TODO -//! - support `~` in -//! - provide tooling to create config file from shell +//! - TODO: support `~` in +//! - TODO: provide tooling to create config file from shell + use crate::protocol::{SPk, SSk}; use rosenpass_util::file::LoadValue; use std::{ @@ -31,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig { } } -#[derive(Debug, Serialize, Deserialize)] +/// Configuration for the Rosenpass key exchange +/// +/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Rosenpass { // TODO: Raise error if secret key or public key alone is set during deserialization // SEE: https://github.com/serde-rs/serde/issues/2793 @@ -46,7 +50,10 @@ pub struct Rosenpass { /// list of [`SocketAddr`] to listen on /// /// Examples: - /// - `0.0.0.0:123` + /// + /// - `0.0.0.0:123` – Listen on any interface using IPv4, port 123 + /// - `[::1]:1234` – Listen on IPv6 localhost, port 1234 + /// - `[::]:4476` – Listen on any IPv4 or IPv6 interface, port 4476 pub listen: Vec, /// log verbosity @@ -68,6 +75,7 @@ pub struct Rosenpass { pub config_file_path: PathBuf, } +/// Public key and secret key locations. #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] pub struct Keypair { /// path to the public key file @@ -78,6 +86,7 @@ pub struct Keypair { } impl Keypair { + /// Construct a keypair from its fields pub fn new, Sk: AsRef>(public_key: Pk, secret_key: Sk) -> Self { let public_key = public_key.as_ref().to_path_buf(); let secret_key = secret_key.as_ref().to_path_buf(); @@ -88,62 +97,72 @@ impl Keypair { } } -/// ## TODO -/// - replace this type with [`log::LevelFilter`], also see +/// Level of verbosity for [crate::app_server::AppServer] +/// +/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation +/// for details. +/// +/// - TODO: replace this type with [`log::LevelFilter`], also see #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)] pub enum Verbosity { Quiet, Verbose, } -/// ## TODO -/// - examples -/// - documentation +/// Configuration data for a single Rosenpass peer #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct RosenpassPeer { /// path to the public key of the peer pub public_key: PathBuf, - /// ## TODO - /// - documentation + /// The hostname and port to connect to + /// + /// Can be a + /// + /// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427` + /// - IPv4 address and port, e.g. `1.2.3.4:7764` + /// - IPv6 address and port, e.g. `[fe80::24]:7890` pub endpoint: Option, - /// path to the pre-shared key with the peer + /// path to the pre-shared key shared with the peer /// /// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer pub pre_shared_key: Option, - /// ## TODO - /// - documentation + /// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys + /// to the given file and write a notification to standard out to let the calling application + /// know that a new key was exchanged #[serde(default)] pub key_out: Option, - /// ## TODO - /// - documentation - /// - make this field only available on binary builds, not on library builds + /// Information for supplying exchanged keys directly to WireGuard #[serde(flatten)] pub wg: Option, } -/// ## TODO -/// - documentation +/// Information for supplying exchanged keys directly to WireGuard #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct WireGuard { - /// ## TODO - /// - documentation + /// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass + /// key exchange pub device: String, - /// ## TODO - /// - documentation + /// WireGuard public key of the peer to supply with pre-shared keys pub peer: String, - /// ## TODO - /// - documentation + /// Extra parameters passed to the `wg` command #[serde(default)] pub extra_params: Vec, } impl Default for Rosenpass { + /// Generate an empty configuration + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_new.rs")] + #[doc = "```"] fn default() -> Self { Self::empty() } @@ -156,8 +175,15 @@ impl Rosenpass { /// checked whether they even exist. /// /// ## TODO + /// /// - consider using a different algorithm to determine home directory – the below one may /// behave unexpectedly on Windows + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_store.rs")] + #[doc = "```"] pub fn load>(p: P) -> anyhow::Result { // read file and deserialize let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?; @@ -185,7 +211,13 @@ impl Rosenpass { Ok(config) } - /// Write a config to a file + /// Encode a configuration object as toml and write it to a file + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_store.rs")] + #[doc = "```"] pub fn store>(&self, p: P) -> anyhow::Result<()> { let serialized_config = toml::to_string_pretty(&self).expect("unable to serialize the default config"); @@ -194,6 +226,12 @@ impl Rosenpass { } /// Commit the configuration to where it came from, overwriting the original file + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_store.rs")] + #[doc = "```"] pub fn commit(&self) -> anyhow::Result<()> { let mut f = fopen_w(&self.config_file_path, Visibility::Public)?; f.write_all(toml::to_string_pretty(&self)?.as_bytes())?; @@ -201,13 +239,21 @@ impl Rosenpass { self.store(&self.config_file_path) } + /// Apply the configuration in this object to the given [crate::app_server::AppServer] pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> { #[cfg(feature = "experiment_api")] self.api.apply_to_app_server(_srv)?; Ok(()) } - /// Validate a configuration + /// Check that the configuration is sound, ensuring + /// for instance that the referenced files exist + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_validate.rs")] + #[doc = "```"] pub fn validate(&self) -> anyhow::Result<()> { if let Some(ref keypair) = self.keypair { // check the public key file exists @@ -284,6 +330,21 @@ impl Rosenpass { Ok(()) } + /// Check that the configuration is useful given the feature set Rosenpass was compiled with + /// and the configuration values. + /// + /// This was introduced when we introduced a unix-socket API feature allowing the server + /// keypair to be supplied via the API; in this process we also made [Self::keypair] optional. + /// With respect to this particular feature, this function ensures that [Self::keypair] is set + /// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is + /// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is + /// enabled in the configuration. + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_validate.rs")] + #[doc = "```"] pub fn check_usefullness(&self) -> anyhow::Result<()> { #[cfg(not(feature = "experiment_api"))] ensure!(self.keypair.is_some(), "Server keypair missing."); @@ -299,15 +360,38 @@ impl Rosenpass { Ok(()) } + /// Produce an empty confuguration + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_new.rs")] + #[doc = "```"] pub fn empty() -> Self { Self::new(None) } + /// Produce configuration from the keypair + /// + /// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)). + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_new.rs")] + #[doc = "```"] pub fn from_sk_pk, Pk: AsRef>(sk: Sk, pk: Pk) -> Self { Self::new(Some(Keypair::new(pk, sk))) } - /// Creates a new configuration + /// Initialize a minimal configuration with the [Self::keypair] field supplied + /// as a parameter + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_new.rs")] + #[doc = "```"] pub fn new(keypair: Option) -> Self { Self { keypair, @@ -321,6 +405,14 @@ impl Rosenpass { } /// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces + /// + /// I.e. listen on any interface. + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")] + #[doc = "```"] pub fn add_if_any(&mut self, port: u16) { let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port)); let ipv6_any = SocketAddr::V6(SocketAddrV6::new( @@ -333,8 +425,20 @@ impl Rosenpass { self.listen.push(ipv6_any); } - /// from chaotic args - /// Quest: the grammar is undecideable, what do we do here? + /// Parser for the old, IP style grammar. + /// + /// See out manual page rosenpass-exchange(1) on details about the grammar. + /// + /// This function parses the grammar and turns it into an instance of the configuration + /// struct. + /// + /// TODO: the grammar is undecidable, what do we do here? + /// + /// # Examples + /// + #[doc = "```ignore"] + #[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")] + #[doc = "```"] pub fn parse_args(args: Vec) -> anyhow::Result { let mut config = Self::new(Some(Keypair::new("", ""))); @@ -525,11 +629,13 @@ impl Rosenpass { } impl Default for Verbosity { + /// Self::Quiet fn default() -> Self { Self::Quiet } } +/// Example configuration generated by the command `rosenpass gen-config `. pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key" secret_key = "/path/to/rp-secret-key" listen = [] @@ -553,7 +659,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key mod test { use super::*; - use std::{borrow::Borrow, net::IpAddr}; + use std::borrow::Borrow; fn toml_des>(s: S) -> Result { toml::from_str(s.borrow()) @@ -664,37 +770,6 @@ mod test { Ok(()) } - #[test] - fn test_simple_cli_parse() { - let args = split_str( - "public-key /my/public-key secret-key /my/secret-key verbose \ - listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \ - peer.test:9999 outfile /peer/rp-out", - ); - - let config = Rosenpass::parse_args(args).unwrap(); - - assert_eq!( - config.keypair, - Some(Keypair::new("/my/public-key", "/my/secret-key")) - ); - assert_eq!(config.verbosity, Verbosity::Verbose); - assert_eq!( - &config.listen, - &vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)] - ); - assert_eq!( - config.peers, - vec![RosenpassPeer { - public_key: PathBuf::from("/peer/public-key"), - endpoint: Some("peer.test:9999".into()), - pre_shared_key: None, - key_out: Some(PathBuf::from("/peer/rp-out")), - ..Default::default() - }] - ) - } - #[test] fn test_cli_parse_multiple_peers() { let args = split_str( diff --git a/rosenpass/tests/config_Rosenpass_add_if_any.rs b/rosenpass/tests/config_Rosenpass_add_if_any.rs new file mode 100644 index 0000000..0653700 --- /dev/null +++ b/rosenpass/tests/config_Rosenpass_add_if_any.rs @@ -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")); +} diff --git a/rosenpass/tests/config_Rosenpass_new.rs b/rosenpass/tests/config_Rosenpass_new.rs new file mode 100644 index 0000000..d8b19e7 --- /dev/null +++ b/rosenpass/tests/config_Rosenpass_new.rs @@ -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); +} diff --git a/rosenpass/tests/config_Rosenpass_parse_args_simple.rs b/rosenpass/tests/config_Rosenpass_parse_args_simple.rs new file mode 100644 index 0000000..3ea77ca --- /dev/null +++ b/rosenpass/tests/config_Rosenpass_parse_args_simple.rs @@ -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() + }] + ); +} diff --git a/rosenpass/tests/config_Rosenpass_store.rs b/rosenpass/tests/config_Rosenpass_store.rs new file mode 100644 index 0000000..43d2a27 --- /dev/null +++ b/rosenpass/tests/config_Rosenpass_store.rs @@ -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(()) +} diff --git a/rosenpass/tests/config_Rosenpass_validate.rs b/rosenpass/tests/config_Rosenpass_validate.rs new file mode 100644 index 0000000..48e73fe --- /dev/null +++ b/rosenpass/tests/config_Rosenpass_validate.rs @@ -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(()) +}