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
51820
    pub fn matches(&self, pat: &KeyPathPattern) -> Option<Vec<ArtiPathRange>> {
60
        use KeyPathPattern::*;
61

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

            
67
51820
        glob_match::glob_match_with_captures(pattern, self.as_ref())
68
52212
            .map(|res| res.into_iter().map(|r| r.into()).collect())
69
51820
    }
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
52300
    pub fn matches(&self, pat: &KeyPathPattern) -> bool {
90
        use KeyPathPattern::*;
91

            
92
52300
        match (self, pat) {
93
51820
            (KeyPath::Arti(p), Arti(_)) => p.matches(pat).is_some(),
94
            (KeyPath::CTor(p), CTor(pat)) if p == pat => true,
95
480
            _ => false,
96
        }
97
52300
    }
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
20
    pub fn arti(&self) -> Option<&ArtiPath> {
103
20
        match self {
104
20
            KeyPath::Arti(arti) => Some(arti),
105
            KeyPath::CTor(_) => None,
106
        }
107
20
    }
108

            
109
    /// Return the underlying [`CTorPath`], if this is a `KeyPath::CTor`.
110
368
    pub fn ctor(&self) -> Option<&CTorPath> {
111
368
        match self {
112
            KeyPath::Arti(_) => None,
113
368
            KeyPath::CTor(ctor) => Some(ctor),
114
        }
115
368
    }
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
/// A pattern specifying some or all of a kind of certificate
136
///
137
/// Generally implemented on `SomeCertSpecifierPattern` by applying
138
/// [`#[derive_deftly(CertSpecifier)`](crate::derive_deftly_template_CertSpecifier)
139
/// to `SomeCertSpecifier`.
140
#[cfg(feature = "experimental-api")]
141
pub trait CertSpecifierPattern {
142
    /// The key specifier pattern of the subject key.
143
    ///
144
    /// Used to build the first part of the certificate specifier pattern
145
    /// (certificate paths consist of the ArtiPath of its subject key,
146
    /// followed by the cert denotators)
147
    type SubjectKeySpecifierPattern: KeySpecifierPattern;
148

            
149
    /// Obtain a pattern template that matches all certs of this type.
150
    ///
151
    /// The pattern consists of the [`KeySpecifierPattern::new_any`]
152
    /// of the `SubjectKeySpecifierPattern`, followed by a pattern
153
    /// that matches all the certificate denotators, if there are any.
154
    fn new_any() -> Self
155
    where
156
        Self: Sized;
157

            
158
    /// Get a [`KeyPathPattern`] that can match the [`ArtiPath`]s
159
    /// of some or all the keys of this type.
160
    fn arti_pattern(&self) -> Result<KeyPathPattern, Bug>;
161
}
162

            
163
/// An error while attempting to extract information about a key given its path
164
///
165
/// For example, from a [`KeyPathInfoExtractor`].
166
///
167
/// See also `crate::keystore::arti::MalformedPathError`,
168
/// which occurs at a lower level.
169
#[derive(Debug, Clone, thiserror::Error)]
170
#[non_exhaustive]
171
pub enum KeyPathError {
172
    /// An error while trying to extract information from an [`ArtiPath`].
173
    #[error("{err}")]
174
    Arti {
175
        /// The path that caused the error.
176
        path: ArtiPath,
177
        /// The underlying error
178
        err: ArtiPathError,
179
    },
180

            
181
    /// An error while trying to extract information from an [`CTorPath`].
182
    #[error("{err}")]
183
    CTor {
184
        /// The path that caused the error.
185
        path: CTorPath,
186
        /// The underlying error
187
        err: CTorPathError,
188
    },
189

            
190
    /// An internal error.
191
    #[error("Internal error")]
192
    Bug(#[from] tor_error::Bug),
193
}
194

            
195
/// An error while attempting to extract information from an [`ArtiPath`].
196
#[derive(Debug, Clone, thiserror::Error)]
197
#[non_exhaustive]
198
pub enum ArtiPathError {
199
    /// The path did not match the expected pattern.
200
    #[error("Path does not match expected pattern")]
201
    PatternNotMatched,
202

            
203
    /// Found an invalid [`ArtiPath`], which is syntactically invalid on its face
204
    #[error("ArtiPath is invalid")]
205
    InvalidArtiPath(ArtiPathSyntaxError),
206

            
207
    /// An invalid key path component value string was encountered
208
    ///
209
    /// When attempting to interpret a key path, one of the elements in the path
210
    /// contained a string value which wasn't a legitimate representation of the
211
    /// type of data expected there for this kind of key.
212
    ///
213
    /// (But the key path is in the proper character set.)
214
    #[error("invalid string value for element of key path")]
215
    InvalidKeyPathComponentValue {
216
        /// What was wrong with the value
217
        #[source]
218
        error: InvalidKeyPathComponentValue,
219
        /// The name of the "key" (what data we were extracting)
220
        ///
221
        /// Should be valid Rust identifier syntax.
222
        key: String,
223
        /// The substring of the `ArtiPath` that couldn't be parsed.
224
        value: Slug,
225
    },
226

            
227
    /// An internal error.
228
    #[error("Internal error")]
229
    Bug(#[from] tor_error::Bug),
230
}
231

            
232
/// An error while attempting to convert a [`CTorPath`]
233
/// to its corresponding key specifier type.
234
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
235
#[non_exhaustive]
236
pub enum CTorPathError {
237
    /// Attempted to convert a C Tor path to a mismatched specifier kind.
238
    #[error("C Tor path cannot be converted to {0}")]
239
    KeySpecifierMismatch(String),
240

            
241
    /// Attempted to convert a C Tor path to a key specifier
242
    /// that does not have a C Tor path.
243
    #[error("Key specifier {0} does not have a C Tor path")]
244
    MissingCTorPath(String),
245
}
246

            
247
/// Error to be returned by `KeySpecifierComponent::from_slug` implementations
248
///
249
/// Currently this error contains little information,
250
/// but the context and value are provided in
251
/// [`ArtiPathError::InvalidKeyPathComponentValue`].
252
#[derive(Error, Clone, Debug)]
253
#[non_exhaustive]
254
pub enum InvalidKeyPathComponentValue {
255
    /// Found an invalid slug.
256
    ///
257
    /// The inner string should be a description of what is wrong with the slug.
258
    /// It should not say that the keystore was corrupted,
259
    /// (keystore corruption errors are reported using higher level
260
    /// [`KeystoreCorruptionError`s](crate::KeystoreCorruptionError)),
261
    /// or where the information came from (the context is encoded in the
262
    /// enclosing [`ArtiPathError::InvalidKeyPathComponentValue`] error).
263
    #[error("{0}")]
264
    Slug(String),
265

            
266
    /// An internal error.
267
    ///
268
    /// The [`KeySpecifierComponentViaDisplayFromStr`] trait maps any errors returned by the
269
    /// [`FromStr`] implementation of the implementing type to this variant.
270
    #[error("Internal error")]
271
    Bug(#[from] tor_error::Bug),
272
}
273

            
274
/// Information about a [`KeyPath`].
275
///
276
/// The information is extracted from the [`KeyPath`] itself
277
/// (_not_ from the key data) by a [`KeyPathInfoExtractor`].
278
//
279
// TODO  maybe the getters should be combined with the builder, or something?
280
#[derive(Debug, Clone, PartialEq, derive_builder::Builder, amplify::Getters)]
281
pub struct KeyPathInfo {
282
    /// A human-readable summary string describing what the [`KeyPath`] is for.
283
    ///
284
    /// This should *not* recapitulate information in the `extra_info`.
285
    summary: String,
286
    /// The key role, ie its official name in the Tor Protocols.
287
    ///
288
    /// This should usually start with `KS_`.
289
    //
290
    // TODO (#1195): see the comment for #[deftly(role)] in derive.rs
291
    role: String,
292
    /// Additional information, in the form of key-value pairs.
293
    ///
294
    /// This will contain human-readable information that describes the individual
295
    /// components of a KeyPath. For example, for the [`ArtiPath`]
296
    /// `hs/foo/KS_hs_id.expanded_ed25519_private`, the extra information could
297
    /// be `("kind", "service)`, `("nickname", "foo")`, etc.
298
    #[builder(default, setter(custom))]
299
    extra_info: BTreeMap<String, String>,
300
}
301

            
302
impl KeyPathInfo {
303
    /// Start to build a [`KeyPathInfo`]: return a fresh [`KeyPathInfoBuilder`]
304
8
    pub fn builder() -> KeyPathInfoBuilder {
305
8
        KeyPathInfoBuilder::default()
306
8
    }
307
}
308

            
309
impl KeyPathInfoBuilder {
310
    /// Initialize the additional information of this builder with the specified values.
311
    ///
312
    /// Erases the preexisting `extra_info`.
313
6
    pub fn set_all_extra_info(
314
6
        &mut self,
315
6
        all_extra_info: impl Iterator<Item = (String, String)>,
316
6
    ) -> &mut Self {
317
6
        self.extra_info = Some(all_extra_info.collect());
318
6
        self
319
6
    }
320

            
321
    /// Append the specified key-value pair to the `extra_info`.
322
    ///
323
    /// The preexisting `extra_info` is preserved.
324
1542
    pub fn extra_info(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
325
1542
        let extra_info = self.extra_info.get_or_insert(Default::default());
326
1542
        extra_info.insert(key.into(), value.into());
327
1542
        self
328
1542
    }
329
}
330

            
331
/// A trait for extracting info out of a [`KeyPath`]s.
332
///
333
/// This trait is used by [`KeyMgr::describe`](crate::KeyMgr::describe)
334
/// to extract information out of [`KeyPath`]s.
335
pub trait KeyPathInfoExtractor: Send + Sync {
336
    /// Describe the specified `path`.
337
    fn describe(&self, path: &KeyPath) -> StdResult<KeyPathInfo, KeyPathError>;
338
}
339

            
340
/// Register a [`KeyPathInfoExtractor`] for use with [`KeyMgr`](crate::KeyMgr).
341
#[macro_export]
342
macro_rules! register_key_info_extractor {
343
    ($kv:expr) => {{
344
        $crate::inventory::submit!(&$kv as &dyn $crate::KeyPathInfoExtractor);
345
    }};
346
}
347

            
348
/// A pattern that can be used to match [`ArtiPath`]s or [`CTorPath`]s.
349
///
350
/// Create a new `KeyPathPattern`.
351
///
352
/// ## Syntax
353
///
354
/// NOTE: this table is copied verbatim from the [`glob-match`] docs.
355
///
356
/// | Syntax  | Meaning                                                                                                                                                                                             |
357
/// | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
358
/// | `?`     | Matches any single character.                                                                                                                                                                       |
359
/// | `*`     | Matches zero or more characters, except for path separators (e.g. `/`).                                                                                                                             |
360
/// | `**`    | 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).                                                  |
361
/// | `[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. |
362
/// | `{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.                                     |
363
/// | `!`     | When at the start of the glob, this negates the result. Multiple `!` characters negate the glob multiple times.                                                                                     |
364
/// | `\`     | A backslash character may be used to escape any of the above special characters.                                                                                                                    |
365
///
366
/// [`glob-match`]: https://crates.io/crates/glob-match
367
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
368
#[non_exhaustive]
369
pub enum KeyPathPattern {
370
    /// A pattern for matching [`ArtiPath`]s.
371
    Arti(String),
372
    /// A pattern for matching [`CTorPath`]s.
373
    CTor(CTorPath),
374
}
375

            
376
/// The path of a key in the C Tor key store.
377
#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display)] //
378
#[non_exhaustive]
379
pub enum CTorPath {
380
    /// A client descriptor encryption key, to be looked up in ClientOnionAuthDir.
381
    ///
382
    /// Represents an entry in C Tor's `ClientOnionAuthDir`.
383
    ///
384
    /// We can't statically know exactly *which* entry has the key for this `HsId`
385
    /// (we'd need to read and parse each file from `ClientOnionAuthDir` to find out).
386
    //
387
    // TODO: Perhaps we should redact this sometimes.
388
    #[display("HsClientDescEncKeypair({})", hs_id.display_unredacted())]
389
    HsClientDescEncKeypair {
390
        /// The hidden service this restricted discovery keypair is for.
391
        hs_id: HsId,
392
    },
393
    /// C Tor's `HiddenServiceDirectory/hs_ed25519_public_key`.
394
    #[display("hs_ed25519_public_key")]
395
    HsIdPublicKey {
396
        /// The nickname of the service,
397
        nickname: HsNickname,
398
    },
399
    /// C Tor's `HiddenServiceDirectory/hs_ed25519_secret_key`.
400
    #[display("hs_ed25519_secret_key")]
401
    HsIdKeypair {
402
        /// The nickname of the service,
403
        nickname: HsNickname,
404
    },
405
}
406

            
407
/// The "specifier" of a key, which identifies an instance of a key.
408
///
409
/// [`KeySpecifier::arti_path()`] should uniquely identify an instance of a key.
410
pub trait KeySpecifier {
411
    /// The location of the key in the Arti key store.
412
    ///
413
    /// This also acts as a unique identifier for a specific key instance.
414
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError>;
415

            
416
    /// The location of the key in the C Tor key store (if supported).
417
    ///
418
    /// This function should return `None` for keys that are recognized by Arti's key stores, but
419
    /// not by C Tor's key store (such as `HsClientIntroAuthKeypair`).
420
    fn ctor_path(&self) -> Option<CTorPath>;
421

            
422
    /// If this is the specifier for a public key, the specifier for
423
    /// the corresponding (secret) keypair from which it can be derived
424
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>>;
425
}
426

            
427
/// A trait for serializing and deserializing specific types of [`Slug`]s.
428
///
429
/// A `KeySpecifierComponent` is a specific kind of `Slug`. A `KeySpecifierComponent` is
430
/// always a valid `Slug`, but may have a more restricted charset, or more specific
431
/// validation rules. A `Slug` is not always a valid `KeySpecifierComponent`
432
/// instance.
433
///
434
/// If you are deriving [`DefaultKeySpecifier`](crate::derive_deftly_template_KeySpecifier) for a
435
/// struct, all of its fields must implement this trait.
436
///
437
/// If you are implementing [`KeySpecifier`] and [`KeyPathInfoExtractor`] manually rather than by
438
/// deriving `DefaultKeySpecifier`, you do not need to implement this trait.
439
pub trait KeySpecifierComponent {
440
    /// Return the [`Slug`] representation of this type.
441
    fn to_slug(&self) -> Result<Slug, Bug>;
442
    /// Try to convert `s` into an object of this type.
443
    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
444
    where
445
        Self: Sized;
446
    /// Display the value in a human-meaningful representation
447
    ///
448
    /// The output should be a single line (without trailing full stop).
449
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result;
450
}
451

            
452
/// An error returned by a [`KeySpecifier`].
453
///
454
/// The putative `KeySpecifier` might be simply invalid,
455
/// or it might be being used in an inappropriate context.
456
#[derive(Error, Debug, Clone)]
457
#[non_exhaustive]
458
pub enum ArtiPathUnavailableError {
459
    /// An internal error.
460
    #[error("Internal error")]
461
    Bug(#[from] tor_error::Bug),
462

            
463
    /// An error returned by a [`KeySpecifier`] that does not have an [`ArtiPath`].
464
    ///
465
    /// This is returned, for example, by [`CTorPath`]'s [`KeySpecifier::arti_path`]
466
    /// implementation.
467
    #[error("ArtiPath unavailable")]
468
    ArtiPathUnavailable,
469
}
470

            
471
impl KeySpecifier for ArtiPath {
472
3502
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
473
3502
        Ok(self.clone())
474
3502
    }
475

            
476
    fn ctor_path(&self) -> Option<CTorPath> {
477
        None
478
    }
479

            
480
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
481
        None
482
    }
483
}
484

            
485
impl KeySpecifier for CTorPath {
486
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
487
        Err(ArtiPathUnavailableError::ArtiPathUnavailable)
488
    }
489

            
490
964
    fn ctor_path(&self) -> Option<CTorPath> {
491
964
        Some(self.clone())
492
964
    }
493

            
494
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
495
        None
496
    }
497
}
498

            
499
impl KeySpecifier for KeyPath {
500
2154
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
501
2154
        match self {
502
2154
            KeyPath::Arti(p) => p.arti_path(),
503
            KeyPath::CTor(p) => p.arti_path(),
504
        }
505
2154
    }
