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(())
}
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 {
pub fn new(
sk: SSk,
pk: SPk,
keypair: Option<(SSk, SPk)>,
addrs: Vec<SocketAddr>,
verbosity: Verbosity,
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 {
crypto_site: ConstructionSite::from_product(CryptoServer::new(sk, pk)),
crypto_site,
peers: Vec::new(),
verbosity,
sockets,

View File

@@ -1,4 +1,4 @@
use anyhow::{bail, ensure};
use anyhow::{bail, ensure, Context};
use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
@@ -303,8 +303,11 @@ impl CliArgs {
);
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()),
_ => {
@@ -343,6 +346,7 @@ impl CliArgs {
let mut config = config::Rosenpass::load(config_file)?;
config.validate()?;
self.apply_to_config(&mut config)?;
config.check_usefullness()?;
Self::event_loop(config, broker_interface, test_helpers)?;
}
@@ -363,6 +367,7 @@ impl CliArgs {
}
config.validate()?;
self.apply_to_config(&mut config)?;
config.check_usefullness()?;
Self::event_loop(config, broker_interface, test_helpers)?;
}
@@ -394,13 +399,19 @@ impl CliArgs {
const MAX_PSK_SIZE: usize = 1000;
// load own keys
let sk = SSk::load(&config.secret_key)?;
let pk = SPk::load(&config.public_key)?;
let keypair = config
.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
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
sk,
pk,
keypair,
config.listen.clone(),
config.verbosity,
test_helpers,

View File

@@ -23,11 +23,10 @@ use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize)]
pub struct Rosenpass {
/// path to the public key file
pub public_key: PathBuf,
/// path to the secret key file
pub secret_key: PathBuf,
// TODO: Raise error if secret key or public key alone is set during deserialization
// SEE: https://github.com/serde-rs/serde/issues/2793
#[serde(flatten)]
pub keypair: Option<Keypair>,
/// Location of the API listen sockets
#[cfg(feature = "experiment_api")]
@@ -58,6 +57,26 @@ pub struct Rosenpass {
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
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
@@ -113,6 +132,12 @@ pub struct WireGuard {
pub extra_params: Vec<String>,
}
impl Default for Rosenpass {
fn default() -> Self {
Self::empty()
}
}
impl Rosenpass {
/// load configuration from a TOML file
///
@@ -128,8 +153,10 @@ impl Rosenpass {
// resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237)
use util::resolve_path_with_tilde;
resolve_path_with_tilde(&mut config.public_key);
resolve_path_with_tilde(&mut config.secret_key);
if let Some(ref mut keypair) = config.keypair {
resolve_path_with_tilde(&mut keypair.public_key);
resolve_path_with_tilde(&mut keypair.secret_key);
}
for peer in config.peers.iter_mut() {
resolve_path_with_tilde(&mut peer.public_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
/// - warn if neither out_key nor exchange_command of a peer is defined (v.i.)
pub fn validate(&self) -> anyhow::Result<()> {
// check the public key file exists
ensure!(
self.public_key.is_file(),
"could not find public-key file {:?}: no such file",
self.public_key
);
if let Some(ref keypair) = self.keypair {
// check the public key file exists
ensure!(
keypair.public_key.is_file(),
"could not find public-key file {:?}: no such file",
keypair.public_key
);
// check the secret-key file exists
ensure!(
self.secret_key.is_file(),
"could not find secret-key file {:?}: no such file",
self.secret_key
);
// check the secret-key file exists
ensure!(
keypair.secret_key.is_file(),
"could not find secret-key file {:?}: no such file",
keypair.secret_key
);
}
for (i, peer) in self.peers.iter().enumerate() {
// check peer's public-key file exists
@@ -212,11 +241,33 @@ impl Rosenpass {
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
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(public_key: P1, secret_key: P2) -> Self {
pub fn new(keypair: Option<Keypair>) -> Self {
Self {
public_key: PathBuf::from(public_key.as_ref()),
secret_key: PathBuf::from(secret_key.as_ref()),
keypair,
listen: vec![],
#[cfg(feature = "experiment_api")]
api: crate::api::config::ApiConfig::default(),
@@ -242,7 +293,7 @@ impl Rosenpass {
/// from chaotic args
/// Quest: the grammar is undecideable, what do we do here?
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)]
enum State {
@@ -303,7 +354,7 @@ impl Rosenpass {
already_set.insert(OwnPublicKey),
"public-key was already set"
);
config.public_key = pk.into();
config.keypair.as_mut().unwrap().public_key = pk.into();
Own
}
(OwnSecretKey, sk, None) => {
@@ -311,7 +362,7 @@ impl Rosenpass {
already_set.insert(OwnSecretKey),
"secret-key was already set"
);
config.secret_key = sk.into();
config.keypair.as_mut().unwrap().secret_key = sk.into();
Own
}
(OwnListen, l, None) => {
@@ -446,10 +497,12 @@ impl Rosenpass {
};
Self {
public_key: "/path/to/rp-public-key".into(),
secret_key: "/path/to/rp-secret-key".into(),
keypair: Some(Keypair {
public_key: "/path/to/rp-public-key".into(),
secret_key: "/path/to/rp-secret-key".into(),
}),
peers: vec![peer],
..Self::new("", "")
..Self::new(None)
}
}
}
@@ -462,13 +515,119 @@ impl Default for Verbosity {
#[cfg(test)]
mod test {
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> {
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]
fn test_simple_cli_parse() {
let args = split_str(
@@ -479,8 +638,10 @@ mod test {
let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
@@ -509,8 +670,10 @@ mod test {
let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert!(&config.listen.is_empty());
assert_eq!(

View File

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

View File

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