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

            
3
use std::collections::BTreeMap;
4
use std::fmt::{self, 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 thiserror::Error;
11
use tor_error::{internal, into_internal, Bug};
12
use tor_hscrypto::pk::{HsId, HsIdParseError, HSID_ONION_SUFFIX};
13
use tor_hscrypto::time::TimePeriod;
14
use tor_persist::hsnickname::HsNickname;
15
use tor_persist::slug::Slug;
16

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

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

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

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

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

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

            
66
3524
        glob_match::glob_match_with_captures(pattern, self.as_ref())
67
7134
            .map(|res| res.into_iter().map(|r| r.into()).collect())
68
3524
    }
69
}
70

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

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

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

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

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

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

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

            
134
/// An error while attempting to extract information about a key given its path
135
///
136
/// For example, from a [`KeyPathInfoExtractor`].
137
///
138
/// See also `crate::keystore::arti::MalformedPathError`,
139
/// which occurs at a lower level.
140
///
141
// Note: Currently, all KeyPathErrors (except Unrecognized and Bug) are only returned from
142
// functions that parse ArtiPaths and/or ArtiPath denotators, so their context contains an
143
// `ArtiPath` rather than a `KeyPath` (i.e. PatternNotMatched, InvalidArtiPath,
144
// InvalidKeyPathComponent value can only happen if we're dealing with an ArtiPath).
145
//
146
// For now this is alright, but we might want to rethink this error enum (for example, a better
147
// idea might be to create an ArtiPathError { path: ArtiPath, kind: ArtiPathErrorKind } error type
148
// and move PatternNotMatched, InvalidArtiPath, InvalidKeyPathComponentValue to the new
149
// ArtiPathErrorKind enum.
150
#[derive(Debug, Clone, thiserror::Error)]
151
#[non_exhaustive]
152
pub enum KeyPathError {
153
    /// The path did not match the expected pattern.
154
    #[error("Path does not match expected pattern")]
155
    PatternNotMatched(ArtiPath),
156

            
157
    /// The path is not recognized.
158
    ///
159
    /// Returned by [`KeyMgr::describe`](crate::KeyMgr::describe) when none of its
160
    /// [`KeyPathInfoExtractor`]s is able to parse the specified [`KeyPath`].
161
    #[error("Unrecognized path: {0}")]
162
    Unrecognized(KeyPath),
163

            
164
    /// Found an invalid [`ArtiPath`], which is syntactically invalid on its face
165
    #[error("ArtiPath {path} is invalid")]
166
    InvalidArtiPath {
167
        /// What was wrong with the value
168
        #[source]
169
        error: ArtiPathSyntaxError,
170
        /// The offending `ArtiPath`.
171
        path: ArtiPath,
172
    },
173

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

            
196
    /// An internal error.
197
    #[error("Internal error")]
198
    Bug(#[from] tor_error::Bug),
199
}
200

            
201
/// Error to be returned by `KeySpecifierComponent::from_slug` implementations
202
///
203
/// Currently this error contains little information,
204
/// but the context and value are provided in
205
/// [`KeyPathError::InvalidKeyPathComponentValue`].
206
#[derive(Error, Clone, Debug)]
207
#[non_exhaustive]
208
pub enum InvalidKeyPathComponentValue {
209
    /// Found an invalid slug.
210
    ///
211
    /// The inner string should be a description of what is wrong with the slug.
212
    /// It should not say that the keystore was corrupted,
213
    /// (keystore corruption errors are reported using higher level
214
    /// [`KeystoreCorruptionError`s](crate::KeystoreCorruptionError)),
215
    /// or where the information came from (the context is encoded in the
216
    /// enclosing [`KeyPathError::InvalidKeyPathComponentValue`] error).
217
    #[error("{0}")]
218
    Slug(String),
219

            
220
    /// An internal error.
221
    ///
222
    /// The [`KeySpecifierComponentViaDisplayFromStr`] trait maps any errors returned by the
223
    /// [`FromStr`] implementation of the implementing type to this variant.
224
    #[error("Internal error")]
225
    Bug(#[from] tor_error::Bug),
226
}
227

            
228
/// Information about a [`KeyPath`].
229
///
230
/// The information is extracted from the [`KeyPath`] itself
231
/// (_not_ from the key data) by a [`KeyPathInfoExtractor`].
232
//
233
// TODO  maybe the getters should be combined with the builder, or something?
234
40
#[derive(Debug, Clone, PartialEq, derive_builder::Builder, amplify::Getters)]
235
pub struct KeyPathInfo {
236
    /// A human-readable summary string describing what the [`KeyPath`] is for.
237
    ///
238
    /// This should *not* recapitulate information in the `extra_info`.
239
    summary: String,
240
    /// The key role, ie its official name in the Tor Protocols.
241
    ///
242
    /// This should usually start with `KS_`.
243
    //
244
    // TODO (#1195): see the comment for #[deftly(role)] in derive.rs
245
    role: String,
246
    /// Additional information, in the form of key-value pairs.
247
    ///
248
    /// This will contain human-readable information that describes the individual
249
    /// components of a KeyPath. For example, for the [`ArtiPath`]
250
    /// `hs/foo/KS_hs_id.expanded_ed25519_private`, the extra information could
251
    /// be `("kind", "service)`, `("nickname", "foo")`, etc.
252
    #[builder(default, setter(custom))]
253
    extra_info: BTreeMap<String, String>,
254
}
255

            
256
impl KeyPathInfo {
257
    /// Start to build a [`KeyPathInfo`]: return a fresh [`KeyPathInfoBuilder`]
258
8
    pub fn builder() -> KeyPathInfoBuilder {
259
8
        KeyPathInfoBuilder::default()
260
8
    }
261
}
262

            
263
impl KeyPathInfoBuilder {
264
    /// Initialize the additional information of this builder with the specified values.
265
    ///
266
    /// Erases the preexisting `extra_info`.
267
6
    pub fn set_all_extra_info(
268
6
        &mut self,
269
6
        all_extra_info: impl Iterator<Item = (String, String)>,
270
6
    ) -> &mut Self {
271
6
        self.extra_info = Some(all_extra_info.collect());
272
6
        self
273
6
    }
274

            
275
    /// Append the specified key-value pair to the `extra_info`.
276
    ///
277
    /// The preexisting `extra_info` is preserved.
278
22
    pub fn extra_info(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
279
22
        let extra_info = self.extra_info.get_or_insert(Default::default());
280
22
        extra_info.insert(key.into(), value.into());
281
22
        self
282
22
    }
283
}
284

            
285
/// A trait for extracting info out of a [`KeyPath`]s.
286
///
287
/// This trait is used by [`KeyMgr::describe`](crate::KeyMgr::describe)
288
/// to extract information out of [`KeyPath`]s.
289
pub trait KeyPathInfoExtractor: Send + Sync {
290
    /// Describe the specified `path`.
291
    fn describe(&self, path: &KeyPath) -> StdResult<KeyPathInfo, KeyPathError>;
292
}
293

            
294
/// Register a [`KeyPathInfoExtractor`] for use with [`KeyMgr`](crate::KeyMgr).
295
#[macro_export]
296
macro_rules! register_key_info_extractor {
297
    ($kv:expr) => {{
298
        $crate::inventory::submit!(&$kv as &dyn $crate::KeyPathInfoExtractor);
299
    }};
300
}
301

            
302
/// A pattern that can be used to match [`ArtiPath`]s or [`CTorPath`]s.
303
///
304
/// Create a new `KeyPathPattern`.
305
///
306
/// ## Syntax
307
///
308
/// NOTE: this table is copied verbatim from the [`glob-match`] docs.
309
///
310
/// | Syntax  | Meaning                                                                                                                                                                                             |
311
/// | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
312
/// | `?`     | Matches any single character.                                                                                                                                                                       |
313
/// | `*`     | Matches zero or more characters, except for path separators (e.g. `/`).                                                                                                                             |
314
/// | `**`    | 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).                                                  |
315
/// | `[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. |
316
/// | `{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.                                     |
317
/// | `!`     | When at the start of the glob, this negates the result. Multiple `!` characters negate the glob multiple times.                                                                                     |
318
/// | `\`     | A backslash character may be used to escape any of the above special characters.                                                                                                                    |
319
///
320
/// [`glob-match`]: https://crates.io/crates/glob-match
321
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
322
#[non_exhaustive]
323
pub enum KeyPathPattern {
324
    /// A pattern for matching [`ArtiPath`]s.
325
    Arti(String),
326
    /// A pattern for matching [`CTorPath`]s.
327
    CTor(CTorPath),
328
}
329

            
330
/// The path of a key in the C Tor key store.
331
#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display)] //
332
#[non_exhaustive]
333
pub enum CTorPath {
334
    /// A client descriptor encryption key, to be looked up in ClientOnionAuthDir.
335
    ///
336
    /// Represents an entry in C Tor's `ClientOnionAuthDir`.
337
    ///
338
    /// We can't statically know exactly *which* entry has the key for this `HsId`
339
    /// (we'd need to read and parse each file from `ClientOnionAuthDir` to find out).
340
    #[display("ClientHsDescEncKey({})", _0)]
341
    ClientHsDescEncKey(HsId),
342
    /// A service key path.
343
    #[display("{path}")]
344
    Service {
345
        /// The nickname of the service,
346
        nickname: HsNickname,
347
        /// The relative path of this key.
348
        path: CTorServicePath,
349
    },
350
}
351

            
352
/// The relative path in a C Tor key store.
353
#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display)] //
354
#[non_exhaustive]
355
pub enum CTorServicePath {
356
    /// C Tor's `HiddenServiceDirectory/hs_ed25519_public_key`.
357
    #[display("hs_ed25519_public_key")]
358
    PublicKey,
359
    /// C Tor's `HiddenServiceDirectory/hs_ed25519_secret_key`.
360
    #[display("hs_ed25519_secret_key")]
361
    PrivateKey,
362
}
363

            
364
impl CTorPath {
365
    /// Create a CTorPath that represents a service key.
366
    pub fn service(nickname: HsNickname, path: CTorServicePath) -> Self {
367
        Self::Service { nickname, path }
368
    }
369

            
370
    /// Create a CTorPath that represents a client authorization key.
371
    pub fn client(hsid: HsId) -> Self {
372
        Self::ClientHsDescEncKey(hsid)
373
    }
374
}
375

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

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

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

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

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

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

            
440
impl KeySpecifier for ArtiPath {
441
680
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
442
680
        Ok(self.clone())
443
680
    }
444

            
445
    fn ctor_path(&self) -> Option<CTorPath> {
446
        None
447
    }
448

            
449
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
450
        None
451
    }
452
}
453

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

            
459
4
    fn ctor_path(&self) -> Option<CTorPath> {
460
4
        Some(self.clone())
461
4
    }