506

            
507
240
    fn ctor_path(&self) -> Option<CTorPath> {
508
240
        match self {
509
            KeyPath::Arti(p) => p.ctor_path(),
510
240
            KeyPath::CTor(p) => p.ctor_path(),
511
        }
512
240
    }
513

            
514
4
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
515
4
        None
516
4
    }
517
}
518

            
519
impl KeySpecifierComponent for TimePeriod {
520
9696
    fn to_slug(&self) -> Result<Slug, Bug> {
521
9696
        Slug::new(format!(
522
            "{}_{}_{}",
523
9696
            self.interval_num(),
524
9696
            self.length(),
525
9696
            self.epoch_offset_in_sec()
526
        ))
527
9696
        .map_err(into_internal!("TP formatting went wrong"))
528
9696
    }
529

            
530
86
    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
531
86
    where
532
86
        Self: Sized,
533
    {
534
        use itertools::Itertools;
535

            
536
86
        let s = s.to_string();
537
        #[allow(clippy::redundant_closure)] // the closure makes things slightly more readable
538
88
        let err_ctx = |e: &str| InvalidKeyPathComponentValue::Slug(e.to_string());
539
86
        let (interval, len, offset) = s
540
86
            .split('_')
541
86
            .collect_tuple()
542
88
            .ok_or_else(|| err_ctx("invalid number of subcomponents"))?;
543

            
544
82
        let length = len.parse().map_err(|_| err_ctx("invalid length"))?;
545
82
        let interval_num = interval
546
82
            .parse()
547
82
            .map_err(|_| err_ctx("invalid interval_num"))?;
548
82
        let offset_in_sec = offset
549
82
            .parse()
550
82
            .map_err(|_| err_ctx("invalid offset_in_sec"))?;
551

            
552
82
        Ok(TimePeriod::from_parts(length, interval_num, offset_in_sec))
553
86
    }
554

            
555
4
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
556
4
        Display::fmt(&self, f)
557
4
    }
