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/// 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")]
141pub 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]
171pub 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]
198pub 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]
236pub 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]
254pub 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)]
281pub 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
302impl KeyPathInfo {
303    /// Start to build a [`KeyPathInfo`]: return a fresh [`KeyPathInfoBuilder`]
304    pub fn builder() -> KeyPathInfoBuilder {
305        KeyPathInfoBuilder::default()
306    }
307}
308
309impl KeyPathInfoBuilder {
310    /// Initialize the additional information of this builder with the specified values.
311    ///
312    /// Erases the preexisting `extra_info`.
313    pub fn set_all_extra_info(
314        &mut self,
315        all_extra_info: impl Iterator<Item = (String, String)>,
316    ) -> &mut Self {
317        self.extra_info = Some(all_extra_info.collect());
318        self
319    }
320
321    /// Append the specified key-value pair to the `extra_info`.
322    ///
323    /// The preexisting `extra_info` is preserved.
324    pub fn extra_info(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
325        let extra_info = self.extra_info.get_or_insert(Default::default());
326        extra_info.insert(key.into(), value.into());
327        self
328    }
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.
335pub 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]
342macro_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]
369pub 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]
379pub 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.
410pub 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.
439pub 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]
458pub 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
471impl KeySpecifier for ArtiPath {
472    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
473        Ok(self.clone())
474    }
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
485impl KeySpecifier for CTorPath {
486    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
487        Err(ArtiPathUnavailableError::ArtiPathUnavailable)
488    }
489
490    fn ctor_path(&self) -> Option<CTorPath> {
491        Some(self.clone())
492    }
493
494    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
495        None
496    }
497}
498
499impl KeySpecifier for KeyPath {
500    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
501        match self {
502            KeyPath::Arti(p) => p.arti_path(),
503            KeyPath::CTor(p) => p.arti_path(),
504        }
505    }
506
507    fn ctor_path(&self) -> Option<CTorPath> {
508        match self {
509            KeyPath::Arti(p) => p.ctor_path(),
510            KeyPath::CTor(p) => p.ctor_path(),
511        }
512    }
513
514    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
515        None
516    }
517}
518
519impl KeySpecifierComponent for TimePeriod {
520    fn to_slug(&self) -> Result<Slug, Bug> {
521        Slug::new(format!(
522            "{}_{}_{}",
523            self.interval_num(),
524            self.length(),
525            self.epoch_offset_in_sec()
526        ))
527        .map_err(into_internal!("TP formatting went wrong"))
528    }
529
530    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
531    where
532        Self: Sized,
533    {
534        use itertools::Itertools;
535
536        let s = s.to_string();
537        #[allow(clippy::redundant_closure)] // the closure makes things slightly more readable
538        let err_ctx = |e: &str| InvalidKeyPathComponentValue::Slug(e.to_string());
539        let (interval, len, offset) = s
540            .split('_')
541            .collect_tuple()
542            .ok_or_else(|| err_ctx("invalid number of subcomponents"))?;
543
544        let length = len.parse().map_err(|_| err_ctx("invalid length"))?;
545        let interval_num = interval
546            .parse()
547            .map_err(|_| err_ctx("invalid interval_num"))?;
548        let offset_in_sec = offset
549            .parse()
550            .map_err(|_| err_ctx("invalid offset_in_sec"))?;
551
552        Ok(TimePeriod::from_parts(length, interval_num, offset_in_sec))
553    }
554
555    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
556        Display::fmt(&self, f)
557    }
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`].
567pub trait KeySpecifierComponentViaDisplayFromStr: Display + FromStr {}
568impl<T: KeySpecifierComponentViaDisplayFromStr> KeySpecifierComponent for T {
569    fn to_slug(&self) -> Result<Slug, Bug> {
570        self.to_string()
571            .try_into()
572            .map_err(into_internal!("Display generated bad Slug"))
573    }
574    fn from_slug(s: &Slug) -> Result<Self, InvalidKeyPathComponentValue>
575    where
576        Self: Sized,
577    {
578        s.as_str()
579            .parse()
580            .map_err(|_| internal!("slug cannot be parsed as component").into())
581    }
582    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
583        Display::fmt(self, f)
584    }
585}
586
587impl KeySpecifierComponentViaDisplayFromStr for HsNickname {}
588
589impl KeySpecifierComponent for HsId {
590    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        let hsid = self.display_unredacted().to_string();
595        let hsid_slug = hsid
596            .strip_suffix(HSID_ONION_SUFFIX)
597            .ok_or_else(|| internal!("HsId Display impl missing .onion suffix?!"))?;
598        hsid_slug
599            .to_owned()
600            .try_into()
601            .map_err(into_internal!("Display generated bad Slug"))
602    }
603
604    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
605    where
606        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        let onion = format!("{}{HSID_ONION_SUFFIX}", s.as_str());
617
618        onion
619            .parse()
620            .map_err(|e: HsIdParseError| InvalidKeyPathComponentValue::Slug(e.to_string()))
621    }
622
623    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
624        Display::fmt(&self.display_redacted(), f)
625    }
626}
627
628/// Wrapper for `KeySpecifierComponent` that `Displays` via `fmt_pretty`
629struct KeySpecifierComponentPrettyHelper<'c>(&'c dyn KeySpecifierComponent);
630
631impl Display for KeySpecifierComponentPrettyHelper<'_> {
632    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
633        KeySpecifierComponent::fmt_pretty(self.0, f)
634    }
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
655pub 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
669impl<T: KeyCertificateSpecifier + ?Sized> KeySpecifier for T {
670    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
671        let subject_key_arti_path = self
672            .subject_key_specifier()
673            .arti_path()
674            .map_err(|_| internal!("subject key does not have an ArtiPath?!"))?;
675
676        let path =
677            ArtiPath::from_path_and_denotators(subject_key_arti_path, &self.cert_denotators())
678                .map_err(into_internal!("invalid certificate specifier"))?;
679
680        Ok(path)
681    }
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`].
697pub 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)]
711mod 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##"
824KeyPathInfo {
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}
835            "##
836            .trim()
837        );
838    }
839
840    #[test]
841    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        let key_spec = TestSpecifier {};
850
851        check_key_specifier(&key_spec, "encabulator/marzlevane");
852
853        assert_eq!(
854            TestSpecifierPattern {}.arti_pattern().unwrap(),
855            KeyPathPattern::Arti("encabulator/marzlevane".into())
856        );
857    }
858
859    #[test]
860    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        let key_spec = TestSpecifier { count: 6 };
872
873        check_key_specifier(&key_spec, "encabulator/marzlevane+6");
874
875        assert_eq!(
876            TestSpecifierPattern { count: None }.arti_pattern().unwrap(),
877            KeyPathPattern::Arti("encabulator/marzlevane+*".into())
878        );
879    }
880
881    #[test]
882    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        let key_spec = TestSpecifier {
895            casing: "logarithmic".into(),
896            bearings: "spurving".into(),
897        };
898
899        check_key_specifier(&key_spec, "encabulator/logarithmic/spurving/fan");
900
901        assert_eq!(
902            TestSpecifierPattern {
903                casing: Some("logarithmic".into()),
904                bearings: Some("prefabulating".into()),
905            }
906            .arti_pattern()
907            .unwrap(),
908            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan".into())
909        );
910
911        let ctor_path = CTorPath::HsIdPublicKey {
912            nickname: HsNickname::from_str("foo").unwrap(),
913        };
914
915        assert_eq!(
916            TestSpecifier::from_ctor_path(ctor_path).unwrap_err(),
917            CTorPathError::MissingCTorPath("TestSpecifier".into()),
918        );
919    }
920
921    #[test]
922    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        let key_spec = TestSpecifier {
944            casing: "logarithmic".into(),
945            bearings: "spurving".into(),
946            count: 8,
947            length: 2000,
948            kind: "lunar".into(),
949        };
950
951        check_key_specifier(
952            &key_spec,
953            "encabulator/logarithmic/spurving/fan+8+2000+lunar",
954        );
955
956        assert_eq!(
957            TestSpecifierPattern {
958                casing: Some("logarithmic".into()),
959                bearings: Some("prefabulating".into()),
960                ..TestSpecifierPattern::new_any()
961            }
962            .arti_pattern()
963            .unwrap(),
964            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan+*+*+*".into())
965        );
966    }
967
968    #[test]
969    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        check_key_specifier(
983            &TestSpecifier {
984                i: 1,
985                role: "role".to_string(),
986                den: true,
987            },
988            "prefix/1/role+true",
989        );
990    }
991
992    #[test]
993    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")]
1000        struct TestSpecifier {
1001            nickname: HsNickname,
1002        }
1003
1004        let spec = TestSpecifier {
1005            nickname: HsNickname::from_str("42").unwrap(),
1006        };
1007
1008        check_key_specifier(&spec, "p/42/r");
1009
1010        let ctor_path = KeySpecifier::ctor_path(&spec);
1011
1012        assert_eq!(
1013            ctor_path,
1014            Some(CTorPath::HsIdPublicKey {
1015                nickname: HsNickname::from_str("42").unwrap(),
1016            }),
1017        );
1018
1019        assert_eq!(
1020            TestSpecifier::from_ctor_path(ctor_path.unwrap()).unwrap(),
1021            spec,
1022        );
1023
1024        /// An .onion address to put for test client CTorPaths.
1025        const HSID: &str = "yc6v7oeksrbech4ctv53di7rfjuikjagkyfrwu3yclzkfyv5haay6mqd.onion";
1026        let wrong_paths = &[
1027            CTorPath::HsClientDescEncKeypair {
1028                hs_id: HsId::from_str(HSID).unwrap(),
1029            },
1030            CTorPath::HsIdKeypair {
1031                nickname: HsNickname::from_str("42").unwrap(),
1032            },
1033        ];
1034
1035        for path in wrong_paths {
1036            assert_eq!(
1037                TestSpecifier::from_ctor_path(path.clone()).unwrap_err(),
1038                CTorPathError::KeySpecifierMismatch("TestSpecifier".into()),
1039            );
1040        }
1041    }
1042
1043    #[test]
1044    fn define_key_specifier_fixed_path_component() {
1045        #[derive(Deftly, Debug, Eq, PartialEq)]
1046        #[derive_deftly(KeySpecifier)]
1047        #[deftly(prefix = "prefix")]
1048        #[deftly(role = "role")]
1049        #[deftly(summary = "test key")]
1050        struct TestSpecifier {
1051            x: usize,
1052            #[deftly(fixed_path_component = "fixed")]
1053            z: bool,
1054        }
1055
1056        check_key_specifier(&TestSpecifier { x: 1, z: true }, "prefix/1/fixed/true/role");
1057    }
1058
1059    #[test]
1060    #[cfg(feature = "experimental-api")]
1061    fn define_cert_specifier_with_multiple_denotators() {
1062        #[derive(Deftly, Debug, PartialEq)]
1063        #[derive_deftly(KeySpecifier)]
1064        #[deftly(prefix = "encabulator")]
1065        #[deftly(role = "fan")]
1066        #[deftly(summary = "test key")]
1067        struct TestKeySpecifier {
1068            casing: String,
1069            bearings: String,
1070            #[deftly(denotator)]
1071            count: usize,
1072        }
1073
1074        #[derive(Deftly, Debug, PartialEq)]
1075        #[derive_deftly(CertSpecifier)]
1076        #[allow(dead_code)]
1077        struct TestCertSpecifier {
1078            #[deftly(subject)]
1079            subject: TestKeySpecifier,
1080            #[deftly(denotator)]
1081            length: usize,
1082            #[deftly(denotator)]
1083            width: usize,
1084        }
1085
1086        let cert_pat = TestCertSpecifierPattern::new_any();
1087        assert_eq!(
1088            cert_pat.arti_pattern().unwrap(),
1089            KeyPathPattern::Arti("encabulator/*/*/fan+*@*+*".into())
1090        );
1091    }
1092
1093    #[test]
1094    fn encode_time_period() {
1095        let period = TimePeriod::from_parts(1, 2, 3);
1096        let encoded_period = period.to_slug().unwrap();
1097
1098        assert_eq!(encoded_period.to_string(), "2_1_3");
1099        assert_eq!(period, TimePeriod::from_slug(&encoded_period).unwrap());
1100
1101        assert!(TimePeriod::from_slug(&Slug::new("invalid_tp".to_string()).unwrap()).is_err());
1102        assert!(TimePeriod::from_slug(&Slug::new("2_1_3_4".to_string()).unwrap()).is_err());
1103    }
1104
1105    #[test]
1106    fn encode_hsid() {
1107        let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
1108        let onion = format!("{b32}.onion");
1109        let hsid = HsId::from_str(&onion).unwrap();
1110        let hsid_slug = hsid.to_slug().unwrap();
1111
1112        assert_eq!(hsid_slug.to_string(), b32);
1113        assert_eq!(hsid, HsId::from_slug(&hsid_slug).unwrap());
1114    }
1115
1116    #[test]
1117    fn key_info_builder() {
1118        // A helper to check the extra_info of a `KeyPathInfo`
1119        macro_rules! assert_extra_info_eq {
1120            ($key_info:expr, [$(($k:expr, $v:expr),)*]) => {{
1121                assert_eq!(
1122                    $key_info.extra_info.into_iter().collect_vec(),
1123                    vec![
1124                        $(($k.into(), $v.into()),)*
1125                    ]
1126                );
1127            }}
1128        }
1129        let extra_info = vec![("nickname".into(), "bar".into())];
1130
1131        let key_info = KeyPathInfo::builder()
1132            .summary("test summary".into())
1133            .role("KS_vote".to_string())
1134            .set_all_extra_info(extra_info.clone().into_iter())
1135            .build()
1136            .unwrap();
1137
1138        assert_eq!(key_info.extra_info.into_iter().collect_vec(), extra_info);
1139
1140        let key_info = KeyPathInfo::builder()
1141            .summary("test summary".into())
1142            .role("KS_vote".to_string())
1143            .set_all_extra_info(extra_info.clone().into_iter())
1144            .extra_info("type", "service")
1145            .extra_info("time period", "100")
1146            .build()
1147            .unwrap();
1148
1149        assert_extra_info_eq!(
1150            key_info,
1151            [
1152                ("nickname", "bar"),
1153                ("time period", "100"),
1154                ("type", "service"),
1155            ]
1156        );
1157
1158        let key_info = KeyPathInfo::builder()
1159            .summary("test summary".into())
1160            .role("KS_vote".to_string())
1161            .extra_info("type", "service")
1162            .extra_info("time period", "100")
1163            .set_all_extra_info(extra_info.clone().into_iter())
1164            .build()
1165            .unwrap();
1166
1167        assert_extra_info_eq!(key_info, [("nickname", "bar"),]);
1168
1169        let key_info = KeyPathInfo::builder()
1170            .summary("test summary".into())
1171            .role("KS_vote".to_string())
1172            .extra_info("type", "service")
1173            .extra_info("time period", "100")
1174            .build()
1175            .unwrap();
1176
1177        assert_extra_info_eq!(key_info, [("time period", "100"), ("type", "service"),]);
1178    }
1179}