pub use tor_config::{ConfigBuildError, ConfigurationSource, Reconfigure};
pub use tor_config_path::{CfgPath, CfgPathError};
use amplify::Getters;
use derive_deftly::Deftly;
use serde::{Deserialize, Serialize};
use tor_config::derive::prelude::*;
use tor_config::{BoolOrAuto, ExplicitOrAuto, define_list_builder_helper, impl_not_auto_value};
use tor_persist::hsnickname::HsNickname;
use std::collections::BTreeMap;
use std::path::PathBuf;
use crate::KeystoreId;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ArtiKeystoreKind {
Native,
#[cfg(feature = "ephemeral-keystore")]
Ephemeral,
}
impl_not_auto_value! {ArtiKeystoreKind}
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(pre_build = "Self::validate"))]
pub struct ArtiKeystoreConfig {
#[deftly(tor_config(default))]
enabled: BoolOrAuto,
#[deftly(tor_config(sub_builder))]
primary: PrimaryKeystoreConfig,
#[deftly(tor_config(sub_builder))]
ctor: CTorKeystoreConfig,
}
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(pre_build = "Self::validate"))]
pub struct CTorKeystoreConfig {
#[deftly(tor_config(sub_builder))]
services: CTorServiceKeystoreConfigMap,
#[deftly(tor_config(no_magic, sub_builder))]
clients: CTorClientKeystoreConfigList,
}
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize)]
#[derive_deftly(TorConfig)]
pub struct PrimaryKeystoreConfig {
#[deftly(tor_config(default))]
kind: ExplicitOrAuto<ArtiKeystoreKind>,
}
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(no_default_trait))]
pub struct CTorServiceKeystoreConfig {
#[deftly(tor_config(no_default))]
id: KeystoreId,
#[deftly(tor_config(no_default))]
path: PathBuf,
#[deftly(tor_config(no_default))]
nickname: HsNickname,
}
pub(crate) type CTorServiceKeystoreConfigMap = BTreeMap<HsNickname, CTorServiceKeystoreConfig>;
type CTorServiceKeystoreConfigBuilderMap = BTreeMap<HsNickname, CTorServiceKeystoreConfigBuilder>;
define_list_builder_helper! {
pub struct CTorServiceKeystoreConfigMapBuilder {
stores: [CTorServiceKeystoreConfigBuilder],
}
built: CTorServiceKeystoreConfigMap = build_ctor_service_list(stores)?;
default = vec![];
#[serde(try_from="CTorServiceKeystoreConfigBuilderMap", into="CTorServiceKeystoreConfigBuilderMap")]
}
impl TryFrom<CTorServiceKeystoreConfigBuilderMap> for CTorServiceKeystoreConfigMapBuilder {
type Error = ConfigBuildError;
fn try_from(value: CTorServiceKeystoreConfigBuilderMap) -> Result<Self, Self::Error> {
let mut list_builder = CTorServiceKeystoreConfigMapBuilder::default();
for (nickname, mut cfg) in value {
match &cfg.nickname {
Some(n) if n == &nickname => (),
None => (),
Some(other) => {
return Err(ConfigBuildError::Inconsistent {
fields: vec![nickname.to_string(), format!("{nickname}.{other}")],
problem: "mismatched nicknames on onion service.".into(),
});
}
}
cfg.nickname = Some(nickname);
list_builder.access().push(cfg);
}
Ok(list_builder)
}
}
impl From<CTorServiceKeystoreConfigMapBuilder> for CTorServiceKeystoreConfigBuilderMap {
fn from(value: CTorServiceKeystoreConfigMapBuilder) -> CTorServiceKeystoreConfigBuilderMap {
let mut map = BTreeMap::new();
for cfg in value.stores.into_iter().flatten() {
let nickname = cfg.nickname.clone().unwrap_or_else(|| {
"Unnamed"
.to_string()
.try_into()
.expect("'Unnamed' was not a valid nickname")
});
map.insert(nickname, cfg);
}
map
}
}
fn build_ctor_service_list(
ctor_stores: Vec<CTorServiceKeystoreConfig>,
) -> Result<CTorServiceKeystoreConfigMap, ConfigBuildError> {
use itertools::Itertools as _;
if !ctor_stores.iter().map(|s| &s.id).all_unique() {
return Err(ConfigBuildError::Inconsistent {
fields: ["id"].map(Into::into).into_iter().collect(),
problem: "the C Tor keystores do not have unique IDs".into(),
});
}
let mut map = BTreeMap::new();
for service in ctor_stores {
if let Some(previous_value) = map.insert(service.nickname.clone(), service) {
return Err(ConfigBuildError::Inconsistent {
fields: vec!["nickname".into()],
problem: format!(
"Multiple C Tor service keystores for service with nickname {}",
previous_value.nickname
),
});
};
}
Ok(map)
}
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(no_default_trait))]
pub struct CTorClientKeystoreConfig {
#[deftly(tor_config(no_default))]
id: KeystoreId,
#[deftly(tor_config(no_default))]
path: PathBuf,
}
pub type CTorClientKeystoreConfigList = Vec<CTorClientKeystoreConfig>;
define_list_builder_helper! {
pub struct CTorClientKeystoreConfigListBuilder {
stores: [CTorClientKeystoreConfigBuilder],
}
built: CTorClientKeystoreConfigList = build_ctor_client_store_config(stores)?;
default = vec![];
}
fn build_ctor_client_store_config(
ctor_stores: Vec<CTorClientKeystoreConfig>,
) -> Result<CTorClientKeystoreConfigList, ConfigBuildError> {
use itertools::Itertools as _;
if !ctor_stores.iter().map(|s| &s.id).all_unique() {
return Err(ConfigBuildError::Inconsistent {
fields: ["id"].map(Into::into).into_iter().collect(),
problem: "the C Tor keystores do not have unique IDs".into(),
});
}
Ok(ctor_stores)
}
impl ArtiKeystoreConfig {
pub fn is_enabled(&self) -> bool {
let default = cfg!(feature = "keymgr");
self.enabled.as_bool().unwrap_or(default)
}
pub fn primary_kind(&self) -> Option<ArtiKeystoreKind> {
use ExplicitOrAuto as EoA;
if !self.is_enabled() {
return None;
}
let kind = match self.primary.kind {
EoA::Explicit(kind) => kind,
EoA::Auto => ArtiKeystoreKind::Native,
};
Some(kind)
}
pub fn ctor_svc_stores(&self) -> impl Iterator<Item = &CTorServiceKeystoreConfig> {
self.ctor.services.values()
}
pub fn ctor_client_stores(&self) -> impl Iterator<Item = &CTorClientKeystoreConfig> {
self.ctor.clients.iter()
}
}
impl ArtiKeystoreConfigBuilder {
#[cfg(not(feature = "keymgr"))]
#[allow(clippy::unnecessary_wraps)]
fn validate(&self) -> Result<(), ConfigBuildError> {
use BoolOrAuto as BoA;
use ExplicitOrAuto as EoA;
if self.enabled == Some(BoA::Explicit(true)) {
return Err(ConfigBuildError::Inconsistent {
fields: ["enabled"].map(Into::into).into_iter().collect(),
problem: "keystore enabled=true, but keymgr feature not enabled".into(),
});
}
let () = match self.primary.kind {
None | Some(EoA::Auto) => Ok(()),
_ => Err(ConfigBuildError::Inconsistent {
fields: ["enabled", "kind"].map(Into::into).into_iter().collect(),
problem: "kind!=auto, but keymgr feature not enabled".into(),
}),
}?;
Ok(())
}
#[cfg(feature = "keymgr")]
#[allow(clippy::unnecessary_wraps)]
fn validate(&self) -> Result<(), ConfigBuildError> {
Ok(())
}
pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
self.ctor.ctor_service(builder);
self
}
}
impl CTorKeystoreConfigBuilder {
#[cfg(not(feature = "ctor-keystore"))]
fn validate(&self) -> Result<(), ConfigBuildError> {
let no_compile_time_support = |field: &str| ConfigBuildError::NoCompileTimeSupport {
field: field.into(),
problem: format!("{field} configured but ctor-keystore feature not enabled"),
};
if self
.services
.stores
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or_default()
{
return Err(no_compile_time_support("C Tor service keystores"));
}
if self
.clients
.stores
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or_default()
{
return Err(no_compile_time_support("C Tor client keystores"));
}
Ok(())
}
#[cfg(feature = "ctor-keystore")]
fn validate(&self) -> Result<(), ConfigBuildError> {
use itertools::Itertools as _;
use itertools::chain;
let Self { services, clients } = self;
let mut ctor_store_ids = chain![
services.stores.iter().flatten().map(|s| &s.id),
clients.stores.iter().flatten().map(|s| &s.id)
];
if !ctor_store_ids.all_unique() {
return Err(ConfigBuildError::Inconsistent {
fields: ["id"].map(Into::into).into_iter().collect(),
problem: "the C Tor keystores do not have unique IDs".into(),
});
}
Ok(())
}
pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
if let Some(ref mut stores) = self.services.stores {
stores.push(builder);
} else {
self.services.stores = Some(vec![builder]);
}
self
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use std::path::PathBuf;
use std::str::FromStr as _;
use tor_config::assert_config_error;
fn svc_config_builder(
id: &str,
path: &str,
nickname: &str,
) -> CTorServiceKeystoreConfigBuilder {
let mut b = CTorServiceKeystoreConfigBuilder::default();
b.id(KeystoreId::from_str(id).unwrap());
b.path(PathBuf::from(path));
b.nickname(HsNickname::from_str(nickname).unwrap());
b
}
fn client_config_builder(id: &str, path: &str) -> CTorClientKeystoreConfigBuilder {
let mut b = CTorClientKeystoreConfigBuilder::default();
b.id(KeystoreId::from_str(id).unwrap());
b.path(PathBuf::from(path));
b
}
#[test]
#[cfg(all(feature = "ctor-keystore", feature = "keymgr"))]
fn invalid_config() {
let mut builder = ArtiKeystoreConfigBuilder::default();
builder
.ctor()
.clients()
.access()
.push(client_config_builder("foo", "/var/lib/foo"));
builder
.ctor()
.clients()
.access()
.push(client_config_builder("foo", "/var/lib/bar"));
let err = builder.build().unwrap_err();
assert_config_error!(
err,
Inconsistent,
"the C Tor keystores do not have unique IDs"
);
let mut builder = ArtiKeystoreConfigBuilder::default();
builder
.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
let err = builder.build().unwrap_err();
assert_config_error!(
err,
Inconsistent,
"the C Tor keystores do not have unique IDs"
);
let mut builder = ArtiKeystoreConfigBuilder::default();
builder
.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
.ctor_service(svc_config_builder("bar", "/var/lib/bar", "pungent"));
let err = builder.build().unwrap_err();
assert_config_error!(
err,
Inconsistent,
"Multiple C Tor service keystores for service with nickname pungent"
);
}
#[test]
#[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
fn invalid_config() {
let mut builder = ArtiKeystoreConfigBuilder::default();
builder
.ctor()
.clients()
.access()
.push(client_config_builder("foo", "/var/lib/foo"));
let err = builder.build().unwrap_err();
assert_config_error!(
err,
NoCompileTimeSupport,
"C Tor client keystores configured but ctor-keystore feature not enabled"
);
let mut builder = ArtiKeystoreConfigBuilder::default();
builder.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
let err = builder.build().unwrap_err();
assert_config_error!(
err,
NoCompileTimeSupport,
"C Tor service keystores configured but ctor-keystore feature not enabled"
);
}
#[test]
#[cfg(not(feature = "keymgr"))]
fn invalid_config() {
let mut builder = ArtiKeystoreConfigBuilder::default();
builder.enabled(BoolOrAuto::Explicit(true));
let err = builder.build().unwrap_err();
assert_config_error!(
err,
Inconsistent,
"keystore enabled=true, but keymgr feature not enabled"
);
}
#[test]
#[cfg(feature = "ctor-keystore")]
fn valid_config() {
let mut builder = ArtiKeystoreConfigBuilder::default();
builder
.ctor()
.clients()
.access()
.push(client_config_builder("foo", "/var/lib/foo"));
builder
.ctor()
.clients()
.access()
.push(client_config_builder("bar", "/var/lib/bar"));
let res = builder.build();
assert!(res.is_ok(), "{:?}", res);
}
#[test]
#[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
fn valid_config() {
let mut builder = ArtiKeystoreConfigBuilder::default();
builder
.enabled(BoolOrAuto::Explicit(true))
.primary()
.kind(ExplicitOrAuto::Explicit(ArtiKeystoreKind::Native));
let res = builder.build();
assert!(res.is_ok(), "{:?}", res);
}
}