558
}
559

            
560
/// Implement [`KeySpecifierComponent`] in terms of [`Display`] and [`FromStr`] (helper trait)
561
///
562
/// The default [`from_slug`](KeySpecifierComponent::from_slug) implementation maps any errors
563
/// returned from [`FromStr`] to [`InvalidKeyPathComponentValue::Bug`].
564
/// Key specifier components that cannot readily be parsed from a string should have a bespoke
565
/// [`from_slug`](KeySpecifierComponent::from_slug) implementation, and
566
/// return more descriptive errors through [`InvalidKeyPathComponentValue::Slug`].
567
pub trait KeySpecifierComponentViaDisplayFromStr: Display + FromStr {}
568
impl<T: KeySpecifierComponentViaDisplayFromStr> KeySpecifierComponent for T {
569
2932
    fn to_slug(&self) -> Result<Slug, Bug> {
570
2932
        self.to_string()
571
2932
            .try_into()
572
2932
            .map_err(into_internal!("Display generated bad Slug"))
573
2932
    }
574
2610
    fn from_slug(s: &Slug) -> Result<Self, InvalidKeyPathComponentValue>
575
2610
    where
576
2610
        Self: Sized,
577
    {
578
2610
        s.as_str()
579
2610
            .parse()
580
2610
            .map_err(|_| internal!("slug cannot be parsed as component").into())
581
2610
    }
582
1340
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
583
1340
        Display::fmt(self, f)
584
1340
    }
