1pub use tor_config::{ConfigBuildError, ConfigurationSource, Reconfigure};
4pub use tor_config_path::{CfgPath, CfgPathError};
5
6use amplify::Getters;
7use derive_deftly::Deftly;
8use serde::{Deserialize, Serialize};
9use tor_config::derive::prelude::*;
10use tor_config::{BoolOrAuto, ExplicitOrAuto, define_list_builder_helper, impl_not_auto_value};
11use tor_persist::hsnickname::HsNickname;
12
13use std::collections::BTreeMap;
14use std::path::PathBuf;
15
16use crate::KeystoreId;
17
18#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21#[non_exhaustive]
22pub enum ArtiKeystoreKind {
23 Native,
25 #[cfg(feature = "ephemeral-keystore")]
27 Ephemeral,
28}
29impl_not_auto_value! {ArtiKeystoreKind}
30
31#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
33#[derive_deftly(TorConfig)]
34#[deftly(tor_config(pre_build = "Self::validate"))]
35pub struct ArtiKeystoreConfig {
36 #[deftly(tor_config(default))]
38 enabled: BoolOrAuto,
39
40 #[deftly(tor_config(sub_builder))]
42 primary: PrimaryKeystoreConfig,
43
44 #[deftly(tor_config(sub_builder))]
53 ctor: CTorKeystoreConfig,
54}
55
56#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
58#[derive_deftly(TorConfig)]
59#[deftly(tor_config(pre_build = "Self::validate"))]
60pub struct CTorKeystoreConfig {
61 #[deftly(tor_config(sub_builder))]
65 services: CTorServiceKeystoreConfigMap,
66
67 #[deftly(tor_config(no_magic, sub_builder))]
71 clients: CTorClientKeystoreConfigList,
72}
73
74#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize)]
76#[derive_deftly(TorConfig)]
77pub struct PrimaryKeystoreConfig {
78 #[deftly(tor_config(default))]
80 kind: ExplicitOrAuto<ArtiKeystoreKind>,
81}
82
83#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
85#[derive_deftly(TorConfig)]
86#[deftly(tor_config(no_default_trait))]
87pub struct CTorServiceKeystoreConfig {
88 #[deftly(tor_config(no_default))]
97 id: KeystoreId,
98
99 #[deftly(tor_config(no_default))]
107 path: PathBuf,
108
109 #[deftly(tor_config(no_default))]
111 nickname: HsNickname,
112}
113
114pub(crate) type CTorServiceKeystoreConfigMap = BTreeMap<HsNickname, CTorServiceKeystoreConfig>;
117
118type CTorServiceKeystoreConfigBuilderMap = BTreeMap<HsNickname, CTorServiceKeystoreConfigBuilder>;
121
122define_list_builder_helper! {
123 pub struct CTorServiceKeystoreConfigMapBuilder {
124 stores: [CTorServiceKeystoreConfigBuilder],
125 }
126 built: CTorServiceKeystoreConfigMap = build_ctor_service_list(stores)?;
127 default = vec![];
128 #[serde(try_from="CTorServiceKeystoreConfigBuilderMap", into="CTorServiceKeystoreConfigBuilderMap")]
129}
130
131impl TryFrom<CTorServiceKeystoreConfigBuilderMap> for CTorServiceKeystoreConfigMapBuilder {
132 type Error = ConfigBuildError;
133
134 fn try_from(value: CTorServiceKeystoreConfigBuilderMap) -> Result<Self, Self::Error> {
135 let mut list_builder = CTorServiceKeystoreConfigMapBuilder::default();
136 for (nickname, mut cfg) in value {
137 match &cfg.nickname {
138 Some(n) if n == &nickname => (),
139 None => (),
140 Some(other) => {
141 return Err(ConfigBuildError::Inconsistent {
142 fields: vec![nickname.to_string(), format!("{nickname}.{other}")],
143 problem: "mismatched nicknames on onion service.".into(),
144 });
145 }
146 }
147 cfg.nickname = Some(nickname);
148 list_builder.access().push(cfg);
149 }
150 Ok(list_builder)
151 }
152}
153
154impl From<CTorServiceKeystoreConfigMapBuilder> for CTorServiceKeystoreConfigBuilderMap {
155 fn from(value: CTorServiceKeystoreConfigMapBuilder) -> CTorServiceKeystoreConfigBuilderMap {
166 let mut map = BTreeMap::new();
167 for cfg in value.stores.into_iter().flatten() {
168 let nickname = cfg.nickname.clone().unwrap_or_else(|| {
169 "Unnamed"
170 .to_string()
171 .try_into()
172 .expect("'Unnamed' was not a valid nickname")
173 });
174 map.insert(nickname, cfg);
175 }
176 map
177 }
178}
179
180fn build_ctor_service_list(
185 ctor_stores: Vec<CTorServiceKeystoreConfig>,
186) -> Result<CTorServiceKeystoreConfigMap, ConfigBuildError> {
187 use itertools::Itertools as _;
188
189 if !ctor_stores.iter().map(|s| &s.id).all_unique() {
190 return Err(ConfigBuildError::Inconsistent {
191 fields: ["id"].map(Into::into).into_iter().collect(),
192 problem: "the C Tor keystores do not have unique IDs".into(),
193 });
194 }
195
196 let mut map = BTreeMap::new();
197 for service in ctor_stores {
198 if let Some(previous_value) = map.insert(service.nickname.clone(), service) {
199 return Err(ConfigBuildError::Inconsistent {
200 fields: vec!["nickname".into()],
201 problem: format!(
202 "Multiple C Tor service keystores for service with nickname {}",
203 previous_value.nickname
204 ),
205 });
206 };
207 }
208
209 Ok(map)
210}
211
212#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
214#[derive_deftly(TorConfig)]
215#[deftly(tor_config(no_default_trait))]
216pub struct CTorClientKeystoreConfig {
217 #[deftly(tor_config(no_default))]
222 id: KeystoreId,
223
224 #[deftly(tor_config(no_default))]
236 path: PathBuf,
237}
238
239pub type CTorClientKeystoreConfigList = Vec<CTorClientKeystoreConfig>;
241
242define_list_builder_helper! {
243 pub struct CTorClientKeystoreConfigListBuilder {
244 stores: [CTorClientKeystoreConfigBuilder],
245 }
246 built: CTorClientKeystoreConfigList = build_ctor_client_store_config(stores)?;
247 default = vec![];
248}
249
250fn build_ctor_client_store_config(
254 ctor_stores: Vec<CTorClientKeystoreConfig>,
255) -> Result<CTorClientKeystoreConfigList, ConfigBuildError> {
256 use itertools::Itertools as _;
257
258 if !ctor_stores.iter().map(|s| &s.id).all_unique() {
259 return Err(ConfigBuildError::Inconsistent {
260 fields: ["id"].map(Into::into).into_iter().collect(),
261 problem: "the C Tor keystores do not have unique IDs".into(),
262 });
263 }
264
265 Ok(ctor_stores)
266}
267
268impl ArtiKeystoreConfig {
269 pub fn is_enabled(&self) -> bool {
271 let default = cfg!(feature = "keymgr");
272
273 self.enabled.as_bool().unwrap_or(default)
274 }
275
276 pub fn primary_kind(&self) -> Option<ArtiKeystoreKind> {
280 use ExplicitOrAuto as EoA;
281
282 if !self.is_enabled() {
283 return None;
284 }
285
286 let kind = match self.primary.kind {
287 EoA::Explicit(kind) => kind,
288 EoA::Auto => ArtiKeystoreKind::Native,
289 };
290
291 Some(kind)
292 }
293
294 pub fn ctor_svc_stores(&self) -> impl Iterator<Item = &CTorServiceKeystoreConfig> {
296 self.ctor.services.values()
297 }
298
299 pub fn ctor_client_stores(&self) -> impl Iterator<Item = &CTorClientKeystoreConfig> {
301 self.ctor.clients.iter()
302 }
303}
304
305impl ArtiKeystoreConfigBuilder {
306 #[cfg(not(feature = "keymgr"))]
308 #[allow(clippy::unnecessary_wraps)]
309 fn validate(&self) -> Result<(), ConfigBuildError> {
310 use BoolOrAuto as BoA;
311 use ExplicitOrAuto as EoA;
312 if self.enabled == Some(BoA::Explicit(true)) {
316 return Err(ConfigBuildError::Inconsistent {
317 fields: ["enabled"].map(Into::into).into_iter().collect(),
318 problem: "keystore enabled=true, but keymgr feature not enabled".into(),
319 });
320 }
321
322 let () = match self.primary.kind {
323 None | Some(EoA::Auto) => Ok(()),
325 _ => Err(ConfigBuildError::Inconsistent {
326 fields: ["enabled", "kind"].map(Into::into).into_iter().collect(),
327 problem: "kind!=auto, but keymgr feature not enabled".into(),
328 }),
329 }?;
330
331 Ok(())
332 }
333
334 #[cfg(feature = "keymgr")]
336 #[allow(clippy::unnecessary_wraps)]
337 fn validate(&self) -> Result<(), ConfigBuildError> {
338 Ok(())
339 }
340
341 pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
343 self.ctor.ctor_service(builder);
344 self
345 }
346}
347
348impl CTorKeystoreConfigBuilder {
349 fn validate(&self) -> Result<(), ConfigBuildError> {
354 use itertools::Itertools as _;
355 use itertools::chain;
356
357 let Self { services, clients } = self;
358 let mut ctor_store_ids = chain![
359 services.stores.iter().flatten().map(|s| &s.id),
360 clients.stores.iter().flatten().map(|s| &s.id)
361 ];
362
363 if !ctor_store_ids.all_unique() {
366 return Err(ConfigBuildError::Inconsistent {
367 fields: ["id"].map(Into::into).into_iter().collect(),
368 problem: "the C Tor keystores do not have unique IDs".into(),
369 });
370 }
371
372 Ok(())
373 }
374
375 pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
377 if let Some(ref mut stores) = self.services.stores {
378 stores.push(builder);
379 } else {
380 self.services.stores = Some(vec![builder]);
381 }
382
383 self
384 }
385}
386
387#[cfg(test)]
388mod test {
389 #![allow(clippy::bool_assert_comparison)]
391 #![allow(clippy::clone_on_copy)]
392 #![allow(clippy::dbg_macro)]
393 #![allow(clippy::mixed_attributes_style)]
394 #![allow(clippy::print_stderr)]
395 #![allow(clippy::print_stdout)]
396 #![allow(clippy::single_char_pattern)]
397 #![allow(clippy::unwrap_used)]
398 #![allow(clippy::unchecked_time_subtraction)]
399 #![allow(clippy::useless_vec)]
400 #![allow(clippy::needless_pass_by_value)]
401 #![allow(clippy::string_slice)] use super::*;
405
406 use std::path::PathBuf;
407 use std::str::FromStr as _;
408 use tor_config::assert_config_error;
409
410 fn svc_config_builder(
412 id: &str,
413 path: &str,
414 nickname: &str,
415 ) -> CTorServiceKeystoreConfigBuilder {
416 let mut b = CTorServiceKeystoreConfigBuilder::default();
417 b.id(KeystoreId::from_str(id).unwrap());
418 b.path(PathBuf::from(path));
419 b.nickname(HsNickname::from_str(nickname).unwrap());
420 b
421 }
422
423 fn client_config_builder(id: &str, path: &str) -> CTorClientKeystoreConfigBuilder {
425 let mut b = CTorClientKeystoreConfigBuilder::default();
426 b.id(KeystoreId::from_str(id).unwrap());
427 b.path(PathBuf::from(path));
428 b
429 }
430
431 #[test]
432 fn invalid_config() {
433 let mut builder = ArtiKeystoreConfigBuilder::default();
434 builder
436 .ctor()
437 .clients()
438 .access()
439 .push(client_config_builder("foo", "/var/lib/foo"));
440
441 builder
442 .ctor()
443 .clients()
444 .access()
445 .push(client_config_builder("foo", "/var/lib/bar"));
446 let err = builder.build().unwrap_err();
447
448 assert_config_error!(
449 err,
450 Inconsistent,
451 "the C Tor keystores do not have unique IDs"
452 );
453
454 let mut builder = ArtiKeystoreConfigBuilder::default();
455 builder
457 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
458 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
459 let err = builder.build().unwrap_err();
460
461 assert_config_error!(
462 err,
463 Inconsistent,
464 "the C Tor keystores do not have unique IDs"
465 );
466
467 let mut builder = ArtiKeystoreConfigBuilder::default();
468 builder
470 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
471 .ctor_service(svc_config_builder("bar", "/var/lib/bar", "pungent"));
472 let err = builder.build().unwrap_err();
473
474 assert_config_error!(
475 err,
476 Inconsistent,
477 "Multiple C Tor service keystores for service with nickname pungent"
478 );
479 }
480
481 #[test]
482 #[cfg(not(feature = "keymgr"))]
483 fn keystore_use_requires_keymgr_feat() {
484 let mut builder = ArtiKeystoreConfigBuilder::default();
485 builder.enabled(BoolOrAuto::Explicit(true));
486
487 let err = builder.build().unwrap_err();
488 assert_config_error!(
489 err,
490 Inconsistent,
491 "keystore enabled=true, but keymgr feature not enabled"
492 );
493 }
494
495 #[test]
496 #[cfg(feature = "keymgr")]
497 fn valid_config() {
498 let mut builder = ArtiKeystoreConfigBuilder::default();
499 builder
500 .enabled(BoolOrAuto::Explicit(true))
501 .primary()
502 .kind(ExplicitOrAuto::Explicit(ArtiKeystoreKind::Native));
503
504 builder
505 .ctor()
506 .clients()
507 .access()
508 .push(client_config_builder("foo", "/var/lib/foo"));
509 builder
510 .ctor()
511 .clients()
512 .access()
513 .push(client_config_builder("bar", "/var/lib/bar"));
514
515 let res = builder.build();
516 assert!(res.is_ok(), "{:?}", res);
517 }
518}