1
//! Configuration options for types implementing [`Keystore`](crate::Keystore)
2

            
3
pub use tor_config::{ConfigBuildError, ConfigurationSource, Reconfigure};
4
pub use tor_config_path::{CfgPath, CfgPathError};
5

            
6
use amplify::Getters;
7
use derive_deftly::Deftly;
8
use serde::{Deserialize, Serialize};
9
use tor_config::derive::prelude::*;
10
use tor_config::{BoolOrAuto, ExplicitOrAuto, define_list_builder_helper, impl_not_auto_value};
11
use tor_persist::hsnickname::HsNickname;
12

            
13
use std::collections::BTreeMap;
14
use std::path::PathBuf;
15

            
16
use crate::KeystoreId;
17

            
18
/// The kind of keystore to use
19
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
20
#[serde(rename_all = "lowercase")]
21
#[non_exhaustive]
22
pub enum ArtiKeystoreKind {
23
    /// Use the [`ArtiNativeKeystore`](crate::ArtiNativeKeystore).
24
    Native,
25
    /// Use the [`ArtiEphemeralKeystore`](crate::ArtiEphemeralKeystore).
26
    #[cfg(feature = "ephemeral-keystore")]
27
    Ephemeral,
28
}
29
impl_not_auto_value! {ArtiKeystoreKind}
30

            
31
/// [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
32
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
33
#[derive_deftly(TorConfig)]
34
#[deftly(tor_config(pre_build = "Self::validate"))]
35
pub struct ArtiKeystoreConfig {
36
    /// Whether keystore use is enabled.
37
    #[deftly(tor_config(default))]
38
    enabled: BoolOrAuto,
39

            
40
    /// The primary keystore.
41
    #[deftly(tor_config(sub_builder))]
42
    primary: PrimaryKeystoreConfig,
43

            
44
    /// Optionally configure C Tor keystores for arti to use.
45
    ///
46
    /// Note: The keystores listed here are read-only (keys are only
47
    /// ever written to the primary keystore, configured in
48
    /// `storage.keystore.primary`).
49
    ///
50
    /// Each C Tor keystore **must** have a unique identifier.
51
    /// It is an error to configure multiple keystores with the same [`KeystoreId`].
52
    #[deftly(tor_config(sub_builder))]
53
    ctor: CTorKeystoreConfig,
54
}
55

            
56
/// [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
57
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
58
#[derive_deftly(TorConfig)]
59
#[deftly(tor_config(pre_build = "Self::validate"))]
60
pub struct CTorKeystoreConfig {
61
    /// C Tor hidden service keystores.
62
    //
63
    // NOTE: This could become a map builder, but it would change the API.
64
    #[deftly(tor_config(sub_builder))]
65
    services: CTorServiceKeystoreConfigMap,
66

            
67
    /// C Tor hidden service client keystores.
68
    //
69
    // NOTE: This could become a list builder, but it would change the API.
70
    #[deftly(tor_config(no_magic, sub_builder))]
71
    clients: CTorClientKeystoreConfigList,
72
}
73

            
74
/// Primary [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
75
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize)]
76
#[derive_deftly(TorConfig)]
77
pub struct PrimaryKeystoreConfig {
78
    /// The type of keystore to use, or none at all.
79
    #[deftly(tor_config(default))]
80
    kind: ExplicitOrAuto<ArtiKeystoreKind>,
81
}
82

            
83
/// C Tor [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
84
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
85
#[derive_deftly(TorConfig)]
86
#[deftly(tor_config(no_default_trait))]
87
pub struct CTorServiceKeystoreConfig {
88
    /// The identifier of this keystore.
89
    ///
90
    /// Each C Tor keystore **must**:
91
    ///
92
    ///   * have a unique identifier. It is an error to configure multiple keystores
93
    ///     with the same [`KeystoreId`].
94
    ///   * have a corresponding arti hidden service configured in the
95
    ///     `[onion_services]` section with the same nickname
96
    #[deftly(tor_config(no_default))]
97
    id: KeystoreId,
98

            
99
    /// The root directory of this keystore.
100
    ///
101
    /// This should be set to the `HiddenServiceDirectory` of your hidden service.
102
    /// Arti will read `HiddenServiceDirectory/hostname` and `HiddenServiceDirectory/private_key`.
103
    /// (Note: if your service is running in restricted discovery mode, you must also set the
104
    /// `[[onion_services."<the nickname of your svc>".restricted_discovery.key_dirs]]`
105
    /// to `HiddenServiceDirectory/client_keys`).
106
    #[deftly(tor_config(no_default))]
107
    path: PathBuf,
108

            
109
    /// The nickname of the service this keystore is to be used with.
110
    #[deftly(tor_config(no_default))]
111
    nickname: HsNickname,
112
}
113

            
114
/// Alias for a `BTreeMap` of `CTorServiceKeystoreConfig`; used to make derive_builder
115
/// happy.
116
pub(crate) type CTorServiceKeystoreConfigMap = BTreeMap<HsNickname, CTorServiceKeystoreConfig>;
117

            
118
/// The serialized format of an CTorServiceKeystoreConfigListBuilder:
119
/// a map from nickname to `CTorServiceKeystoreConfigBuilder`
120
type CTorServiceKeystoreConfigBuilderMap = BTreeMap<HsNickname, CTorServiceKeystoreConfigBuilder>;
121

            
122
define_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

            
131
impl TryFrom<CTorServiceKeystoreConfigBuilderMap> for CTorServiceKeystoreConfigMapBuilder {
132
    type Error = ConfigBuildError;
133

            
134
714
    fn try_from(value: CTorServiceKeystoreConfigBuilderMap) -> Result<Self, Self::Error> {
135
714
        let mut list_builder = CTorServiceKeystoreConfigMapBuilder::default();
136
714
        for (nickname, mut cfg) in value {
137
            match &cfg.nickname {
138
                Some(n) if n == &nickname => (),
139
714
                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
714
            cfg.nickname = Some(nickname);
148
714
            list_builder.access().push(cfg);
149
        }
150
714
        Ok(list_builder)
151
714
    }
152
}
153

            
154
impl From<CTorServiceKeystoreConfigMapBuilder> for CTorServiceKeystoreConfigBuilderMap {
155
    // Note: this is *similar* to the OnionServiceProxyConfigMap implementation (it duplicates much
156
    // of that logic, so perhaps at some point it's worth abstracting all of it away behind a
157
    // general-purpose map builder API).
158
    //
159
    /// Convert our Builder representation of a set of C Tor service configs into the
160
    /// format that serde will serialize.
161
    ///
162
    /// Note: This is a potentially lossy conversion, since the serialized format
163
    /// can't represent partially-built configs without a nickname, or
164
    /// a collection of configs with duplicate nicknames.
165
255
    fn from(value: CTorServiceKeystoreConfigMapBuilder) -> CTorServiceKeystoreConfigBuilderMap {
166
255
        let mut map = BTreeMap::new();
167
255
        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
255
        map
177
255
    }
178
}
179

            
180
/// Construct a CTorServiceKeystoreConfigList from a vec of CTorServiceKeystoreConfig;
181
/// enforce that nicknames are unique.
182
///
183
/// Returns an error if the [`KeystoreId`] of the `CTorServiceKeystoreConfig`s are not unique.
184
7605
fn build_ctor_service_list(
185
7605
    ctor_stores: Vec<CTorServiceKeystoreConfig>,
186
7605
) -> Result<CTorServiceKeystoreConfigMap, ConfigBuildError> {
187
    use itertools::Itertools as _;
188

            
189
7605
    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
7605
    }
195

            
196
7605
    let mut map = BTreeMap::new();
197
7605
    for service in ctor_stores {
198
716
        if let Some(previous_value) = map.insert(service.nickname.clone(), service) {
199
1
            return Err(ConfigBuildError::Inconsistent {
200
1
                fields: vec!["nickname".into()],
201
1
                problem: format!(
202
1
                    "Multiple C Tor service keystores for service with nickname {}",
203
1
                    previous_value.nickname
204
1
                ),
205
1
            });
206
715
        };
207
    }
208

            
209
7604
    Ok(map)
210
7605
}
211

            
212
/// C Tor [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
213
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
214
#[derive_deftly(TorConfig)]
215
#[deftly(tor_config(no_default_trait))]
216
pub struct CTorClientKeystoreConfig {
217
    /// The identifier of this keystore.
218
    ///
219
    /// Each keystore **must** have a unique identifier.
220
    /// It is an error to configure multiple keystores with the same [`KeystoreId`].
221
    #[deftly(tor_config(no_default))]
222
    id: KeystoreId,
223

            
224
    /// The root directory of this keystore.
225
    ///
226
    /// This should be set to the `ClientOnionAuthDir` of your client.
227
    /// If Arti is configured to run as a client (i.e. if it runs in SOCKS proxy mode),
228
    /// it will read the client restricted discovery keys from this path.
229
    ///
230
    /// The key files are expected to have the `.auth_private` extension,
231
    /// and their content **must** be of the form:
232
    /// `<56-char-onion-addr-without-.onion-part>:descriptor:x25519:<x25519 private key in base32>`.
233
    ///
234
    /// Malformed files, and files that don't have the `.auth_private` extension, will be ignored.
235
    #[deftly(tor_config(no_default))]
236
    path: PathBuf,
237
}
238

            
239
/// The serialized format of a [`CTorClientKeystoreConfigListBuilder`]:
240
pub type CTorClientKeystoreConfigList = Vec<CTorClientKeystoreConfig>;
241

            
242
define_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

            
250
/// Helper for building and validating a [`CTorClientKeystoreConfigList`].
251
///
252
/// Returns an error if the [`KeystoreId`]s of the `CTorClientKeystoreConfig`s are not unique.
253
7604
fn build_ctor_client_store_config(
254
7604
    ctor_stores: Vec<CTorClientKeystoreConfig>,
255
7604
) -> Result<CTorClientKeystoreConfigList, ConfigBuildError> {
256
    use itertools::Itertools as _;
257

            
258
7604
    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
7604
    }