585
}
586

            
587
impl KeySpecifierComponentViaDisplayFromStr for HsNickname {}
588

            
589
impl KeySpecifierComponent for HsId {
590
1642
    fn to_slug(&self) -> StdResult<Slug, Bug> {
591
        // We can't implement KeySpecifierComponentViaDisplayFromStr for HsId,
592
        // because its Display impl contains the `.onion` suffix, and Slugs can't
593
        // contain `.`.
594
1642
        let hsid = self.display_unredacted().to_string();
595
1642
        let hsid_slug = hsid
596
1642
            .strip_suffix(HSID_ONION_SUFFIX)
597
1642
            .ok_or_else(|| internal!("HsId Display impl missing .onion suffix?!"))?;
598
1642
        hsid_slug
599
1642
            .to_owned()
600
1642
            .try_into()
601
1642
            .map_err(into_internal!("Display generated bad Slug"))
602
1642
    }
603

            
604
82
    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
605
82
    where
606
82
        Self: Sized,
607
    {
608
        // Note: HsId::from_str expects the string to have a .onion suffix,
609
        // but the string representation of our slug doesn't have it
610
        // (because we manually strip it away, see to_slug()).
611
        //
612
        // We have to manually add it for this to work.
613
        //
614
        // TODO: HsId should have some facilities for converting base32 HsIds (sans suffix)
615
        // to and from string.
616
82
        let onion = format!("{}{HSID_ONION_SUFFIX}", s.as_str());
617

            
618
82
        onion
619
82
            .parse()
620
82
            .map_err(|e: HsIdParseError| InvalidKeyPathComponentValue::Slug(e.to_string()))
621
82
    }
622

            
623
80
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
624
80
        Display::fmt(&self.display_redacted(), f)
625
80
    }
