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 #[cfg(not(feature = "ctor-keystore"))]
352 fn validate(&self) -> Result<(), ConfigBuildError> {
353 let no_compile_time_support = |field: &str| ConfigBuildError::NoCompileTimeSupport {
354 field: field.into(),
355 problem: format!("{field} configured but ctor-keystore feature not enabled"),
356 };
357
358 if self
359 .services
360 .stores
361 .as_ref()
362 .map(|s| !s.is_empty())
363 .unwrap_or_default()
364 {
365 return Err(no_compile_time_support("C Tor service keystores"));
366 }
367
368 if self
369 .clients
370 .stores
371 .as_ref()
372 .map(|s| !s.is_empty())
373 .unwrap_or_default()
374 {
375 return Err(no_compile_time_support("C Tor client keystores"));
376 }
377
378 Ok(())
379 }
380
381 #[cfg(feature = "ctor-keystore")]
383 fn validate(&self) -> Result<(), ConfigBuildError> {
384 use itertools::Itertools as _;
385 use itertools::chain;
386
387 let Self { services, clients } = self;
388 let mut ctor_store_ids = chain![
389 services.stores.iter().flatten().map(|s| &s.id),
390 clients.stores.iter().flatten().map(|s| &s.id)
391 ];
392
393 if !ctor_store_ids.all_unique() {
396 return Err(ConfigBuildError::Inconsistent {
397 fields: ["id"].map(Into::into).into_iter().collect(),
398 problem: "the C Tor keystores do not have unique IDs".into(),
399 });
400 }
401
402 Ok(())
403 }
404
405 pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
407 if let Some(ref mut stores) = self.services.stores {
408 stores.push(builder);
409 } else {
410 self.services.stores = Some(vec![builder]);
411 }
412
413 self
414 }
415}
416
417#[cfg(test)]
418mod test {
419 #![allow(clippy::bool_assert_comparison)]
421 #![allow(clippy::clone_on_copy)]
422 #![allow(clippy::dbg_macro)]
423 #![allow(clippy::mixed_attributes_style)]
424 #![allow(clippy::print_stderr)]
425 #![allow(clippy::print_stdout)]
426 #![allow(clippy::single_char_pattern)]
427 #![allow(clippy::unwrap_used)]
428 #![allow(clippy::unchecked_time_subtraction)]
429 #![allow(clippy::useless_vec)]
430 #![allow(clippy::needless_pass_by_value)]
431 #![allow(clippy::string_slice)] use super::*;
435
436 use std::path::PathBuf;
437 use std::str::FromStr as _;
438 use tor_config::assert_config_error;
439
440 fn svc_config_builder(
442 id: &str,
443 path: &str,
444 nickname: &str,
445 ) -> CTorServiceKeystoreConfigBuilder {
446 let mut b = CTorServiceKeystoreConfigBuilder::default();
447 b.id(KeystoreId::from_str(id).unwrap());
448 b.path(PathBuf::from(path));
449 b.nickname(HsNickname::from_str(nickname).unwrap());
450 b
451 }
452
453 fn client_config_builder(id: &str, path: &str) -> CTorClientKeystoreConfigBuilder {
455 let mut b = CTorClientKeystoreConfigBuilder::default();
456 b.id(KeystoreId::from_str(id).unwrap());
457 b.path(PathBuf::from(path));
458 b
459 }
460
461 #[test]
462 #[cfg(all(feature = "ctor-keystore", feature = "keymgr"))]
463 fn invalid_config() {
464 let mut builder = ArtiKeystoreConfigBuilder::default();
465 builder
467 .ctor()
468 .clients()
469 .access()
470 .push(client_config_builder("foo", "/var/lib/foo"));
471
472 builder
473 .ctor()
474 .clients()
475 .access()
476 .push(client_config_builder("foo", "/var/lib/bar"));
477 let err = builder.build().unwrap_err();
478
479 assert_config_error!(
480 err,
481 Inconsistent,
482 "the C Tor keystores do not have unique IDs"
483 );
484
485 let mut builder = ArtiKeystoreConfigBuilder::default();
486 builder
488 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
489 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
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("bar", "/var/lib/bar", "pungent"));
503 let err = builder.build().unwrap_err();
504
505 assert_config_error!(
506 err,
507 Inconsistent,
508 "Multiple C Tor service keystores for service with nickname pungent"
509 );
510 }
511
512 #[test]
513 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
514 fn invalid_config() {
515 let mut builder = ArtiKeystoreConfigBuilder::default();
516 builder
517 .ctor()
518 .clients()
519 .access()
520 .push(client_config_builder("foo", "/var/lib/foo"));
521 let err = builder.build().unwrap_err();
522
523 assert_config_error!(
524 err,
525 NoCompileTimeSupport,
526 "C Tor client keystores configured but ctor-keystore feature not enabled"
527 );
528
529 let mut builder = ArtiKeystoreConfigBuilder::default();
530 builder.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
531 let err = builder.build().unwrap_err();
532
533 assert_config_error!(
534 err,
535 NoCompileTimeSupport,
536 "C Tor service keystores configured but ctor-keystore feature not enabled"
537 );
538 }
539
540 #[test]
541 #[cfg(not(feature = "keymgr"))]
542 fn invalid_config() {
543 let mut builder = ArtiKeystoreConfigBuilder::default();
544 builder.enabled(BoolOrAuto::Explicit(true));
545
546 let err = builder.build().unwrap_err();
547 assert_config_error!(
548 err,
549 Inconsistent,
550 "keystore enabled=true, but keymgr feature not enabled"
551 );
552 }
553
554 #[test]
555 #[cfg(feature = "ctor-keystore")]
556 fn valid_config() {
557 let mut builder = ArtiKeystoreConfigBuilder::default();
558 builder
559 .ctor()
560 .clients()
561 .access()
562 .push(client_config_builder("foo", "/var/lib/foo"));
563 builder
564 .ctor()
565 .clients()
566 .access()
567 .push(client_config_builder("bar", "/var/lib/bar"));
568
569 let res = builder.build();
570 assert!(res.is_ok(), "{:?}", res);
571 }
572
573 #[test]
574 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
575 fn valid_config() {
576 let mut builder = ArtiKeystoreConfigBuilder::default();
577 builder
578 .enabled(BoolOrAuto::Explicit(true))
579 .primary()
580 .kind(ExplicitOrAuto::Explicit(ArtiKeystoreKind::Native));
581
582 let res = builder.build();
583 assert!(res.is_ok(), "{:?}", res);
584 }
585}