462

            
463
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
464
        None
465
    }
466
}
467

            
468
impl KeySpecifier for KeyPath {
469
652
    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
470
652
        match self {
471
652
            KeyPath::Arti(p) => p.arti_path(),
472
            KeyPath::CTor(p) => p.arti_path(),
473
        }
474
652
    }
475

            
476
    fn ctor_path(&self) -> Option<CTorPath> {
477
        match self {
478
            KeyPath::Arti(p) => p.ctor_path(),
479
            KeyPath::CTor(p) => p.ctor_path(),
480
        }
481
    }
482

            
483
    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
484
        None
485
    }
486
}
487

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

            
499
84
    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
500
84
    where
501
84
        Self: Sized,
502
84
    {
503
        use itertools::Itertools;
504

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

            
513
80
        let length = len.parse().map_err(|_| err_ctx("invalid length"))?;
514
80
        let interval_num = interval
515
80
            .parse()
516
80
            .map_err(|_| err_ctx("invalid interval_num"))?;
517
80
        let offset_in_sec = offset
518
80
            .parse()
519
80
            .map_err(|_| err_ctx("invalid offset_in_sec"))?;
520

            
521
80
        Ok(TimePeriod::from_parts(length, interval_num, offset_in_sec))
522
84
    }
523

            
524
4
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
525
4
        Display::fmt(&self, f)
