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 use super::*;
434
435 use std::path::PathBuf;
436 use std::str::FromStr as _;
437 use tor_config::assert_config_error;
438
439 fn svc_config_builder(
441 id: &str,
442 path: &str,
443 nickname: &str,
444 ) -> CTorServiceKeystoreConfigBuilder {
445 let mut b = CTorServiceKeystoreConfigBuilder::default();
446 b.id(KeystoreId::from_str(id).unwrap());
447 b.path(PathBuf::from(path));
448 b.nickname(HsNickname::from_str(nickname).unwrap());
449 b
450 }
451
452 fn client_config_builder(id: &str, path: &str) -> CTorClientKeystoreConfigBuilder {
454 let mut b = CTorClientKeystoreConfigBuilder::default();
455 b.id(KeystoreId::from_str(id).unwrap());
456 b.path(PathBuf::from(path));
457 b
458 }
459
460 #[test]
461 #[cfg(all(feature = "ctor-keystore", feature = "keymgr"))]
462 fn invalid_config() {
463 let mut builder = ArtiKeystoreConfigBuilder::default();
464 builder
466 .ctor()
467 .clients()
468 .access()
469 .push(client_config_builder("foo", "/var/lib/foo"));
470
471 builder
472 .ctor()
473 .clients()
474 .access()
475 .push(client_config_builder("foo", "/var/lib/bar"));
476 let err = builder.build().unwrap_err();
477
478 assert_config_error!(
479 err,
480 Inconsistent,
481 "the C Tor keystores do not have unique IDs"
482 );
483
484 let mut builder = ArtiKeystoreConfigBuilder::default();
485 builder
487 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
488 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
489 let err = builder.build().unwrap_err();
490
491 assert_config_error!(
492 err,
493 Inconsistent,
494 "the C Tor keystores do not have unique IDs"
495 );
496
497 let mut builder = ArtiKeystoreConfigBuilder::default();
498 builder
500 .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
501 .ctor_service(svc_config_builder("bar", "/var/lib/bar", "pungent"));
502 let err = builder.build().unwrap_err();
503
504 assert_config_error!(
505 err,
506 Inconsistent,
507 "Multiple C Tor service keystores for service with nickname pungent"
508 );
509 }
510
511 #[test]
512 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
513 fn invalid_config() {
514 let mut builder = ArtiKeystoreConfigBuilder::default();
515 builder
516 .ctor()
517 .clients()
518 .access()
519 .push(client_config_builder("foo", "/var/lib/foo"));
520 let err = builder.build().unwrap_err();
521
522 assert_config_error!(
523 err,
524 NoCompileTimeSupport,
525 "C Tor client keystores configured but ctor-keystore feature not enabled"
526 );
527
528 let mut builder = ArtiKeystoreConfigBuilder::default();
529 builder.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
530 let err = builder.build().unwrap_err();
531
532 assert_config_error!(
533 err,
534 NoCompileTimeSupport,
535 "C Tor service keystores configured but ctor-keystore feature not enabled"
536 );
537 }
538
539 #[test]
540 #[cfg(not(feature = "keymgr"))]
541 fn invalid_config() {
542 let mut builder = ArtiKeystoreConfigBuilder::default();
543 builder.enabled(BoolOrAuto::Explicit(true));
544
545 let err = builder.build().unwrap_err();
546 assert_config_error!(
547 err,
548 Inconsistent,
549 "keystore enabled=true, but keymgr feature not enabled"
550 );
551 }
552
553 #[test]
554 #[cfg(feature = "ctor-keystore")]
555 fn valid_config() {
556 let mut builder = ArtiKeystoreConfigBuilder::default();
557 builder
558 .ctor()
559 .clients()
560 .access()
561 .push(client_config_builder("foo", "/var/lib/foo"));
562 builder
563 .ctor()
564 .clients()
565 .access()
566 .push(client_config_builder("bar", "/var/lib/bar"));
567
568 let res = builder.build();
569 assert!(res.is_ok(), "{:?}", res);
570 }
571
572 #[test]
573 #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
574 fn valid_config() {
575 let mut builder = ArtiKeystoreConfigBuilder::default();
576 builder
577 .enabled(BoolOrAuto::Explicit(true))
578 .primary()
579 .kind(ExplicitOrAuto::Explicit(ArtiKeystoreKind::Native));
580
581 let res = builder.build();
582 assert!(res.is_ok(), "{:?}", res);
583 }
584}