1
//! The [`KeySpecifier`] trait and its implementations.
2

            
3
use std::collections::BTreeMap;
4
use std::fmt::{self, Debug, Display};
5
use std::ops::Range;
6
use std::result::Result as StdResult;
7
use std::str::FromStr;
8

            
9
use derive_more::From;
10
use safelog::DisplayRedacted as _;
11
use thiserror::Error;
12
use tor_error::{Bug, internal, into_internal};
13
use tor_hscrypto::pk::{HSID_ONION_SUFFIX, HsId, HsIdParseError};
14
use tor_hscrypto::time::TimePeriod;
15
use tor_persist::hsnickname::HsNickname;
16
use tor_persist::slug::Slug;
17

            
18
use crate::{ArtiPath, ArtiPathSyntaxError};
19

            
20
// #[doc(hidden)] applied at crate toplevel
21
#[macro_use]
22
pub mod derive;
23

            
24
/// The identifier of a key.
25
#[derive(Clone, Debug, PartialEq, Eq, Hash, From, derive_more::Display)]
26
#[non_exhaustive]
27
pub enum KeyPath {
28
    /// An Arti key path.
29
    Arti(ArtiPath),
30
    /// A C-Tor key path.
31
    CTor(CTorPath),
32
}
33

            
34
/// A range specifying a substring of a [`KeyPath`].
35
#[derive(Clone, Debug, PartialEq, Eq, Hash, From)]
36
pub struct ArtiPathRange(pub(crate) Range<usize>);
37

            
38
impl ArtiPath {
39
    /// Check whether this `ArtiPath` matches the specified [`KeyPathPattern`].
40
    ///
41
    /// If the `ArtiPath` matches the pattern, this returns the ranges that match its dynamic parts.
42
    ///
43
    /// ### Example
44
    /// ```
45
    /// # use tor_keymgr::{ArtiPath, KeyPath, KeyPathPattern, ArtiPathSyntaxError};
46
    /// # fn demo() -> Result<(), ArtiPathSyntaxError> {
47
    /// let path = ArtiPath::new("foo_bar_baz_1".into())?;
48
    /// let pattern = KeyPathPattern::Arti("*_bar_baz_*".into());
49
    /// let matches = path.matches(&pattern).unwrap();
50
    ///
51
    /// assert_eq!(matches.len(), 2);
52
    /// assert_eq!(path.substring(&matches[0]), Some("foo"));
53
    /// assert_eq!(path.substring(&matches[1]), Some("1"));
54
    /// # Ok(())
55
    /// # }
56
    /// #
57
    /// # demo().unwrap();
58
    /// ```
59
5622
    pub fn matches(&self, pat: &KeyPathPattern) -> Option<Vec<ArtiPathRange>> {
60
        use KeyPathPattern::*;
61

            
62
5622
        let pattern: &str = match pat {
63
5622
            Arti(pat) => pat.as_ref(),
64
            _ => return None,
65
        };
66

            
67
5622
        glob_match::glob_match_with_captures(pattern, self.as_ref())
68
11349
            .map(|res| res.into_iter().map(|r| r.into()).collect())
69
5622
    }
70
}
71

            
72
impl KeyPath {
73
    /// Check whether this `KeyPath` matches the specified [`KeyPathPattern`].
74
    ///
75
    /// Returns `true` if the `KeyPath` matches the pattern.
76
    ///
77
    /// ### Example
78
    /// ```
79
    /// # use tor_keymgr::{ArtiPath, KeyPath, KeyPathPattern, ArtiPathSyntaxError};
80
    /// # fn demo() -> Result<(), ArtiPathSyntaxError> {
81
    /// let path = KeyPath::Arti(ArtiPath::new("foo_bar_baz_1".into())?);
82
    /// let pattern = KeyPathPattern::Arti("*_bar_baz_*".into());
83
    /// assert!(path.matches(&pattern));
84
    /// # Ok(())
85
    /// # }
86
    /// #
87
    /// # demo().unwrap();
88
    /// ```
89
6246
    pub fn matches(&self, pat: &KeyPathPattern) -> bool {
90
        use KeyPathPattern::*;
91

            
92
6246
        match (self, pat) {
93
5622
            (KeyPath::Arti(p), Arti(_)) => p.matches(pat).is_some(),
94
            (KeyPath::CTor(p), CTor(pat)) if p == pat => true,
95
624
            _ => false,
96
        }
97
6246
    }
98

            
99
    // TODO: rewrite these getters using derive_adhoc if KeyPath grows more variants.
100

            
101
    /// Return the underlying [`ArtiPath`], if this is a `KeyPath::Arti`.
102
    pub fn arti(&self) -> Option<&ArtiPath> {
103
        match self {
104
            KeyPath::Arti(arti) => Some(arti),
105
            KeyPath::CTor(_) => None,
106
        }
107
    }
108

            
109
    /// Return the underlying [`CTorPath`], if this is a `KeyPath::CTor`.
110
476
    pub fn ctor(&self) -> Option<&CTorPath> {
111
476
        match self {
112
            KeyPath::Arti(_) => None,
113
476
            KeyPath::CTor(ctor) => Some(ctor),
114
        }
115
476
    }
116
}
117

            
118
/// A pattern specifying some or all of a kind of key
119
///
120
/// Generally implemented on `SomeKeySpecifierPattern` by
121
/// applying
122
/// [`#[derive_deftly(KeySpecifier)`](crate::derive_deftly_template_KeySpecifier)
123
/// to `SomeKeySpecifier`.
124
pub trait KeySpecifierPattern {
125
    /// Obtain a pattern template that matches all keys of this type.
126
    fn new_any() -> Self
127
    where
128
        Self: Sized;
129

            
130
    /// Get a [`KeyPathPattern`] that can match the [`ArtiPath`]s
131
    /// of some or all the keys of this type.
132
    fn arti_pattern(&self) -> Result<KeyPathPattern, Bug>;
133
}
134

            
135
/// An error while attempting to extract information about a key given its path
136
///
137
/// For example, from a [`KeyPathInfoExtractor`].
138
///
139
/// See also `crate::keystore::arti::MalformedPathError`,
140
/// which occurs at a lower level.
141
#[derive(Debug, Clone, thiserror::Error)]
142
#[non_exhaustive]
143
pub enum KeyPathError {
144
    /// An error while trying to extract information from an [`ArtiPath`].
145
    #[error("{err}")]
146
    Arti {
147
        /// The path that caused the error.
148
        path: ArtiPath,
149
        /// The underlying error
150
        err: ArtiPathError,
151
    },
152

            
153
    /// An error while trying to extract information from an [`CTorPath`].
154
    #[error("{err}")]
155
    CTor {
156
        /// The path that caused the error.
157
        path: CTorPath,
158
        /// The underlying error
159
        err: CTorPathError,
160
    },
161

            
162
    /// An internal error.
163
    #[error("Internal error")]
164
    Bug(#[from] tor_error::Bug),
165
}
166

            
167
/// An error while attempting to extract information from an [`ArtiPath`].
168
#[derive(Debug, Clone, thiserror::Error)]
169
#[non_exhaustive]
170
pub enum ArtiPathError {
171
    /// The path did not match the expected pattern.
172
    #[error("Path does not match expected pattern")]
173
    PatternNotMatched,
174

            
175
    /// Found an invalid [`ArtiPath`], which is syntactically invalid on its face
176
    #[error("ArtiPath is invalid")]
177
    InvalidArtiPath(ArtiPathSyntaxError),
178

            
179
    /// An invalid key path component value string was encountered
180
    ///
181
    /// When attempting to interpret a key path, one of the elements in the path
182
    /// contained a string value which wasn't a legitimate representation of the
183
    /// type of data expected there for this kind of key.
184
    ///
185
    /// (But the key path is in the proper character set.)
186
    #[error("invalid string value for element of key path")]
187
    InvalidKeyPathComponentValue {
188
        /// What was wrong with the value
189
        #[source]
190
        error: InvalidKeyPathComponentValue,
191
        /// The name of the "key" (what data we were extracting)
192
        ///
193
        /// Should be valid Rust identifier syntax.
194
        key: String,
195
        /// The substring of the `ArtiPath` that couldn't be parsed.
196
        value: Slug,
197
    },
198

            
199
    /// An internal error.
200
    #[error("Internal error")]
201
    Bug(#[from] tor_error::Bug),
202
}
203

            
204
/// An error while attempting to convert a [`CTorPath`]
205
/// to its corresponding key specifier type.
206
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
207
#[non_exhaustive]
208
pub enum CTorPathError {
209
    /// Attempted to convert a C Tor path to a mismatched specifier kind.
210
    #[error("C Tor path cannot be converted to {0}")]
211
    KeySpecifierMismatch(String),
212

            
213
    /// Attempted to convert a C Tor path to a key specifier
214
    /// that does not have a C Tor path.
215
    #[error("Key specifier {0} does not have a C Tor path")]
216
    MissingCTorPath(String),
217
}
218

            
219
/// Error to be returned by `KeySpecifierComponent::from_slug` implementations
220
///
221
/// Currently this error contains little information,
222
/// but the context and value are provided in
223
/// [`ArtiPathError::InvalidKeyPathComponentValue`].
224
#[derive(Error, Clone, Debug)]
225
#[non_exhaustive]
226
pub enum InvalidKeyPathComponentValue {
227
    /// Found an invalid slug.
228
    ///
229
    /// The inner string should be a description of what is wrong with the slug.
230
    /// It should not say that the keystore was corrupted,
231
    /// (keystore corruption errors are reported using higher level
232
    /// [`KeystoreCorruptionError`s](crate::KeystoreCorruptionError)),
233
    /// or where the information came from (the context is encoded in the
234
    /// enclosing [`ArtiPathError::InvalidKeyPathComponentValue`] error).
235
    #[error("{0}")]
236
    Slug(String),
237

            
238
    /// An internal error.
239
    ///
240
    /// The [`KeySpecifierComponentViaDisplayFromStr`] trait maps any errors returned by the
241
    /// [`FromStr`] implementation of the implementing type to this variant.
242
    #[error("Internal error")]
243
    Bug(#[from] tor_error::Bug),
244
}
245

            
246
/// Information about a [`KeyPath`].
247
///
248
/// The information is extracted from the [`KeyPath`] itself
249
/// (_not_ from the key data) by a [`KeyPathInfoExtractor`].
250
//
251
// TODO  maybe the getters should be combined with the builder, or something?
252
#[derive(Debug, Clone, PartialEq, derive_builder::Builder, amplify::Getters)]
253
pub struct KeyPathInfo {
254
    /// A human-readable summary string describing what the [`KeyPath`] is for.
255
    ///
256
    /// This should *not* recapitulate information in the `extra_info`.
257
    summary: String,
258
    /// The key role, ie its official name in the Tor Protocols.
259
    ///
260
    /// This should usually start with `KS_`.
261
    //
262
    // TODO (#1195): see the comment for #[deftly(role)] in derive.rs
263
    role: String,
264
    /// Additional information, in the form of key-value pairs.
265
    ///
266
    /// This will contain human-readable information that describes the individual
267
    /// components of a KeyPath. For example, for the [`ArtiPath`]
268
    /// `hs/foo/KS_hs_id.expanded_ed25519_private`, the extra information could
269
    /// be `("kind", "service)`, `("nickname", "foo")`, etc.
270
    #[builder(default, setter(custom))]
271
    extra_info: BTreeMap<String, String>,
272
}
273

            
274
impl KeyPathInfo {
275
    /// Start to build a [`KeyPathInfo`]: return a fresh [`KeyPathInfoBuilder`]
276
8
    pub fn builder() -> KeyPathInfoBuilder {
277
8
        KeyPathInfoBuilder::default()
278
8
    }
279
}
280

            
281
impl KeyPathInfoBuilder {
282
    /// Initialize the additional information of this builder with the specified values.
283
    ///
284
    /// Erases the preexisting `extra_info`.
285
6
    pub fn set_all_extra_info(
286
6
        &mut self,
287
6
        all_extra_info: impl Iterator<Item = (String, String)>,
288
6
    ) -> &mut Self {
289
6
        self.extra_info = Some(all_extra_info.collect());
290
6
        self
291
6
    }
292

            
293
    /// Append the specified key-value pair to the `extra_info`.
294
    ///
295
    /// The preexisting `extra_info` is preserved.
296
1166
    pub fn extra_info(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
297
1166
        let extra_info = self.extra_info.get_or_insert(Default::default());
298
1166
        extra_info.insert(key.into(), value.into());
299
1166
        self
300
1166
    }
301
}
302

            
303
/// A trait for extracting info out of a [`KeyPath`]s.
304
///
305
/// This trait is used by [`KeyMgr::describe`](crate::KeyMgr::describe)
306
/// to extract information out of [`KeyPath`]s.
307
pub trait KeyPathInfoExtractor: Send + Sync {
308
    /// Describe the specified `path`.
309
    fn describe(&self, path: &KeyPath) -> StdResult<KeyPathInfo, KeyPathError>;
310
}
311

            
312
/// Register a [`KeyPathInfoExtractor`] for use with [`KeyMgr`](crate::KeyMgr).
313
#[macro_export]
314
macro_rules! register_key_info_extractor {
315
    ($kv:expr) => {{
316
        $crate::inventory::submit!(&$kv as &dyn $crate::KeyPathInfoExtractor);
317
    }};
318
}
319

            
320
/// A pattern that can be used to match [`ArtiPath`]s or [`CTorPath`]s.
321
///
322
/// Create a new `KeyPathPattern`.
323
///
324
/// ## Syntax
325
///
326
/// NOTE: this table is copied verbatim from the [`glob-match`] docs.
327
///
328
/// | Syntax  | Meaning                                                                                                                                                                                             |
329
/// | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
330
/// | `?`     | Matches any single character.                                                                                                                                                                       |
331
/// | `*`     | Matches zero or more characters, except for path separators (e.g. `/`).                                                                                                                             |
332
/// | `**`    | Matches zero or more characters, including path separators. Must match a complete path segment (i.e. followed by a `/` or the end of the pattern).                                                  |
333
/// | `[ab]`  | Matches one of the characters contained in the brackets. Character ranges, e.g. `[a-z]` are also supported. Use `[!ab]` or `[^ab]` to match any character _except_ those contained in the brackets. |
334
/// | `{a,b}` | Matches one of the patterns contained in the braces. Any of the wildcard characters can be used in the sub-patterns. Braces may be nested up to 10 levels deep.                                     |
335
/// | `!`     | When at the start of the glob, this negates the result. Multiple `!` characters negate the glob multiple times.                                                                                     |
336
/// | `\`     | A backslash character may be used to escape any of the above special characters.                                                                                                                    |
337
///
338
/// [`glob-match`]: https://crates.io/crates/glob-match
339
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
340
#[non_exhaustive]
341
pub enum KeyPathPattern {
342
    /// A pattern for matching [`ArtiPath`]s.
343
    Arti(String),
344
    /// A pattern for matching [`CTorPath`]s.
345
    CTor(CTorPath),
346
}
347

            
348
/// The path of a key in the C Tor key store.
349
#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display)] //
350
#[non_exhaustive]
351
pub enum CTorPath {
352
    /// A client descriptor encryption key, to be looked up in ClientOnionAuthDir.
353
    ///
354
    /// Represents an entry in C Tor's `ClientOnionAuthDir`.
355
    ///
356
    /// We can't statically know exactly *which* entry has the key for this `HsId`
357
    /// (we'd need to read and parse each file from `ClientOnionAuthDir` to find out).
358
    //
359
    // TODO: Perhaps we should redact this sometimes.
360
    #[display("HsClientDescEncKeypair({})", hs_id.display_unredacted())]
361
    HsClientDescEncKeypair {
362
        /// The hidden service this restricted discovery keypair is for.
363
        hs_id: HsId,
364
    },
365
    /// C Tor's `HiddenServiceDirectory/hs_ed25519_public_key`.
366
    #[display("hs_ed25519_public_key")]
367
    HsIdPublicKey {
368
        /// The nickname of the service,
369
        nickname: HsNickname,
370
    },
371
    /// C Tor's `HiddenServiceDirectory/hs_ed25519_secret_key`.
372
    #[display("hs_ed25519_secret_key")]
373
    HsIdKeypair {
374
        /// The nickname of the service,
375
        nickname: HsNickname,
376
    },
377
}
378

            
379
/// The "specifier" of a key, which identifies an instance of a key.
380
///
381
/// [`KeySpecifier::arti_path()`] should uniquely identify an instance of a key.
382
pub trait KeySpecifier {
383
    /// The location of the key in the Arti key store.
384
    ///
385
    /// This also acts as a unique identifier for a specific key instance.
386
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError>;
387

            
388
    /// The location of the key in the C Tor key store (if supported).
389
    ///
390
    /// This function should return `None` for keys that are recognized by Arti's key stores, but
391
    /// not by C Tor's key store (such as `HsClientIntroAuthKeypair`).
392
    fn ctor_path(&self) -> Option<CTorPath>;
393

            
394
    /// If this is the specifier for a public key, the specifier for
395
    /// the corresponding (secret) keypair from which it can be derived
396
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>>;
397
}
398

            
399
/// A trait for serializing and deserializing specific types of [`Slug`]s.
400
///
401
/// A `KeySpecifierComponent` is a specific kind of `Slug`. A `KeySpecifierComponent` is
402
/// always a valid `Slug`, but may have a more restricted charset, or more specific
403
/// validation rules. A `Slug` is not always a valid `KeySpecifierComponent`
404
/// instance.
405
///
406
/// If you are deriving [`DefaultKeySpecifier`](crate::derive_deftly_template_KeySpecifier) for a
407
/// struct, all of its fields must implement this trait.
408
///
409
/// If you are implementing [`KeySpecifier`] and [`KeyPathInfoExtractor`] manually rather than by
410
/// deriving `DefaultKeySpecifier`, you do not need to implement this trait.
411
pub trait KeySpecifierComponent {
412
    /// Return the [`Slug`] representation of this type.
413
    fn to_slug(&self) -> Result<Slug, Bug>;
414
    /// Try to convert `s` into an object of this type.
415
    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
416
    where
417
        Self: Sized;
418
    /// Display the value in a human-meaningful representation
419
    ///
420
    /// The output should be a single line (without trailing full stop).
421
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result;
422
}
423

            
424
/// An error returned by a [`KeySpecifier`].
425
///
426
/// The putative `KeySpecifier` might be simply invalid,
427
/// or it might be being used in an inappropriate context.
428
#[derive(Error, Debug, Clone)]
429
#[non_exhaustive]
430
pub enum ArtiPathUnavailableError {
431
    /// An internal error.
432
    #[error("Internal error")]
433
    Bug(#[from] tor_error::Bug),
434

            
435
    /// An error returned by a [`KeySpecifier`] that does not have an [`ArtiPath`].
436
    ///
437
    /// This is returned, for example, by [`CTorPath`]'s [`KeySpecifier::arti_path`]
438
    /// implementation.
439
    #[error("ArtiPath unavailable")]
440
    ArtiPathUnavailable,
441
}
442

            
443
impl KeySpecifier for ArtiPath {
444
1548
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
445
1548
        Ok(self.clone())
446
1548
    }
447

            
448
    fn ctor_path(&self) -> Option<CTorPath> {
449
        None
450
    }
451

            
452
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
453
        None
454
    }
455
}
456

            
457
impl KeySpecifier for CTorPath {
458
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
459
        Err(ArtiPathUnavailableError::ArtiPathUnavailable)
460
    }
461

            
462
1252
    fn ctor_path(&self) -> Option<CTorPath> {
463
1252
        Some(self.clone())
464
1252
    }
465

            
466
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
467
        None
468
    }
469
}
470

            
471
impl KeySpecifier for KeyPath {
472
1520
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
473
1520
        match self {
474
1520
            KeyPath::Arti(p) => p.arti_path(),
475
            KeyPath::CTor(p) => p.arti_path(),
476
        }
477
1520
    }
