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(crate) 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(crate) use timeimpl::*;
17
18#[cfg(feature = "dangerous-expose-struct-fields")]
19pub use nickname::Nickname;
20#[cfg(not(feature = "dangerous-expose-struct-fields"))]
21pub(crate) use nickname::Nickname;
22
23/// Describes a value that van be decoded from a bunch of bytes.
24///
25/// Used for decoding the objects between BEGIN and END tags.
26pub(crate) trait FromBytes: Sized {
27    /// Try to parse a value of this type from a byte slice
28    fn from_bytes(b: &[u8], p: crate::Pos) -> crate::Result<Self>;
29    /// Try to parse a value of this type from a vector of bytes,
30    /// and consume that value
31    fn from_vec(v: Vec<u8>, p: crate::Pos) -> crate::Result<Self> {
32        Self::from_bytes(&v[..], p)
33    }
34}
35
36/// Types for decoding base64-encoded values.
37mod b64impl {
38    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
39    use base64ct::{Base64, Base64Unpadded, Encoding};
40    use std::ops::RangeBounds;
41
42    /// A byte array, encoded in base64 with optional padding.
43    pub(crate) struct B64(Vec<u8>);
44
45    impl std::str::FromStr for B64 {
46        type Err = Error;
47        fn from_str(s: &str) -> Result<Self> {
48            let v: core::result::Result<Vec<u8>, base64ct::Error> = match s.len() % 4 {
49                0 => Base64::decode_vec(s),
50                _ => Base64Unpadded::decode_vec(s),
51            };
52            let v = v.map_err(|_| {
53                EK::BadArgument
54                    .with_msg("Invalid base64")
55                    .at_pos(Pos::at(s))
56            })?;
57            Ok(B64(v))
58        }
59    }
60
61    impl B64 {
62        /// Return the byte array from this object.
63        pub(crate) fn as_bytes(&self) -> &[u8] {
64            &self.0[..]
65        }
66        /// Return this object if its length is within the provided bounds
67        /// object, or an error otherwise.
68        pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
69            if bounds.contains(&self.0.len()) {
70                Ok(self)
71            } else {
72                Err(EK::BadObjectVal.with_msg("Invalid length on base64 data"))
73            }
74        }
75
76        /// Try to convert this object into an array of N bytes.
77        ///
78        /// Return an error if the length is wrong.
79        pub(crate) fn into_array<const N: usize>(self) -> Result<[u8; N]> {
80            self.0
81                .try_into()
82                .map_err(|_| EK::BadObjectVal.with_msg("Invalid length on base64 data"))
83        }
84    }
85
86    impl From<B64> for Vec<u8> {
87        fn from(w: B64) -> Vec<u8> {
88            w.0
89        }
90    }
91}
92
93// ============================================================
94
95/// Types for decoding hex-encoded values.
96mod b16impl {
97    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
98
99    /// A byte array encoded in hexadecimal.
100    pub(crate) struct B16(Vec<u8>);
101
102    impl std::str::FromStr for B16 {
103        type Err = Error;
104        fn from_str(s: &str) -> Result<Self> {
105            let bytes = hex::decode(s).map_err(|_| {
106                EK::BadArgument
107                    .at_pos(Pos::at(s))
108                    .with_msg("invalid hexadecimal")
109            })?;
110            Ok(B16(bytes))
111        }
112    }
113
114    impl B16 {
115        /// Return the underlying byte array.
116        #[allow(unused)]
117        pub(crate) fn as_bytes(&self) -> &[u8] {
118            &self.0[..]
119        }
120    }
121
122    impl From<B16> for Vec<u8> {
123        fn from(w: B16) -> Vec<u8> {
124            w.0
125        }
126    }
127}
128
129// ============================================================
130
131/// Types for decoding curve25519 keys
132mod curve25519impl {
133    use super::B64;
134    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
135    use tor_llcrypto::pk::curve25519::PublicKey;
136
137    /// A Curve25519 public key, encoded in base64 with optional padding
138    pub(crate) struct Curve25519Public(PublicKey);
139
140    impl std::str::FromStr for Curve25519Public {
141        type Err = Error;
142        fn from_str(s: &str) -> Result<Self> {
143            let b64: B64 = s.parse()?;
144            let array: [u8; 32] = b64.as_bytes().try_into().map_err(|_| {
145                EK::BadArgument
146                    .at_pos(Pos::at(s))
147                    .with_msg("bad length for curve25519 key.")
148            })?;
149            Ok(Curve25519Public(array.into()))
150        }
151    }
152
153    impl From<Curve25519Public> for PublicKey {
154        fn from(w: Curve25519Public) -> PublicKey {
155            w.0
156        }
157    }
158}
159
160// ============================================================
161
162/// Types for decoding ed25519 keys
163mod ed25519impl {
164    use super::B64;
165    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
166    use tor_llcrypto::pk::ed25519::Ed25519Identity;
167
168    /// An alleged ed25519 public key, encoded in base64 with optional
169    /// padding.
170    pub(crate) struct Ed25519Public(Ed25519Identity);
171
172    impl std::str::FromStr for Ed25519Public {
173        type Err = Error;
174        fn from_str(s: &str) -> Result<Self> {
175            let b64: B64 = s.parse()?;
176            if b64.as_bytes().len() != 32 {
177                return Err(EK::BadArgument
178                    .at_pos(Pos::at(s))
179                    .with_msg("bad length for ed25519 key."));
180            }
181            let key = Ed25519Identity::from_bytes(b64.as_bytes()).ok_or_else(|| {
182                EK::BadArgument
183                    .at_pos(Pos::at(s))
184                    .with_msg("bad value for ed25519 key.")
185            })?;
186            Ok(Ed25519Public(key))
187        }
188    }
189
190    impl From<Ed25519Public> for Ed25519Identity {
191        fn from(pk: Ed25519Public) -> Ed25519Identity {
192            pk.0
193        }
194    }
195}
196
197// ============================================================
198
199/// Types for decoding times and dates
200mod timeimpl {
201    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
202    use std::time::SystemTime;
203    use time::{
204        format_description::FormatItem, macros::format_description, OffsetDateTime,
205        PrimitiveDateTime,
206    };
207
208    /// A wall-clock time, encoded in Iso8601 format with an intervening
209    /// space between the date and time.
210    ///
211    /// (Example: "2020-10-09 17:38:12")
212    #[derive(derive_more::Into, derive_more::From)]
213    pub(crate) struct Iso8601TimeSp(SystemTime);
214
215    /// Formatting object for parsing the space-separated Iso8601 format.
216    const ISO_8601SP_FMT: &[FormatItem] =
217        format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
218
219    impl std::str::FromStr for Iso8601TimeSp {
220        type Err = Error;
221        fn from_str(s: &str) -> Result<Iso8601TimeSp> {
222            let d = PrimitiveDateTime::parse(s, &ISO_8601SP_FMT).map_err(|e| {
223                EK::BadArgument
224                    .at_pos(Pos::at(s))
225                    .with_msg(format!("invalid time: {}", e))
226            })?;
227            Ok(Iso8601TimeSp(d.assume_utc().into()))
228        }
229    }
230
231    /// Formats a SystemTime according to the given format description
232    ///
233    /// Also converts any time::error::format to std::fmt::Error
234    /// so that it can be unwrapped in the Display trait impl
235    fn fmt_with(
236        t: SystemTime,
237        format_desc: &[FormatItem],
238    ) -> core::result::Result<String, std::fmt::Error> {
239        OffsetDateTime::from(t)
240            .format(format_desc)
241            .map_err(|_| std::fmt::Error)
242    }
243
244    impl std::fmt::Display for Iso8601TimeSp {
245        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246            write!(f, "{}", fmt_with(self.0, ISO_8601SP_FMT)?)
247        }
248    }
249
250    /// A wall-clock time, encoded in ISO8601 format without an intervening
251    /// space.
252    ///
253    /// This represents a specific UTC instant (ie an instant in global civil time).
254    /// But it may not be able to represent leap seconds.
255    ///
256    /// The timezone is not included in the string representation; `+0000` is implicit.
257    ///
258    /// (Example: "2020-10-09T17:38:12")
259    #[derive(derive_more::Into, derive_more::From)]
260    pub(crate) struct Iso8601TimeNoSp(SystemTime);
261
262    /// Formatting object for parsing the space-separated Iso8601 format.
263    const ISO_8601NOSP_FMT: &[FormatItem] =
264        format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
265
266    impl std::str::FromStr for Iso8601TimeNoSp {
267        type Err = Error;
268        fn from_str(s: &str) -> Result<Iso8601TimeNoSp> {
269            let d = PrimitiveDateTime::parse(s, &ISO_8601NOSP_FMT).map_err(|e| {
270                EK::BadArgument
271                    .at_pos(Pos::at(s))
272                    .with_msg(format!("invalid time: {}", e))
273            })?;
274            Ok(Iso8601TimeNoSp(d.assume_utc().into()))
275        }
276    }
277
278    impl std::fmt::Display for Iso8601TimeNoSp {
279        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280            write!(f, "{}", fmt_with(self.0, ISO_8601NOSP_FMT)?)
281        }
282    }
283}
284
285/// Types for decoding RSA keys
286mod rsa {
287    use crate::{NetdocErrorKind as EK, Pos, Result};
288    use std::ops::RangeBounds;
289    use tor_llcrypto::pk::rsa::PublicKey;
290
291    /// An RSA public key, as parsed from a base64-encoded object.
292    #[allow(non_camel_case_types)]
293    #[derive(Clone, Debug)]
294    pub(crate) struct RsaPublic(PublicKey, Pos);
295
296    impl From<RsaPublic> for PublicKey {
297        fn from(k: RsaPublic) -> PublicKey {
298            k.0
299        }
300    }
301    impl super::FromBytes for RsaPublic {
302        fn from_bytes(b: &[u8], pos: Pos) -> Result<Self> {
303            let key = PublicKey::from_der(b)
304                .ok_or_else(|| EK::BadObjectVal.with_msg("unable to decode RSA public key"))?;
305            Ok(RsaPublic(key, pos))
306        }
307    }
308    impl RsaPublic {
309        /// Give an error if the exponent of this key is not 'e'
310        pub(crate) fn check_exponent(self, e: u32) -> Result<Self> {
311            if self.0.exponent_is(e) {
312                Ok(self)
313            } else {
314                Err(EK::BadObjectVal
315                    .at_pos(self.1)
316                    .with_msg("invalid RSA exponent"))
317            }
318        }
319        /// Give an error if the length of this key's modulus, in
320        /// bits, is not contained in 'bounds'
321        pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
322            if bounds.contains(&self.0.bits()) {
323                Ok(self)
324            } else {
325                Err(EK::BadObjectVal
326                    .at_pos(self.1)
327                    .with_msg("invalid RSA length"))
328            }
329        }
330        /// Give an error if the length of this key's modulus, in
331        /// bits, is not exactly `n`.
332        pub(crate) fn check_len_eq(self, n: usize) -> Result<Self> {
333            self.check_len(n..=n)
334        }
335    }
336}
337
338/// Types for decoding Ed25519 certificates
339#[cfg(any(feature = "routerdesc", feature = "hs-common"))]
340mod edcert {
341    use crate::{NetdocErrorKind as EK, Pos, Result};
342    use tor_cert::{CertType, Ed25519Cert, KeyUnknownCert};
343    #[cfg(feature = "routerdesc")]
344    use tor_llcrypto::pk::ed25519;
345
346    /// An ed25519 certificate as parsed from a directory object, with
347    /// signature not validated.
348    #[derive(Debug, Clone)]
349    pub(crate) struct UnvalidatedEdCert(KeyUnknownCert, Pos);
350
351    impl super::FromBytes for UnvalidatedEdCert {
352        fn from_bytes(b: &[u8], p: Pos) -> Result<Self> {
353            let cert = Ed25519Cert::decode(b).map_err(|e| {
354                EK::BadObjectVal
355                    .at_pos(p)
356                    .with_msg("Bad certificate")
357                    .with_source(e)
358            })?;
359
360            Ok(Self(cert, p))
361        }
362        fn from_vec(v: Vec<u8>, p: Pos) -> Result<Self> {
363            Self::from_bytes(&v[..], p)
364        }
365    }
366    impl UnvalidatedEdCert {
367        /// Give an error if this certificate's type is not `desired_type`.
368        pub(crate) fn check_cert_type(self, desired_type: CertType) -> Result<Self> {
369            if self.0.peek_cert_type() != desired_type {
370                return Err(EK::BadObjectVal.at_pos(self.1).with_msg(format!(
371                    "bad certificate type {} (wanted {})",
372                    self.0.peek_cert_type(),
373                    desired_type
374                )));
375            }
376            Ok(self)
377        }
378        /// Give an error if this certificate's subject_key is not `pk`
379        #[cfg(feature = "routerdesc")]
380        pub(crate) fn check_subject_key_is(self, pk: &ed25519::Ed25519Identity) -> Result<Self> {
381            if self.0.peek_subject_key().as_ed25519() != Some(pk) {
382                return Err(EK::BadObjectVal
383                    .at_pos(self.1)
384                    .with_msg("incorrect subject key"));
385            }
386            Ok(self)
387        }
388        /// Consume this object and return the inner Ed25519 certificate.
389        pub(crate) fn into_unchecked(self) -> KeyUnknownCert {
390            self.0
391        }
392    }
393}
394
395/// Types for decoding RSA fingerprints
396mod fingerprint {
397    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
398    use tor_llcrypto::pk::rsa::RsaIdentity;
399
400    /// A hex-encoded fingerprint with spaces in it.
401    pub(crate) struct SpFingerprint(RsaIdentity);
402
403    /// A hex-encoded fingerprint with no spaces.
404    pub(crate) struct Fingerprint(RsaIdentity);
405
406    /// A "long identity" in the format used for Family members.
407    pub(crate) struct LongIdent(RsaIdentity);
408
409    impl From<SpFingerprint> for RsaIdentity {
410        fn from(f: SpFingerprint) -> RsaIdentity {
411            f.0
412        }
413    }
414
415    impl From<LongIdent> for RsaIdentity {
416        fn from(f: LongIdent) -> RsaIdentity {
417            f.0
418        }
419    }
420
421    impl From<Fingerprint> for RsaIdentity {
422        fn from(f: Fingerprint) -> RsaIdentity {
423            f.0
424        }
425    }
426
427    /// Helper: parse an identity from a hexadecimal string
428    fn parse_hex_ident(s: &str) -> Result<RsaIdentity> {
429        RsaIdentity::from_hex(s).ok_or_else(|| {
430            EK::BadArgument
431                .at_pos(Pos::at(s))
432                .with_msg("wrong length on fingerprint")
433        })
434    }
435
436    impl std::str::FromStr for SpFingerprint {
437        type Err = Error;
438        fn from_str(s: &str) -> Result<SpFingerprint> {
439            let ident = parse_hex_ident(&s.replace(' ', "")).map_err(|e| e.at_pos(Pos::at(s)))?;
440            Ok(SpFingerprint(ident))
441        }
442    }
443
444    impl std::str::FromStr for Fingerprint {
445        type Err = Error;
446        fn from_str(s: &str) -> Result<Fingerprint> {
447            let ident = parse_hex_ident(s).map_err(|e| e.at_pos(Pos::at(s)))?;
448            Ok(Fingerprint(ident))
449        }
450    }
451
452    impl std::str::FromStr for LongIdent {
453        type Err = Error;
454        fn from_str(mut s: &str) -> Result<LongIdent> {
455            if s.starts_with('$') {
456                s = &s[1..];
457            }
458            if let Some(idx) = s.find(['=', '~']) {
459                s = &s[..idx];
460            }
461            let ident = parse_hex_ident(s)?;
462            Ok(LongIdent(ident))
463        }
464    }
465}
466
467/// A type for relay nicknames
468mod nickname {
469    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
470    use tinystr::TinyAsciiStr;
471
472    /// This is a strange limit, but it comes from Tor.
473    const MAX_NICKNAME_LEN: usize = 19;
474
475    /// The nickname for a Tor relay.
476    ///
477    /// These nicknames are legacy mechanism that's occasionally useful in
478    /// debugging. They should *never* be used to uniquely identify relays;
479    /// nothing prevents two relays from having the same nickname.
480    ///
481    /// Nicknames are required to be ASCII, alphanumeric, and between 1 and 19
482    /// characters inclusive.
483    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
484    #[cfg_attr(feature = "dangerous-expose-struct-fields", visibility::make(pub))]
485    #[derive(Clone, Debug)]
486    pub(crate) struct Nickname(tinystr::TinyAsciiStr<MAX_NICKNAME_LEN>);
487
488    impl Nickname {
489        /// Return a view of this nickname as a string slice.
490        pub(crate) fn as_str(&self) -> &str {
491            self.0.as_str()
492        }
493    }
494
495    impl std::fmt::Display for Nickname {
496        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
497            self.as_str().fmt(f)
498        }
499    }
500
501    impl std::str::FromStr for Nickname {
502        type Err = Error;
503
504        fn from_str(s: &str) -> Result<Self> {
505            let tiny = TinyAsciiStr::from_str(s).map_err(|_| {
506                EK::BadArgument
507                    .at_pos(Pos::at(s))
508                    .with_msg("Invalid nickname")
509            })?;
510
511            if tiny.is_ascii_alphanumeric() && !tiny.is_empty() {
512                Ok(Nickname(tiny))
513            } else {
514                Err(EK::BadArgument
515                    .at_pos(Pos::at(s))
516                    .with_msg("Invalid nickname"))
517            }
518        }
519    }
520}
521
522#[cfg(test)]
523mod test {
524    // @@ begin test lint list maintained by maint/add_warning @@
525    #![allow(clippy::bool_assert_comparison)]
526    #![allow(clippy::clone_on_copy)]
527    #![allow(clippy::dbg_macro)]
528    #![allow(clippy::mixed_attributes_style)]
529    #![allow(clippy::print_stderr)]
530    #![allow(clippy::print_stdout)]
531    #![allow(clippy::single_char_pattern)]
532    #![allow(clippy::unwrap_used)]
533    #![allow(clippy::unchecked_duration_subtraction)]
534    #![allow(clippy::useless_vec)]
535    #![allow(clippy::needless_pass_by_value)]
536    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
537    use itertools::Itertools;
538
539    use base64ct::Encoding;
540
541    use super::*;
542    use crate::{Pos, Result};
543
544    /// Decode s as a multi-line base64 string, ignoring ascii whitespace.
545    fn base64_decode_ignore_ws(s: &str) -> std::result::Result<Vec<u8>, base64ct::Error> {
546        let mut s = s.to_string();
547        s.retain(|c| !c.is_ascii_whitespace());
548        base64ct::Base64::decode_vec(s.as_str())
549    }
550
551    #[test]
552    fn base64() -> Result<()> {
553        // Test parsing success:
554        // Unpadded:
555        assert_eq!("Mi43MTgyOA".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
556        assert!("Mi43MTgyOA".parse::<B64>()?.check_len(7..8).is_ok());
557        assert_eq!("Mg".parse::<B64>()?.as_bytes(), &b"2"[..]);
558        assert!("Mg".parse::<B64>()?.check_len(1..2).is_ok());
559        assert_eq!(
560            "8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
561                .parse::<B64>()?
562                .as_bytes(),
563            "๐Ÿ’๐Ÿ’๐Ÿ’๐Ÿ’๐Ÿ’๐Ÿ’".as_bytes()
564        );
565        assert!("8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
566            .parse::<B64>()?
567            .check_len(24..25)
568            .is_ok());
569        assert!("ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8="
570            .parse::<B64>()?
571            .check_len(32..33)
572            .is_ok());
573        // Padded:
574        assert_eq!("Mi43MTgyOA==".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
575        assert!("Mi43MTgyOA==".parse::<B64>()?.check_len(7..8).is_ok());
576        assert_eq!("Mg==".parse::<B64>()?.as_bytes(), &b"2"[..]);
577        assert!("Mg==".parse::<B64>()?.check_len(1..2).is_ok());
578
579        // Test parsing failures:
580        // Invalid character.
581        assert!("Mi43!!!!!!".parse::<B64>().is_err());
582        // Invalid last character.
583        assert!("Mi".parse::<B64>().is_err());
584        assert!("ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
585            .parse::<B64>()
586            .is_err());
587        // Invalid length.
588        assert!("Mi43MTgyOA".parse::<B64>()?.check_len(8..).is_err());
589        Ok(())
590    }
591
592    #[test]
593    fn base64_lengths() -> Result<()> {
594        assert_eq!("".parse::<B64>()?.as_bytes(), b"");
595        assert!("=".parse::<B64>().is_err());
596        assert!("==".parse::<B64>().is_err());
597        assert!("B".parse::<B64>().is_err());
598        assert!("B=".parse::<B64>().is_err());
599        assert!("B==".parse::<B64>().is_err());
600        assert!("Bg=".parse::<B64>().is_err());
601        assert_eq!("Bg".parse::<B64>()?.as_bytes(), b"\x06");
602        assert_eq!("Bg==".parse::<B64>()?.as_bytes(), b"\x06");
603        assert_eq!("BCg".parse::<B64>()?.as_bytes(), b"\x04\x28");
604        assert_eq!("BCg=".parse::<B64>()?.as_bytes(), b"\x04\x28");
605        assert!("BCg==".parse::<B64>().is_err());
606        assert_eq!("BCDE".parse::<B64>()?.as_bytes(), b"\x04\x20\xc4");
607        assert!("BCDE=".parse::<B64>().is_err());
608        assert!("BCDE==".parse::<B64>().is_err());
609        Ok(())
610    }
611
612    #[test]
613    fn base64_rev() {
614        use base64ct::{Base64, Base64Unpadded};
615
616        // Check that strings that we accept are precisely ones which
617        // can be generated by either Base64 or Base64Unpadded
618        for n in 0..=5 {
619            for c_vec in std::iter::repeat_n("ACEQg/=".chars(), n).multi_cartesian_product() {
620                let s: String = c_vec.into_iter().collect();
621                #[allow(clippy::print_stderr)]
622                let b = match s.parse::<B64>() {
623                    Ok(b) => {
624                        eprintln!("{:10} {:?}", &s, b.as_bytes());
625                        b
626                    }
627                    Err(_) => {
628                        eprintln!("{:10} Err", &s);
629                        continue;
630                    }
631                };
632                let b = b.as_bytes();
633
634                let ep = Base64::encode_string(b);
635                let eu = Base64Unpadded::encode_string(b);
636
637                assert!(
638                    s == ep || s == eu,
639                    "{:?} decoded to {:?} giving neither {:?} nor {:?}",
640                    s,
641                    b,
642                    ep,
643                    eu
644                );
645            }
646        }
647    }
648
649    #[test]
650    fn base16() -> Result<()> {
651        assert_eq!("332e313432".parse::<B16>()?.as_bytes(), &b"3.142"[..]);
652        assert_eq!("332E313432".parse::<B16>()?.as_bytes(), &b"3.142"[..]);
653        assert_eq!("332E3134".parse::<B16>()?.as_bytes(), &b"3.14"[..]);
654        assert!("332E313".parse::<B16>().is_err());
655        assert!("332G3134".parse::<B16>().is_err());
656        Ok(())
657    }
658
659    #[test]
660    fn curve25519() -> Result<()> {
661        use tor_llcrypto::pk::curve25519::PublicKey;
662        let k1 = "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8=";
663        let k2 = hex::decode("a69c2d8475d6f245c3d1ff5f13b50f62c38002ee2e8f9391c12a2608cc4a933f")
664            .unwrap();
665        let k2: &[u8; 32] = &k2[..].try_into().unwrap();
666
667        let k1: PublicKey = k1.parse::<Curve25519Public>()?.into();
668        assert_eq!(k1, (*k2).into());
669
670        assert!("ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz"
671            .parse::<Curve25519Public>()
672            .is_err());
673        assert!("ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORSomCMxKkz"
674            .parse::<Curve25519Public>()
675            .is_err());
676        assert!("ppwthHXW8kXD0f9fE7UPYsOAAu4uj5wSomCMxKkz"
677            .parse::<Curve25519Public>()
678            .is_err());
679        assert!("ppwthHXW8kXD0f9fE7UPYsOAAu4ORwSomCMxKkz"
680            .parse::<Curve25519Public>()
681            .is_err());
682
683        Ok(())
684    }
685
686    #[test]
687    fn ed25519() -> Result<()> {
688        use tor_llcrypto::pk::ed25519::Ed25519Identity;
689        let k1 = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0";
690        let k2 = hex::decode("59520f43ca00ac0a8b6385f391ca48388e94f0ab14247050846f120b9eeaaeed")
691            .unwrap();
692
693        let k1: Ed25519Identity = k1.parse::<Ed25519Public>()?.into();
694        assert_eq!(k1, Ed25519Identity::from_bytes(&k2).unwrap());
695
696        assert!("WVIPQ8oArAqLY4Xzk0!!!!8KsUJHBQhG8SC57qru"
697            .parse::<Ed25519Public>()
698            .is_err());
699        assert!("WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qru"
700            .parse::<Ed25519Public>()
701            .is_err());
702        assert!("WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qr"
703            .parse::<Ed25519Public>()
704            .is_err());
705        // right length, bad key:
706        assert!("ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
707            .parse::<Curve25519Public>()
708            .is_err());
709        Ok(())
710    }
711
712    #[test]
713    fn time() -> Result<()> {
714        use humantime::parse_rfc3339;
715        use std::time::SystemTime;
716
717        let t = "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?;
718        let t: SystemTime = t.into();
719        assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
720
721        assert!("2020-FF-29 13:36:33".parse::<Iso8601TimeSp>().is_err());
722        assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeSp>().is_err());
723        assert!("2020-09-29".parse::<Iso8601TimeSp>().is_err());
724        assert!("too bad, waluigi time".parse::<Iso8601TimeSp>().is_err());
725
726        assert_eq!(
727            "2020-09-29 13:36:33",
728            "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?.to_string()
729        );
730
731        let t = "2020-09-29T13:36:33".parse::<Iso8601TimeNoSp>()?;
732        let t: SystemTime = t.into();
733        assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
734
735        assert!("2020-09-29 13:36:33".parse::<Iso8601TimeNoSp>().is_err());
736        assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeNoSp>().is_err());
737        assert!("2020-09-29".parse::<Iso8601TimeNoSp>().is_err());
738        assert!("too bad, waluigi time".parse::<Iso8601TimeNoSp>().is_err());
739
740        assert_eq!(
741            "2020-09-29T13:36:33",
742            "2020-09-29T13:36:33"
743                .parse::<Iso8601TimeNoSp>()?
744                .to_string()
745        );
746
747        Ok(())
748    }
749
750    #[test]
751    fn rsa_public_key() {
752        // Taken from a chutney network.
753        let key_b64 = r#"
754        MIIBigKCAYEAsDkzTcKS4kAF56R2ijb9qCek53tKC1EwMdpWMk58bB28fY6kHc55
755        E7n1hB+LC5neZlx88GKuZ9k8P3g0MlO5ejalcfBdIIm28Nz86JXf/L23YnEpxnG/
756        IpxZEcmx/EYN+vwp72W3DGuzyntaoaut6lGJk+O/aRCLLcTm4MNznvN1ackK2H6b
757        Xm2ejRwtVRLoPKODJiPGl43snCfXXWsMH3IALFOgm0szPLv2fAJzBI8VWrUN81M/
758        lgwJhG6+xbr1CkrXI5fKs/TNr0B0ydC9BIZplmPrnXaeNklnw1cqUJ1oxDSgBrvx
759        rpDo7paObjSPV26opa68QKGa7Gu2MZQC3RzViNCbawka/108g6hSUkoM+Om2oivr
760        DvtMOs10MjsfibEBVnwEhqnlb/gj3hJkYoGRsCwAyMIaMObHcmAevMJRWAjGCc8T
761        GMS9dSmg1IZst+U+V2OCcIHXT6wZ1zPsBM0pYKVLCwtewaq1306k0n+ekriEo7eI
762        FS3Dd/Dx/a6jAgMBAAE=
763        "#;
764        let key_bytes = base64_decode_ignore_ws(key_b64).unwrap();
765        let rsa = RsaPublic::from_vec(key_bytes, Pos::None).unwrap();
766
767        let bits = tor_llcrypto::pk::rsa::PublicKey::from(rsa.clone()).bits();
768        assert_eq!(bits, 3072);
769
770        // tests on a valid key
771        assert!(rsa.clone().check_exponent(65537).is_ok());
772        assert!(rsa.clone().check_exponent(1337).is_err());
773        assert!(rsa.clone().check_len_eq(3072).is_ok());
774        assert!(rsa.clone().check_len(1024..=4096).is_ok());
775        assert!(rsa.clone().check_len(1024..=1024).is_err());
776        assert!(rsa.check_len(4096..).is_err());
777
778        // A string of bytes that is not an RSA key.
779        let failure = RsaPublic::from_vec(vec![1, 2, 3], Pos::None);
780        assert!(failure.is_err());
781    }
782
783    #[cfg(feature = "routerdesc")]
784    #[test]
785    fn ed_cert() {
786        use tor_llcrypto::pk::ed25519::Ed25519Identity;
787
788        // From a chutney network.
789        let cert_b64 = r#"
790        AQQABwRNAR6m3kq5h8i3wwac+Ti293opoOP8RKGP9MT0WD4Bbz7YAQAgBACGCdys
791        G7AwsoYMIKenDN6In6ReiGF8jaYoGqmWKDVBdGGMDIZyNIq+VdhgtAB1EyNFHJU1
792        jGM0ir9dackL+PIsHbzJH8s/P/8RfUsKIL6/ZHbn3nKMxLH/8kjtxp5ScAA=
793        "#;
794        let cert_bytes = base64_decode_ignore_ws(cert_b64).unwrap();
795        // From the cert above.
796        let right_subject_key: Ed25519Identity = "HqbeSrmHyLfDBpz5OLb3eimg4/xEoY/0xPRYPgFvPtg"
797            .parse::<Ed25519Public>()
798            .unwrap()
799            .into();
800        // From `ed25519()` test above.
801        let wrong_subject_key: Ed25519Identity = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0"
802            .parse::<Ed25519Public>()
803            .unwrap()
804            .into();
805
806        // decode and check correct type and key
807        let cert = UnvalidatedEdCert::from_vec(cert_bytes, Pos::None)
808            .unwrap()
809            .check_cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)
810            .unwrap()
811            .check_subject_key_is(&right_subject_key)
812            .unwrap();
813        // check wrong type.
814        assert!(cert
815            .clone()
816            .check_cert_type(tor_cert::CertType::RSA_ID_X509)
817            .is_err());
818        // check wrong key.
819        assert!(cert.check_subject_key_is(&wrong_subject_key).is_err());
820
821        // Try an invalid object that isn't a certificate.
822        let failure = UnvalidatedEdCert::from_vec(vec![1, 2, 3], Pos::None);
823        assert!(failure.is_err());
824    }
825
826    #[test]
827    fn fingerprint() -> Result<()> {
828        use tor_llcrypto::pk::rsa::RsaIdentity;
829        let fp1 = "7467 A97D 19CD 2B4F 2BC0 388A A99C 5E67 710F 847E";
830        let fp2 = "7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
831        let fp3 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
832        let fp4 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E=fred";
833
834        let k = hex::decode(fp2).unwrap();
835        let k = RsaIdentity::from_bytes(&k[..]).unwrap();
836
837        assert_eq!(RsaIdentity::from(fp1.parse::<SpFingerprint>()?), k);
838        assert_eq!(RsaIdentity::from(fp2.parse::<SpFingerprint>()?), k);
839        assert!(fp3.parse::<SpFingerprint>().is_err());
840        assert!(fp4.parse::<SpFingerprint>().is_err());
841
842        assert!(fp1.parse::<Fingerprint>().is_err());
843        assert_eq!(RsaIdentity::from(fp2.parse::<Fingerprint>()?), k);
844        assert!(fp3.parse::<Fingerprint>().is_err());
845        assert!(fp4.parse::<Fingerprint>().is_err());
846
847        assert!(fp1.parse::<LongIdent>().is_err());
848        assert_eq!(RsaIdentity::from(fp2.parse::<LongIdent>()?), k);
849        assert_eq!(RsaIdentity::from(fp3.parse::<LongIdent>()?), k);
850        assert_eq!(RsaIdentity::from(fp4.parse::<LongIdent>()?), k);
851
852        assert!("xxxx".parse::<Fingerprint>().is_err());
853        assert!("ffffffffff".parse::<Fingerprint>().is_err());
854        Ok(())
855    }
856
857    #[test]
858    fn nickname() -> Result<()> {
859        let n: Nickname = "Foo".parse()?;
860        assert_eq!(n.as_str(), "Foo");
861        assert_eq!(n.to_string(), "Foo");
862
863        let word = "Untr1gonometr1cally";
864        assert_eq!(word.len(), 19);
865        let long: Nickname = word.parse()?;
866        assert_eq!(long.as_str(), word);
867
868        let too_long = "abcdefghijklmnopqrstuvwxyz";
869        let not_ascii = "Eyjafjallajรถkull";
870        let too_short = "";
871        let other_invalid = "contains space";
872        assert!(not_ascii.len() <= 19);
873        assert!(too_long.parse::<Nickname>().is_err());
874        assert!(not_ascii.parse::<Nickname>().is_err());
875        assert!(too_short.parse::<Nickname>().is_err());
876        assert!(other_invalid.parse::<Nickname>().is_err());
877
878        Ok(())
879    }
880}