chore(API): Specifying a keypair should be opt. at startup

…so we can specify it later using the API.
This commit is contained in:
Karolin Varner
2024-08-10 14:09:49 +02:00
parent 48f7ff93e3
commit d5a8c85abe
6 changed files with 237 additions and 53 deletions

View File

@@ -38,4 +38,12 @@ impl ApiConfig {
Ok(()) Ok(())
} }
pub fn count_api_sources(&self) -> usize {
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
}
pub fn has_api_sources(&self) -> bool {
self.count_api_sources() > 0
}
} }

View File

@@ -514,8 +514,7 @@ impl HostPathDiscoveryEndpoint {
impl AppServer { impl AppServer {
pub fn new( pub fn new(
sk: SSk, keypair: Option<(SSk, SPk)>,
pk: SPk,
addrs: Vec<SocketAddr>, addrs: Vec<SocketAddr>,
verbosity: Verbosity, verbosity: Verbosity,
test_helpers: Option<AppServerTest>, test_helpers: Option<AppServerTest>,
@@ -605,10 +604,13 @@ impl AppServer {
)?; )?;
} }
// TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux let crypto_site = match keypair {
Some((sk, pk)) => ConstructionSite::from_product(CryptoServer::new(sk, pk)),
None => ConstructionSite::new(BuildCryptoServer::empty()),
};
Ok(Self { Ok(Self {
crypto_site: ConstructionSite::from_product(CryptoServer::new(sk, pk)), crypto_site,
peers: Vec::new(), peers: Vec::new(),
verbosity, verbosity,
sockets, sockets,

View File

@@ -1,4 +1,4 @@
use anyhow::{bail, ensure}; use anyhow::{bail, ensure, Context};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem; use rosenpass_ciphers::kem::StaticKem;
@@ -303,8 +303,11 @@ impl CliArgs {
); );
let config = config::Rosenpass::load(config_file)?; let config = config::Rosenpass::load(config_file)?;
let keypair = config
.keypair
.context("Config file present, but no keypair is specified.")?;
(config.public_key, config.secret_key) (keypair.public_key, keypair.secret_key)
} }
(_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()), (_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()),
_ => { _ => {
@@ -343,6 +346,7 @@ impl CliArgs {
let mut config = config::Rosenpass::load(config_file)?; let mut config = config::Rosenpass::load(config_file)?;
config.validate()?; config.validate()?;
self.apply_to_config(&mut config)?; self.apply_to_config(&mut config)?;
config.check_usefullness()?;
Self::event_loop(config, broker_interface, test_helpers)?; Self::event_loop(config, broker_interface, test_helpers)?;
} }
@@ -363,6 +367,7 @@ impl CliArgs {
} }
config.validate()?; config.validate()?;
self.apply_to_config(&mut config)?; self.apply_to_config(&mut config)?;
config.check_usefullness()?;
Self::event_loop(config, broker_interface, test_helpers)?; Self::event_loop(config, broker_interface, test_helpers)?;
} }
@@ -394,13 +399,19 @@ impl CliArgs {
const MAX_PSK_SIZE: usize = 1000; const MAX_PSK_SIZE: usize = 1000;
// load own keys // load own keys
let sk = SSk::load(&config.secret_key)?; let keypair = config
let pk = SPk::load(&config.public_key)?; .keypair
.as_ref()
.map(|kp| -> anyhow::Result<_> {
let sk = SSk::load(&kp.secret_key)?;
let pk = SPk::load(&kp.public_key)?;
Ok((sk, pk))
})
.transpose()?;
// start an application server // start an application server
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new( let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
sk, keypair,
pk,
config.listen.clone(), config.listen.clone(),
config.verbosity, config.verbosity,
test_helpers, test_helpers,

View File

@@ -23,11 +23,10 @@ use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Rosenpass { pub struct Rosenpass {
/// path to the public key file // TODO: Raise error if secret key or public key alone is set during deserialization
pub public_key: PathBuf, // SEE: https://github.com/serde-rs/serde/issues/2793
#[serde(flatten)]
/// path to the secret key file pub keypair: Option<Keypair>,
pub secret_key: PathBuf,
/// Location of the API listen sockets /// Location of the API listen sockets
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
@@ -58,6 +57,26 @@ pub struct Rosenpass {
pub config_file_path: PathBuf, pub config_file_path: PathBuf,
} }
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct Keypair {
/// path to the public key file
pub public_key: PathBuf,
/// path to the secret key file
pub secret_key: PathBuf,
}
impl Keypair {
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
let public_key = public_key.as_ref().to_path_buf();
let secret_key = secret_key.as_ref().to_path_buf();
Self {
public_key,
secret_key,
}
}
}
/// ## TODO /// ## TODO
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246> /// - 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)]
@@ -113,6 +132,12 @@ pub struct WireGuard {
pub extra_params: Vec<String>, pub extra_params: Vec<String>,
} }
impl Default for Rosenpass {
fn default() -> Self {
Self::empty()
}
}
impl Rosenpass { impl Rosenpass {
/// load configuration from a TOML file /// load configuration from a TOML file
/// ///
@@ -128,8 +153,10 @@ impl Rosenpass {
// resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237) // resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237)
use util::resolve_path_with_tilde; use util::resolve_path_with_tilde;
resolve_path_with_tilde(&mut config.public_key); if let Some(ref mut keypair) = config.keypair {
resolve_path_with_tilde(&mut config.secret_key); resolve_path_with_tilde(&mut keypair.public_key);
resolve_path_with_tilde(&mut keypair.secret_key);
}
for peer in config.peers.iter_mut() { for peer in config.peers.iter_mut() {
resolve_path_with_tilde(&mut peer.public_key); resolve_path_with_tilde(&mut peer.public_key);
if let Some(ref mut psk) = &mut peer.pre_shared_key { if let Some(ref mut psk) = &mut peer.pre_shared_key {
@@ -175,19 +202,21 @@ impl Rosenpass {
/// - check that files do not just exist but are also readable /// - check that files do not just exist but are also readable
/// - warn if neither out_key nor exchange_command of a peer is defined (v.i.) /// - warn if neither out_key nor exchange_command of a peer is defined (v.i.)
pub fn validate(&self) -> anyhow::Result<()> { pub fn validate(&self) -> anyhow::Result<()> {
// check the public key file exists if let Some(ref keypair) = self.keypair {
ensure!( // check the public key file exists
self.public_key.is_file(), ensure!(
"could not find public-key file {:?}: no such file", keypair.public_key.is_file(),
self.public_key "could not find public-key file {:?}: no such file",
); keypair.public_key
);
// check the secret-key file exists // check the secret-key file exists
ensure!( ensure!(
self.secret_key.is_file(), keypair.secret_key.is_file(),
"could not find secret-key file {:?}: no such file", "could not find secret-key file {:?}: no such file",
self.secret_key keypair.secret_key
); );
}
for (i, peer) in self.peers.iter().enumerate() { for (i, peer) in self.peers.iter().enumerate() {
// check peer's public-key file exists // check peer's public-key file exists
@@ -212,11 +241,33 @@ impl Rosenpass {
Ok(()) Ok(())
} }
pub fn check_usefullness(&self) -> anyhow::Result<()> {
#[cfg(not(feature = "experiment_api"))]
ensure!(self.keypair.is_some(), "Server keypair missing.");
#[cfg(feature = "experiment_api")]
ensure!(
self.keypair.is_some() || self.api.has_api_sources(),
"{}{}",
"Specify a server keypair or some API connections to configure the keypair with.",
"Without a keypair, rosenpass can not operate."
);
Ok(())
}
pub fn empty() -> Self {
Self::new(None)
}
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
Self::new(Some(Keypair::new(pk, sk)))
}
/// Creates a new configuration /// Creates a new configuration
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(public_key: P1, secret_key: P2) -> Self { pub fn new(keypair: Option<Keypair>) -> Self {
Self { Self {
public_key: PathBuf::from(public_key.as_ref()), keypair,
secret_key: PathBuf::from(secret_key.as_ref()),
listen: vec![], listen: vec![],
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
api: crate::api::config::ApiConfig::default(), api: crate::api::config::ApiConfig::default(),
@@ -242,7 +293,7 @@ impl Rosenpass {
/// from chaotic args /// from chaotic args
/// Quest: the grammar is undecideable, what do we do here? /// Quest: the grammar is undecideable, what do we do here?
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("", ""); let mut config = Self::new(Some(Keypair::new("", "")));
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
enum State { enum State {
@@ -303,7 +354,7 @@ impl Rosenpass {
already_set.insert(OwnPublicKey), already_set.insert(OwnPublicKey),
"public-key was already set" "public-key was already set"
); );
config.public_key = pk.into(); config.keypair.as_mut().unwrap().public_key = pk.into();
Own Own
} }
(OwnSecretKey, sk, None) => { (OwnSecretKey, sk, None) => {
@@ -311,7 +362,7 @@ impl Rosenpass {
already_set.insert(OwnSecretKey), already_set.insert(OwnSecretKey),
"secret-key was already set" "secret-key was already set"
); );
config.secret_key = sk.into(); config.keypair.as_mut().unwrap().secret_key = sk.into();
Own Own
} }
(OwnListen, l, None) => { (OwnListen, l, None) => {
@@ -446,10 +497,12 @@ impl Rosenpass {
}; };
Self { Self {
public_key: "/path/to/rp-public-key".into(), keypair: Some(Keypair {
secret_key: "/path/to/rp-secret-key".into(), public_key: "/path/to/rp-public-key".into(),
secret_key: "/path/to/rp-secret-key".into(),
}),
peers: vec![peer], peers: vec![peer],
..Self::new("", "") ..Self::new(None)
} }
} }
} }
@@ -462,13 +515,119 @@ impl Default for Verbosity {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use std::net::IpAddr; use std::{borrow::Borrow, net::IpAddr};
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
toml::from_str(s.borrow())
}
fn toml_ser<S: Serialize>(s: S) -> Result<toml::Table, toml::ser::Error> {
toml::Table::try_from(s)
}
fn assert_toml<L: Serialize, R: Borrow<str>>(l: L, r: R, info: &str) -> anyhow::Result<()> {
fn lines_prepend(prefix: &str, s: &str) -> anyhow::Result<String> {
use std::fmt::Write;
let mut buf = String::new();
for line in s.lines() {
writeln!(&mut buf, "{prefix}{line}")?;
}
Ok(buf)
}
let l = toml_ser(l)?;
let r = toml_des(r.borrow())?;
ensure!(
l == r,
"{}{}TOML value mismatch.\n Have:\n{}\n Expected:\n{}",
info,
if info.is_empty() { "" } else { ": " },
lines_prepend(" ", &toml::to_string_pretty(&l)?)?,
lines_prepend(" ", &toml::to_string_pretty(&r)?)?
);
Ok(())
}
fn assert_toml_round<'de, L: Serialize + Deserialize<'de>, R: Borrow<str>>(
l: L,
r: R,
) -> anyhow::Result<()> {
let l = toml_ser(l)?;
assert_toml(&l, r.borrow(), "Straight deserialization")?;
let l: L = l.try_into().unwrap();
let l = toml_ser(l).unwrap();
assert_toml(l, r.borrow(), "Roundtrip deserialization")?;
Ok(())
}
fn split_str(s: &str) -> Vec<String> { fn split_str(s: &str) -> Vec<String> {
s.split(' ').map(|s| s.to_string()).collect() s.split(' ').map(|s| s.to_string()).collect()
} }
#[test]
fn toml_serialization() -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
assert_toml_round(
Rosenpass::empty(),
r#"
listen = []
verbosity = "Quiet"
peers = []
[api]
listen_path = []
listen_fd = []
stream_fd = []
"#,
)?;
#[cfg(not(feature = "experiment_api"))]
assert_toml_round(
Rosenpass::empty(),
r#"
listen = []
verbosity = "Quiet"
peers = []
"#,
)?;
#[cfg(feature = "experiment_api")]
assert_toml_round(
Rosenpass::from_sk_pk("/my/sk", "/my/pk"),
r#"
public_key = "/my/pk"
secret_key = "/my/sk"
listen = []
verbosity = "Quiet"
peers = []
[api]
listen_path = []
listen_fd = []
stream_fd = []
"#,
)?;
#[cfg(not(feature = "experiment_api"))]
assert_toml_round(
Rosenpass::from_sk_pk("/my/sk", "/my/pk"),
r#"
public_key = "/my/pk"
secret_key = "/my/sk"
listen = []
verbosity = "Quiet"
peers = []
"#,
)?;
Ok(())
}
#[test] #[test]
fn test_simple_cli_parse() { fn test_simple_cli_parse() {
let args = split_str( let args = split_str(
@@ -479,8 +638,10 @@ mod test {
let config = Rosenpass::parse_args(args).unwrap(); let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(config.public_key, PathBuf::from("/my/public-key")); assert_eq!(
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key")); config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose); assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!( assert_eq!(
&config.listen, &config.listen,
@@ -509,8 +670,10 @@ mod test {
let config = Rosenpass::parse_args(args).unwrap(); let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(config.public_key, PathBuf::from("/my/public-key")); assert_eq!(
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key")); config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose); assert_eq!(config.verbosity, Verbosity::Verbose);
assert!(&config.listen.is_empty()); assert!(&config.listen.is_empty());
assert_eq!( assert_eq!(

View File

@@ -37,10 +37,11 @@ fn api_integration_test() -> anyhow::Result<()> {
let peer_b_osk = tempfile!("b.osk"); let peer_b_osk = tempfile!("b.osk");
use rosenpass::config; use rosenpass::config;
let peer_a_keypair = config::Keypair::new(tempfile!("a.pk"), tempfile!("a.sk"));
let peer_a = config::Rosenpass { let peer_a = config::Rosenpass {
config_file_path: tempfile!("a.config"), config_file_path: tempfile!("a.config"),
secret_key: tempfile!("a.sk"), keypair: Some(peer_a_keypair.clone()),
public_key: tempfile!("a.pk"),
listen: peer_a_endpoint.to_socket_addrs()?.collect(), // TODO: This could collide by accident listen: peer_a_endpoint.to_socket_addrs()?.collect(), // TODO: This could collide by accident
verbosity: config::Verbosity::Verbose, verbosity: config::Verbosity::Verbose,
api: api::config::ApiConfig { api: api::config::ApiConfig {
@@ -57,10 +58,10 @@ fn api_integration_test() -> anyhow::Result<()> {
}], }],
}; };
let peer_b_keypair = config::Keypair::new(tempfile!("b.pk"), tempfile!("b.sk"));
let peer_b = config::Rosenpass { let peer_b = config::Rosenpass {
config_file_path: tempfile!("b.config"), config_file_path: tempfile!("b.config"),
secret_key: tempfile!("b.sk"), keypair: Some(peer_b_keypair.clone()),
public_key: tempfile!("b.pk"),
listen: vec![], listen: vec![],
verbosity: config::Verbosity::Verbose, verbosity: config::Verbosity::Verbose,
api: api::config::ApiConfig { api: api::config::ApiConfig {
@@ -79,12 +80,12 @@ fn api_integration_test() -> anyhow::Result<()> {
// Generate the keys // Generate the keys
rosenpass::cli::testing::generate_and_save_keypair( rosenpass::cli::testing::generate_and_save_keypair(
peer_a.secret_key.clone(), peer_a_keypair.secret_key.clone(),
peer_a.public_key.clone(), peer_a_keypair.public_key.clone(),
)?; )?;
rosenpass::cli::testing::generate_and_save_keypair( rosenpass::cli::testing::generate_and_save_keypair(
peer_b.secret_key.clone(), peer_b_keypair.secret_key.clone(),
peer_b.public_key.clone(), peer_b_keypair.public_key.clone(),
)?; )?;
// Write the configuration files // Write the configuration files

View File

@@ -188,8 +188,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
let pk = SPk::load(&pqpk)?; let pk = SPk::load(&pqpk)?;
let mut srv = Box::new(AppServer::new( let mut srv = Box::new(AppServer::new(
sk, Some((sk, pk)),
pk,
if let Some(listen) = options.listen { if let Some(listen) = options.listen {
vec![listen] vec![listen]
} else { } else {