Skip to main content

tor_netdoc/types/
misc.rs

1//! Types used to parse arguments of entries in a directory document.
2//!
3//! There are some types that are pretty common, like "ISOTime",
4//! "base64-encoded data", and so on.
5//!
6//! These types shouldn't be exposed outside of the netdoc crate.
7
8pub(crate) use b16impl::*;
9pub use b64impl::*;
10pub(crate) use curve25519impl::*;
11pub(crate) use ed25519impl::*;
12#[cfg(any(feature = "routerdesc", feature = "hs-common"))]
13pub(crate) use edcert::*;
14pub(crate) use fingerprint::*;
15pub(crate) use rsa::*;
16pub use timeimpl::*;
17
18#[cfg(feature = "encode")]
19use {
20    crate::encode::{
21        self,
22        ItemEncoder,
23        ItemObjectEncodable,
24        ItemValueEncodable,
25        // `E` for "encode`; different from `parse2::MultiplicitySelector`
26        MultiplicitySelector as EMultiplicitySelector,
27    },
28    std::iter,
29};
30#[cfg(feature = "parse2")]
31use {
32    crate::parse2::multiplicity::{
33        ItemSetMethods,
34        // `P2` for "parse2`; different from `encode::MultiplicitySelector`
35        MultiplicitySelector as P2MultiplicitySelector,
36        ObjectSetMethods,
37    },
38    crate::parse2::{ArgumentError, ArgumentStream, ItemArgumentParseable, ItemObjectParseable}, //
39};
40
41pub use nickname::Nickname;
42
43pub use fingerprint::{Base64Fingerprint, Fingerprint};
44
45pub use identified_digest::{DigestName, IdentifiedDigest};
46
47pub use ignored_impl::{Ignored, IgnoredItemOrObjectValue, NotPresent};
48
49use crate::NormalItemArgument;
50use derive_deftly::{Deftly, define_derive_deftly};
51use std::cmp::{self, PartialOrd};
52use std::fmt::{self, Display};
53use std::marker::PhantomData;
54use std::str::FromStr;
55use tor_error::{Bug, ErrorReport as _, internal};
56use void::{ResultVoidExt as _, Void};
57
58/// Describes a value that van be decoded from a bunch of bytes.
59///
60/// Used for decoding the objects between BEGIN and END tags.
61pub(crate) trait FromBytes: Sized {
62    /// Try to parse a value of this type from a byte slice
63    fn from_bytes(b: &[u8], p: crate::Pos) -> crate::Result<Self>;
64    /// Try to parse a value of this type from a vector of bytes,
65    /// and consume that value
66    fn from_vec(v: Vec<u8>, p: crate::Pos) -> crate::Result<Self> {
67        Self::from_bytes(&v[..], p)
68    }
69}
70
71/// Types for decoding base64-encoded values.
72mod b64impl {
73    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
74    use base64ct::{Base64, Base64Unpadded, Encoding};
75    use std::fmt::{self, Display};
76    use std::ops::RangeBounds;
77    use subtle::{Choice, ConstantTimeEq};
78
79    /// A byte array, encoded in base64 with optional padding.
80    ///
81    /// On output (`Display`), output is unpadded.
82    #[derive(Clone)]
83    #[allow(clippy::derived_hash_with_manual_eq)]
84    #[derive(Hash, derive_more::Debug, derive_more::From, derive_more::Into)]
85    #[debug(r#"B64("{self}")"#)]
86    pub struct B64(Vec<u8>);
87
88    impl ConstantTimeEq for B64 {
89        fn ct_eq(&self, other: &B64) -> Choice {
90            self.0.ct_eq(&other.0)
91        }
92    }
93    /// `B64` is `Eq` via its constant-time implementation.
94    impl PartialEq for B64 {
95        fn eq(&self, other: &B64) -> bool {
96            self.ct_eq(other).into()
97        }
98    }
99    impl Eq for B64 {}
100
101    impl std::str::FromStr for B64 {
102        type Err = Error;
103        fn from_str(s: &str) -> Result<Self> {
104            let v: core::result::Result<Vec<u8>, base64ct::Error> = match s.len() % 4 {
105                0 => Base64::decode_vec(s),
106                _ => Base64Unpadded::decode_vec(s),
107            };
108            let v = v.map_err(|_| {
109                EK::BadArgument
110                    .with_msg("Invalid base64")
111                    .at_pos(Pos::at(s))
112            })?;
113            Ok(B64(v))
114        }
115    }
116
117    impl Display for B64 {
118        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119            Display::fmt(&Base64Unpadded::encode_string(&self.0), f)
120        }
121    }
122
123    impl B64 {
124        /// Return the byte array from this object.
125        pub fn as_bytes(&self) -> &[u8] {
126            &self.0[..]
127        }
128        /// Return this object if its length is within the provided bounds
129        /// object, or an error otherwise.
130        pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
131            if bounds.contains(&self.0.len()) {
132                Ok(self)
133            } else {
134                Err(EK::BadObjectVal.with_msg("Invalid length on base64 data"))
135            }
136        }
137
138        /// Try to convert this object into an array of N bytes.
139        ///
140        /// Return an error if the length is wrong.
141        pub fn into_array<const N: usize>(self) -> Result<[u8; N]> {
142            self.0
143                .try_into()
144                .map_err(|_| EK::BadObjectVal.with_msg("Invalid length on base64 data"))
145        }
146    }
147}
148
149// ============================================================
150
151/// Types for decoding hex-encoded values.
152mod b16impl {
153    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
154
155    /// A byte array encoded in hexadecimal.
156    pub(crate) struct B16(Vec<u8>);
157
158    impl std::str::FromStr for B16 {
159        type Err = Error;
160        fn from_str(s: &str) -> Result<Self> {
161            let bytes = hex::decode(s).map_err(|_| {
162                EK::BadArgument
163                    .at_pos(Pos::at(s))
164                    .with_msg("invalid hexadecimal")
165            })?;
166            Ok(B16(bytes))
167        }
168    }
169
170    impl B16 {
171        /// Return the underlying byte array.
172        #[allow(unused)]
173        pub(crate) fn as_bytes(&self) -> &[u8] {
174            &self.0[..]
175        }
176    }
177
178    impl From<B16> for Vec<u8> {
179        fn from(w: B16) -> Vec<u8> {
180            w.0
181        }
182    }
183}
184
185// ============================================================
186
187/// Types for decoding curve25519 keys
188mod curve25519impl {
189    use super::B64;
190    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
191    use tor_llcrypto::pk::curve25519::PublicKey;
192
193    /// A Curve25519 public key, encoded in base64 with optional padding
194    pub(crate) struct Curve25519Public(PublicKey);
195
196    impl std::str::FromStr for Curve25519Public {
197        type Err = Error;
198        fn from_str(s: &str) -> Result<Self> {
199            let b64: B64 = s.parse()?;
200            let array: [u8; 32] = b64.as_bytes().try_into().map_err(|_| {
201                EK::BadArgument
202                    .at_pos(Pos::at(s))
203                    .with_msg("bad length for curve25519 key.")
204            })?;
205            Ok(Curve25519Public(array.into()))
206        }
207    }
208
209    impl From<Curve25519Public> for PublicKey {
210        fn from(w: Curve25519Public) -> PublicKey {
211            w.0
212        }
213    }
214}
215
216// ============================================================
217
218/// Types for decoding ed25519 keys
219mod ed25519impl {
220    use super::B64;
221    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
222    use tor_llcrypto::pk::ed25519::Ed25519Identity;
223
224    /// An alleged ed25519 public key, encoded in base64 with optional
225    /// padding.
226    pub(crate) struct Ed25519Public(Ed25519Identity);
227
228    impl std::str::FromStr for Ed25519Public {
229        type Err = Error;
230        fn from_str(s: &str) -> Result<Self> {
231            let b64: B64 = s.parse()?;
232            if b64.as_bytes().len() != 32 {
233                return Err(EK::BadArgument
234                    .at_pos(Pos::at(s))
235                    .with_msg("bad length for ed25519 key."));
236            }
237            let key = Ed25519Identity::from_bytes(b64.as_bytes()).ok_or_else(|| {
238                EK::BadArgument
239                    .at_pos(Pos::at(s))
240                    .with_msg("bad value for ed25519 key.")
241            })?;
242            Ok(Ed25519Public(key))
243        }
244    }
245
246    impl From<Ed25519Public> for Ed25519Identity {
247        fn from(pk: Ed25519Public) -> Ed25519Identity {
248            pk.0
249        }
250    }
251}
252
253// ============================================================
254
255/// Dummy types like [`Ignored`]
256mod ignored_impl {
257    use super::*;
258
259    #[cfg(feature = "parse2")]
260    use crate::parse2::ErrorProblem as EP;
261
262    /// Part of a network document, that isn't actually there.
263    ///
264    /// Used as a standin in `ns_type!` calls in various netstatus `each_variety.rs`.
265    /// The effect is as if the field were omitted from the containing type.
266    ///
267    ///  * When used as item(s) (ie, a field type when deriving `NetdocParseable\[Fields\]`):
268    ///    **ignores any number** of items with that field's keyword during parsing,
269    ///    and emits none during encoding.
270    ///
271    ///    (To *reject* documents containing this item, use `Option<Void>`,
272    ///    but note that the spec says unknown items should be ignored,
273    ///    which would normally include items which are merely missing from one variety.)
274    ///
275    ///  * When used as an argument (ie, a field type when deriving `ItemValueParseable`,
276    ///    or with `netdoc(single_arg)`  when deriving `NetdocParseable\[Fields\]`):
277    ///    consumes **no arguments** during parsing, and emits none during encoding.
278    ///
279    ///  * When used as an object field (ie, `netdoc(object)` when deriving `ItemValueParseable`):
280    ///    **rejects** an object - failing the parse if one is present.
281    ///    (Functions similarly to `Option<Void>`, but prefer `NotPresent` as it's clearer.)
282    ///
283    /// There are bespoke impls of the multiplicity traits
284    /// `ItemSetMethods` and `ObjectSetMethods`:
285    /// don't wrap this type in `Option` or `Vec`.
286    //
287    // TODO we'll need to implement ItemArgument etc., for encoding, too.
288    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
289    #[allow(clippy::exhaustive_structs)]
290    #[cfg_attr(
291        feature = "parse2",
292        derive(Deftly),
293        derive_deftly(NetdocParseableFields)
294    )]
295    pub struct NotPresent;
296
297    /// Ignored part of a network document.
298    ///
299    /// With `parse2`, can be used as an item, object, or even flattened-fields.
300    ///
301    /// When deriving `parse2` traits, and a field is absent in a particular netstatus variety,
302    /// use `ns_type!` with [`NotPresent`], rather than `Ignored`.
303    ///
304    /// During encoding as an Items or Objects, will be entirely omitted,
305    /// via the multiplicity arrangements.
306    ///
307    /// Cannot be encoded as an Argument: if this is not the last
308    /// Argument, we need something to put into the output document to avoid generating
309    /// a document with the arguments out of step.  If it *is* the last argument,
310    /// it could simply be omitted, since additional arguments are in any case ignored.
311    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
312    #[cfg_attr(
313        feature = "parse2",
314        derive(Deftly),
315        derive_deftly(ItemValueParseable, NetdocParseableFields)
316    )]
317    #[allow(clippy::exhaustive_structs)]
318    pub struct Ignored;
319
320    /// An Item or Object that would be ignored during parsing and is omitted during encoding
321    ///
322    /// This is the "single" item type for encoding multiplicity for Items or Objects,
323    /// for [`Ignored`].
324    ///
325    /// This type is uninhabited.
326    pub struct IgnoredItemOrObjectValue(Void);
327
328    #[cfg(feature = "parse2")]
329    impl ItemSetMethods for P2MultiplicitySelector<NotPresent> {
330        type Each = Ignored;
331        type Field = NotPresent;
332        fn can_accumulate(self, _acc: &Option<NotPresent>) -> Result<(), EP> {
333            Ok(())
334        }
335        fn accumulate(self, _acc: &mut Option<NotPresent>, _item: Ignored) -> Result<(), EP> {
336            Ok(())
337        }
338        fn finish(self, _acc: Option<NotPresent>, _: &'static str) -> Result<NotPresent, EP> {
339            Ok(NotPresent)
340        }
341    }
342
343    #[cfg(feature = "parse2")]
344    impl ItemArgumentParseable for NotPresent {
345        fn from_args(_: &mut ArgumentStream) -> Result<NotPresent, ArgumentError> {
346            Ok(NotPresent)
347        }
348    }
349
350    #[cfg(feature = "parse2")]
351    impl ObjectSetMethods for P2MultiplicitySelector<NotPresent> {
352        type Field = NotPresent;
353        type Each = Void;
354        fn resolve_option(self, _found: Option<Void>) -> Result<NotPresent, EP> {
355            Ok(NotPresent)
356        }
357    }
358
359    #[cfg(feature = "encode")]
360    impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<NotPresent> {
361        type Field = NotPresent;
362        type Each = Void;
363        fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
364            iter::empty()
365        }
366    }
367
368    #[cfg(feature = "encode")]
369    impl encode::OptionalityMethods for EMultiplicitySelector<NotPresent> {
370        type Field = NotPresent;
371        type Each = Void;
372        fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
373            None
374        }
375    }
376
377    impl FromStr for Ignored {
378        type Err = Void;
379        fn from_str(_s: &str) -> Result<Ignored, Void> {
380            Ok(Ignored)
381        }
382    }
383
384    #[cfg(feature = "parse2")]
385    impl ItemArgumentParseable for Ignored {
386        fn from_args(_: &mut ArgumentStream) -> Result<Ignored, ArgumentError> {
387            Ok(Ignored)
388        }
389    }
390
391    #[cfg(feature = "parse2")]
392    impl ItemObjectParseable for Ignored {
393        fn check_label(_label: &str) -> Result<(), EP> {
394            // allow any label
395            Ok(())
396        }
397        fn from_bytes(_input: &[u8]) -> Result<Self, EP> {
398            Ok(Ignored)
399        }
400    }
401
402    #[cfg(feature = "parse2")]
403    impl ObjectSetMethods for P2MultiplicitySelector<Ignored> {
404        type Field = Ignored;
405        type Each = Ignored;
406        fn resolve_option(self, _found: Option<Ignored>) -> Result<Ignored, EP> {
407            Ok(Ignored)
408        }
409    }
410
411    #[cfg(feature = "encode")]
412    impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<Ignored> {
413        type Field = Ignored;
414        type Each = IgnoredItemOrObjectValue;
415        fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
416            iter::empty()
417        }
418    }
419
420    #[cfg(feature = "encode")]
421    impl encode::OptionalityMethods for EMultiplicitySelector<Ignored> {
422        type Field = Ignored;
423        type Each = IgnoredItemOrObjectValue;
424        fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
425            None
426        }
427    }
428
429    #[cfg(feature = "encode")]
430    impl ItemValueEncodable for IgnoredItemOrObjectValue {
431        fn write_item_value_onto(&self, _: ItemEncoder) -> Result<(), Bug> {
432            void::unreachable(self.0)
433        }
434    }
435
436    #[cfg(feature = "encode")]
437    impl ItemObjectEncodable for IgnoredItemOrObjectValue {
438        fn label(&self) -> &str {
439            void::unreachable(self.0)
440        }
441        fn write_object_onto(&self, _: &mut Vec<u8>) -> Result<(), Bug> {
442            void::unreachable(self.0)
443        }
444    }
445}
446
447// ============================================================
448
449/// Information about unknown values, which may have been retained as a `T`
450///
451/// Won't grow additional variants - but, `Retained` is only included conditionally.
452///
453/// Also used in the form `Unknown<()>` to indicate whether unknown values *should* be retained.
454///
455/// ### Example
456///
457/// ```
458/// # {
459/// #![cfg(feature = "retain-unknown")]
460///
461/// use tor_netdoc::types::Unknown;
462///
463/// let mut unk: Unknown<Vec<String>> = Unknown::new_retained_default();
464/// unk.with_mut_unknown(|u| u.push("something-we-found".into()));
465/// assert_eq!(unk.into_retained().unwrap(), ["something-we-found"]);
466/// # }
467/// ```
468///
469/// ### Equality comparison, semantics
470///
471/// Two `Unknown` are consider equal if both have the same record of unknown values,
472/// or if neither records unknown values at all.
473///
474/// `Unknown` is not `Eq` or `Ord` because we won't want to relate a `Discarded`
475/// to a `Retained`.  That would be a logic error.  `partial_cmp` gives `None` for this.
476#[derive(Debug, PartialEq, Clone, Copy, Hash)]
477#[non_exhaustive]
478pub enum Unknown<T> {
479    /// The parsing discarded unknown values and they are no longer available.
480    Discarded(PhantomData<T>),
481
482    /// The document parsing retained (or should retain) unknown values.
483    #[cfg(feature = "retain-unknown")]
484    Retained(T),
485}
486
487impl<T> Unknown<T> {
488    /// Create an `Unknown` which specifies that values were discarded (or should be)
489    pub fn new_discard() -> Self {
490        Unknown::Discarded(PhantomData)
491    }
492
493    /// Map the `Retained`, if there is one
494    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Unknown<U> {
495        self.try_map(move |t| Ok::<_, Void>(f(t))).void_unwrap()
496    }
497
498    /// Map the `Retained`, fallibly
499    pub fn try_map<U, E>(self, f: impl FnOnce(T) -> Result<U, E>) -> Result<Unknown<U>, E> {
500        Ok(match self {
501            Unknown::Discarded(_) => Unknown::Discarded(PhantomData),
502            #[cfg(feature = "retain-unknown")]
503            Unknown::Retained(t) => Unknown::Retained(f(t)?),
504        })
505    }
506
507    /// Obtain an `Unknown` containing (maybe) a reference
508    pub fn as_ref(&self) -> Option<&T> {
509        match self {
510            Unknown::Discarded(_) => None,
511            #[cfg(feature = "retain-unknown")]
512            Unknown::Retained(t) => Some(t),
513        }
514    }
515
516    /// Obtain the `Retained` data
517    ///
518    /// Treats lack of retention as an internal error.
519    #[cfg(feature = "retain-unknown")]
520    pub fn into_retained(self) -> Result<T, Bug> {
521        match self {
522            Unknown::Discarded(_) => Err(internal!("Unknown::retained but data not collected")),
523            Unknown::Retained(t) => Ok(t),
524        }
525    }
526
527    /// Start recording unknown information, with a default value for `T`
528    #[cfg(feature = "retain-unknown")]
529    pub fn new_retained_default() -> Self
530    where
531        T: Default,
532    {
533        Unknown::Retained(T::default())
534    }
535
536    /// Update the `Retained`, if there is one
537    ///
538    /// Intended for use in parsing, when we encounter an unknown value.
539    ///
540    /// Not provided in `try_` form.  If you think you need this, instead, unconditionally
541    /// parse and verify the unknown value, and then conditionally insert it with this function.
542    /// Don't parse it conditionally - that would skip some validation.
543    pub fn with_mut_unknown(&mut self, f: impl FnOnce(&mut T)) {
544        match self {
545            Unknown::Discarded(_) => {}
546            #[cfg(feature = "retain-unknown")]
547            Unknown::Retained(t) => f(t),
548        }
549    }
550}
551
552impl<T: PartialOrd> PartialOrd for Unknown<T> {
553    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
554        use Unknown::*;
555        match (self, other) {
556            (Discarded(_), Discarded(_)) => Some(cmp::Ordering::Equal),
557            #[cfg(feature = "retain-unknown")]
558            (Discarded(_), Retained(_)) | (Retained(_), Discarded(_)) => None,
559            #[cfg(feature = "retain-unknown")]
560            (Retained(a), Retained(b)) => a.partial_cmp(b),
561        }
562    }
563}
564
565// ============================================================
566
567/// Types for decoding times and dates
568mod timeimpl {
569    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
570    use std::time::SystemTime;
571    use time::{
572        OffsetDateTime, PrimitiveDateTime, format_description::FormatItem,
573        macros::format_description,
574    };
575
576    /// A wall-clock time, encoded in Iso8601 format with an intervening
577    /// space between the date and time.
578    ///
579    /// (Example: "2020-10-09 17:38:12")
580    #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] //
581    #[derive(derive_more::Into, derive_more::From, derive_more::Deref)]
582    #[allow(clippy::exhaustive_structs)]
583    pub struct Iso8601TimeSp(pub SystemTime);
584
585    /// Formatting object for parsing the space-separated Iso8601 format.
586    const ISO_8601SP_FMT: &[FormatItem] =
587        format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
588
589    impl std::str::FromStr for Iso8601TimeSp {
590        type Err = Error;
591        fn from_str(s: &str) -> Result<Iso8601TimeSp> {
592            let d = PrimitiveDateTime::parse(s, &ISO_8601SP_FMT).map_err(|e| {
593                EK::BadArgument
594                    .at_pos(Pos::at(s))
595                    .with_msg(format!("invalid time: {}", e))
596            })?;
597            Ok(Iso8601TimeSp(d.assume_utc().into()))
598        }
599    }
600
601    /// Formats a SystemTime according to the given format description
602    ///
603    /// Also converts any time::error::format to std::fmt::Error
604    /// so that it can be unwrapped in the Display trait impl
605    fn fmt_with(
606        t: SystemTime,
607        format_desc: &[FormatItem],
608    ) -> core::result::Result<String, std::fmt::Error> {
609        OffsetDateTime::from(t)
610            .format(format_desc)
611            .map_err(|_| std::fmt::Error)
612    }
613
614    impl std::fmt::Display for Iso8601TimeSp {
615        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
616            write!(f, "{}", fmt_with(self.0, ISO_8601SP_FMT)?)
617        }
618    }
619
620    /// A wall-clock time, encoded in ISO8601 format without an intervening
621    /// space.
622    ///
623    /// This represents a specific UTC instant (ie an instant in global civil time).
624    /// But it may not be able to represent leap seconds.
625    ///
626    /// The timezone is not included in the string representation; `+0000` is implicit.
627    ///
628    /// (Example: "2020-10-09T17:38:12")
629    #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] //
630    #[derive(derive_more::Into, derive_more::From, derive_more::Deref)]
631    #[allow(clippy::exhaustive_structs)]
632    pub struct Iso8601TimeNoSp(pub SystemTime);
633
634    /// Formatting object for parsing the space-separated Iso8601 format.
635    const ISO_8601NOSP_FMT: &[FormatItem] =
636        format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
637
638    impl std::str::FromStr for Iso8601TimeNoSp {
639        type Err = Error;
640        fn from_str(s: &str) -> Result<Iso8601TimeNoSp> {
641            let d = PrimitiveDateTime::parse(s, &ISO_8601NOSP_FMT).map_err(|e| {
642                EK::BadArgument
643                    .at_pos(Pos::at(s))
644                    .with_msg(format!("invalid time: {}", e))
645            })?;
646            Ok(Iso8601TimeNoSp(d.assume_utc().into()))
647        }
648    }
649
650    impl std::fmt::Display for Iso8601TimeNoSp {
651        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
652            write!(f, "{}", fmt_with(self.0, ISO_8601NOSP_FMT)?)
653        }
654    }
655
656    impl crate::NormalItemArgument for Iso8601TimeNoSp {}
657}
658
659/// Types for decoding RSA keys
660mod rsa {
661    use crate::{NetdocErrorKind as EK, Pos, Result};
662    use std::ops::RangeBounds;
663    use tor_llcrypto::pk::rsa::PublicKey;
664
665    /// The fixed exponent which we require when parsing any RSA key in a netdoc
666    //
667    // TODO this value is duplicated a lot in the v1 parser
668    pub(crate) const RSA_FIXED_EXPONENT: u32 = 65537;
669
670    /// The fixed exponent which we require when parsing any RSA key in a netdoc
671    //
672    // TODO this value is duplicated a lot in the v1 parser
673    pub(crate) const RSA_MIN_BITS: usize = 1024;
674
675    /// RSA public key, partially processed by `crate::paarse`.
676    ///
677    /// As parsed from a base64-encoded object.
678    /// They key's properties (exponent and size) haven't been checked.
679    #[allow(non_camel_case_types)]
680    #[derive(Clone, Debug)]
681    pub(crate) struct RsaPublicParse1Helper(PublicKey, Pos);
682
683    impl From<RsaPublicParse1Helper> for PublicKey {
684        fn from(k: RsaPublicParse1Helper) -> PublicKey {
685            k.0
686        }
687    }
688    impl super::FromBytes for RsaPublicParse1Helper {
689        fn from_bytes(b: &[u8], pos: Pos) -> Result<Self> {
690            let key = PublicKey::from_der(b)
691                .ok_or_else(|| EK::BadObjectVal.with_msg("unable to decode RSA public key"))?;
692            Ok(RsaPublicParse1Helper(key, pos))
693        }
694    }
695    impl RsaPublicParse1Helper {
696        /// Give an error if the exponent of this key is not 'e'
697        pub(crate) fn check_exponent(self, e: u32) -> Result<Self> {
698            if self.0.exponent_is(e) {
699                Ok(self)
700            } else {
701                Err(EK::BadObjectVal
702                    .at_pos(self.1)
703                    .with_msg("invalid RSA exponent"))
704            }
705        }
706        /// Give an error if the length of this key's modulus, in
707        /// bits, is not contained in 'bounds'
708        pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
709            if bounds.contains(&self.0.bits()) {
710                Ok(self)
711            } else {
712                Err(EK::BadObjectVal
713                    .at_pos(self.1)
714                    .with_msg("invalid RSA length"))
715            }
716        }
717        /// Give an error if the length of this key's modulus, in
718        /// bits, is not exactly `n`.
719        pub(crate) fn check_len_eq(self, n: usize) -> Result<Self> {
720            self.check_len(n..=n)
721        }
722    }
723}
724
725/// Types for decoding Ed25519 certificates
726#[cfg(any(feature = "routerdesc", feature = "hs-common"))]
727mod edcert {
728    use crate::{NetdocErrorKind as EK, Pos, Result};
729    use tor_cert::{CertType, Ed25519Cert, KeyUnknownCert};
730    #[cfg(feature = "routerdesc")]
731    use tor_llcrypto::pk::ed25519;
732
733    /// An ed25519 certificate as parsed from a directory object, with
734    /// signature not validated.
735    #[derive(Debug, Clone)]
736    pub(crate) struct UnvalidatedEdCert(KeyUnknownCert, Pos);
737
738    impl super::FromBytes for UnvalidatedEdCert {
739        fn from_bytes(b: &[u8], p: Pos) -> Result<Self> {
740            let cert = Ed25519Cert::decode(b).map_err(|e| {
741                EK::BadObjectVal
742                    .at_pos(p)
743                    .with_msg("Bad certificate")
744                    .with_source(e)
745            })?;
746
747            Ok(Self(cert, p))
748        }
749        fn from_vec(v: Vec<u8>, p: Pos) -> Result<Self> {
750            Self::from_bytes(&v[..], p)
751        }
752    }
753    impl UnvalidatedEdCert {
754        /// Give an error if this certificate's type is not `desired_type`.
755        pub(crate) fn check_cert_type(self, desired_type: CertType) -> Result<Self> {
756            if self.0.peek_cert_type() != desired_type {
757                return Err(EK::BadObjectVal.at_pos(self.1).with_msg(format!(
758                    "bad certificate type {} (wanted {})",
759                    self.0.peek_cert_type(),
760                    desired_type
761                )));
762            }
763            Ok(self)
764        }
765        /// Give an error if this certificate's subject_key is not `pk`
766        #[cfg(feature = "routerdesc")]
767        pub(crate) fn check_subject_key_is(self, pk: &ed25519::Ed25519Identity) -> Result<Self> {
768            if self.0.peek_subject_key().as_ed25519() != Some(pk) {
769                return Err(EK::BadObjectVal
770                    .at_pos(self.1)
771                    .with_msg("incorrect subject key"));
772            }
773            Ok(self)
774        }
775        /// Consume this object and return the inner Ed25519 certificate.
776        pub(crate) fn into_unchecked(self) -> KeyUnknownCert {
777            self.0
778        }
779    }
780}
781
782/// Digest identifiers, and digests in the form `ALGORITHM=BASE64U`
783///
784/// As found in a vote's `m` line.
785mod identified_digest {
786    use super::*;
787
788    define_derive_deftly! {
789        /// impl `FromStr` and `Display` for an enum with unit variants but also "unknown"
790        ///
791        /// Expected input: an enum whose variants are either
792        ///  * unit variants, perhaps with `#[deftly(string_repr = "string")]`
793        ///  * singleton tuple variant, containing `String` (or near equivalent)
794        ///
795        /// If `#[deftly(string_repro)]` is not specified,
796        /// the default is snake case of the variant name.
797        //
798        // This macro may seem overkill, but open-coding these impls gives opportunities
799        // for mismatches between FromStr, Display, and the variant name.
800        //
801        // TODO consider putting this in tor-basic-utils (maybe with a better name),
802        // or possibly asking if derive_more want their FromStr to have this.
803        StringReprUnitsOrUnknown for enum, expect items, beta_deftly:
804
805        ${define STRING_REPR {
806            ${vmeta(string_repr)
807              as str,
808              default { ${concat ${snake_case $vname}} }
809            }
810        }}
811
812        impl FromStr for $ttype {
813            type Err = Void;
814            fn from_str(s: &str) -> Result<Self, Void> {
815                $(
816                    ${when v_is_unit}
817                    if s == $STRING_REPR {
818                        return Ok($vtype)
819                    }
820                )
821                $(
822                    ${when not(v_is_unit)} // anything else had better be Unknown
823                    // not using `return ..;` makes this a syntax error if there are several.
824                    Ok($vtype { 0: s.into() })
825                )
826            }
827        }
828        impl Display for $ttype {
829            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
830                let s: &str = match self {
831                    $(
832                        ${when v_is_unit}
833                        $vtype => $STRING_REPR,
834                    )
835                    $(
836                        ${when not(v_is_unit)}
837                        $vpat => f_0,
838                    )
839                };
840                Display::fmt(s, f)
841            }
842        }
843    }
844
845    /// The name of a digest algorithm.
846    ///
847    /// Can represent an unrecognised algorithm, so it's parsed and reproduced.
848    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
849    #[derive_deftly(StringReprUnitsOrUnknown)]
850    #[non_exhaustive]
851    pub enum DigestName {
852        /// SHA-256
853        Sha256,
854        /// Unknown
855        Unknown(String),
856    }
857
858    /// A single digest made with a nominated digest algorithm, `ALGORITHM=DIGEST`
859    #[derive(Debug, Clone, Eq, PartialEq, Hash, derive_more::Display)]
860    #[display("{alg}={value}")]
861    #[non_exhaustive]
862    pub struct IdentifiedDigest {
863        /// The algorithm name.
864        alg: DigestName,
865
866        /// The digest value.
867        ///
868        /// Invariant: length is correct for `alg`, assuming `alg` is known.
869        value: B64,
870    }
871
872    impl NormalItemArgument for DigestName {}
873    impl NormalItemArgument for IdentifiedDigest {}
874
875    /// Invalid syntax parsing an `IdentifiedDigest`
876    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, thiserror::Error)]
877    #[error("invalid syntax, expected ALGORITHM=DIGEST: {0}")]
878    pub struct IdentifiedDigestParseError(String);
879
880    impl FromStr for IdentifiedDigest {
881        type Err = IdentifiedDigestParseError;
882
883        fn from_str(s: &str) -> Result<Self, Self::Err> {
884            (|| {
885                let (alg, value) = s.split_once('=').ok_or("missing equals sign")?;
886
887                let alg = alg.parse().void_unwrap();
888                let value = value
889                    .parse::<B64>()
890                    .map_err(|e| format!("bad value: {}", e.report()))?;
891
892                if let Some(exp_len) = (|| {
893                    Some({
894                        use DigestName::*;
895                        match alg {
896                            Sha256 => 32,
897                            Unknown(_) => None?,
898                        }
899                    })
900                })() {
901                    let val_len = value.as_bytes().len();
902                    if val_len != exp_len {
903                        return Err(format!("got {val_len} bytes, expected {exp_len}"));
904                    }
905                }
906
907                Ok(IdentifiedDigest { alg, value })
908            })()
909            .map_err(IdentifiedDigestParseError)
910        }
911    }
912}
913
914/// Types for decoding RSA fingerprints
915mod fingerprint {
916    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
917    use base64ct::{Base64Unpadded, Encoding as _};
918    use std::fmt::{self, Display};
919    use tor_llcrypto::pk::rsa::RsaIdentity;
920
921    /// A hex-encoded RSA key identity (fingerprint) with spaces in it.
922    ///
923    /// Netdoc parsing adapter for [`RsaIdentity`]
924    #[derive(Debug, Clone, Eq, PartialEq, derive_more::Deref, derive_more::Into)]
925    #[allow(clippy::exhaustive_structs)]
926    pub(crate) struct SpFingerprint(pub RsaIdentity);
927
928    /// A hex-encoded fingerprint with no spaces.
929    ///
930    /// Netdoc parsing adapter for [`RsaIdentity`]
931    #[derive(Debug, Clone, Eq, PartialEq, derive_more::Deref, derive_more::Into)]
932    #[allow(clippy::exhaustive_structs)]
933    pub struct Fingerprint(pub RsaIdentity);
934
935    /// A base64-encoded fingerprint (unpadded)
936    ///
937    /// Netdoc parsing adapter for [`RsaIdentity`]
938    #[derive(Debug, Clone, Eq, PartialEq, derive_more::Deref, derive_more::Into)]
939    #[allow(clippy::exhaustive_structs)]
940    pub struct Base64Fingerprint(pub RsaIdentity);
941
942    /// A "long identity" in the format used for Family members.
943    ///
944    /// Netdoc parsing adapter for [`RsaIdentity`]
945    #[derive(Debug, Clone, Eq, PartialEq, derive_more::Deref, derive_more::Into)]
946    #[allow(clippy::exhaustive_structs)]
947    pub(crate) struct LongIdent(pub RsaIdentity);
948
949    /// Helper: parse an identity from a hexadecimal string
950    fn parse_hex_ident(s: &str) -> Result<RsaIdentity> {
951        RsaIdentity::from_hex(s).ok_or_else(|| {
952            EK::BadArgument
953                .at_pos(Pos::at(s))
954                .with_msg("wrong length on fingerprint")
955        })
956    }
957
958    impl std::str::FromStr for SpFingerprint {
959        type Err = Error;
960        fn from_str(s: &str) -> Result<SpFingerprint> {
961            let ident = parse_hex_ident(&s.replace(' ', "")).map_err(|e| e.at_pos(Pos::at(s)))?;
962            Ok(SpFingerprint(ident))
963        }
964    }
965
966    impl std::str::FromStr for Base64Fingerprint {
967        type Err = Error;
968        fn from_str(s: &str) -> Result<Base64Fingerprint> {
969            let b = s.parse::<super::B64>()?;
970            let ident = RsaIdentity::from_bytes(b.as_bytes()).ok_or_else(|| {
971                EK::BadArgument
972                    .at_pos(Pos::at(s))
973                    .with_msg("Wrong identity length")
974            })?;
975            Ok(Base64Fingerprint(ident))
976        }
977    }
978
979    impl Display for Base64Fingerprint {
980        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
981            Display::fmt(&Base64Unpadded::encode_string(self.as_bytes()), f)
982        }
983    }
984
985    impl std::str::FromStr for Fingerprint {
986        type Err = Error;
987        fn from_str(s: &str) -> Result<Fingerprint> {
988            let ident = parse_hex_ident(s).map_err(|e| e.at_pos(Pos::at(s)))?;
989            Ok(Fingerprint(ident))
990        }
991    }
992
993    impl Display for Fingerprint {
994        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
995            Display::fmt(&hex::encode_upper(self.as_bytes()), f)
996        }
997    }
998
999    impl std::str::FromStr for LongIdent {
1000        type Err = Error;
1001        fn from_str(mut s: &str) -> Result<LongIdent> {
1002            if s.starts_with('$') {
1003                s = &s[1..];
1004            }
1005            if let Some(idx) = s.find(['=', '~']) {
1006                s = &s[..idx];
1007            }
1008            let ident = parse_hex_ident(s)?;
1009            Ok(LongIdent(ident))
1010        }
1011    }
1012
1013    impl crate::NormalItemArgument for Fingerprint {}
1014    impl crate::NormalItemArgument for Base64Fingerprint {}
1015}
1016
1017/// A type for relay nicknames
1018mod nickname {
1019    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
1020    use tinystr::TinyAsciiStr;
1021
1022    /// This is a strange limit, but it comes from Tor.
1023    const MAX_NICKNAME_LEN: usize = 19;
1024
1025    /// The nickname for a Tor relay.
1026    ///
1027    /// These nicknames are legacy mechanism that's occasionally useful in
1028    /// debugging. They should *never* be used to uniquely identify relays;
1029    /// nothing prevents two relays from having the same nickname.
1030    ///
1031    /// Nicknames are required to be ASCII, alphanumeric, and between 1 and 19
1032    /// characters inclusive.
1033    #[derive(Clone, Debug)]
1034    pub struct Nickname(tinystr::TinyAsciiStr<MAX_NICKNAME_LEN>);
1035
1036    impl Nickname {
1037        /// Return a view of this nickname as a string slice.
1038        pub(crate) fn as_str(&self) -> &str {
1039            self.0.as_str()
1040        }
1041    }
1042
1043    impl std::fmt::Display for Nickname {
1044        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1045            self.as_str().fmt(f)
1046        }
1047    }
1048
1049    impl std::str::FromStr for Nickname {
1050        type Err = Error;
1051
1052        fn from_str(s: &str) -> Result<Self> {
1053            let tiny = TinyAsciiStr::from_str(s).map_err(|_| {
1054                EK::BadArgument
1055                    .at_pos(Pos::at(s))
1056                    .with_msg("Invalid nickname")
1057            })?;
1058
1059            if tiny.is_ascii_alphanumeric() && !tiny.is_empty() {
1060                Ok(Nickname(tiny))
1061            } else {
1062                Err(EK::BadArgument
1063                    .at_pos(Pos::at(s))
1064                    .with_msg("Invalid nickname"))
1065            }
1066        }
1067    }
1068
1069    impl crate::NormalItemArgument for Nickname {}
1070}
1071
1072#[cfg(test)]
1073mod test {
1074    // @@ begin test lint list maintained by maint/add_warning @@
1075    #![allow(clippy::bool_assert_comparison)]
1076    #![allow(clippy::clone_on_copy)]
1077    #![allow(clippy::dbg_macro)]
1078    #![allow(clippy::mixed_attributes_style)]
1079    #![allow(clippy::print_stderr)]
1080    #![allow(clippy::print_stdout)]
1081    #![allow(clippy::single_char_pattern)]
1082    #![allow(clippy::unwrap_used)]
1083    #![allow(clippy::unchecked_time_subtraction)]
1084    #![allow(clippy::useless_vec)]
1085    #![allow(clippy::needless_pass_by_value)]
1086    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
1087    use itertools::Itertools;
1088
1089    use base64ct::Encoding;
1090
1091    use super::*;
1092    use crate::{Pos, Result};
1093
1094    /// Decode s as a multi-line base64 string, ignoring ascii whitespace.
1095    fn base64_decode_ignore_ws(s: &str) -> std::result::Result<Vec<u8>, base64ct::Error> {
1096        let mut s = s.to_string();
1097        s.retain(|c| !c.is_ascii_whitespace());
1098        base64ct::Base64::decode_vec(s.as_str())
1099    }
1100
1101    #[test]
1102    fn base64() -> Result<()> {
1103        // Test parsing success:
1104        // Unpadded:
1105        assert_eq!("Mi43MTgyOA".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
1106        assert!("Mi43MTgyOA".parse::<B64>()?.check_len(7..8).is_ok());
1107        assert_eq!("Mg".parse::<B64>()?.as_bytes(), &b"2"[..]);
1108        assert!("Mg".parse::<B64>()?.check_len(1..2).is_ok());
1109        assert_eq!(
1110            "8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
1111                .parse::<B64>()?
1112                .as_bytes(),
1113            "๐Ÿ’๐Ÿ’๐Ÿ’๐Ÿ’๐Ÿ’๐Ÿ’".as_bytes()
1114        );
1115        assert!(
1116            "8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
1117                .parse::<B64>()?
1118                .check_len(24..25)
1119                .is_ok()
1120        );
1121        assert!(
1122            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8="
1123                .parse::<B64>()?
1124                .check_len(32..33)
1125                .is_ok()
1126        );
1127        // Padded:
1128        assert_eq!("Mi43MTgyOA==".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
1129        assert!("Mi43MTgyOA==".parse::<B64>()?.check_len(7..8).is_ok());
1130        assert_eq!("Mg==".parse::<B64>()?.as_bytes(), &b"2"[..]);
1131        assert!("Mg==".parse::<B64>()?.check_len(1..2).is_ok());
1132
1133        // Test parsing failures:
1134        // Invalid character.
1135        assert!("Mi43!!!!!!".parse::<B64>().is_err());
1136        // Invalid last character.
1137        assert!("Mi".parse::<B64>().is_err());
1138        assert!(
1139            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
1140                .parse::<B64>()
1141                .is_err()
1142        );
1143        // Invalid length.
1144        assert!("Mi43MTgyOA".parse::<B64>()?.check_len(8..).is_err());
1145        Ok(())
1146    }
1147
1148    #[test]
1149    fn base64_lengths() -> Result<()> {
1150        assert_eq!("".parse::<B64>()?.as_bytes(), b"");
1151        assert!("=".parse::<B64>().is_err());
1152        assert!("==".parse::<B64>().is_err());
1153        assert!("B".parse::<B64>().is_err());
1154        assert!("B=".parse::<B64>().is_err());
1155        assert!("B==".parse::<B64>().is_err());
1156        assert!("Bg=".parse::<B64>().is_err());
1157        assert_eq!("Bg".parse::<B64>()?.as_bytes(), b"\x06");
1158        assert_eq!("Bg==".parse::<B64>()?.as_bytes(), b"\x06");
1159        assert_eq!("BCg".parse::<B64>()?.as_bytes(), b"\x04\x28");
1160        assert_eq!("BCg=".parse::<B64>()?.as_bytes(), b"\x04\x28");
1161        assert!("BCg==".parse::<B64>().is_err());
1162        assert_eq!("BCDE".parse::<B64>()?.as_bytes(), b"\x04\x20\xc4");
1163        assert!("BCDE=".parse::<B64>().is_err());
1164        assert!("BCDE==".parse::<B64>().is_err());
1165        Ok(())
1166    }
1167
1168    #[test]
1169    fn base64_rev() {
1170        use base64ct::{Base64, Base64Unpadded};
1171
1172        // Check that strings that we accept are precisely ones which
1173        // can be generated by either Base64 or Base64Unpadded
1174        for n in 0..=5 {
1175            for c_vec in std::iter::repeat_n("ACEQg/=".chars(), n).multi_cartesian_product() {
1176                let s: String = c_vec.into_iter().collect();
1177                #[allow(clippy::print_stderr)]
1178                let b = match s.parse::<B64>() {
1179                    Ok(b) => {
1180                        eprintln!("{:10} {:?}", &s, b.as_bytes());
1181                        b
1182                    }
1183                    Err(_) => {
1184                        eprintln!("{:10} Err", &s);
1185                        continue;
1186                    }
1187                };
1188                let b = b.as_bytes();
1189
1190                let ep = Base64::encode_string(b);
1191                let eu = Base64Unpadded::encode_string(b);
1192
1193                assert!(
1194                    s == ep || s == eu,
1195                    "{:?} decoded to {:?} giving neither {:?} nor {:?}",
1196                    s,
1197                    b,
1198                    ep,
1199                    eu
1200                );
1201            }
1202        }
1203    }
1204
1205    #[test]
1206    fn base16() -> Result<()> {
1207        assert_eq!("332e313432".parse::<B16>()?.as_bytes(), &b"3.142"[..]);
1208        assert_eq!("332E313432".parse::<B16>()?.as_bytes(), &b"3.142"[..]);
1209        assert_eq!("332E3134".parse::<B16>()?.as_bytes(), &b"3.14"[..]);
1210        assert!("332E313".parse::<B16>().is_err());
1211        assert!("332G3134".parse::<B16>().is_err());
1212        Ok(())
1213    }
1214
1215    #[test]
1216    fn curve25519() -> Result<()> {
1217        use tor_llcrypto::pk::curve25519::PublicKey;
1218        let k1 = "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8=";
1219        let k2 = hex::decode("a69c2d8475d6f245c3d1ff5f13b50f62c38002ee2e8f9391c12a2608cc4a933f")
1220            .unwrap();
1221        let k2: &[u8; 32] = &k2[..].try_into().unwrap();
1222
1223        let k1: PublicKey = k1.parse::<Curve25519Public>()?.into();
1224        assert_eq!(k1, (*k2).into());
1225
1226        assert!(
1227            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz"
1228                .parse::<Curve25519Public>()
1229                .is_err()
1230        );
1231        assert!(
1232            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORSomCMxKkz"
1233                .parse::<Curve25519Public>()
1234                .is_err()
1235        );
1236        assert!(
1237            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5wSomCMxKkz"
1238                .parse::<Curve25519Public>()
1239                .is_err()
1240        );
1241        assert!(
1242            "ppwthHXW8kXD0f9fE7UPYsOAAu4ORwSomCMxKkz"
1243                .parse::<Curve25519Public>()
1244                .is_err()
1245        );
1246
1247        Ok(())
1248    }
1249
1250    #[test]
1251    fn ed25519() -> Result<()> {
1252        use tor_llcrypto::pk::ed25519::Ed25519Identity;
1253        let k1 = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0";
1254        let k2 = hex::decode("59520f43ca00ac0a8b6385f391ca48388e94f0ab14247050846f120b9eeaaeed")
1255            .unwrap();
1256
1257        let k1: Ed25519Identity = k1.parse::<Ed25519Public>()?.into();
1258        assert_eq!(k1, Ed25519Identity::from_bytes(&k2).unwrap());
1259
1260        assert!(
1261            "WVIPQ8oArAqLY4Xzk0!!!!8KsUJHBQhG8SC57qru"
1262                .parse::<Ed25519Public>()
1263                .is_err()
1264        );
1265        assert!(
1266            "WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qru"
1267                .parse::<Ed25519Public>()
1268                .is_err()
1269        );
1270        assert!(
1271            "WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qr"
1272                .parse::<Ed25519Public>()
1273                .is_err()
1274        );
1275        // right length, bad key:
1276        assert!(
1277            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
1278                .parse::<Curve25519Public>()
1279                .is_err()
1280        );
1281        Ok(())
1282    }
1283
1284    #[test]
1285    fn time() -> Result<()> {
1286        use humantime::parse_rfc3339;
1287        use std::time::SystemTime;
1288
1289        let t = "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?;
1290        let t: SystemTime = t.into();
1291        assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
1292
1293        assert!("2020-FF-29 13:36:33".parse::<Iso8601TimeSp>().is_err());
1294        assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeSp>().is_err());
1295        assert!("2020-09-29".parse::<Iso8601TimeSp>().is_err());
1296        assert!("too bad, waluigi time".parse::<Iso8601TimeSp>().is_err());
1297
1298        assert_eq!(
1299            "2020-09-29 13:36:33",
1300            "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?.to_string()
1301        );
1302
1303        let t = "2020-09-29T13:36:33".parse::<Iso8601TimeNoSp>()?;
1304        let t: SystemTime = t.into();
1305        assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
1306
1307        assert!("2020-09-29 13:36:33".parse::<Iso8601TimeNoSp>().is_err());
1308        assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeNoSp>().is_err());
1309        assert!("2020-09-29".parse::<Iso8601TimeNoSp>().is_err());
1310        assert!("too bad, waluigi time".parse::<Iso8601TimeNoSp>().is_err());
1311
1312        assert_eq!(
1313            "2020-09-29T13:36:33",
1314            "2020-09-29T13:36:33"
1315                .parse::<Iso8601TimeNoSp>()?
1316                .to_string()
1317        );
1318
1319        Ok(())
1320    }
1321
1322    #[test]
1323    fn rsa_public_key() {
1324        // Taken from a chutney network.
1325        let key_b64 = r#"
1326        MIIBigKCAYEAsDkzTcKS4kAF56R2ijb9qCek53tKC1EwMdpWMk58bB28fY6kHc55
1327        E7n1hB+LC5neZlx88GKuZ9k8P3g0MlO5ejalcfBdIIm28Nz86JXf/L23YnEpxnG/
1328        IpxZEcmx/EYN+vwp72W3DGuzyntaoaut6lGJk+O/aRCLLcTm4MNznvN1ackK2H6b
1329        Xm2ejRwtVRLoPKODJiPGl43snCfXXWsMH3IALFOgm0szPLv2fAJzBI8VWrUN81M/
1330        lgwJhG6+xbr1CkrXI5fKs/TNr0B0ydC9BIZplmPrnXaeNklnw1cqUJ1oxDSgBrvx
1331        rpDo7paObjSPV26opa68QKGa7Gu2MZQC3RzViNCbawka/108g6hSUkoM+Om2oivr
1332        DvtMOs10MjsfibEBVnwEhqnlb/gj3hJkYoGRsCwAyMIaMObHcmAevMJRWAjGCc8T
1333        GMS9dSmg1IZst+U+V2OCcIHXT6wZ1zPsBM0pYKVLCwtewaq1306k0n+ekriEo7eI
1334        FS3Dd/Dx/a6jAgMBAAE=
1335        "#;
1336        let key_bytes = base64_decode_ignore_ws(key_b64).unwrap();
1337        let rsa = RsaPublicParse1Helper::from_vec(key_bytes, Pos::None).unwrap();
1338
1339        let bits = tor_llcrypto::pk::rsa::PublicKey::from(rsa.clone()).bits();
1340        assert_eq!(bits, 3072);
1341
1342        // tests on a valid key
1343        assert!(rsa.clone().check_exponent(65537).is_ok());
1344        assert!(rsa.clone().check_exponent(1337).is_err());
1345        assert!(rsa.clone().check_len_eq(3072).is_ok());
1346        assert!(rsa.clone().check_len(1024..=4096).is_ok());
1347        assert!(rsa.clone().check_len(1024..=1024).is_err());
1348        assert!(rsa.check_len(4096..).is_err());
1349
1350        // A string of bytes that is not an RSA key.
1351        let failure = RsaPublicParse1Helper::from_vec(vec![1, 2, 3], Pos::None);
1352        assert!(failure.is_err());
1353    }
1354
1355    #[cfg(feature = "routerdesc")]
1356    #[test]
1357    fn ed_cert() {
1358        use tor_llcrypto::pk::ed25519::Ed25519Identity;
1359
1360        // From a chutney network.
1361        let cert_b64 = r#"
1362        AQQABwRNAR6m3kq5h8i3wwac+Ti293opoOP8RKGP9MT0WD4Bbz7YAQAgBACGCdys
1363        G7AwsoYMIKenDN6In6ReiGF8jaYoGqmWKDVBdGGMDIZyNIq+VdhgtAB1EyNFHJU1
1364        jGM0ir9dackL+PIsHbzJH8s/P/8RfUsKIL6/ZHbn3nKMxLH/8kjtxp5ScAA=
1365        "#;
1366        let cert_bytes = base64_decode_ignore_ws(cert_b64).unwrap();
1367        // From the cert above.
1368        let right_subject_key: Ed25519Identity = "HqbeSrmHyLfDBpz5OLb3eimg4/xEoY/0xPRYPgFvPtg"
1369            .parse::<Ed25519Public>()
1370            .unwrap()
1371            .into();
1372        // From `ed25519()` test above.
1373        let wrong_subject_key: Ed25519Identity = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0"
1374            .parse::<Ed25519Public>()
1375            .unwrap()
1376            .into();
1377
1378        // decode and check correct type and key
1379        let cert = UnvalidatedEdCert::from_vec(cert_bytes, Pos::None)
1380            .unwrap()
1381            .check_cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)
1382            .unwrap()
1383            .check_subject_key_is(&right_subject_key)
1384            .unwrap();
1385        // check wrong type.
1386        assert!(
1387            cert.clone()
1388                .check_cert_type(tor_cert::CertType::RSA_ID_X509)
1389                .is_err()
1390        );
1391        // check wrong key.
1392        assert!(cert.check_subject_key_is(&wrong_subject_key).is_err());
1393
1394        // Try an invalid object that isn't a certificate.
1395        let failure = UnvalidatedEdCert::from_vec(vec![1, 2, 3], Pos::None);
1396        assert!(failure.is_err());
1397    }
1398
1399    #[test]
1400    fn fingerprint() -> Result<()> {
1401        use tor_llcrypto::pk::rsa::RsaIdentity;
1402        let fp1 = "7467 A97D 19CD 2B4F 2BC0 388A A99C 5E67 710F 847E";
1403        let fp2 = "7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
1404        let fp3 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
1405        let fp4 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E=fred";
1406
1407        let k = hex::decode(fp2).unwrap();
1408        let k = RsaIdentity::from_bytes(&k[..]).unwrap();
1409
1410        assert_eq!(RsaIdentity::from(fp1.parse::<SpFingerprint>()?), k);
1411        assert_eq!(RsaIdentity::from(fp2.parse::<SpFingerprint>()?), k);
1412        assert!(fp3.parse::<SpFingerprint>().is_err());
1413        assert!(fp4.parse::<SpFingerprint>().is_err());
1414
1415        assert!(fp1.parse::<Fingerprint>().is_err());
1416        assert_eq!(RsaIdentity::from(fp2.parse::<Fingerprint>()?), k);
1417        assert!(fp3.parse::<Fingerprint>().is_err());
1418        assert!(fp4.parse::<Fingerprint>().is_err());
1419        assert_eq!(Fingerprint(k).to_string(), fp2);
1420
1421        assert!(fp1.parse::<LongIdent>().is_err());
1422        assert_eq!(RsaIdentity::from(fp2.parse::<LongIdent>()?), k);
1423        assert_eq!(RsaIdentity::from(fp3.parse::<LongIdent>()?), k);
1424        assert_eq!(RsaIdentity::from(fp4.parse::<LongIdent>()?), k);
1425
1426        assert!("xxxx".parse::<Fingerprint>().is_err());
1427        assert!("ffffffffff".parse::<Fingerprint>().is_err());
1428
1429        let fp_b64 = "dGepfRnNK08rwDiKqZxeZ3EPhH4";
1430        assert_eq!(RsaIdentity::from(fp_b64.parse::<Base64Fingerprint>()?), k);
1431        assert_eq!(Base64Fingerprint(k).to_string(), fp_b64);
1432
1433        Ok(())
1434    }
1435
1436    #[test]
1437    fn nickname() -> Result<()> {
1438        let n: Nickname = "Foo".parse()?;
1439        assert_eq!(n.as_str(), "Foo");
1440        assert_eq!(n.to_string(), "Foo");
1441
1442        let word = "Untr1gonometr1cally";
1443        assert_eq!(word.len(), 19);
1444        let long: Nickname = word.parse()?;
1445        assert_eq!(long.as_str(), word);
1446
1447        let too_long = "abcdefghijklmnopqrstuvwxyz";
1448        let not_ascii = "Eyjafjallajรถkull";
1449        let too_short = "";
1450        let other_invalid = "contains space";
1451        assert!(not_ascii.len() <= 19);
1452        assert!(too_long.parse::<Nickname>().is_err());
1453        assert!(not_ascii.parse::<Nickname>().is_err());
1454        assert!(too_short.parse::<Nickname>().is_err());
1455        assert!(other_invalid.parse::<Nickname>().is_err());
1456
1457        Ok(())
1458    }
1459}