Skip to main content

tor_linkspec/
ids.rs

1//! Code to abstract over the notion of relays having one or more identities.
2//!
3//! Currently (2022), every Tor relay has exactly two identities: A legacy
4//! identity that is based on the SHA-1 hash of an RSA-1024 public key, and a
5//! modern identity that is an Ed25519 public key.  This code lets us abstract
6//! over those types, and over other new types that may exist in the future.
7
8use std::fmt;
9
10use derive_deftly::{Deftly, define_derive_deftly};
11use derive_more::{Display, From};
12use safelog::Redactable;
13use tor_llcrypto::pk::{
14    ed25519::{ED25519_ID_LEN, Ed25519Identity},
15    rsa::{RSA_ID_LEN, RsaIdentity},
16};
17
18pub(crate) mod by_id;
19pub(crate) mod set;
20
21/// The type of a relay identity.
22///
23#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] //
24#[derive(Display, strum::EnumIter, strum::EnumCount, Deftly)]
25#[derive_deftly_adhoc]
26#[derive_deftly(RelayId)]
27#[non_exhaustive]
28pub enum RelayIdType {
29    /// An Ed25519 identity.
30    ///
31    /// Every relay (currently) has one of these identities. It is the same
32    /// as the encoding of the relay's public Ed25519 identity key.
33    #[display("Ed25519")] // Display of this enum variant, ie of just the id type
34    #[deftly(display_id = "ed25519:{}")] // Display of a relay id value of this type
35    Ed25519,
36    /// An RSA identity.
37    ///
38    /// Every relay (currently) has one of these identities.  It is computed as
39    /// a SHA-1 digest of the DER encoding of the relay's public RSA 1024-bit
40    /// identity key.  Because of short key length, this type of identity should
41    /// not be considered secure on its own.
42    #[display("RSA (legacy)")]
43    #[deftly(display_id = "{}")]
44    Rsa,
45}
46
47impl fmt::Display for RelayId {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        fmt::Display::fmt(&self.as_ref(), f)
50    }
51}
52
53define_derive_deftly! {
54    /// Derives `enum RelayId`, `enum RelayIdRef`, and many impls
55    RelayId expect items, beta_deftly:
56
57    ${define IDENTITY $<$vname Identity>}
58
59    /// A single relay identity.
60    #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, From, Hash)]
61    #[non_exhaustive]
62    pub enum RelayId {
63        $(
64            ${vattrs doc}
65            $vname($IDENTITY),
66        )
67    }
68
69    /// A reference to a single relay identity.
70    #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] //
71    #[derive(Display, From, derive_more::TryInto)]
72    #[non_exhaustive]
73    pub enum RelayIdRef<'a> {
74        $(
75            ${vattrs doc}
76            #[display(${vmeta(display_id) as str}, _0)]
77            $vname(&'a $IDENTITY),
78        )
79    }
80
81    impl RelayIdType {
82        /// The number of distinct types currently implemented.
83        pub const COUNT: usize = <RelayIdType as strum::EnumCount>::COUNT;
84
85        /// Return an iterator over all
86        pub fn all_types() -> RelayIdTypeIter {
87            use strum::IntoEnumIterator;
88            Self::iter()
89        }
90
91        /// Return the length of this identity, in bytes.
92        pub fn id_len(&self) -> usize {
93            match self { $(
94                $vtype => ${shouty_snake_case $vname _ID_LEN},
95            ) }
96        }
97    }
98
99    impl RelayId {
100        /// Return a [`RelayIdRef`] pointing to the contents of this identity.
101        pub fn as_ref(&self) -> RelayIdRef<'_> {
102            match self { $(
103                RelayId::$vname(key) => key.into(),
104            ) }
105        }
106
107        /// Try to construct a RelayId of a provided `id_type` from a byte-slice.
108        ///
109        /// Return [`RelayIdError::BadLength`] if the slice is not the correct length for the key.
110        pub fn from_type_and_bytes(id_type: RelayIdType, id: &[u8]) -> Result<Self, RelayIdError> {
111            Ok(match id_type { $(
112                $vtype => $IDENTITY::from_bytes(id)
113                    .ok_or(RelayIdError::BadLength)?
114                    .into(),
115            ) })
116        }
117
118        /// Return the type of this relay identity.
119        pub fn id_type(&self) -> RelayIdType {
120            self.as_ref().id_type()
121        }
122
123        /// Return a byte-slice corresponding to the contents of this identity.
124        ///
125        /// The return value discards the type of the identity, and so should be
126        /// handled with care to make sure that it does not get confused with an
127        /// identity of some other type.
128        pub fn as_bytes(&self) -> &[u8] {
129            self.as_ref().as_bytes()
130        }
131    }
132
133    impl<'a> RelayIdRef<'a> {
134        /// Copy this reference into a new [`RelayId`] object.
135        //
136        // TODO(nickm): I wish I could make this a proper `ToOwned` implementation,
137        // but I see no way to do as long as RelayIdRef<'a> implements Clone too.
138        pub fn to_owned(&self) -> RelayId {
139            match *self { $(
140                RelayIdRef::$vname(key) => (*key).into(),
141            ) }
142        }
143
144        /// Return the type of this relay identity.
145        pub fn id_type(&self) -> RelayIdType {
146            match self { $(
147                RelayIdRef::$vname(_) => $vtype,
148            ) }
149        }
150
151        /// Return a byte-slice corresponding to the contents of this identity.
152        pub fn as_bytes(&self) -> &'a [u8] {
153            match self { $(
154                RelayIdRef::$vname(key) => key.as_bytes(),
155            ) }
156        }
157
158      $(
159       $/// Extract the `$IDENTITY` from a RelayIdRef that is known to hold one.
160        ///
161        /// # Panics
162        ///
163        /// Panics if this is not an `$vname` identity.
164        pub(crate) fn ${snake_case unwrap_ $vname}(self) -> &'a $IDENTITY {
165            match self {
166                RelayIdRef::$vname(key) => key,
167                _ => panic!($"Not an $vname identity."),
168            }
169        }
170      )
171    }
172
173  $(
174    impl<'a> PartialEq<$IDENTITY> for RelayIdRef<'a> {
175        fn eq(&self, other: &$IDENTITY) -> bool {
176            matches!(self, RelayIdRef::$vname(this) if this == &other)
177        }
178    }
179    impl PartialEq<$IDENTITY> for RelayId {
180        fn eq(&self, other: &$IDENTITY) -> bool {
181            self.as_ref() == *other
182        }
183    }
184  )
185}
186#[allow(clippy::single_component_path_imports)] // rust-clippy/issues/13419
187use derive_deftly_template_RelayId; // allows putting the macro after RelayIdType
188
189impl<'a> From<&'a RelayId> for RelayIdRef<'a> {
190    fn from(ident: &'a RelayId) -> Self {
191        ident.as_ref()
192    }
193}
194
195impl Redactable for RelayId {
196    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        self.as_ref().display_redacted(f)
198    }
199
200    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        self.as_ref().debug_redacted(f)
202    }
203}
204
205impl<'a> Redactable for RelayIdRef<'a> {
206    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        match self {
208            RelayIdRef::Ed25519(k) => write!(f, "ed25519:{}", k.redacted()),
209            RelayIdRef::Rsa(k) => write!(f, "${}", k.redacted()),
210        }
211    }
212
213    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        use std::fmt::Debug;
215        match self {
216            RelayIdRef::Ed25519(k) => Debug::fmt(*k.redacted(), f),
217            RelayIdRef::Rsa(k) => Debug::fmt(*k.redacted(), f),
218        }
219    }
220}
221
222impl std::str::FromStr for RelayIdType {
223    type Err = RelayIdError;
224
225    fn from_str(s: &str) -> Result<Self, Self::Err> {
226        if s.eq_ignore_ascii_case("rsa") {
227            Ok(RelayIdType::Rsa)
228        } else if s.eq_ignore_ascii_case("ed25519") {
229            Ok(RelayIdType::Ed25519)
230        } else {
231            Err(RelayIdError::UnrecognizedIdType)
232        }
233    }
234}
235
236impl std::str::FromStr for RelayId {
237    type Err = RelayIdError;
238
239    /// Try to parse `s` as a RelayId.
240    ///
241    /// We use the following format, based on the one used by C tor.
242    ///
243    /// * An optional `$` followed by a 40 byte hex string is always an RSA key.
244    /// * A 43 character un-padded base-64 string is always an Ed25519 key.
245    /// * The name of an algorithm ("rsa" or "ed25519"), followed by a colon and
246    ///   and an un-padded base-64 string is a key of that type.
247    fn from_str(s: &str) -> Result<Self, Self::Err> {
248        use base64ct::{Base64Unpadded, Encoding as _};
249        if let Some((alg, key)) = s.split_once(':') {
250            let alg: RelayIdType = alg.parse()?;
251            let len = alg.id_len();
252            let mut v = vec![0_u8; len];
253            let bytes = Base64Unpadded::decode(key, &mut v[..])?;
254            RelayId::from_type_and_bytes(alg, bytes)
255        } else if s.len() == RSA_ID_LEN * 2 || s.starts_with('$') {
256            let s = s.trim_start_matches('$');
257            let bytes = hex::decode(s).map_err(|_| RelayIdError::BadHex)?;
258            RelayId::from_type_and_bytes(RelayIdType::Rsa, &bytes)
259        } else {
260            let mut v = [0_u8; ED25519_ID_LEN];
261            let bytes = Base64Unpadded::decode(s, &mut v[..])?;
262            RelayId::from_type_and_bytes(RelayIdType::Ed25519, bytes)
263        }
264    }
265}
266
267impl serde::Serialize for RelayId {
268    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
269    where
270        S: serde::Serializer,
271    {
272        self.as_ref().serialize(serializer)
273    }
274}
275impl<'a> serde::Serialize for RelayIdRef<'a> {
276    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
277    where
278        S: serde::Serializer,
279    {
280        // TODO(nickm): maybe encode this as bytes when dealing with
281        // non-human-readable formats.
282        self.to_string().serialize(serializer)
283    }
284}
285
286impl<'de> serde::Deserialize<'de> for RelayId {
287    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
288    where
289        D: serde::Deserializer<'de>,
290    {
291        // TODO(nickm): maybe allow bytes when dealing with non-human-readable
292        // formats.
293        use serde::de::Error as _;
294        let s = <std::borrow::Cow<'_, str> as serde::Deserialize>::deserialize(deserializer)?;
295        s.parse()
296            .map_err(|e: RelayIdError| D::Error::custom(e.to_string()))
297    }
298}
299
300/// An error returned while trying to parse a RelayId.
301#[derive(Clone, Debug, thiserror::Error)]
302#[non_exhaustive]
303pub enum RelayIdError {
304    /// We didn't recognize the type of a relay identity.
305    ///
306    /// This can happen when a type that we have never heard of is specified, or when a type
307    #[error("Unrecognized type for relay identity")]
308    UnrecognizedIdType,
309    /// We encountered base64 data that we couldn't parse.
310    #[error("Invalid base64 data")]
311    BadBase64,
312    /// We encountered hex data that we couldn't parse.
313    #[error("Invalid hexadecimal data")]
314    BadHex,
315    /// We got a key that was the wrong length.
316    #[error("Invalid length for relay identity")]
317    BadLength,
318}
319
320impl From<base64ct::Error> for RelayIdError {
321    fn from(err: base64ct::Error) -> Self {
322        match err {
323            base64ct::Error::InvalidEncoding => RelayIdError::BadBase64,
324            base64ct::Error::InvalidLength => RelayIdError::BadLength,
325        }
326    }
327}
328
329#[cfg(test)]
330mod test {
331    // @@ begin test lint list maintained by maint/add_warning @@
332    #![allow(clippy::bool_assert_comparison)]
333    #![allow(clippy::clone_on_copy)]
334    #![allow(clippy::dbg_macro)]
335    #![allow(clippy::mixed_attributes_style)]
336    #![allow(clippy::print_stderr)]
337    #![allow(clippy::print_stdout)]
338    #![allow(clippy::single_char_pattern)]
339    #![allow(clippy::unwrap_used)]
340    #![allow(clippy::unchecked_time_subtraction)]
341    #![allow(clippy::useless_vec)]
342    #![allow(clippy::needless_pass_by_value)]
343    #![allow(clippy::string_slice)] // See arti#2571
344    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
345    use hex_literal::hex;
346    use serde_test::{Token, assert_tokens};
347    use std::str::FromStr;
348
349    use super::*;
350
351    #[test]
352    fn parse_and_display() -> Result<(), RelayIdError> {
353        fn normalizes_to(s: &str, expected: &str) -> Result<(), RelayIdError> {
354            let k: RelayId = s.parse()?;
355            let s2 = k.to_string();
356            assert_eq!(s2, expected);
357            let k2: RelayId = s2.parse()?;
358            let s3 = k2.to_string();
359            assert_eq!(s3, s2);
360            let s4 = k2.as_ref().to_string();
361            assert_eq!(s4, s3);
362            Ok(())
363        }
364        fn check(s: &str) -> Result<(), RelayIdError> {
365            normalizes_to(s, s)
366        }
367
368        // Try a few RSA identities.
369        check("$1234567812345678123456781234567812345678")?;
370        normalizes_to(
371            "abcdefabcdefabcdefabcdefabcdef1234567890",
372            "$abcdefabcdefabcdefabcdefabcdef1234567890",
373        )?;
374        normalizes_to(
375            "abcdefabcdefABCDEFabcdefabcdef1234567890",
376            "$abcdefabcdefabcdefabcdefabcdef1234567890",
377        )?;
378        normalizes_to(
379            "rsa:q83vq83vq83vq83vq83vEjRWeJA",
380            "$abcdefabcdefabcdefabcdefabcdef1234567890",
381        )?;
382
383        // Try a few ed25519 identities
384        check("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")?;
385        normalizes_to(
386            "dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
387            "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
388        )?;
389
390        Ok(())
391    }
392
393    #[test]
394    fn parse_fail() {
395        use std::str::FromStr;
396        let e = RelayId::from_str("tooshort").unwrap_err();
397        assert!(matches!(e, RelayIdError::BadLength));
398
399        let e = RelayId::from_str("this_string_is_40_bytes_but_it_isnt_hex!").unwrap_err();
400        assert!(matches!(e, RelayIdError::BadHex));
401
402        let e = RelayId::from_str("merkle-hellman:bestavoided").unwrap_err();
403        assert!(matches!(e, RelayIdError::UnrecognizedIdType));
404
405        let e = RelayId::from_str("ed25519:q83vq83vq83vq83vq83vEjRWeJA").unwrap_err();
406        assert!(matches!(e, RelayIdError::BadLength));
407
408        let e = RelayId::from_str("ed25519:🤨🤨🤨🤨🤨").unwrap_err();
409        assert!(matches!(e, RelayIdError::BadBase64));
410    }
411
412    #[test]
413    fn types() {
414        assert_eq!(
415            RelayId::from_str("$1234567812345678123456781234567812345678")
416                .unwrap()
417                .id_type(),
418            RelayIdType::Rsa,
419        );
420        assert_eq!(
421            RelayId::from_str("$1234567812345678123456781234567812345678")
422                .unwrap()
423                .as_ref()
424                .id_type(),
425            RelayIdType::Rsa,
426        );
427
428        assert_eq!(
429            RelayId::from_str("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")
430                .unwrap()
431                .id_type(),
432            RelayIdType::Ed25519,
433        );
434
435        assert_eq!(
436            RelayId::from_str("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")
437                .unwrap()
438                .as_ref()
439                .id_type(),
440            RelayIdType::Ed25519,
441        );
442    }
443
444    #[test]
445    fn equals_other() {
446        let rsa1 = RsaIdentity::from(*b"You just have to kno");
447        let rsa2 = RsaIdentity::from(*b"w who you are and st");
448        let ed1 = Ed25519Identity::from(*b"ay true to that. So I'm going to");
449        let ed2 = Ed25519Identity::from(*b"keep fighting for people the onl");
450
451        assert_eq!(RelayId::from(rsa1), rsa1);
452        assert_ne!(RelayId::from(rsa1), rsa2);
453        assert_ne!(RelayId::from(rsa1), ed1);
454
455        assert_eq!(RelayId::from(ed1), ed1);
456        assert_ne!(RelayId::from(ed1), ed2);
457        assert_ne!(RelayId::from(ed1), rsa1);
458
459        assert_eq!(RelayIdRef::from(&rsa1), rsa1);
460        assert_ne!(RelayIdRef::from(&rsa1), rsa2);
461        assert_ne!(RelayIdRef::from(&rsa1), ed1);
462
463        assert_eq!(RelayIdRef::from(&ed1), ed1);
464        assert_ne!(RelayIdRef::from(&ed1), ed2);
465        assert_ne!(RelayIdRef::from(&ed1), rsa1);
466    }
467    #[test]
468    fn as_bytes() {
469        assert_eq!(
470            RelayId::from_str("$1234567812345678123456781234567812345678")
471                .unwrap()
472                .as_bytes(),
473            hex!("1234567812345678123456781234567812345678"),
474        );
475        assert_eq!(
476            RelayId::from_str("$1234567812345678123456781234567812345678")
477                .unwrap()
478                .as_ref()
479                .as_bytes(),
480            hex!("1234567812345678123456781234567812345678"),
481        );
482
483        assert_eq!(
484            RelayId::from_str("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")
485                .unwrap()
486                .as_bytes(),
487            b"this is incredibly silly!!!!!!!!"
488        );
489        assert_eq!(
490            RelayId::from_str("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")
491                .unwrap()
492                .as_ref()
493                .as_bytes(),
494            b"this is incredibly silly!!!!!!!!"
495        );
496    }
497
498    #[test]
499    fn unwrap_ok() {
500        let rsa = RelayId::from_str("$1234567812345678123456781234567812345678").unwrap();
501        assert_eq!(
502            rsa.as_ref().unwrap_rsa(),
503            &RsaIdentity::from_bytes(&hex!("1234567812345678123456781234567812345678")).unwrap()
504        );
505
506        let ed = RelayId::from_str("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE").unwrap();
507        assert_eq!(
508            ed.as_ref().unwrap_ed25519(),
509            &Ed25519Identity::from_bytes(b"this is incredibly silly!!!!!!!!").unwrap()
510        );
511    }
512
513    #[test]
514    #[should_panic]
515    fn unwrap_rsa_panic() {
516        if let Ok(ed) = RelayId::from_str("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE") {
517            let _nope = RelayIdRef::from(&ed).unwrap_rsa();
518        }
519    }
520
521    #[test]
522    #[should_panic]
523    fn unwrap_ed_panic() {
524        if let Ok(ed) = RelayId::from_str("$1234567812345678123456781234567812345678") {
525            let _nope = RelayIdRef::from(&ed).unwrap_ed25519();
526        }
527    }
528
529    #[test]
530    fn serde_owned() {
531        let rsa1 = RsaIdentity::from(*b"You just have to kno");
532        let ed1 = Ed25519Identity::from(*b"ay true to that. So I'm going to");
533        let keys = vec![RelayId::from(rsa1), RelayId::from(ed1)];
534
535        assert_tokens(
536            &keys,
537            &[
538                Token::Seq { len: Some(2) },
539                Token::String("$596f75206a757374206861766520746f206b6e6f"),
540                Token::String("ed25519:YXkgdHJ1ZSB0byB0aGF0LiBTbyBJJ20gZ29pbmcgdG8"),
541                Token::SeqEnd,
542            ],
543        );
544    }
545}