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 BoolOrAuto, ExplicitOrAuto, define_list_builder_helper, impl_not_auto_value,
11 impl_standard_builder,
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::Itertools as _;
396 use itertools::chain;
397
398 let Self { services, clients } = self;
399 let mut ctor_store_ids = chain![
400 services.stores.iter().flatten().map(|s| &s.id),
401 clients.stores.iter().flatten().map(|s| &s.id)
402 ];
403
404 if !ctor_store_ids.all_unique() {
407 return Err(ConfigBuildError::Inconsistent {
408 fields: ["id"].map(Into::into).into_iter().collect(),
409 problem: "the C Tor keystores do not have unique IDs".into(),
410 });
411 }
412
413 Ok(())
414 }
415
416 pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
418 if let Some(ref mut stores) = self.services.stores {
419 stores.push(builder);
420 } else {
421 self.services.stores = Some(vec![builder]);
422 }
423
424 self
425 }
426}
427
428#[cfg(test)]
429mod test {
430 #![allow(clippy::bool_assert_comparison)]
432 #![allow(clippy::clone_on_copy)]
433 #![allow(clippy::dbg_macro)]
434 #![allow(clippy::mixed_attributes_style)]
435 #![allow(clippy::print_stderr)]
436 #![allow(clippy::print_stdout)]
437 #![allow(clippy::single_char_pattern)]
438 #![allow(clippy::unwrap_used)]
439 #![allow(clippy::unchecked_time_subtraction)]
440 #![allow(clippy::useless_vec)]
441 #![allow(clippy::needless_pass_by_value)]
442 use super::*;
445
446 use std::path::PathBuf;
447 use std::str::FromStr as _;
448 use tor_config::assert_config_error;
449
450 fn svc_config_builder(
452 id: &str,
453 path: &str,
454 nickname: &str,
455 ) -> CTorServiceKeystoreConfigBuilder {
456 let mut b = CTorServiceKeystoreConfigBuilder::default();
457 b.id(KeystoreId::from_str(id).unwrap());
458 b.path(PathBuf::from(path));
459 b.nickname(HsNickname::from_str(nickname).unwrap());
460 b
461 }
462
463 fn client_config_builder(id: &str, path: &str) -> CTorClientKeystoreConfigBuilder {
465 let mut b = CTorClientKeystoreConfigBuilder::default();
466 b.id(KeystoreId::from_str(id).unwrap());
467 b.path(PathBuf::from(path));
468 b
469 }
470
471 #[test]
472 #[cfg(all(feature = "ctor-keystore", feature = "keymgr"))]
473 fn invalid_config() {
474 let mut builder = ArtiKeystoreConfigBuilder::default();
475 builder
477 .ctor()
478 .clients()
479 .access()
480 .push(client_config_builder("foo", "/var/lib/foo"));
481
482 builder
483 .ctor()
484 .clients()
485 .access()
486 .push(client_config_builder("foo", "/var/lib/bar"));
487 let err = builder.build().unwrap_err();
488
489 assert_config_error!(
490 err,
491 Inconsistent,
492 "the C Tor keystores do not have unique IDs"
493 );
494
495 let mut builder = ArtiKeystoreConfigBuilder::default();
496 builder
498 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
499 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
500 let err = builder.build().unwrap_err();
501
502 assert_config_error!(
503 err,
504 Inconsistent,
505 "the C Tor keystores do not have unique IDs"
506 );
507
508 let mut builder = ArtiKeystoreConfigBuilder::default();
509 builder
511 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
512 .ctor_service(svc_config_builder("bar", "/var/lib/bar", "pungent"));
513 let err = builder.build().unwrap_err();
514
515 assert_config_error!(
516 err,
517 Inconsistent,
518 "Multiple C Tor service keystores for service with nickname pungent"
519 );
520 }
521
522 #[test]
523 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
524 fn invalid_config() {
525 let mut builder = ArtiKeystoreConfigBuilder::default();
526 builder
527 .ctor()
528 .clients()
529 .access()
530 .push(client_config_builder("foo", "/var/lib/foo"));
531 let err = builder.build().unwrap_err();
532
533 assert_config_error!(
534 err,
535 NoCompileTimeSupport,
536 "C Tor client keystores configured but ctor-keystore feature not enabled"
537 );
538
539 let mut builder = ArtiKeystoreConfigBuilder::default();
540 builder.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
541 let err = builder.build().unwrap_err();
542
543 assert_config_error!(
544 err,
545 NoCompileTimeSupport,
546 "C Tor service keystores configured but ctor-keystore feature not enabled"
547 );
548 }
549
550 #[test]
551 #[cfg(not(feature = "keymgr"))]
552 fn invalid_config() {
553 let mut builder = ArtiKeystoreConfigBuilder::default();
554 builder.enabled(BoolOrAuto::Explicit(true));
555
556 let err = builder.build().unwrap_err();
557 assert_config_error!(
558 err,
559 Inconsistent,
560 "keystore enabled=true, but keymgr feature not enabled"
561 );
562 }
563
564 #[test]
565 #[cfg(feature = "ctor-keystore")]
566 fn valid_config() {
567 let mut builder = ArtiKeystoreConfigBuilder::default();
568 builder
569 .ctor()
570 .clients()
571 .access()
572 .push(client_config_builder("foo", "/var/lib/foo"));
573 builder
574 .ctor()
575 .clients()
576 .access()
577 .push(client_config_builder("bar", "/var/lib/bar"));
578
579 let res = builder.build();
580 assert!(res.is_ok(), "{:?}", res);
581 }
582
583 #[test]
584 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
585 fn valid_config() {
586 let mut builder = ArtiKeystoreConfigBuilder::default();
587 builder
588 .enabled(BoolOrAuto::Explicit(true))
589 .primary()
590 .kind(ExplicitOrAuto::Explicit(ArtiKeystoreKind::Native));
591
592 let res = builder.build();
593 assert!(res.is_ok(), "{:?}", res);
594 }
595}