1pub use tor_config::{ConfigBuildError, ConfigurationSource, Reconfigure};
4pub use tor_config_path::{CfgPath, CfgPathError};
5
6use amplify::Getters;
7use derive_builder::Builder;
8use serde::{Deserialize, Serialize};
9use tor_config::{
10 define_list_builder_helper, impl_not_auto_value, impl_standard_builder, BoolOrAuto,
11 ExplicitOrAuto,
12};
13use tor_persist::hsnickname::HsNickname;
14
15use std::collections::BTreeMap;
16use std::path::PathBuf;
17
18use crate::KeystoreId;
19
20#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23#[non_exhaustive]
24pub enum ArtiKeystoreKind {
25 Native,
27 #[cfg(feature = "ephemeral-keystore")]
29 Ephemeral,
30}
31impl_not_auto_value! {ArtiKeystoreKind}
32
33#[derive(Debug, Clone, Builder, Eq, PartialEq, Serialize, Deserialize, Getters)]
35#[builder(derive(Serialize, Deserialize, Debug))]
36#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
37#[non_exhaustive]
38#[builder_struct_attr(non_exhaustive)]
39pub struct ArtiKeystoreConfig {
40 #[builder_field_attr(serde(default))]
42 #[builder(default)]
43 enabled: BoolOrAuto,
44
45 #[builder(sub_builder)]
47 #[builder_field_attr(serde(default))]
48 primary: PrimaryKeystoreConfig,
49
50 #[builder(sub_builder)]
59 #[builder_field_attr(serde(default))]
60 ctor: CTorKeystoreConfig,
61}
62
63#[derive(Debug, Clone, Builder, Eq, PartialEq, Serialize, Deserialize, Getters)]
65#[builder(derive(Serialize, Deserialize, Debug))]
66#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
67#[non_exhaustive]
68#[builder_struct_attr(non_exhaustive)]
69pub struct CTorKeystoreConfig {
70 #[builder(default, sub_builder(fn_name = "build"), setter(custom))]
72 #[builder_field_attr(serde(default))]
73 services: CTorServiceKeystoreConfigMap,
74
75 #[builder(default, sub_builder(fn_name = "build"))]
77 #[builder_field_attr(serde(default))]
78 clients: CTorClientKeystoreConfigList,
79}
80
81#[derive(Debug, Clone, Builder, Eq, PartialEq, Serialize, Deserialize)]
83#[builder(derive(Serialize, Deserialize, Debug))]
84#[builder(build_fn(error = "ConfigBuildError"))]
85#[non_exhaustive]
86#[builder_struct_attr(non_exhaustive)]
87pub struct PrimaryKeystoreConfig {
88 #[builder_field_attr(serde(default))]
90 #[builder(default)]
91 kind: ExplicitOrAuto<ArtiKeystoreKind>,
92}
93
94#[derive(Debug, Clone, Builder, Eq, PartialEq, Serialize, Deserialize, Getters)]
96#[builder(derive(Serialize, Deserialize, Debug))]
97#[builder(build_fn(error = "ConfigBuildError"))]
98#[non_exhaustive]
99#[builder_struct_attr(non_exhaustive)]
100pub struct CTorServiceKeystoreConfig {
101 id: KeystoreId,
110
111 path: PathBuf,
119
120 nickname: HsNickname,
122}
123
124pub(crate) type CTorServiceKeystoreConfigMap = BTreeMap<HsNickname, CTorServiceKeystoreConfig>;
127
128type CTorServiceKeystoreConfigBuilderMap = BTreeMap<HsNickname, CTorServiceKeystoreConfigBuilder>;
131
132define_list_builder_helper! {
133 pub struct CTorServiceKeystoreConfigMapBuilder {
134 stores: [CTorServiceKeystoreConfigBuilder],
135 }
136 built: CTorServiceKeystoreConfigMap = build_ctor_service_list(stores)?;
137 default = vec![];
138 #[serde(try_from="CTorServiceKeystoreConfigBuilderMap", into="CTorServiceKeystoreConfigBuilderMap")]
139}
140
141impl TryFrom<CTorServiceKeystoreConfigBuilderMap> for CTorServiceKeystoreConfigMapBuilder {
142 type Error = ConfigBuildError;
143
144 fn try_from(value: CTorServiceKeystoreConfigBuilderMap) -> Result<Self, Self::Error> {
145 let mut list_builder = CTorServiceKeystoreConfigMapBuilder::default();
146 for (nickname, mut cfg) in value {
147 match &cfg.nickname {
148 Some(n) if n == &nickname => (),
149 None => (),
150 Some(other) => {
151 return Err(ConfigBuildError::Inconsistent {
152 fields: vec![nickname.to_string(), format!("{nickname}.{other}")],
153 problem: "mismatched nicknames on onion service.".into(),
154 });
155 }
156 }
157 cfg.nickname = Some(nickname);
158 list_builder.access().push(cfg);
159 }
160 Ok(list_builder)
161 }
162}
163
164impl From<CTorServiceKeystoreConfigMapBuilder> for CTorServiceKeystoreConfigBuilderMap {
165 fn from(value: CTorServiceKeystoreConfigMapBuilder) -> CTorServiceKeystoreConfigBuilderMap {
176 let mut map = BTreeMap::new();
177 for cfg in value.stores.into_iter().flatten() {
178 let nickname = cfg.nickname.clone().unwrap_or_else(|| {
179 "Unnamed"
180 .to_string()
181 .try_into()
182 .expect("'Unnamed' was not a valid nickname")
183 });
184 map.insert(nickname, cfg);
185 }
186 map
187 }
188}
189
190fn build_ctor_service_list(
195 ctor_stores: Vec<CTorServiceKeystoreConfig>,
196) -> Result<CTorServiceKeystoreConfigMap, ConfigBuildError> {
197 use itertools::Itertools as _;
198
199 if !ctor_stores.iter().map(|s| &s.id).all_unique() {
200 return Err(ConfigBuildError::Inconsistent {
201 fields: ["id"].map(Into::into).into_iter().collect(),
202 problem: "the C Tor keystores do not have unique IDs".into(),
203 });
204 }
205
206 let mut map = BTreeMap::new();
207 for service in ctor_stores {
208 if let Some(previous_value) = map.insert(service.nickname.clone(), service) {
209 return Err(ConfigBuildError::Inconsistent {
210 fields: vec!["nickname".into()],
211 problem: format!(
212 "Multiple C Tor service keystores for service with nickname {}",
213 previous_value.nickname
214 ),
215 });
216 };
217 }
218
219 Ok(map)
220}
221
222#[derive(Debug, Clone, Builder, Eq, PartialEq, Serialize, Deserialize, Getters)]
224#[builder(derive(Serialize, Deserialize, Debug))]
225#[builder(build_fn(error = "ConfigBuildError"))]
226#[non_exhaustive]
227#[builder_struct_attr(non_exhaustive)]
228pub struct CTorClientKeystoreConfig {
229 id: KeystoreId,
234
235 path: PathBuf,
247}
248
249pub type CTorClientKeystoreConfigList = Vec<CTorClientKeystoreConfig>;
251
252define_list_builder_helper! {
253 pub struct CTorClientKeystoreConfigListBuilder {
254 stores: [CTorClientKeystoreConfigBuilder],
255 }
256 built: CTorClientKeystoreConfigList = build_ctor_client_store_config(stores)?;
257 default = vec![];
258}
259
260fn build_ctor_client_store_config(
264 ctor_stores: Vec<CTorClientKeystoreConfig>,
265) -> Result<CTorClientKeystoreConfigList, ConfigBuildError> {
266 use itertools::Itertools as _;
267
268 if !ctor_stores.iter().map(|s| &s.id).all_unique() {
269 return Err(ConfigBuildError::Inconsistent {
270 fields: ["id"].map(Into::into).into_iter().collect(),
271 problem: "the C Tor keystores do not have unique IDs".into(),
272 });
273 }
274
275 Ok(ctor_stores)
276}
277
278impl ArtiKeystoreConfig {
279 pub fn is_enabled(&self) -> bool {
281 let default = cfg!(feature = "keymgr");
282
283 self.enabled.as_bool().unwrap_or(default)
284 }
285
286 pub fn primary_kind(&self) -> Option<ArtiKeystoreKind> {
290 use ExplicitOrAuto as EoA;
291
292 if !self.is_enabled() {
293 return None;
294 }
295
296 let kind = match self.primary.kind {
297 EoA::Explicit(kind) => kind,
298 EoA::Auto => ArtiKeystoreKind::Native,
299 };
300
301 Some(kind)
302 }
303
304 pub fn ctor_svc_stores(&self) -> impl Iterator<Item = &CTorServiceKeystoreConfig> {
306 self.ctor.services.values()
307 }
308
309 pub fn ctor_client_stores(&self) -> impl Iterator<Item = &CTorClientKeystoreConfig> {
311 self.ctor.clients.iter()
312 }
313}
314
315impl_standard_builder! { ArtiKeystoreConfig }
316
317impl ArtiKeystoreConfigBuilder {
318 #[cfg(not(feature = "keymgr"))]
320 #[allow(clippy::unnecessary_wraps)]
321 fn validate(&self) -> Result<(), ConfigBuildError> {
322 use BoolOrAuto as BoA;
323 use ExplicitOrAuto as EoA;
324
325 if self.enabled == Some(BoA::Explicit(true)) {
327 return Err(ConfigBuildError::Inconsistent {
328 fields: ["enabled"].map(Into::into).into_iter().collect(),
329 problem: "keystore enabled=true, but keymgr feature not enabled".into(),
330 });
331 }
332
333 let () = match self.primary.kind {
334 None | Some(EoA::Auto) => Ok(()),
336 _ => Err(ConfigBuildError::Inconsistent {
337 fields: ["enabled", "kind"].map(Into::into).into_iter().collect(),
338 problem: "kind!=auto, but keymgr feature not enabled".into(),
339 }),
340 }?;
341
342 Ok(())
343 }
344
345 #[cfg(feature = "keymgr")]
347 #[allow(clippy::unnecessary_wraps)]
348 fn validate(&self) -> Result<(), ConfigBuildError> {
349 Ok(())
350 }
351
352 pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
354 self.ctor.ctor_service(builder);
355 self
356 }
357}
358
359impl CTorKeystoreConfigBuilder {
360 #[cfg(not(feature = "ctor-keystore"))]
363 fn validate(&self) -> Result<(), ConfigBuildError> {
364 let no_compile_time_support = |field: &str| ConfigBuildError::NoCompileTimeSupport {
365 field: field.into(),
366 problem: format!("{field} configured but ctor-keystore feature not enabled"),
367 };
368
369 if self
370 .services
371 .stores
372 .as_ref()
373 .map(|s| !s.is_empty())
374 .unwrap_or_default()
375 {
376 return Err(no_compile_time_support("C Tor service keystores"));
377 }
378
379 if self
380 .clients
381 .stores
382 .as_ref()
383 .map(|s| !s.is_empty())
384 .unwrap_or_default()
385 {
386 return Err(no_compile_time_support("C Tor client keystores"));
387 }
388
389 Ok(())
390 }
391
392 #[cfg(feature = "ctor-keystore")]
394 fn validate(&self) -> Result<(), ConfigBuildError> {
395 use itertools::chain;
396 use itertools::Itertools as _;
397
398 let Self {
399 ref services,
400 ref clients,
401 } = self;
402 let mut ctor_store_ids = chain![
403 services.stores.iter().flatten().map(|s| &s.id),
404 clients.stores.iter().flatten().map(|s| &s.id)
405 ];
406
407 if !ctor_store_ids.all_unique() {
410 return Err(ConfigBuildError::Inconsistent {
411 fields: ["id"].map(Into::into).into_iter().collect(),
412 problem: "the C Tor keystores do not have unique IDs".into(),
413 });
414 }
415
416 Ok(())
417 }
418
419 pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
421 if let Some(ref mut stores) = self.services.stores {
422 stores.push(builder);
423 } else {
424 self.services.stores = Some(vec![builder]);
425 }
426
427 self
428 }
429}
430
431#[cfg(test)]
432mod test {
433 #![allow(clippy::bool_assert_comparison)]
435 #![allow(clippy::clone_on_copy)]
436 #![allow(clippy::dbg_macro)]
437 #![allow(clippy::mixed_attributes_style)]
438 #![allow(clippy::print_stderr)]
439 #![allow(clippy::print_stdout)]
440 #![allow(clippy::single_char_pattern)]
441 #![allow(clippy::unwrap_used)]
442 #![allow(clippy::unchecked_duration_subtraction)]
443 #![allow(clippy::useless_vec)]
444 #![allow(clippy::needless_pass_by_value)]
445 use super::*;
448
449 use std::path::PathBuf;
450 use std::str::FromStr as _;
451 use tor_config::assert_config_error;
452
453 fn svc_config_builder(
455 id: &str,
456 path: &str,
457 nickname: &str,
458 ) -> CTorServiceKeystoreConfigBuilder {
459 let mut b = CTorServiceKeystoreConfigBuilder::default();
460 b.id(KeystoreId::from_str(id).unwrap());
461 b.path(PathBuf::from(path));
462 b.nickname(HsNickname::from_str(nickname).unwrap());
463 b
464 }
465
466 fn client_config_builder(id: &str, path: &str) -> CTorClientKeystoreConfigBuilder {
468 let mut b = CTorClientKeystoreConfigBuilder::default();
469 b.id(KeystoreId::from_str(id).unwrap());
470 b.path(PathBuf::from(path));
471 b
472 }
473
474 #[test]
475 #[cfg(all(feature = "ctor-keystore", feature = "keymgr"))]
476 fn invalid_config() {
477 let mut builder = ArtiKeystoreConfigBuilder::default();
478 builder
480 .ctor()
481 .clients()
482 .access()
483 .push(client_config_builder("foo", "/var/lib/foo"));
484
485 builder
486 .ctor()
487 .clients()
488 .access()
489 .push(client_config_builder("foo", "/var/lib/bar"));
490 let err = builder.build().unwrap_err();
491
492 assert_config_error!(
493 err,
494 Inconsistent,
495 "the C Tor keystores do not have unique IDs"
496 );
497
498 let mut builder = ArtiKeystoreConfigBuilder::default();
499 builder
501 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
502 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
503 let err = builder.build().unwrap_err();
504
505 assert_config_error!(
506 err,
507 Inconsistent,
508 "the C Tor keystores do not have unique IDs"
509 );
510
511 let mut builder = ArtiKeystoreConfigBuilder::default();
512 builder
514 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
515 .ctor_service(svc_config_builder("bar", "/var/lib/bar", "pungent"));
516 let err = builder.build().unwrap_err();
517
518 assert_config_error!(
519 err,
520 Inconsistent,
521 "Multiple C Tor service keystores for service with nickname pungent"
522 );
523 }
524
525 #[test]
526 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
527 fn invalid_config() {
528 let mut builder = ArtiKeystoreConfigBuilder::default();
529 builder
530 .ctor()
531 .clients()
532 .access()
533 .push(client_config_builder("foo", "/var/lib/foo"));
534 let err = builder.build().unwrap_err();
535
536 assert_config_error!(
537 err,
538 NoCompileTimeSupport,
539 "C Tor client keystores configured but ctor-keystore feature not enabled"
540 );
541
542 let mut builder = ArtiKeystoreConfigBuilder::default();
543 builder.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
544 let err = builder.build().unwrap_err();
545
546 assert_config_error!(
547 err,
548 NoCompileTimeSupport,
549 "C Tor service keystores configured but ctor-keystore feature not enabled"
550 );
551 }
552
553 #[test]
554 #[cfg(not(feature = "keymgr"))]
555 fn invalid_config() {
556 let mut builder = ArtiKeystoreConfigBuilder::default();
557 builder.enabled(BoolOrAuto::Explicit(true));
558
559 let err = builder.build().unwrap_err();
560 assert_config_error!(
561 err,
562 Inconsistent,
563 "keystore enabled=true, but keymgr feature not enabled"
564 );
565 }
566
567 #[test]
568 #[cfg(feature = "ctor-keystore")]
569 fn valid_config() {
570 let mut builder = ArtiKeystoreConfigBuilder::default();
571 builder
572 .ctor()
573 .clients()
574 .access()
575 .push(client_config_builder("foo", "/var/lib/foo"));
576 builder
577 .ctor()
578 .clients()
579 .access()
580 .push(client_config_builder("bar", "/var/lib/bar"));
581
582 let res = builder.build();
583 assert!(res.is_ok(), "{:?}", res);
584 }
585
586 #[test]
587 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
588 fn valid_config() {
589 let mut builder = ArtiKeystoreConfigBuilder::default();
590 builder
591 .enabled(BoolOrAuto::Explicit(true))
592 .primary()
593 .kind(ExplicitOrAuto::Explicit(ArtiKeystoreKind::Native));
594
595 let res = builder.build();
596 assert!(res.is_ok(), "{:?}", res);
597 }
598}