tor_keymgr/
key_specifier.rs

1//! The [`KeySpecifier`] trait and its implementations.
2
3use std::collections::BTreeMap;
4use std::fmt::{self, Display};
5use std::ops::Range;
6use std::result::Result as StdResult;
7use std::str::FromStr;
8
9use derive_more::From;
10use thiserror::Error;
11use tor_error::{internal, into_internal, Bug};
12use tor_hscrypto::pk::{HsId, HsIdParseError, HSID_ONION_SUFFIX};
13use tor_hscrypto::time::TimePeriod;
14use tor_persist::hsnickname::HsNickname;
15use tor_persist::slug::Slug;
16
17use crate::{ArtiPath, ArtiPathSyntaxError};
18
19// #[doc(hidden)] applied at crate toplevel
20#[macro_use]
21pub mod derive;
22
23/// The identifier of a key.
24#[derive(Clone, Debug, PartialEq, Eq, Hash, From, derive_more::Display)]
25#[non_exhaustive]
26pub 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)]
35pub struct ArtiPathRange(pub(crate) Range<usize>);
36
37impl 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    pub fn matches(&self, pat: &KeyPathPattern) -> Option<Vec<ArtiPathRange>> {
59        use KeyPathPattern::*;
60
61        let pattern: &str = match pat {
62            Arti(pat) => pat.as_ref(),
63            _ => return None,
64        };
65
66        glob_match::glob_match_with_captures(pattern, self.as_ref())
67            .map(|res| res.into_iter().map(|r| r.into()).collect())
68    }
69}
70
71impl 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    pub fn matches(&self, pat: &KeyPathPattern) -> bool {
89        use KeyPathPattern::*;
90
91        match (self, pat) {
92            (KeyPath::Arti(p), Arti(_)) => p.matches(pat).is_some(),
93            (KeyPath::CTor(p), CTor(pat)) if p == pat => true,
94            _ => false,
95        }
96    }
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`.
123pub 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]
152pub 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]
208pub 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#[derive(Debug, Clone, PartialEq, derive_builder::Builder, amplify::Getters)]
235pub 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
256impl KeyPathInfo {
257    /// Start to build a [`KeyPathInfo`]: return a fresh [`KeyPathInfoBuilder`]
258    pub fn builder() -> KeyPathInfoBuilder {
259        KeyPathInfoBuilder::default()
260    }
261}
262
263impl KeyPathInfoBuilder {
264    /// Initialize the additional information of this builder with the specified values.
265    ///
266    /// Erases the preexisting `extra_info`.
267    pub fn set_all_extra_info(
268        &mut self,
269        all_extra_info: impl Iterator<Item = (String, String)>,
270    ) -> &mut Self {
271        self.extra_info = Some(all_extra_info.collect());
272        self
273    }
274
275    /// Append the specified key-value pair to the `extra_info`.
276    ///
277    /// The preexisting `extra_info` is preserved.
278    pub fn extra_info(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
279        let extra_info = self.extra_info.get_or_insert(Default::default());
280        extra_info.insert(key.into(), value.into());
281        self
282    }
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.
289pub 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]
296macro_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]
323pub 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]
333pub 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]
355pub 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
364impl 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.
379pub 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.
408pub 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]
427pub 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
440impl KeySpecifier for ArtiPath {
441    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
442        Ok(self.clone())
443    }
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
454impl KeySpecifier for CTorPath {
455    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
456        Err(ArtiPathUnavailableError::ArtiPathUnavailable)
457    }
458
459    fn ctor_path(&self) -> Option<CTorPath> {
460        Some(self.clone())
461    }
462
463    fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
464        None
465    }
466}
467
468impl KeySpecifier for KeyPath {
469    fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
470        match self {
471            KeyPath::Arti(p) => p.arti_path(),
472            KeyPath::CTor(p) => p.arti_path(),
473        }
474    }
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
488impl KeySpecifierComponent for TimePeriod {
489    fn to_slug(&self) -> Result<Slug, Bug> {
490        Slug::new(format!(
491            "{}_{}_{}",
492            self.interval_num(),
493            self.length(),
494            self.epoch_offset_in_sec()
495        ))
496        .map_err(into_internal!("TP formatting went wrong"))
497    }
498
499    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
500    where
501        Self: Sized,
502    {
503        use itertools::Itertools;
504
505        let s = s.to_string();
506        #[allow(clippy::redundant_closure)] // the closure makes things slightly more readable
507        let err_ctx = |e: &str| InvalidKeyPathComponentValue::Slug(e.to_string());
508        let (interval, len, offset) = s
509            .split('_')
510            .collect_tuple()
511            .ok_or_else(|| err_ctx("invalid number of subcomponents"))?;
512
513        let length = len.parse().map_err(|_| err_ctx("invalid length"))?;
514        let interval_num = interval
515            .parse()
516            .map_err(|_| err_ctx("invalid interval_num"))?;
517        let offset_in_sec = offset
518            .parse()
519            .map_err(|_| err_ctx("invalid offset_in_sec"))?;
520
521        Ok(TimePeriod::from_parts(length, interval_num, offset_in_sec))
522    }
523
524    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
525        Display::fmt(&self, f)
526    }
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`].
536pub trait KeySpecifierComponentViaDisplayFromStr: Display + FromStr {}
537impl<T: KeySpecifierComponentViaDisplayFromStr> KeySpecifierComponent for T {
538    fn to_slug(&self) -> Result<Slug, Bug> {
539        self.to_string()
540            .try_into()
541            .map_err(into_internal!("Display generated bad Slug"))
542    }
543    fn from_slug(s: &Slug) -> Result<Self, InvalidKeyPathComponentValue>
544    where
545        Self: Sized,
546    {
547        s.as_str()
548            .parse()
549            .map_err(|_| internal!("slug cannot be parsed as component").into())
550    }
551    fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
552        Display::fmt(self, f)
553    }
554}
555
556impl KeySpecifierComponentViaDisplayFromStr for HsNickname {}
557
558impl KeySpecifierComponent for HsId {
559    fn to_slug(&self) -> StdResult<Slug, Bug> {
560        // We can't implement KeySpecifierComponentViaDisplayFromStr for HsId,
561        // because its Display impl contains the `.onion` suffix, and Slugs can't
562        // contain `.`.
563        let hsid = self.to_string();
564        let hsid_slug = hsid
565            .strip_suffix(HSID_ONION_SUFFIX)
566            .ok_or_else(|| internal!("HsId Display impl missing .onion suffix?!"))?;
567        hsid_slug
568            .to_owned()
569            .try_into()
570            .map_err(into_internal!("Display generated bad Slug"))
571    }
572
573    fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
574    where
575        Self: Sized,
576    {
577        // Note: HsId::from_str expects the string to have a .onion suffix,
578        // but the string representation of our slug doesn't have it
579        // (because we manually strip it away, see to_slug()).
580        //
581        // We have to manually add it for this to work.
582        //
583        // TODO: HsId should have some facilities for converting base32 HsIds (sans suffix)
584        // to and from string.
585        let onion = format!("{}{HSID_ONION_SUFFIX}", s.as_str());
586
587        onion
588            .parse()
589            .map_err(|e: HsIdParseError| InvalidKeyPathComponentValue::Slug(e.to_string()))
590    }
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`
598struct KeySpecifierComponentPrettyHelper<'c>(&'c dyn KeySpecifierComponent);
599
600impl Display for KeySpecifierComponentPrettyHelper<'_> {
601    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
602        KeySpecifierComponent::fmt_pretty(self.0, f)
603    }
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)
611pub 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)]
634mod 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##"
747KeyPathInfo {
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}
758            "##
759            .trim()
760        );
761    }
762
763    #[test]
764    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        let key_spec = TestSpecifier {};
773
774        check_key_specifier(&key_spec, "encabulator/marzlevane");
775
776        assert_eq!(
777            TestSpecifierPattern {}.arti_pattern().unwrap(),
778            KeyPathPattern::Arti("encabulator/marzlevane".into())
779        );
780    }
781
782    #[test]
783    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        let key_spec = TestSpecifier { count: 6 };
795
796        check_key_specifier(&key_spec, "encabulator/marzlevane+6");
797
798        assert_eq!(
799            TestSpecifierPattern { count: None }.arti_pattern().unwrap(),
800            KeyPathPattern::Arti("encabulator/marzlevane+*".into())
801        );
802    }
803
804    #[test]
805    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        let key_spec = TestSpecifier {
818            casing: "logarithmic".into(),
819            bearings: "spurving".into(),
820        };
821
822        check_key_specifier(&key_spec, "encabulator/logarithmic/spurving/fan");
823
824        assert_eq!(
825            TestSpecifierPattern {
826                casing: Some("logarithmic".into()),
827                bearings: Some("prefabulating".into()),
828            }
829            .arti_pattern()
830            .unwrap(),
831            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan".into())
832        );
833
834        assert_eq!(key_spec.ctor_path(), None);
835    }
836
837    #[test]
838    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        let key_spec = TestSpecifier {
860            casing: "logarithmic".into(),
861            bearings: "spurving".into(),
862            count: 8,
863            length: 2000,
864            kind: "lunar".into(),
865        };
866
867        check_key_specifier(
868            &key_spec,
869            "encabulator/logarithmic/spurving/fan+8+2000+lunar",
870        );
871
872        assert_eq!(
873            TestSpecifierPattern {
874                casing: Some("logarithmic".into()),
875                bearings: Some("prefabulating".into()),
876                ..TestSpecifierPattern::new_any()
877            }
878            .arti_pattern()
879            .unwrap(),
880            KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan+*+*+*".into())
881        );
882    }
883
884    #[test]
885    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        check_key_specifier(
899            &TestSpecifier {
900                i: 1,
901                role: "role".to_string(),
902                den: true,
903            },
904            "prefix/1/role+true",
905        );
906    }
907
908    #[test]
909    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            fn ctp(&self) -> CTorPath {
922                CTorPath::Service {
923                    nickname: HsNickname::from_str("allium-cepa").unwrap(),
924                    path: CTorServicePath::PublicKey,
925                }
926            }
927        }
928
929        let spec = TestSpecifier { i: 42 };
930
931        check_key_specifier(&spec, "p/42/r");
932
933        assert_eq!(
934            spec.ctor_path(),
935            Some(CTorPath::Service {
936                nickname: HsNickname::from_str("allium-cepa").unwrap(),
937                path: CTorServicePath::PublicKey,
938            }),
939        );
940    }
941
942    #[test]
943    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        check_key_specifier(&TestSpecifier { x: 1, z: true }, "prefix/1/fixed/true/role");
956    }
957
958    #[test]
959    fn encode_time_period() {
960        let period = TimePeriod::from_parts(1, 2, 3);
961        let encoded_period = period.to_slug().unwrap();
962
963        assert_eq!(encoded_period.to_string(), "2_1_3");
964        assert_eq!(period, TimePeriod::from_slug(&encoded_period).unwrap());
965
966        assert!(TimePeriod::from_slug(&Slug::new("invalid_tp".to_string()).unwrap()).is_err());
967        assert!(TimePeriod::from_slug(&Slug::new("2_1_3_4".to_string()).unwrap()).is_err());
968    }
969
970    #[test]
971    fn encode_hsid() {
972        let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
973        let onion = format!("{b32}.onion");
974        let hsid = HsId::from_str(&onion).unwrap();
975        let hsid_slug = hsid.to_slug().unwrap();
976
977        assert_eq!(hsid_slug.to_string(), b32);
978        assert_eq!(hsid, HsId::from_slug(&hsid_slug).unwrap());
979    }
980
981    #[test]
982    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        let extra_info = vec![("nickname".into(), "bar".into())];
995
996        let key_info = KeyPathInfo::builder()
997            .summary("test summary".into())
998            .role("KS_vote".to_string())
999            .set_all_extra_info(extra_info.clone().into_iter())
1000            .build()
1001            .unwrap();
1002
1003        assert_eq!(key_info.extra_info.into_iter().collect_vec(), extra_info);
1004
1005        let key_info = KeyPathInfo::builder()
1006            .summary("test summary".into())
1007            .role("KS_vote".to_string())
1008            .set_all_extra_info(extra_info.clone().into_iter())
1009            .extra_info("type", "service")
1010            .extra_info("time period", "100")
1011            .build()
1012            .unwrap();
1013
1014        assert_extra_info_eq!(
1015            key_info,
1016            [
1017                ("nickname", "bar"),
1018                ("time period", "100"),
1019                ("type", "service"),
1020            ]
1021        );
1022
1023        let key_info = KeyPathInfo::builder()
1024            .summary("test summary".into())
1025            .role("KS_vote".to_string())
1026            .extra_info("type", "service")
1027            .extra_info("time period", "100")
1028            .set_all_extra_info(extra_info.clone().into_iter())
1029            .build()
1030            .unwrap();
1031
1032        assert_extra_info_eq!(key_info, [("nickname", "bar"),]);
1033
1034        let key_info = KeyPathInfo::builder()
1035            .summary("test summary".into())
1036            .role("KS_vote".to_string())
1037            .extra_info("type", "service")
1038            .extra_info("time period", "100")
1039            .build()
1040            .unwrap();
1041
1042        assert_extra_info_eq!(key_info, [("time period", "100"), ("type", "service"),]);
1043    }
1044}