626
}
627

            
628
/// Wrapper for `KeySpecifierComponent` that `Displays` via `fmt_pretty`
629
struct KeySpecifierComponentPrettyHelper<'c>(&'c dyn KeySpecifierComponent);
630

            
631
impl Display for KeySpecifierComponentPrettyHelper<'_> {
632
1532
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
633
1532
        KeySpecifierComponent::fmt_pretty(self.0, f)
634
1532
    }
635
}
636

            
637
/// The "specifier" of a key certificate, which identifies an instance of a cert,
638
/// as well as its signing and subject keys.
639
///
640
/// Certificates can only be fetched from Arti key stores
641
/// (we will not support loading certs from C Tor's key directory)
642
///
643
/// Types implementing this trait get an auto-generated [`KeySpecifier`] implementation
644
/// that returns the `ArtiPath` of the certificate.
645
///
646
/// The generated [`KeySpecifier::arti_path()`] returns
647
///
648
///   * the `ArtiPath` of the [`KeyCertificateSpecifier::subject_key_specifier`],
649
///     if [`KeyCertificateSpecifier::cert_denotators`] is empty, or
650
///   * the `ArtiPath` of the [`KeyCertificateSpecifier::subject_key_specifier`],
651
///     followed by a [`DENOTATOR_GROUP_SEP`](crate::DENOTATOR_GROUP_SEP) character and the
652
///     [`KeyCertificateSpecifier::cert_denotators`] encoded as described
653
///     in the [`ArtiPath`] docs,
654
///     if [`KeyCertificateSpecifier::cert_denotators`] is non-empty
655
pub trait KeyCertificateSpecifier {
656
    /// The denotators of the certificate.
657
    ///
658
    /// Used by `KeyMgr` to derive the `ArtiPath` of the certificate.
659
    /// The `ArtiPath` of a certificate is obtained
660
    /// by concatenating the `ArtiPath` of the subject key with the
661
    /// denotators provided by this function,
662
    /// with a `+` between the `ArtiPath` of the subject key and
663
    /// the denotators (the `+` is omitted if there are no denotators).
664
    fn cert_denotators(&self) -> Vec<&dyn KeySpecifierComponent>;
665
    /// The key specifier of the subject key.
666
    fn subject_key_specifier(&self) -> &dyn KeySpecifier;
667
}
668

            
669
impl<T: KeyCertificateSpecifier + ?Sized> KeySpecifier for T {
670
2
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
671
2
        let subject_key_arti_path = self
672
2
            .subject_key_specifier()
673
2
            .arti_path()
674
2
            .map_err(|_| internal!("subject key does not have an ArtiPath?!"))?;
675

            
676
2
        let path =
677
2
            ArtiPath::from_path_and_denotators(subject_key_arti_path, &self.cert_denotators())
678
2
                .map_err(into_internal!("invalid certificate specifier"))?;
679

            
680
2
        Ok(path)
681
2
    }
