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