478

            
479
312
    fn ctor_path(&self) -> Option<CTorPath> {
480
312
        match self {
481
            KeyPath::Arti(p) => p.ctor_path(),
482
312
            KeyPath::CTor(p) => p.ctor_path(),
483
        }
484
312
    }
485

            
486
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
487
        None
488
    }
489
}
490

            
491
impl KeySpecifierComponent for TimePeriod {
492
9544
    fn to_slug(&self) -> Result<Slug, Bug> {
493
9544
        Slug::new(format!(
494
9544
            "{}_{}_{}",
495
9544
            self.interval_num(),
496
9544
            self.length(),
497
9544
            self.epoch_offset_in_sec()
498
        ))
499
9544
        .map_err(into_internal!("TP formatting went wrong"))
500
9544
    }
501

            
502
238
    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
503
238
    where
504
238
        Self: Sized,
505
    {
506
        use itertools::Itertools;
507

            
508
238
        let s = s.to_string();
509
        #[allow(clippy::redundant_closure)] // the closure makes things slightly more readable
510
240
        let err_ctx = |e: &str| InvalidKeyPathComponentValue::Slug(e.to_string());
511
238
        let (interval, len, offset) = s
512
238
            .split('_')
513
238
            .collect_tuple()
514
240
            .ok_or_else(|| err_ctx("invalid number of subcomponents"))?;
515

            
516
234
        let length = len.parse().map_err(|_| err_ctx("invalid length"))?;
517
234
        let interval_num = interval
518
234
            .parse()
519
234
            .map_err(|_| err_ctx("invalid interval_num"))?;
520
234
        let offset_in_sec = offset
521
234
            .parse()
522
234
            .map_err(|_| err_ctx("invalid offset_in_sec"))?;
523

            
524
234
        Ok(TimePeriod::from_parts(length, interval_num, offset_in_sec))
525
238
    }
526

            
527
156
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
528
156
        Display::fmt(&self, f)
529
156
    }
