Skip to main content

tor_keymgr/
key_specifier.rs

1//! The [`KeySpecifier`] trait and its implementations.
2
3use std::collections::BTreeMap;
4use std::fmt::{self, Debug, Display};
5use std::ops::Range;
6use std::result::Result as StdResult;
7use std::str::FromStr;
8
9use derive_more::From;
10use safelog::DisplayRedacted as _;
11use thiserror::Error;
12use tor_error::{Bug, internal, into_internal};
13use tor_hscrypto::pk::{HSID_ONION_SUFFIX, HsId, HsIdParseError};
14use tor_hscrypto::time::TimePeriod;
15use tor_persist::hsnickname::HsNickname;
16use tor_persist::slug::Slug;
17
18use crate::{ArtiPath, ArtiPathSyntaxError};
19
20// #[doc(hidden)] applied at crate toplevel
21#[macro_use]
22pub mod derive;
23
24/// The identifier of a key.
25#[derive(Clone, Debug, PartialEq, Eq, Hash, From, derive_more::Display)]
26#[non_exhaustive]
27pub 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)]
36pub struct ArtiPathRange(pub(crate) Range<usize>);
37
38impl 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    pub fn matches(&self, pat: &KeyPathPattern) -> Option<Vec<ArtiPathRange>> {
60        use KeyPathPattern::*;
61
62        let pattern: &str = match pat {
63            Arti(pat) => pat.as_ref(),
64            _ => return None,
65        };
66
67        glob_match::glob_match_with_captures(pattern, self.as_ref())
68            .map(|res| res.into_iter().map(|r| r.into()).collect())
69    }
70}
71
72impl 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    pub fn matches(&self, pat: &KeyPathPattern) -> bool {
90        use KeyPathPattern::*;
91
92        match (self, pat) {
93            (KeyPath::Arti(p), Arti(_)) => p.matches(pat).is_some(),
94            (KeyPath::CTor(p), CTor(pat)) if p == pat => true,
95            _ => false,
96        }
97    }
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    pub fn ctor(&self) -> Option<&CTorPath> {
111        match self {
112            KeyPath::Arti(_) => None,
113            KeyPath::CTor(ctor) => Some(ctor),
114        }
115    }
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`.
124pub 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]
143pub 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]
170pub 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]
208pub 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]
226pub 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)]
253pub 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
274impl KeyPathInfo {
275    /// Start to build a [`KeyPathInfo`]: return a fresh [`KeyPathInfoBuilder`]
276    pub fn builder() -> KeyPathInfoBuilder {
277        KeyPathInfoBuilder::default()
278    }
279}
280
281impl KeyPathInfoBuilder {
282    /// Initialize the additional information of this builder with the specified values.
283    ///
284    /// Erases the preexisting `extra_info`.
285    pub fn set_all_extra_info(
286        &mut self,
287        all_extra_info: impl Iterator<Item = (String, String)>,
288    ) -> &mut Self {
289        self.extra_info = Some(all_extra_info.collect());
290        self
291    }
292
293    /// Append the specified key-value pair to the `extra_info`.
294    ///
295    /// The preexisting `extra_info` is preserved.
296    pub fn extra_info(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
297        let extra_info = self.extra_info.get_or_insert(Default::default());
298        extra_info.insert(key.into(), value.into());
299        self
300    }
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.
307pub 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]
314macro_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]
341pub 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]
351pub 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.
382pub 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.
411pub 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]
430pub 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
443impl KeySpecifier for ArtiPath {
444    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
445        Ok(self.clone())
446    }
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
457impl KeySpecifier for CTorPath {
458    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
459        Err(ArtiPathUnavailableError::ArtiPathUnavailable)
460    }
461
462    fn ctor_path(&self) -> Option<CTorPath> {
463        Some(self.clone())
464    }
465
466    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
467        None
468    }
469}
470
471impl KeySpecifier for KeyPath {
472    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
473        match self {
474            KeyPath::Arti(p) => p.arti_path(),
475            KeyPath::CTor(p) => p.arti_path(),
476        }
477    }
478
479    fn ctor_path(&self) -> Option<CTorPath> {
480        match self {
481            KeyPath::Arti(p) => p.ctor_path(),
482            KeyPath::CTor(p) => p.ctor_path(),
483        }
484    }
485
486    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
487        None
488    }
489}
490
491impl KeySpecifierComponent for TimePeriod {
492    fn to_slug(&self) -> Result<Slug, Bug> {
493        Slug::new(format!(
494            "{}_{}_{}",
495            self.interval_num(),
496            self.length(),
497            self.epoch_offset_in_sec()
498        ))
499        .map_err(into_internal!("TP formatting went wrong"))
500    }
501
502    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
503    where
504        Self: Sized,
505    {
506        use itertools::Itertools;
507
508        let s = s.to_string();
509        #[allow(clippy::redundant_closure)] // the closure makes things slightly more readable
510        let err_ctx = |e: &str| InvalidKeyPathComponentValue::Slug(e.to_string());
511        let (interval, len, offset) = s
512            .split('_')
513            .collect_tuple()
514            .ok_or_else(|| err_ctx("invalid number of subcomponents"))?;
515
516        let length = len.parse().map_err(|_| err_ctx("invalid length"))?;
517        let interval_num = interval
518            .parse()
519            .map_err(|_| err_ctx("invalid interval_num"))?;
520        let offset_in_sec = offset
521            .parse()
522            .map_err(|_| err_ctx("invalid offset_in_sec"))?;
523
524        Ok(TimePeriod::from_parts(length, interval_num, offset_in_sec))
525    }
526
527    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
528        Display::fmt(&self, f)
529    }
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`].
539pub trait KeySpecifierComponentViaDisplayFromStr: Display + FromStr {}
540impl<T: KeySpecifierComponentViaDisplayFromStr> KeySpecifierComponent for T {
541    fn to_slug(&self) -> Result<Slug, Bug> {
542        self.to_string()
543            .try_into()
544            .map_err(into_internal!("Display generated bad Slug"))
545    }
546    fn from_slug(s: &Slug) -> Result<Self, InvalidKeyPathComponentValue>
547    where
548        Self: Sized,
549    {
550        s.as_str()
551            .parse()
552            .map_err(|_| internal!("slug cannot be parsed as component").into())
553    }
554    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
555        Display::fmt(self, f)
556    }
557}
558
559impl KeySpecifierComponentViaDisplayFromStr for HsNickname {}
560
561impl KeySpecifierComponent for HsId {
562    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        let hsid = self.display_unredacted().to_string();
567        let hsid_slug = hsid
568            .strip_suffix(HSID_ONION_SUFFIX)
569            .ok_or_else(|| internal!("HsId Display impl missing .onion suffix?!"))?;
570        hsid_slug
571            .to_owned()
572            .try_into()
573            .map_err(into_internal!("Display generated bad Slug"))
574    }
575
576    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
577    where
578        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        let onion = format!("{}{HSID_ONION_SUFFIX}", s.as_str());
589
590        onion
591            .parse()
592            .map_err(|e: HsIdParseError| InvalidKeyPathComponentValue::Slug(e.to_string()))
593    }
594
595    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
596        Display::fmt(&self.display_redacted(), f)
597    }
598}
599
600/// Wrapper for `KeySpecifierComponent` that `Displays` via `fmt_pretty`
601struct KeySpecifierComponentPrettyHelper<'c>(&'c dyn KeySpecifierComponent);
602
603impl Display for KeySpecifierComponentPrettyHelper<'_> {
604    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
605        KeySpecifierComponent::fmt_pretty(self.0, f)
606    }
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)
614pub 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`].
640pub 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)]
654mod 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##"
767KeyPathInfo {
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}
778            "##
779            .trim()
780        );
781    }
782
783    #[test]
784    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        let key_spec = TestSpecifier {};
793
794        check_key_specifier(&key_spec, "encabulator/marzlevane");
795
796        assert_eq!(
797            TestSpecifierPattern {}.arti_pattern().unwrap(),
798            KeyPathPattern::Arti("encabulator/marzlevane".into())
799        );
800    }
801
802    #[test]
803    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        let key_spec = TestSpecifier { count: 6 };
815
816        check_key_specifier(&key_spec, "encabulator/marzlevane+6");
817
818        assert_eq!(
819            TestSpecifierPattern { count: None }.arti_pattern().unwrap(),
820            KeyPathPattern::Arti("encabulator/marzlevane+*".into())
821        );
822    }
823
824    #[test]
825    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        let key_spec = TestSpecifier {
838            casing: "logarithmic".into(),
839            bearings: "spurving".into(),
840        };
841
842        check_key_specifier(&key_spec, "encabulator/logarithmic/spurving/fan");
843
844        assert_eq!(
845            TestSpecifierPattern {
846                casing: Some("logarithmic".into()),
847                bearings: Some("prefabulating".into()),
848            }
849            .arti_pattern()
850            .unwrap(),
851            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan".into())
852        );
853
854        let ctor_path = CTorPath::HsIdPublicKey {
855            nickname: HsNickname::from_str("foo").unwrap(),
856        };
857
858        assert_eq!(
859            TestSpecifier::from_ctor_path(ctor_path).unwrap_err(),
860            CTorPathError::MissingCTorPath("TestSpecifier".into()),
861        );
862    }
863
864    #[test]
865    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        let key_spec = TestSpecifier {
887            casing: "logarithmic".into(),
888            bearings: "spurving".into(),
889            count: 8,
890            length: 2000,
891            kind: "lunar".into(),
892        };
893
894        check_key_specifier(
895            &key_spec,
896            "encabulator/logarithmic/spurving/fan+8+2000+lunar",
897        );
898
899        assert_eq!(
900            TestSpecifierPattern {
901                casing: Some("logarithmic".into()),
902                bearings: Some("prefabulating".into()),
903                ..TestSpecifierPattern::new_any()
904            }
905            .arti_pattern()
906            .unwrap(),
907            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan+*+*+*".into())
908        );
909    }
910
911    #[test]
912    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        check_key_specifier(
926            &TestSpecifier {
927                i: 1,
928                role: "role".to_string(),
929                den: true,
930            },
931            "prefix/1/role+true",
932        );
933    }
934
935    #[test]
936    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        let spec = TestSpecifier {
948            nickname: HsNickname::from_str("42").unwrap(),
949        };
950
951        check_key_specifier(&spec, "p/42/r");
952
953        let ctor_path = KeySpecifier::ctor_path(&spec);
954
955        assert_eq!(
956            ctor_path,
957            Some(CTorPath::HsIdPublicKey {
958                nickname: HsNickname::from_str("42").unwrap(),
959            }),
960        );
961
962        assert_eq!(
963            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        let wrong_paths = &[
970            CTorPath::HsClientDescEncKeypair {
971                hs_id: HsId::from_str(HSID).unwrap(),
972            },
973            CTorPath::HsIdKeypair {
974                nickname: HsNickname::from_str("42").unwrap(),
975            },
976        ];
977
978        for path in wrong_paths {
979            assert_eq!(
980                TestSpecifier::from_ctor_path(path.clone()).unwrap_err(),
981                CTorPathError::KeySpecifierMismatch("TestSpecifier".into()),
982            );
983        }
984    }
985
986    #[test]
987    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        check_key_specifier(&TestSpecifier { x: 1, z: true }, "prefix/1/fixed/true/role");
1000    }
1001
1002    #[test]
1003    fn encode_time_period() {
1004        let period = TimePeriod::from_parts(1, 2, 3);
1005        let encoded_period = period.to_slug().unwrap();
1006
1007        assert_eq!(encoded_period.to_string(), "2_1_3");
1008        assert_eq!(period, TimePeriod::from_slug(&encoded_period).unwrap());
1009
1010        assert!(TimePeriod::from_slug(&Slug::new("invalid_tp".to_string()).unwrap()).is_err());
1011        assert!(TimePeriod::from_slug(&Slug::new("2_1_3_4".to_string()).unwrap()).is_err());
1012    }
1013
1014    #[test]
1015    fn encode_hsid() {
1016        let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
1017        let onion = format!("{b32}.onion");
1018        let hsid = HsId::from_str(&onion).unwrap();
1019        let hsid_slug = hsid.to_slug().unwrap();
1020
1021        assert_eq!(hsid_slug.to_string(), b32);
1022        assert_eq!(hsid, HsId::from_slug(&hsid_slug).unwrap());
1023    }
1024
1025    #[test]
1026    fn key_info_builder() {
1027        // A helper to check the extra_info of a `KeyPathInfo`
1028        macro_rules! assert_extra_info_eq {
1029            ($key_info:expr, [$(($k:expr, $v:expr),)*]) => {{
1030                assert_eq!(
1031                    $key_info.extra_info.into_iter().collect_vec(),
1032                    vec![
1033                        $(($k.into(), $v.into()),)*
1034                    ]
1035                );
1036            }}
1037        }
1038        let extra_info = vec![("nickname".into(), "bar".into())];
1039
1040        let key_info = KeyPathInfo::builder()
1041            .summary("test summary".into())
1042            .role("KS_vote".to_string())
1043            .set_all_extra_info(extra_info.clone().into_iter())
1044            .build()
1045            .unwrap();
1046
1047        assert_eq!(key_info.extra_info.into_iter().collect_vec(), extra_info);
1048
1049        let key_info = KeyPathInfo::builder()
1050            .summary("test summary".into())
1051            .role("KS_vote".to_string())
1052            .set_all_extra_info(extra_info.clone().into_iter())
1053            .extra_info("type", "service")
1054            .extra_info("time period", "100")
1055            .build()
1056            .unwrap();
1057
1058        assert_extra_info_eq!(
1059            key_info,
1060            [
1061                ("nickname", "bar"),
1062                ("time period", "100"),
1063                ("type", "service"),
1064            ]
1065        );
1066
1067        let key_info = KeyPathInfo::builder()
1068            .summary("test summary".into())
1069            .role("KS_vote".to_string())
1070            .extra_info("type", "service")
1071            .extra_info("time period", "100")
1072            .set_all_extra_info(extra_info.clone().into_iter())
1073            .build()
1074            .unwrap();
1075
1076        assert_extra_info_eq!(key_info, [("nickname", "bar"),]);
1077
1078        let key_info = KeyPathInfo::builder()
1079            .summary("test summary".into())
1080            .role("KS_vote".to_string())
1081            .extra_info("type", "service")
1082            .extra_info("time period", "100")
1083            .build()
1084            .unwrap();
1085
1086        assert_extra_info_eq!(key_info, [("time period", "100"), ("type", "service"),]);
1087    }
1088}