264

            
265
7604
    Ok(ctor_stores)
266
7604
}
267

            
268
impl ArtiKeystoreConfig {
269
    /// Whether the keystore is enabled.
270
2652
    pub fn is_enabled(&self) -> bool {
271
2652
        let default = cfg!(feature = "keymgr");
272

            
273
2652
        self.enabled.as_bool().unwrap_or(default)
274
2652
    }
275

            
276
    /// The type of keystore to use
277
    ///
278
    /// Returns `None` if keystore use is disabled.
279
2652
    pub fn primary_kind(&self) -> Option<ArtiKeystoreKind> {
280
        use ExplicitOrAuto as EoA;
281

            
282
2652
        if !self.is_enabled() {
283
            return None;
284
2652
        }
285

            
286
2652
        let kind = match self.primary.kind {
287
            EoA::Explicit(kind) => kind,
288
2652
            EoA::Auto => ArtiKeystoreKind::Native,
289
        };
290

            
291
2652
        Some(kind)
292
2652
    }
293

            
294
    /// The ctor keystore configs
295
2652
    pub fn ctor_svc_stores(&self) -> impl Iterator<Item = &CTorServiceKeystoreConfig> {
296
2652
        self.ctor.services.values()
297
2652
    }
298

            
299
    /// The ctor client keystore configs
300
2652
    pub fn ctor_client_stores(&self) -> impl Iterator<Item = &CTorClientKeystoreConfig> {
301
2652
        self.ctor.clients.iter()
302
2652
    }
303
}
304

            
305
impl ArtiKeystoreConfigBuilder {
306
    /// Check that the keystore configuration is valid
307
    #[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
        // NOTE: This could use #[deftly(tor_config(cfg))], but that would change the behavior a little.
313

            
314
        // Keystore support is disabled unless the `keymgr` feature is enabled.
315
        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
            // only enabled OR kind may be set, and when keymgr is not enabled they must be false|disabled
324
            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
    /// Check that the keystore configuration is valid
335
    #[cfg(feature = "keymgr")]
336
    #[allow(clippy::unnecessary_wraps)]
337
7605
    fn validate(&self) -> Result<(), ConfigBuildError> {
338
7605
        Ok(())
339
7605
    }
340

            
341
    /// Add a `CTorServiceKeystoreConfigBuilder` to this builder.
342
4
    pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
343
4
        self.ctor.ctor_service(builder);
344
4
        self
345
4
    }