530
}
531

            
532
/// Implement [`KeySpecifierComponent`] in terms of [`Display`] and [`FromStr`] (helper trait)
533
///
534
/// The default [`from_slug`](KeySpecifierComponent::from_slug) implementation maps any errors
535
/// returned from [`FromStr`] to [`InvalidKeyPathComponentValue::Bug`].
536
/// Key specifier components that cannot readily be parsed from a string should have a bespoke
537
/// [`from_slug`](KeySpecifierComponent::from_slug) implementation, and
538
/// return more descriptive errors through [`InvalidKeyPathComponentValue::Slug`].
539
pub trait KeySpecifierComponentViaDisplayFromStr: Display + FromStr {}
540
impl<T: KeySpecifierComponentViaDisplayFromStr> KeySpecifierComponent for T {
541
2432
    fn to_slug(&self) -> Result<Slug, Bug> {
542
2432
        self.to_string()
543
2432
            .try_into()
544
2432
            .map_err(into_internal!("Display generated bad Slug"))
545
2432
    }
546
1552
    fn from_slug(s: &Slug) -> Result<Self, InvalidKeyPathComponentValue>
547
1552
    where
548
1552
        Self: Sized,
549
    {
550
1552
        s.as_str()
551
1552
            .parse()
552
1552
            .map_err(|_| internal!("slug cannot be parsed as component").into())
553
1552
    }
554
600
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
555
600
        Display::fmt(self, f)
556
600
    }
