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

            
8
use std::fmt;
9

            
10
use derive_deftly::{Deftly, define_derive_deftly};
11
use derive_more::{Display, From};
12
use safelog::Redactable;
13
use tor_llcrypto::pk::{
14
    ed25519::{ED25519_ID_LEN, Ed25519Identity},
15
    rsa::{RSA_ID_LEN, RsaIdentity},
16
};
17

            
18
pub(crate) mod by_id;
19
pub(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]
28
pub 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

            
47
impl fmt::Display for RelayId {
48
24
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49
24
        fmt::Display::fmt(&self.as_ref(), f)
50
24
    }
51
}
52

            
53
define_derive_deftly! {
54
    /// Derives `enum RelayId`, `enum RelayIdRef`, and many impls
55
    RelayId expect items, beta_deftly:
56

            
57
56124004
    ${define IDENTITY $<$vname Identity>}
58
56124004

            
59
56124004
    /// A single relay identity.
60
56124004
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, From, Hash)]
61
56124004
    #[non_exhaustive]
62
56124004
    pub enum RelayId {
63
56124004
        $(
64
56124004
            ${vattrs doc}
65
56124004
            $vname($IDENTITY),
66
56124004
        )
67
56124004
    }
68
56124004

            
69
56124004
    /// A reference to a single relay identity.
70
56124004
    #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] //
71
56124004
    #[derive(Display, From, derive_more::TryInto)]
72
56124004
    #[non_exhaustive]
73
56124004
    pub enum RelayIdRef<'a> {
74
56124004
        $(
75
56124004
            ${vattrs doc}
76
56124004
            #[display(${vmeta(display_id) as str}, _0)]
77
56124004
            $vname(&'a $IDENTITY),
78
56124004
        )
79
56124004
    }
80
56124004

            
81
56124004
    impl RelayIdType {
82
56124004
        /// The number of distinct types currently implemented.
83
56124004
        pub const COUNT: usize = <RelayIdType as strum::EnumCount>::COUNT;
84
56124004

            
85
56124004
        /// Return an iterator over all
86
80255352
        pub fn all_types() -> RelayIdTypeIter {
87
56124004
            use strum::IntoEnumIterator;
88
56124004
            Self::iter()
89
56124004
        }
90
56124004

            
91
56124004
        /// Return the length of this identity, in bytes.
92
56124004
        pub fn id_len(&self) -> usize {
93
56124004
            match self { $(
94
56124004
                $vtype => ${shouty_snake_case $vname _ID_LEN},
95
56124004
            ) }
96
56124004
        }
97
56124004
    }
98
56124004

            
99
56124004
    impl RelayId {
100
56124004
        /// Return a [`RelayIdRef`] pointing to the contents of this identity.
101
56124004
        pub fn as_ref(&self) -> RelayIdRef<'_> {
102
56124004
            match self { $(
103
56124004
                RelayId::$vname(key) => key.into(),
104
56124004
            ) }
105
56124004
        }
106
56124004

            
107
56124004
        /// Try to construct a RelayId of a provided `id_type` from a byte-slice.
108
56124004
        ///
109
56124004
        /// Return [`RelayIdError::BadLength`] if the slice is not the correct length for the key.
110
56124004
        pub fn from_type_and_bytes(id_type: RelayIdType, id: &[u8]) -> Result<Self, RelayIdError> {
111
56124004
            Ok(match id_type { $(
112
56124004
                $vtype => $IDENTITY::from_bytes(id)
113
56124004
                    .ok_or(RelayIdError::BadLength)?
114
56124004
                    .into(),
115
56124004
            ) })
116
56124004
        }
117
56124004

            
118
56124004
        /// Return the type of this relay identity.
119
56124004
        pub fn id_type(&self) -> RelayIdType {
120
56124004
            self.as_ref().id_type()
121
56124004
        }
122
56124004

            
123
56124004
        /// Return a byte-slice corresponding to the contents of this identity.
124
56124004
        ///
125
56124004
        /// The return value discards the type of the identity, and so should be
126
56124004
        /// handled with care to make sure that it does not get confused with an
127
56124004
        /// identity of some other type.
128
56124004
        pub fn as_bytes(&self) -> &[u8] {
129
56124004
            self.as_ref().as_bytes()
130
56124004
        }
131
56124004
    }
132
56124004

            
133
56124004
    impl<'a> RelayIdRef<'a> {
134
56124004
        /// Copy this reference into a new [`RelayId`] object.
135
56124004
        //
136
56124004
        // TODO(nickm): I wish I could make this a proper `ToOwned` implementation,
137
56124004
        // but I see no way to do as long as RelayIdRef<'a> implements Clone too.
138
56124004
        pub fn to_owned(&self) -> RelayId {
139
56124004
            match *self { $(
140
56124004
                RelayIdRef::$vname(key) => (*key).into(),
141
56124004
            ) }
142
56124004
        }
143
56124004

            
144
56124004
        /// Return the type of this relay identity.
145
56124004
        pub fn id_type(&self) -> RelayIdType {
146
56124004
            match self { $(
147
56124004
                RelayIdRef::$vname(_) => $vtype,
148
56124004
            ) }
149
56124004
        }
150
56124004

            
151
56124004
        /// Return a byte-slice corresponding to the contents of this identity.
152
56124004
        pub fn as_bytes(&self) -> &'a [u8] {
153
56124004
            match self { $(
154
56124004
                RelayIdRef::$vname(key) => key.as_bytes(),
155
56124004
            ) }
156
56124004
        }