682

            
683
    fn ctor_path(&self) -> Option<CTorPath> {
684
        // Certificates don't have a CTorPath
685
        None
686
    }
687

            
688
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
689
        None
690
    }
691
}
692

            
693
/// A trait for converting key specifiers to and from [`CTorPath`].
694
///
695
/// Important: this trait should not be implemented by hand.
696
/// It is auto-implemented for types that derive [`KeySpecifier`].
697
pub trait CTorKeySpecifier: KeySpecifier + Sized {
698
    /// The location of the key in the C Tor key store (if supported).
699
    ///
700
    /// See [`KeySpecifier::ctor_path`].
701
    fn ctor_path(&self) -> Option<CTorPath>;
702

            
703
    /// Try to convert `path` to a specifier of this kind.
704
    ///
705
    /// Returns an error if the `CTorPath` is not the path of a key of this type,
706
    /// or if this type does not have a `CTorPath`.
707
    fn from_ctor_path(path: CTorPath) -> Result<Self, CTorPathError>;
708
}
709

            
710
#[cfg(test)]
711
mod test {
712
    // @@ begin test lint list maintained by maint/add_warning @@
713
    #![allow(clippy::bool_assert_comparison)]
714
    #![allow(clippy::clone_on_copy)]
715
    #![allow(clippy::dbg_macro)]
716
    #![allow(clippy::mixed_attributes_style)]
717
    #![allow(clippy::print_stderr)]
718
    #![allow(clippy::print_stdout)]
719
    #![allow(clippy::single_char_pattern)]
720
    #![allow(clippy::unwrap_used)]
721
    #![allow(clippy::unchecked_time_subtraction)]
722
    #![allow(clippy::useless_vec)]
723
    #![allow(clippy::needless_pass_by_value)]
724
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
725
    use super::*;
726

            
727
    use crate::test_utils::check_key_specifier;
728
    use derive_deftly::Deftly;
729
    use humantime::parse_rfc3339;
730
    use itertools::Itertools;
731
    use serde::{Deserialize, Serialize};
732
    use std::fmt::Debug;
733
    use std::time::Duration;
734

            
735
    impl KeySpecifierComponentViaDisplayFromStr for usize {}
736
    impl KeySpecifierComponentViaDisplayFromStr for String {}
737

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

            
742
    fn test_time_period() -> TimePeriod {
743
        TimePeriod::new(
744
            Duration::from_secs(86400),
745
            parse_rfc3339("2020-09-15T00:00:00Z").unwrap(),
746
            Duration::from_secs(3600),
747
        )
748
        .unwrap()
749
    }
750

            
751
    #[test]
752
    fn pretty_time_period() {
753
        let tp = test_time_period();
754
        assert_eq!(
755
            KeySpecifierComponentPrettyHelper(&tp).to_string(),
756
            "#18519 2020-09-14T01:00:00Z..+24:00",
757
        );
758
    }
759

            
760
    #[test]
761
    fn serde() {
762
        // TODO: clone-and-hack with tor_hsservice::::nickname::test::serde
763
        // perhaps there should be some utility in tor-basic-utils for testing
764
        // validated string newtypes, or something
765
        #[derive(Serialize, Deserialize, Debug)]
766
        struct T {
767
            n: Slug,
768
        }
769
        let j = serde_json::from_str(r#"{ "n": "x" }"#).unwrap();
770
        let t: T = serde_json::from_value(j).unwrap();
771
        assert_eq!(&t.n.to_string(), "x");
772

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

            
775
        let j = serde_json::from_str(r#"{ "n": "!" }"#).unwrap();
776
        let e = serde_json::from_value::<T>(j).unwrap_err();
777
        assert!(
778
            e.to_string()
779
                .contains("character '!' (U+0021) is not allowed"),
780
            "wrong msg {e:?}"
781
        );
782
    }
783

            
784
    #[test]
785
    fn define_key_specifier_with_fields_and_denotator() {
786
        let tp = test_time_period();
787

            
788
        #[derive(Deftly, Debug, PartialEq)]
789
        #[derive_deftly(KeySpecifier)]
790
        #[deftly(prefix = "encabulator")]
791
        #[deftly(role = "marzlevane")]
792
        #[deftly(summary = "test key")]
793
        struct TestSpecifier {
794
            // The remaining fields
795
            kind: String,
796
            base: String,
797
            casing: String,
798
            #[deftly(denotator)]
799
            count: usize,
800
            #[deftly(denotator)]
801
            tp: TimePeriod,
802
        }
803

            
804
        let key_spec = TestSpecifier {
805
            kind: "hydrocoptic".into(),
806
            base: "waneshaft".into(),
807
            casing: "logarithmic".into(),
808
            count: 6,
809
            tp,
810
        };
811

            
812
        check_key_specifier(
813
            &key_spec,
814
            "encabulator/hydrocoptic/waneshaft/logarithmic/marzlevane+6+18519_1440_3600",
815
        );
816

            
817
        let info = TestSpecifierInfoExtractor
818
            .describe(&KeyPath::Arti(key_spec.arti_path().unwrap()))
819
            .unwrap();
820

            
821
        assert_eq!(
822
            format!("{info:#?}"),
823
            r##"
824
KeyPathInfo {
825
    summary: "test key",
826
    role: "marzlevane",
827
    extra_info: {
828
        "base": "waneshaft",
829
        "casing": "logarithmic",
830
        "count": "6",
831
        "kind": "hydrocoptic",
832
        "tp": "#18519 2020-09-14T01:00:00Z..+24:00",
833
    },
834
2
}
835
2
            "##
836
2
            .trim()
837
        );
838
2
    }
839

            
840
    #[test]
841
2
    fn define_key_specifier_no_fields() {
842
        #[derive(Deftly, Debug, PartialEq)]
843
        #[derive_deftly(KeySpecifier)]
844
        #[deftly(prefix = "encabulator")]
845
        #[deftly(role = "marzlevane")]
846
        #[deftly(summary = "test key")]
847
        struct TestSpecifier {}
848

            
849
2
        let key_spec = TestSpecifier {};
850

            
851
2
        check_key_specifier(&key_spec, "encabulator/marzlevane");
852

            
853
2
        assert_eq!(
854
2
            TestSpecifierPattern {}.arti_pattern().unwrap(),
855
2
            KeyPathPattern::Arti("encabulator/marzlevane".into())
856
        );
857
2
    }
858

            
859
    #[test]
860
2
    fn define_key_specifier_with_denotator() {
861
        #[derive(Deftly, Debug, PartialEq)]
862
        #[derive_deftly(KeySpecifier)]
863
        #[deftly(prefix = "encabulator")]
864
        #[deftly(role = "marzlevane")]
865
        #[deftly(summary = "test key")]
866
        struct TestSpecifier {
867
            #[deftly(denotator)]
868
            count: usize,
869
        }
870

            
871
2
        let key_spec = TestSpecifier { count: 6 };
872

            
873
2
        check_key_specifier(&key_spec, "encabulator/marzlevane+6");
874

            
875
2
        assert_eq!(
876
2
            TestSpecifierPattern { count: None }.arti_pattern().unwrap(),
877
2
            KeyPathPattern::Arti("encabulator/marzlevane+*".into())
878
        );
879
2
    }
880

            
881
    #[test]
882
2
    fn define_key_specifier_with_fields() {
883
        #[derive(Deftly, Debug, PartialEq)]
884
        #[derive_deftly(KeySpecifier)]
885
        #[deftly(prefix = "encabulator")]
886
        #[deftly(role = "fan")]
887
        #[deftly(summary = "test key")]
888
        struct TestSpecifier {
889
            casing: String,
890
            /// A doc comment.
891
            bearings: String,
892
        }
893

            
894
2
        let key_spec = TestSpecifier {
895
2
            casing: "logarithmic".into(),
896
2
            bearings: "spurving".into(),
897
2
        };
898

            
899
2
        check_key_specifier(&key_spec, "encabulator/logarithmic/spurving/fan");
900

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

            
911
2
        let ctor_path = CTorPath::HsIdPublicKey {
912
2
            nickname: HsNickname::from_str("foo").unwrap(),
913
2
        };
914

            
915
2
        assert_eq!(
916
2
            TestSpecifier::from_ctor_path(ctor_path).unwrap_err(),
917
2
            CTorPathError::MissingCTorPath("TestSpecifier".into()),
918
        );
919
2
    }
920

            
921
    #[test]
922
2
    fn define_key_specifier_with_multiple_denotators() {
923
        #[derive(Deftly, Debug, PartialEq)]
924
        #[derive_deftly(KeySpecifier)]
925
        #[deftly(prefix = "encabulator")]
926
        #[deftly(role = "fan")]
927
        #[deftly(summary = "test key")]
928
        struct TestSpecifier {
929
            casing: String,
930
            /// A doc comment.
931
            bearings: String,
932

            
933
            #[deftly(denotator)]
934
            count: usize,
935

            
936
            #[deftly(denotator)]
937
            length: usize,
938

            
939
            #[deftly(denotator)]
940
            kind: String,
941
        }
942

            
943
2
        let key_spec = TestSpecifier {
944
2
            casing: "logarithmic".into(),
945
2
            bearings: "spurving".into(),
946
2
            count: 8,
947
2
            length: 2000,
948
2
            kind: "lunar".into(),
949
2
        };
950

            
951
2
        check_key_specifier(
952
2
            &key_spec,
953
2
            "encabulator/logarithmic/spurving/fan+8+2000+lunar",
954
        );
955

            
956
2
        assert_eq!(
957
2
            TestSpecifierPattern {
958
2
                casing: Some("logarithmic".into()),
959
2
                bearings: Some("prefabulating".into()),
960
2
                ..TestSpecifierPattern::new_any()
961
2
            }
962
2
            .arti_pattern()
963
2
            .unwrap(),
964
2
            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan+*+*+*".into())
965
        );
966
2
    }
967

            
968
    #[test]
969
2
    fn define_key_specifier_role_field() {
970
        #[derive(Deftly, Debug, Eq, PartialEq)]
971
        #[derive_deftly(KeySpecifier)]
972
        #[deftly(prefix = "prefix")]
973
        #[deftly(summary = "test key")]
974
        struct TestSpecifier {
975
            #[deftly(role)]
976
            role: String,
977
            i: usize,
978
            #[deftly(denotator)]
979
            den: bool,
980
        }
981

            
982
2
        check_key_specifier(
983
2
            &TestSpecifier {
984
2
                i: 1,
985
2
                role: "role".to_string(),
986
2
                den: true,
987
2
            },
988
2
            "prefix/1/role+true",
989
        );
990
2
    }
991

            
992
    #[test]
993
2
    fn define_key_specifier_ctor_path() {
994
        #[derive(Deftly, Debug, Eq, PartialEq)]
995
        #[derive_deftly(KeySpecifier)]
996
        #[deftly(prefix = "p")]
997
        #[deftly(role = "r")]
998
        #[deftly(ctor_path = HsIdPublicKey)]
999
        #[deftly(summary = "test key")]
        struct TestSpecifier {
            nickname: HsNickname,
        }
2
        let spec = TestSpecifier {
2
            nickname: HsNickname::from_str("42").unwrap(),
2
        };
2
        check_key_specifier(&spec, "p/42/r");
2
        let ctor_path = KeySpecifier::ctor_path(&spec);
2
        assert_eq!(
            ctor_path,
2
            Some(CTorPath::HsIdPublicKey {
2
                nickname: HsNickname::from_str("42").unwrap(),
2
            }),
        );
2
        assert_eq!(
2
            TestSpecifier::from_ctor_path(ctor_path.unwrap()).unwrap(),
            spec,
        );
        /// An .onion address to put for test client CTorPaths.
        const HSID: &str = "yc6v7oeksrbech4ctv53di7rfjuikjagkyfrwu3yclzkfyv5haay6mqd.onion";
2
        let wrong_paths = &[
2
            CTorPath::HsClientDescEncKeypair {
2
                hs_id: HsId::from_str(HSID).unwrap(),
2
            },
2
            CTorPath::HsIdKeypair {
2
                nickname: HsNickname::from_str("42").unwrap(),
2
            },
2
        ];
4
        for path in wrong_paths {
4
            assert_eq!(
4
                TestSpecifier::from_ctor_path(path.clone()).unwrap_err(),
4
                CTorPathError::KeySpecifierMismatch("TestSpecifier".into()),
            );
        }
2
    }
    #[test]
2
    fn define_key_specifier_fixed_path_component() {
        #[derive(Deftly, Debug, Eq, PartialEq)]
        #[derive_deftly(KeySpecifier)]
        #[deftly(prefix = "prefix")]
        #[deftly(role = "role")]
        #[deftly(summary = "test key")]
        struct TestSpecifier {
            x: usize,
            #[deftly(fixed_path_component = "fixed")]
            z: bool,
        }
2
        check_key_specifier(&TestSpecifier { x: 1, z: true }, "prefix/1/fixed/true/role");
2
    }
    #[test]
    #[cfg(feature = "experimental-api")]
2
    fn define_cert_specifier_with_multiple_denotators() {
        #[derive(Deftly, Debug, PartialEq)]
        #[derive_deftly(KeySpecifier)]
        #[deftly(prefix = "encabulator")]
        #[deftly(role = "fan")]
        #[deftly(summary = "test key")]
        struct TestKeySpecifier {
            casing: String,
            bearings: String,
            #[deftly(denotator)]
            count: usize,
        }
        #[derive(Deftly, Debug, PartialEq)]
        #[derive_deftly(CertSpecifier)]
        #[allow(dead_code)]
        struct TestCertSpecifier {
            #[deftly(subject)]
            subject: TestKeySpecifier,
            #[deftly(denotator)]
            length: usize,
            #[deftly(denotator)]
            width: usize,
        }
2
        let cert_pat = TestCertSpecifierPattern::new_any();
2
        assert_eq!(
2
            cert_pat.arti_pattern().unwrap(),
2
            KeyPathPattern::Arti("encabulator/*/*/fan+*@*+*".into())
        );
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
    }
}