346
}
347

            
348
impl CTorKeystoreConfigBuilder {
349
    /// Validate the configured C Tor keystores.
350
    ///
351
    // Note: the config is validated even if the keymgr feature is not enabled
352
    // (i.e. if keystore use is disabled)
353
7607
    fn validate(&self) -> Result<(), ConfigBuildError> {
354
        use itertools::Itertools as _;
355
        use itertools::chain;
356

            
357
7607
        let Self { services, clients } = self;
358
7607
        let mut ctor_store_ids = chain![
359
7607
            services.stores.iter().flatten().map(|s| &s.id),
360
7607
            clients.stores.iter().flatten().map(|s| &s.id)
361
        ];
362

            
363
        // This is also validated by the KeyMgrBuilder (but it's a good idea to catch this sort of
364
        // mistake at configuration-time regardless).
365
7607
        if !ctor_store_ids.all_unique() {
366
2
            return Err(ConfigBuildError::Inconsistent {
367
2
                fields: ["id"].map(Into::into).into_iter().collect(),
368
2
                problem: "the C Tor keystores do not have unique IDs".into(),
369
2
            });
370
7605
        }
371

            
372
7605
        Ok(())
373
7607
    }
374

            
375
    /// Add a `CTorServiceKeystoreConfigBuilder` to this builder.
376
4
    pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
377
4
        if let Some(ref mut stores) = self.services.stores {
378
2
            stores.push(builder);
379
2
        } else {
380
2
            self.services.stores = Some(vec![builder]);
381
2
        }
382

            
383
4
        self
384
4
    }
385
}
386

            
387
#[cfg(test)]
388
mod test {
389
    // @@ begin test lint list maintained by maint/add_warning @@
390
    #![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)] // See arti#2571
402
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
403

            
404
    use super::*;
405

            
406
    use std::path::PathBuf;
407
    use std::str::FromStr as _;
408
    use tor_config::assert_config_error;
409

            
410
    /// Helper for creating [`CTorServiceKeystoreConfigBuilders`].
411
    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
    /// Helper for creating [`CTorClientKeystoreConfigBuilders`].
424
    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
        // Push two clients with the same (default) ID:
435
        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
        // Push two services with the same ID:
456
        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
        // Push two services with different IDs but same nicknames:
469
        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
}