526
4
    }
527
}
528

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

            
556
impl KeySpecifierComponentViaDisplayFromStr for HsNickname {}
557

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

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

            
587
2
        onion
588
2
            .parse()
589
2
            .map_err(|e: HsIdParseError| InvalidKeyPathComponentValue::Slug(e.to_string()))
590
2
    }
591

            
592
    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
593
        Display::fmt(self, f)
594
    }
595
}
596

            
597
/// Wrapper for `KeySpecifierComponent` that `Displays` via `fmt_pretty`
598
struct KeySpecifierComponentPrettyHelper<'c>(&'c dyn KeySpecifierComponent);
599

            
600
impl Display for KeySpecifierComponentPrettyHelper<'_> {
601
12
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
602
12
        KeySpecifierComponent::fmt_pretty(self.0, f)
603
12
    }
604
}
605

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

            
633
#[cfg(test)]
634
mod test {
635
    // @@ begin test lint list maintained by maint/add_warning @@
636
    #![allow(clippy::bool_assert_comparison)]
637
    #![allow(clippy::clone_on_copy)]
638
    #![allow(clippy::dbg_macro)]
639
    #![allow(clippy::mixed_attributes_style)]
640
    #![allow(clippy::print_stderr)]
641
    #![allow(clippy::print_stdout)]
642
    #![allow(clippy::single_char_pattern)]
643
    #![allow(clippy::unwrap_used)]
644
    #![allow(clippy::unchecked_duration_subtraction)]
645
    #![allow(clippy::useless_vec)]
646
    #![allow(clippy::needless_pass_by_value)]
647
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
648
    use super::*;
649

            
650
    use crate::test_utils::check_key_specifier;
651
    use derive_deftly::Deftly;
652
    use humantime::parse_rfc3339;
653
    use itertools::Itertools;
654
    use serde::{Deserialize, Serialize};
655
    use std::fmt::Debug;
656
    use std::time::Duration;
657

            
658
    impl KeySpecifierComponentViaDisplayFromStr for usize {}
659
    impl KeySpecifierComponentViaDisplayFromStr for String {}
660

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

            
665
    fn test_time_period() -> TimePeriod {
666
        TimePeriod::new(
667
            Duration::from_secs(86400),
668
            parse_rfc3339("2020-09-15T00:00:00Z").unwrap(),
669
            Duration::from_secs(3600),
670
        )
671
        .unwrap()
672
    }
673

            
674
    #[test]
675
    fn pretty_time_period() {
676
        let tp = test_time_period();
677
        assert_eq!(
678
            KeySpecifierComponentPrettyHelper(&tp).to_string(),
679
            "#18519 2020-09-14T01:00:00Z..+24:00",
680
        );
681
    }
682

            
683
    #[test]
684
    fn serde() {
685
        // TODO: clone-and-hack with tor_hsservice::::nickname::test::serde
686
        // perhaps there should be some utility in tor-basic-utils for testing
687
        // validated string newtypes, or something
688
        #[derive(Serialize, Deserialize, Debug)]
689
        struct T {
690
            n: Slug,
691
        }
692
        let j = serde_json::from_str(r#"{ "n": "x" }"#).unwrap();
693
        let t: T = serde_json::from_value(j).unwrap();
694
        assert_eq!(&t.n.to_string(), "x");
695

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

            
698
        let j = serde_json::from_str(r#"{ "n": "!" }"#).unwrap();
699
        let e = serde_json::from_value::<T>(j).unwrap_err();
700
        assert!(
701
            e.to_string()
702
                .contains("character '!' (U+0021) is not allowed"),
703
            "wrong msg {e:?}"
704
        );
705
    }
706

            
707
    #[test]
708
    fn define_key_specifier_with_fields_and_denotator() {
709
        let tp = test_time_period();
710

            
711
        #[derive(Deftly, Debug, PartialEq)]
712
        #[derive_deftly(KeySpecifier)]
713
        #[deftly(prefix = "encabulator")]
714
        #[deftly(role = "marzlevane")]
715
        #[deftly(summary = "test key")]
716
        struct TestSpecifier {
717
            // The remaining fields
718
            kind: String,
719
            base: String,
720
            casing: String,
721
            #[deftly(denotator)]
722
            count: usize,
723
            #[deftly(denotator)]
724
            tp: TimePeriod,
725
        }
726

            
727
        let key_spec = TestSpecifier {
728
            kind: "hydrocoptic".into(),
729
            base: "waneshaft".into(),
730
            casing: "logarithmic".into(),
731
            count: 6,
732
            tp,
733
        };
734

            
735
        check_key_specifier(
736
            &key_spec,
737
            "encabulator/hydrocoptic/waneshaft/logarithmic/marzlevane+6+18519_1440_3600",
738
        );
739

            
740
        let info = TestSpecifierInfoExtractor
741
            .describe(&KeyPath::Arti(key_spec.arti_path().unwrap()))
742
            .unwrap();
743

            
744
        assert_eq!(
745
            format!("{info:#?}"),
746
            r##"
747
KeyPathInfo {
748
    summary: "test key",
749
    role: "marzlevane",
750
    extra_info: {
751
        "base": "waneshaft",
752
        "casing": "logarithmic",
753
        "count": "6",
754
        "kind": "hydrocoptic",
755
        "tp": "#18519 2020-09-14T01:00:00Z..+24:00",
756
    },
757
2
}
758
2
            "##
759
2
            .trim()
760
2
        );
761
2
    }
762

            
763
    #[test]
764
2
    fn define_key_specifier_no_fields() {
765
        #[derive(Deftly, Debug, PartialEq)]
766
        #[derive_deftly(KeySpecifier)]
767
        #[deftly(prefix = "encabulator")]
768
        #[deftly(role = "marzlevane")]
769
        #[deftly(summary = "test key")]
770
        struct TestSpecifier {}
771

            
772
2
        let key_spec = TestSpecifier {};
773
2

            
774
2
        check_key_specifier(&key_spec, "encabulator/marzlevane");
775
2

            
776
2
        assert_eq!(
777
2
            TestSpecifierPattern {}.arti_pattern().unwrap(),
778
2
            KeyPathPattern::Arti("encabulator/marzlevane".into())
779
2
        );
780
2
    }
781

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

            
794
2
        let key_spec = TestSpecifier { count: 6 };
795
2

            
796
2
        check_key_specifier(&key_spec, "encabulator/marzlevane+6");
797
2

            
798
2
        assert_eq!(
799
2
            TestSpecifierPattern { count: None }.arti_pattern().unwrap(),
800
2
            KeyPathPattern::Arti("encabulator/marzlevane+*".into())
801
2
        );
802
2
    }
803

            
804
    #[test]
805
2
    fn define_key_specifier_with_fields() {
806
        #[derive(Deftly, Debug, PartialEq)]
807
        #[derive_deftly(KeySpecifier)]
808
        #[deftly(prefix = "encabulator")]
809
        #[deftly(role = "fan")]
810
        #[deftly(summary = "test key")]
811
        struct TestSpecifier {
812
            casing: String,
813
            /// A doc comment.
814
            bearings: String,
815
        }
816

            
817
2
        let key_spec = TestSpecifier {
818
2
            casing: "logarithmic".into(),
819
2
            bearings: "spurving".into(),
820
2
        };
821
2

            
822
2
        check_key_specifier(&key_spec, "encabulator/logarithmic/spurving/fan");
823
2

            
824
2
        assert_eq!(
825
2
            TestSpecifierPattern {
826
2
                casing: Some("logarithmic".into()),
827
2
                bearings: Some("prefabulating".into()),
828
2
            }
829
2
            .arti_pattern()
830
2
            .unwrap(),
831
2
            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan".into())
832
2
        );
833

            
834
2
        assert_eq!(key_spec.ctor_path(), None);
835
2
    }
836

            
837
    #[test]
838
2
    fn define_key_specifier_with_multiple_denotators() {
839
        #[derive(Deftly, Debug, PartialEq)]
840
        #[derive_deftly(KeySpecifier)]
841
        #[deftly(prefix = "encabulator")]
842
        #[deftly(role = "fan")]
843
        #[deftly(summary = "test key")]
844
        struct TestSpecifier {
845
            casing: String,
846
            /// A doc comment.
847
            bearings: String,
848

            
849
            #[deftly(denotator)]
850
            count: usize,
851

            
852
            #[deftly(denotator)]
853
            length: usize,
854

            
855
            #[deftly(denotator)]
856
            kind: String,
857
        }
858

            
859
2
        let key_spec = TestSpecifier {
860
2
            casing: "logarithmic".into(),
861
2
            bearings: "spurving".into(),
862
2
            count: 8,
863
2
            length: 2000,
864
2
            kind: "lunar".into(),
865
2
        };
866
2

            
867
2
        check_key_specifier(
868
2
            &key_spec,
869
2
            "encabulator/logarithmic/spurving/fan+8+2000+lunar",
870
2
        );
871
2

            
872
2
        assert_eq!(
873
2
            TestSpecifierPattern {
874
2
                casing: Some("logarithmic".into()),
875
2
                bearings: Some("prefabulating".into()),
876
2
                ..TestSpecifierPattern::new_any()
877
2
            }
878
2
            .arti_pattern()
879
2
            .unwrap(),
880
2
            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan+*+*+*".into())
881
2
        );
882
2
    }
883

            
884
    #[test]
885
2
    fn define_key_specifier_role_field() {
886
        #[derive(Deftly, Debug, Eq, PartialEq)]
887
        #[derive_deftly(KeySpecifier)]
888
        #[deftly(prefix = "prefix")]
889
        #[deftly(summary = "test key")]
890
        struct TestSpecifier {
891
            #[deftly(role)]
892
            role: String,
893
            i: usize,
894
            #[deftly(denotator)]
895
            den: bool,
896
        }
897

            
898
2
        check_key_specifier(
899
2
            &TestSpecifier {
900
2
                i: 1,
901
2
                role: "role".to_string(),
902
2
                den: true,
903
2
            },
904
2
            "prefix/1/role+true",
905
2
        );
906
2
    }
907

            
908
    #[test]
909
2
    fn define_key_specifier_ctor_path() {
910
        #[derive(Deftly, Debug, Eq, PartialEq)]
911
        #[derive_deftly(KeySpecifier)]
912
        #[deftly(prefix = "p")]
913
        #[deftly(role = "r")]
914
        #[deftly(ctor_path = "Self::ctp")]
915
        #[deftly(summary = "test key")]
916
        struct TestSpecifier {
917
            i: usize,
918
        }
919

            
920
        impl TestSpecifier {
921
2
            fn ctp(&self) -> CTorPath {
922
2
                CTorPath::Service {
923
2
                    nickname: HsNickname::from_str("allium-cepa").unwrap(),
924
2
                    path: CTorServicePath::PublicKey,
925
2
                }
926
2
            }
927
        }
928

            
929
2
        let spec = TestSpecifier { i: 42 };
930
2

            
931
2
        check_key_specifier(&spec, "p/42/r");
932
2

            
933
2
        assert_eq!(
934
2
            spec.ctor_path(),
935
2
            Some(CTorPath::Service {
936
2
                nickname: HsNickname::from_str("allium-cepa").unwrap(),
937
2
                path: CTorServicePath::PublicKey,
938
2
            }),
939
2
        );
940
2
    }
941

            
942
    #[test]
943
2
    fn define_key_specifier_fixed_path_component() {
944
        #[derive(Deftly, Debug, Eq, PartialEq)]
945
        #[derive_deftly(KeySpecifier)]
946
        #[deftly(prefix = "prefix")]
947
        #[deftly(role = "role")]
948
        #[deftly(summary = "test key")]
949
        struct TestSpecifier {
950
            x: usize,
951
            #[deftly(fixed_path_component = "fixed")]
952
            z: bool,
953
        }
954

            
955
2
        check_key_specifier(&TestSpecifier { x: 1, z: true }, "prefix/1/fixed/true/role");
956
2
    }
957

            
958
    #[test]
959
2
    fn encode_time_period() {
960
2
        let period = TimePeriod::from_parts(1, 2, 3);
961
2
        let encoded_period = period.to_slug().unwrap();
962
2

            
963
2
        assert_eq!(encoded_period.to_string(), "2_1_3");
964
2
        assert_eq!(period, TimePeriod::from_slug(&encoded_period).unwrap());
965

            
966
2
        assert!(TimePeriod::from_slug(&Slug::new("invalid_tp".to_string()).unwrap()).is_err());
967
2
        assert!(TimePeriod::from_slug(&Slug::new("2_1_3_4".to_string()).unwrap()).is_err());
968
2
    }
969

            
970
    #[test]
971
2
    fn encode_hsid() {
972
2
        let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
973
2
        let onion = format!("{b32}.onion");
974
2
        let hsid = HsId::from_str(&onion).unwrap();
975
2
        let hsid_slug = hsid.to_slug().unwrap();
976
2

            
977
2
        assert_eq!(hsid_slug.to_string(), b32);
978
2
        assert_eq!(hsid, HsId::from_slug(&hsid_slug).unwrap());
979
2
    }
980

            
981
    #[test]
982
2
    fn key_info_builder() {
983
        // A helper to check the extra_info of a `KeyPathInfo`
984
        macro_rules! assert_extra_info_eq {
985
            ($key_info:expr, [$(($k:expr, $v:expr),)*]) => {{
986
                assert_eq!(
987
                    $key_info.extra_info.into_iter().collect_vec(),
988
                    vec![
989
                        $(($k.into(), $v.into()),)*
990
                    ]
991
                );
992
            }}
993
        }
994
2
        let extra_info = vec![("nickname".into(), "bar".into())];
995
2

            
996
2
        let key_info = KeyPathInfo::builder()
997
2
            .summary("test summary".into())
998
2
            .role("KS_vote".to_string())
999
2
            .set_all_extra_info(extra_info.clone().into_iter())
2
            .build()
2
            .unwrap();
2

            
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

            
2
        assert_extra_info_eq!(
2
            key_info,
2
            [
2
                ("nickname", "bar"),
2
                ("time period", "100"),
2
                ("type", "service"),
2
            ]
2
        );
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

            
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

            
2
        assert_extra_info_eq!(key_info, [("time period", "100"), ("type", "service"),]);
2
    }
}