157
56124004

            
158
56124004
      $(
159
56124004
       $/// Extract the `$IDENTITY` from a RelayIdRef that is known to hold one.
160
56124004
        ///
161
56124004
        /// # Panics
162
56124004
        ///
163
56124004
        /// Panics if this is not an `$vname` identity.
164
56124004
        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
2584
        fn eq(&self, other: &$IDENTITY) -> bool {
176
            matches!(self, RelayIdRef::$vname(this) if this == &other)
177
        }
178
    }
179
    impl PartialEq<$IDENTITY> for RelayId {
180
12
        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
187
use derive_deftly_template_RelayId; // allows putting the macro after RelayIdType
188

            
189
impl<'a> From<&'a RelayId> for RelayIdRef<'a> {
190
3076
    fn from(ident: &'a RelayId) -> Self {
191
3076
        ident.as_ref()
192
3076
    }
193
}
194

            
195
impl 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

            
205
impl<'a> Redactable for RelayIdRef<'a> {
206
2
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207
2
        match self {
208
2
            RelayIdRef::Ed25519(k) => write!(f, "ed25519:{}", k.redacted()),
209
            RelayIdRef::Rsa(k) => write!(f, "${}", k.redacted()),
210
        }
211
2
    }
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

            
222
impl std::str::FromStr for RelayIdType {
223
    type Err = RelayIdError;
224

            
225
2082
    fn from_str(s: &str) -> Result<Self, Self::Err> {
226
2082
        if s.eq_ignore_ascii_case("rsa") {
227
2
            Ok(RelayIdType::Rsa)
228
2080
        } else if s.eq_ignore_ascii_case("ed25519") {
229
2078
            Ok(RelayIdType::Ed25519)
230
        } else {
231
2
            Err(RelayIdError::UnrecognizedIdType)
232
        }
233
2082
    }
234
}
235

            
236
impl 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
9482
    fn from_str(s: &str) -> Result<Self, Self::Err> {
248
        use base64ct::{Base64Unpadded, Encoding as _};
249
9482
        if let Some((alg, key)) = s.split_once(':') {
250
2082
            let alg: RelayIdType = alg.parse()?;
251
2080
            let len = alg.id_len();
252
2080
            let mut v = vec![0_u8; len];
253
2080
            let bytes = Base64Unpadded::decode(key, &mut v[..])?;
254
2078
            RelayId::from_type_and_bytes(alg, bytes)
255
7400
        } else if s.len() == RSA_ID_LEN * 2 || s.starts_with('$') {
256
5668
            let s = s.trim_start_matches('$');
257
5668
            let bytes = hex::decode(s).map_err(|_| RelayIdError::BadHex)?;
258
5666
            RelayId::from_type_and_bytes(RelayIdType::Rsa, &bytes)
259
        } else {
260
1732
            let mut v = [0_u8; ED25519_ID_LEN];
261
1732
            let bytes = Base64Unpadded::decode(s, &mut v[..])?;
262
260
            RelayId::from_type_and_bytes(RelayIdType::Ed25519, bytes)
263
        }
264
9482
    }
265
}
266

            
267
impl serde::Serialize for RelayId {
268
14
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
269
14
    where
270
14
        S: serde::Serializer,
271
    {
272
14
        self.as_ref().serialize(serializer)
273
14
    }
274
}
275
impl<'a> serde::Serialize for RelayIdRef<'a> {
276
18
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
277
18
    where
278
18
        S: serde::Serializer,
279
    {
280
        // TODO(nickm): maybe encode this as bytes when dealing with
281
        // non-human-readable formats.
282
18
        self.to_string().serialize(serializer)
283
18
    }
284
}
285

            
286
impl<'de> serde::Deserialize<'de> for RelayId {
287
28
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
288
28
    where
289
28
        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
28
        let s = <std::borrow::Cow<'_, str> as serde::Deserialize>::deserialize(deserializer)?;
295
28
        s.parse()
296
28
            .map_err(|e: RelayIdError| D::Error::custom(e.to_string()))
297
28
    }
298
}
299

            
300
/// An error returned while trying to parse a RelayId.
301
#[derive(Clone, Debug, thiserror::Error)]
302
#[non_exhaustive]
303
pub 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

            
320
impl From<base64ct::Error> for RelayIdError {
321
1474
    fn from(err: base64ct::Error) -> Self {
322
1474
        match err {
323
1282
            base64ct::Error::InvalidEncoding => RelayIdError::BadBase64,
324
192
            base64ct::Error::InvalidLength => RelayIdError::BadLength,
325
        }
326
1474
    }
327
}
328

            
329
#[cfg(test)]
330
mod 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
}