557
}
558

            
559
impl KeySpecifierComponentViaDisplayFromStr for HsNickname {}
560

            
561
impl KeySpecifierComponent for HsId {
562
2134
    fn to_slug(&self) -> StdResult<Slug, Bug> {
563
        // We can't implement KeySpecifierComponentViaDisplayFromStr for HsId,
564
        // because its Display impl contains the `.onion` suffix, and Slugs can't
565
        // contain `.`.
566
2134
        let hsid = self.display_unredacted().to_string();
567
2134
        let hsid_slug = hsid
568
2134
            .strip_suffix(HSID_ONION_SUFFIX)
569
2134
            .ok_or_else(|| internal!("HsId Display impl missing .onion suffix?!"))?;
570
2134
        hsid_slug
571
2134
            .to_owned()
572
2134
            .try_into()
573
2134
            .map_err(into_internal!("Display generated bad Slug"))
574
2134
    }
575

            
576
106
    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
577
106
    where
578
106
        Self: Sized,
579
    {
580
        // Note: HsId::from_str expects the string to have a .onion suffix,
581
        // but the string representation of our slug doesn't have it
582
        // (because we manually strip it away, see to_slug()).
583
        //
584
        // We have to manually add it for this to work.
585
        //
586
        // TODO: HsId should have some facilities for converting base32 HsIds (sans suffix)
587
        // to and from string.
588
106
        let onion = format!("{}{HSID_ONION_SUFFIX}", s.as_str());
589

            
590
106
        onion
591
106
            .parse()
592
106
            .map_err(|e: HsIdParseError| InvalidKeyPathComponentValue::Slug(e.to_string()))
593
106
    }
594

            
595
104
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
596
104
        Display::fmt(&self.display_redacted(), f)
597
104
    }
598
}
599

            
600
/// Wrapper for `KeySpecifierComponent` that `Displays` via `fmt_pretty`
601
struct KeySpecifierComponentPrettyHelper<'c>(&'c dyn KeySpecifierComponent);
602

            
603
impl Display for KeySpecifierComponentPrettyHelper<'_> {
604
1156
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
605
1156
        KeySpecifierComponent::fmt_pretty(self.0, f)
606
1156
    }
607
}
608

            
609
/// The "specifier" of a key certificate, which identifies an instance of a cert,
610
/// as well as its signing and subject keys.
611
///
612
/// Certificates can only be fetched from Arti key stores
613
/// (we will not support loading certs from C Tor's key directory)
614
pub trait KeyCertificateSpecifier {
615
    /// The denotators of the certificate.
616
    ///
617
    /// Used by `KeyMgr` to derive the `ArtiPath` of the certificate.
618
    /// The `ArtiPath` of a certificate is obtained
619
    /// by concatenating the `ArtiPath` of the subject key with the
620
    /// denotators provided by this function,
621
    /// with a `+` between the `ArtiPath` of the subject key and
622
    /// the denotators (the `+` is omitted if there are no denotators).
623
    fn cert_denotators(&self) -> Vec<&dyn KeySpecifierComponent>;
624
    /// The key specifier of the signing key.
625
    ///
626
    /// Returns `None` if the signing key should not be retrieved from the keystore.
627
    ///
628
    /// Note: a return value of `None` means the signing key will be provided
629
    /// as an argument to the `KeyMgr` accessor this `KeyCertificateSpecifier`
630
    /// will be used with.
631
    fn signing_key_specifier(&self) -> Option<&dyn KeySpecifier>;
632
    /// The key specifier of the subject key.
633
    fn subject_key_specifier(&self) -> &dyn KeySpecifier;
634
}
635

            
636
/// A trait for converting key specifiers to and from [`CTorPath`].
637
///
638
/// Important: this trait should not be implemented by hand.
639
/// It is auto-implemented for types that derive [`KeySpecifier`].
640
pub trait CTorKeySpecifier: KeySpecifier + Sized {
641
    /// The location of the key in the C Tor key store (if supported).
642
    ///
643
    /// See [`KeySpecifier::ctor_path`].
644
    fn ctor_path(&self) -> Option<CTorPath>;
645

            
646
    /// Try to convert `path` to a specifier of this kind.
647
    ///
648
    /// Returns an error if the `CTorPath` is not the path of a key of this type,
649
    /// or if this type does not have a `CTorPath`.
650
    fn from_ctor_path(path: CTorPath) -> Result<Self, CTorPathError>;
651
}
652

            
653
#[cfg(test)]
654
mod test {
655
    // @@ begin test lint list maintained by maint/add_warning @@
656
    #![allow(clippy::bool_assert_comparison)]
657
    #![allow(clippy::clone_on_copy)]
658
    #![allow(clippy::dbg_macro)]
659
    #![allow(clippy::mixed_attributes_style)]
660
    #![allow(clippy::print_stderr)]
661
    #![allow(clippy::print_stdout)]
662
    #![allow(clippy::single_char_pattern)]
663
    #![allow(clippy::unwrap_used)]
664
    #![allow(clippy::unchecked_time_subtraction)]
665
    #![allow(clippy::useless_vec)]
666
    #![allow(clippy::needless_pass_by_value)]
667
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
668
    use super::*;
669

            
670
    use crate::test_utils::check_key_specifier;
671
    use derive_deftly::Deftly;
672
    use humantime::parse_rfc3339;
673
    use itertools::Itertools;
674
    use serde::{Deserialize, Serialize};
675
    use std::fmt::Debug;
676
    use std::time::Duration;
677

            
678
    impl KeySpecifierComponentViaDisplayFromStr for usize {}
679
    impl KeySpecifierComponentViaDisplayFromStr for String {}
680

            
681
    // This impl probably shouldn't be made non-test, since it produces longer paths
682
    // than is necessary.  `t`/`f` would be better representation.  But it's fine for tests.
683
    impl KeySpecifierComponentViaDisplayFromStr for bool {}
684

            
685
    fn test_time_period() -> TimePeriod {
686
        TimePeriod::new(
687
            Duration::from_secs(86400),
688
            parse_rfc3339("2020-09-15T00:00:00Z").unwrap(),
689
            Duration::from_secs(3600),
690
        )
691
        .unwrap()
692
    }
693

            
694
    #[test]
695
    fn pretty_time_period() {
696
        let tp = test_time_period();
697
        assert_eq!(
698
            KeySpecifierComponentPrettyHelper(&tp).to_string(),
699
            "#18519 2020-09-14T01:00:00Z..+24:00",
700
        );
701
    }
702

            
703
    #[test]
704
    fn serde() {
705
        // TODO: clone-and-hack with tor_hsservice::::nickname::test::serde
706
        // perhaps there should be some utility in tor-basic-utils for testing
707
        // validated string newtypes, or something
708
        #[derive(Serialize, Deserialize, Debug)]
709
        struct T {
710
            n: Slug,
711
        }
712
        let j = serde_json::from_str(r#"{ "n": "x" }"#).unwrap();
713
        let t: T = serde_json::from_value(j).unwrap();
714
        assert_eq!(&t.n.to_string(), "x");
715

            
716
        assert_eq!(&serde_json::to_string(&t).unwrap(), r#"{"n":"x"}"#);
717

            
718
        let j = serde_json::from_str(r#"{ "n": "!" }"#).unwrap();
719
        let e = serde_json::from_value::<T>(j).unwrap_err();
720
        assert!(
721
            e.to_string()
722
                .contains("character '!' (U+0021) is not allowed"),
723
            "wrong msg {e:?}"
724
        );
725
    }
726

            
727
    #[test]
728
    fn define_key_specifier_with_fields_and_denotator() {
729
        let tp = test_time_period();
730

            
731
        #[derive(Deftly, Debug, PartialEq)]
732
        #[derive_deftly(KeySpecifier)]
733
        #[deftly(prefix = "encabulator")]
734
        #[deftly(role = "marzlevane")]
735
        #[deftly(summary = "test key")]
736
        struct TestSpecifier {
737
            // The remaining fields
738
            kind: String,
739
            base: String,
740
            casing: String,
741
            #[deftly(denotator)]
742
            count: usize,
743
            #[deftly(denotator)]
744
            tp: TimePeriod,
745
        }
746

            
747
        let key_spec = TestSpecifier {
748
            kind: "hydrocoptic".into(),
749
            base: "waneshaft".into(),
750
            casing: "logarithmic".into(),
751
            count: 6,
752
            tp,
753
        };
754

            
755
        check_key_specifier(
756
            &key_spec,
757
            "encabulator/hydrocoptic/waneshaft/logarithmic/marzlevane+6+18519_1440_3600",
758
        );
759

            
760
        let info = TestSpecifierInfoExtractor
761
            .describe(&KeyPath::Arti(key_spec.arti_path().unwrap()))
762
            .unwrap();
763

            
764
        assert_eq!(
765
            format!("{info:#?}"),
766
            r##"
767
KeyPathInfo {
768
    summary: "test key",
769
    role: "marzlevane",
770
    extra_info: {
771
        "base": "waneshaft",
772
        "casing": "logarithmic",
773
        "count": "6",
774
        "kind": "hydrocoptic",
775
        "tp": "#18519 2020-09-14T01:00:00Z..+24:00",
776
    },
777
2
}
778
2
            "##
779
2
            .trim()
780
        );
781
2
    }
782

            
783
    #[test]
784
2
    fn define_key_specifier_no_fields() {
785
        #[derive(Deftly, Debug, PartialEq)]
786
        #[derive_deftly(KeySpecifier)]
787
        #[deftly(prefix = "encabulator")]
788
        #[deftly(role = "marzlevane")]
789
        #[deftly(summary = "test key")]
790
        struct TestSpecifier {}
791

            
792
2
        let key_spec = TestSpecifier {};
793

            
794
2
        check_key_specifier(&key_spec, "encabulator/marzlevane");
795

            
796
2
        assert_eq!(
797
2
            TestSpecifierPattern {}.arti_pattern().unwrap(),
798
2
            KeyPathPattern::Arti("encabulator/marzlevane".into())
799
        );
800
2
    }
801

            
802
    #[test]
803
2
    fn define_key_specifier_with_denotator() {
804
        #[derive(Deftly, Debug, PartialEq)]
805
        #[derive_deftly(KeySpecifier)]
806
        #[deftly(prefix = "encabulator")]
807
        #[deftly(role = "marzlevane")]
808
        #[deftly(summary = "test key")]
809
        struct TestSpecifier {
810
            #[deftly(denotator)]
811
            count: usize,
812
        }
813

            
814
2
        let key_spec = TestSpecifier { count: 6 };
815

            
816
2
        check_key_specifier(&key_spec, "encabulator/marzlevane+6");
817

            
818
2
        assert_eq!(
819
2
            TestSpecifierPattern { count: None }.arti_pattern().unwrap(),
820
2
            KeyPathPattern::Arti("encabulator/marzlevane+*".into())
821
        );
822
2
    }
823

            
824
    #[test]
825
2
    fn define_key_specifier_with_fields() {
826
        #[derive(Deftly, Debug, PartialEq)]
827
        #[derive_deftly(KeySpecifier)]
828
        #[deftly(prefix = "encabulator")]
829
        #[deftly(role = "fan")]
830
        #[deftly(summary = "test key")]
831
        struct TestSpecifier {
832
            casing: String,
833
            /// A doc comment.
834
            bearings: String,
835
        }
836

            
837
2
        let key_spec = TestSpecifier {
838
2
            casing: "logarithmic".into(),
839
2
            bearings: "spurving".into(),
840
2
        };
841

            
842
2
        check_key_specifier(&key_spec, "encabulator/logarithmic/spurving/fan");
843

            
844
2
        assert_eq!(
845
2
            TestSpecifierPattern {
846
2
                casing: Some("logarithmic".into()),
847
2
                bearings: Some("prefabulating".into()),
848
2
            }
849
2
            .arti_pattern()
850
2
            .unwrap(),
851
2
            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan".into())
852
        );
853

            
854
2
        let ctor_path = CTorPath::HsIdPublicKey {
855
2
            nickname: HsNickname::from_str("foo").unwrap(),
856
2
        };
857

            
858
2
        assert_eq!(
859
2
            TestSpecifier::from_ctor_path(ctor_path).unwrap_err(),
860
2
            CTorPathError::MissingCTorPath("TestSpecifier".into()),
861
        );
862
2
    }
863

            
864
    #[test]
865
2
    fn define_key_specifier_with_multiple_denotators() {
866
        #[derive(Deftly, Debug, PartialEq)]
867
        #[derive_deftly(KeySpecifier)]
868
        #[deftly(prefix = "encabulator")]
869
        #[deftly(role = "fan")]
870
        #[deftly(summary = "test key")]
871
        struct TestSpecifier {
872
            casing: String,
873
            /// A doc comment.
874
            bearings: String,
875

            
876
            #[deftly(denotator)]
877
            count: usize,
878

            
879
            #[deftly(denotator)]
880
            length: usize,
881

            
882
            #[deftly(denotator)]
883
            kind: String,
884
        }
885

            
886
2
        let key_spec = TestSpecifier {
887
2
            casing: "logarithmic".into(),
888
2
            bearings: "spurving".into(),
889
2
            count: 8,
890
2
            length: 2000,
891
2
            kind: "lunar".into(),
892
2
        };
893

            
894
2
        check_key_specifier(
895
2
            &key_spec,
896
2
            "encabulator/logarithmic/spurving/fan+8+2000+lunar",
897
        );
898

            
899
2
        assert_eq!(
900
2
            TestSpecifierPattern {
901
2
                casing: Some("logarithmic".into()),
902
2
                bearings: Some("prefabulating".into()),
903
2
                ..TestSpecifierPattern::new_any()
904
2
            }
905
2
            .arti_pattern()
906
2
            .unwrap(),
907
2
            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan+*+*+*".into())
908
        );
909
2
    }
910

            
911
    #[test]
912
2
    fn define_key_specifier_role_field() {
913
        #[derive(Deftly, Debug, Eq, PartialEq)]
914
        #[derive_deftly(KeySpecifier)]
915
        #[deftly(prefix = "prefix")]
916
        #[deftly(summary = "test key")]
917
        struct TestSpecifier {
918
            #[deftly(role)]
919
            role: String,
920
            i: usize,
921
            #[deftly(denotator)]
922
            den: bool,
923
        }
924

            
925
2
        check_key_specifier(
926
2
            &TestSpecifier {
927
2
                i: 1,
928
2
                role: "role".to_string(),
929
2
                den: true,
930
2
            },
931
2
            "prefix/1/role+true",
932
        );
933
2
    }
934

            
935
    #[test]
936
2
    fn define_key_specifier_ctor_path() {
937
        #[derive(Deftly, Debug, Eq, PartialEq)]
938
        #[derive_deftly(KeySpecifier)]
939
        #[deftly(prefix = "p")]
940
        #[deftly(role = "r")]
941
        #[deftly(ctor_path = "HsIdPublicKey")]
942
        #[deftly(summary = "test key")]
943
        struct TestSpecifier {
944
            nickname: HsNickname,
945
        }
946

            
947
2
        let spec = TestSpecifier {
948
2
            nickname: HsNickname::from_str("42").unwrap(),
949
2
        };
950

            
951
2
        check_key_specifier(&spec, "p/42/r");
952

            
953
2
        let ctor_path = KeySpecifier::ctor_path(&spec);
954

            
955
2
        assert_eq!(
956
            ctor_path,
957
2
            Some(CTorPath::HsIdPublicKey {
958
2
                nickname: HsNickname::from_str("42").unwrap(),
959
2
            }),
960
        );
961

            
962
2
        assert_eq!(
963
2
            TestSpecifier::from_ctor_path(ctor_path.unwrap()).unwrap(),
964
            spec,
965
        );
966

            
967
        /// An .onion address to put for test client CTorPaths.
968
        const HSID: &str = "yc6v7oeksrbech4ctv53di7rfjuikjagkyfrwu3yclzkfyv5haay6mqd.onion";
969
2
        let wrong_paths = &[
970
2
            CTorPath::HsClientDescEncKeypair {
971
2
                hs_id: HsId::from_str(HSID).unwrap(),
972
2
            },
973
2
            CTorPath::HsIdKeypair {
974
2
                nickname: HsNickname::from_str("42").unwrap(),
975
2
            },
976
2
        ];
977

            
978
6
        for path in wrong_paths {
979
4
            assert_eq!(
980
4
                TestSpecifier::from_ctor_path(path.clone()).unwrap_err(),
981
4
                CTorPathError::KeySpecifierMismatch("TestSpecifier".into()),
982
            );
983
        }
984
2
    }
985

            
986
    #[test]
987
2
    fn define_key_specifier_fixed_path_component() {
988
        #[derive(Deftly, Debug, Eq, PartialEq)]
989
        #[derive_deftly(KeySpecifier)]
990
        #[deftly(prefix = "prefix")]
991
        #[deftly(role = "role")]
992
        #[deftly(summary = "test key")]
993
        struct TestSpecifier {
994
            x: usize,
995
            #[deftly(fixed_path_component = "fixed")]
996
            z: bool,
997
        }
998

            
999
2
        check_key_specifier(&TestSpecifier { x: 1, z: true }, "prefix/1/fixed/true/role");
2
    }
    #[test]
2
    fn encode_time_period() {
2
        let period = TimePeriod::from_parts(1, 2, 3);
2
        let encoded_period = period.to_slug().unwrap();
2
        assert_eq!(encoded_period.to_string(), "2_1_3");
2
        assert_eq!(period, TimePeriod::from_slug(&encoded_period).unwrap());
2
        assert!(TimePeriod::from_slug(&Slug::new("invalid_tp".to_string()).unwrap()).is_err());
2
        assert!(TimePeriod::from_slug(&Slug::new("2_1_3_4".to_string()).unwrap()).is_err());
2
    }
    #[test]
2
    fn encode_hsid() {
2
        let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
2
        let onion = format!("{b32}.onion");
2
        let hsid = HsId::from_str(&onion).unwrap();
2
        let hsid_slug = hsid.to_slug().unwrap();
2
        assert_eq!(hsid_slug.to_string(), b32);
2
        assert_eq!(hsid, HsId::from_slug(&hsid_slug).unwrap());
2
    }
    #[test]
2
    fn key_info_builder() {
        // A helper to check the extra_info of a `KeyPathInfo`
        macro_rules! assert_extra_info_eq {
            ($key_info:expr, [$(($k:expr, $v:expr),)*]) => {{
                assert_eq!(
                    $key_info.extra_info.into_iter().collect_vec(),
                    vec![
                        $(($k.into(), $v.into()),)*
                    ]
                );
            }}
        }
2
        let extra_info = vec![("nickname".into(), "bar".into())];
2
        let key_info = KeyPathInfo::builder()
2
            .summary("test summary".into())
2
            .role("KS_vote".to_string())
2
            .set_all_extra_info(extra_info.clone().into_iter())
2
            .build()
2
            .unwrap();
2
        assert_eq!(key_info.extra_info.into_iter().collect_vec(), extra_info);
2
        let key_info = KeyPathInfo::builder()
2
            .summary("test summary".into())
2
            .role("KS_vote".to_string())
2
            .set_all_extra_info(extra_info.clone().into_iter())
2
            .extra_info("type", "service")
2
            .extra_info("time period", "100")
2
            .build()
2
            .unwrap();
2
        assert_extra_info_eq!(
            key_info,
            [
2
                ("nickname", "bar"),
2
                ("time period", "100"),
2
                ("type", "service"),
            ]
        );
2
        let key_info = KeyPathInfo::builder()
2
            .summary("test summary".into())
2
            .role("KS_vote".to_string())
2
            .extra_info("type", "service")
2
            .extra_info("time period", "100")
2
            .set_all_extra_info(extra_info.clone().into_iter())
2
            .build()
2
            .unwrap();
2
        assert_extra_info_eq!(key_info, [("nickname", "bar"),]);
2
        let key_info = KeyPathInfo::builder()
2
            .summary("test summary".into())
2
            .role("KS_vote".to_string())
2
            .extra_info("type", "service")
2
            .extra_info("time period", "100")
2
            .build()
2
            .unwrap();
2
        assert_extra_info_eq!(key_info, [("time period", "100"), ("type", "service"),]);
2
    }
}