tor_netdoc/types/
family.rs

1//! Implements the relay 'family' type.
2//!
3//! Families are opt-in lists of relays with the same operators,
4//! used to avoid building insecure circuits.
5
6use std::sync::Arc;
7
8use crate::types::misc::LongIdent;
9use crate::util::intern::InternCache;
10use crate::{Error, NetdocErrorKind, Pos, Result};
11use base64ct::Encoding;
12use tor_llcrypto::pk::ed25519::{Ed25519Identity, ED25519_ID_LEN};
13use tor_llcrypto::pk::rsa::RsaIdentity;
14
15/// Information about a relay family.
16///
17/// Tor relays may declare that they belong to the same family, to
18/// indicate that they are controlled by the same party or parties,
19/// and as such should not be used in the same circuit. Two relays
20/// belong to the same family if and only if each one lists the other
21/// as belonging to its family.
22///
23/// NOTE: when parsing, this type always discards incorrectly-formatted
24/// entries, including entries that are only nicknames.
25///
26/// TODO: This type probably belongs in a different crate.
27#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)]
28pub struct RelayFamily(Vec<RsaIdentity>);
29
30/// Cache of RelayFamily objects, for saving memory.
31//
32/// This only holds weak references to the policy objects, so we don't
33/// need to worry about running out of space because of stale entries.
34static FAMILY_CACHE: InternCache<RelayFamily> = InternCache::new();
35
36impl RelayFamily {
37    /// Return a new empty RelayFamily.
38    pub fn new() -> Self {
39        RelayFamily::default()
40    }
41
42    /// Add `rsa_id` to this family.
43    pub fn push(&mut self, rsa_id: RsaIdentity) {
44        self.0.push(rsa_id);
45    }
46
47    /// Convert this family to a standard format (with all IDs sorted and de-duplicated).
48    fn normalize(&mut self) {
49        self.0.sort();
50        self.0.dedup();
51    }
52
53    /// Consume this family, and return a new canonical interned representation
54    /// of the family.
55    pub fn intern(mut self) -> Arc<Self> {
56        self.normalize();
57        FAMILY_CACHE.intern(self)
58    }
59
60    /// Does this family include the given relay?
61    pub fn contains(&self, rsa_id: &RsaIdentity) -> bool {
62        self.0.contains(rsa_id)
63    }
64
65    /// Return an iterator over the RSA identity keys listed in this
66    /// family.
67    pub fn members(&self) -> impl Iterator<Item = &RsaIdentity> {
68        self.0.iter()
69    }
70
71    /// Return true if this family has no members.
72    pub fn is_empty(&self) -> bool {
73        self.0.is_empty()
74    }
75}
76
77impl std::str::FromStr for RelayFamily {
78    type Err = Error;
79    fn from_str(s: &str) -> Result<Self> {
80        let v: Result<Vec<RsaIdentity>> = s
81            .split(crate::parse::tokenize::is_sp)
82            .map(|e| e.parse::<LongIdent>().map(|v| v.into()))
83            .filter(Result::is_ok)
84            .collect();
85        Ok(RelayFamily(v?))
86    }
87}
88
89/// An identifier representing a relay family.
90///
91/// In the ["happy families"](https://spec.torproject.org/proposals/321) scheme,
92/// microdescriptors will no longer have to contain a list of relay members,
93/// but will instead contain these identifiers.
94///
95/// If two relays have a `RelayFamilyId` in common, they belong to the same family.
96#[derive(Clone, Debug, Eq, PartialEq)]
97#[non_exhaustive]
98pub enum RelayFamilyId {
99    /// An identifier derived from an Ed25519 relay family key. (`KP_familyid_ed`)
100    Ed25519(Ed25519Identity),
101    /// An unrecognized string.
102    Unrecognized(String),
103}
104
105/// Prefix for a RelayFamilyId derived from an ed25519 `KP_familyid_ed`.
106const ED25519_ID_PREFIX: &str = "ed25519:";
107
108impl std::str::FromStr for RelayFamilyId {
109    type Err = Error;
110
111    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
112        let mut buf = [0_u8; ED25519_ID_LEN];
113        if let Some(s) = s.strip_prefix(ED25519_ID_PREFIX) {
114            if let Ok(decoded) = base64ct::Base64Unpadded::decode(s, &mut buf) {
115                if let Some(ed_id) = Ed25519Identity::from_bytes(decoded) {
116                    return Ok(RelayFamilyId::Ed25519(ed_id));
117                }
118            }
119            return Err(NetdocErrorKind::BadArgument
120                .with_msg("Invalid ed25519 family ID")
121                .at_pos(Pos::at(s)));
122        }
123        Ok(RelayFamilyId::Unrecognized(s.to_string()))
124    }
125}
126
127impl std::fmt::Display for RelayFamilyId {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            RelayFamilyId::Ed25519(id) => write!(f, "{}{}", ED25519_ID_PREFIX, id),
131            RelayFamilyId::Unrecognized(s) => write!(f, "{}", s),
132        }
133    }
134}
135
136impl PartialOrd for RelayFamilyId {
137    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
138        Some(Ord::cmp(self, other))
139    }
140}
141impl Ord for RelayFamilyId {
142    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
143        // We sort RelayFamilyId values by string representation.
144        // This is not super-efficient, but we don't need to do it very often.
145        Ord::cmp(&self.to_string(), &other.to_string())
146    }
147}
148
149#[cfg(test)]
150mod test {
151    // @@ begin test lint list maintained by maint/add_warning @@
152    #![allow(clippy::bool_assert_comparison)]
153    #![allow(clippy::clone_on_copy)]
154    #![allow(clippy::dbg_macro)]
155    #![allow(clippy::mixed_attributes_style)]
156    #![allow(clippy::print_stderr)]
157    #![allow(clippy::print_stdout)]
158    #![allow(clippy::single_char_pattern)]
159    #![allow(clippy::unwrap_used)]
160    #![allow(clippy::unchecked_duration_subtraction)]
161    #![allow(clippy::useless_vec)]
162    #![allow(clippy::needless_pass_by_value)]
163    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
164    use std::str::FromStr;
165
166    use super::*;
167    use crate::Result;
168    #[test]
169    fn family() -> Result<()> {
170        let f = "nickname1 nickname2 $ffffffffffffffffffffffffffffffffffffffff=foo eeeeeeeeeeeeeeeeeeeEEEeeeeeeeeeeeeeeeeee ddddddddddddddddddddddddddddddddd  $cccccccccccccccccccccccccccccccccccccccc~blarg ".parse::<RelayFamily>()?;
171        let v = vec![
172            RsaIdentity::from_bytes(
173                &hex::decode("ffffffffffffffffffffffffffffffffffffffff").unwrap()[..],
174            )
175            .unwrap(),
176            RsaIdentity::from_bytes(
177                &hex::decode("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap()[..],
178            )
179            .unwrap(),
180            RsaIdentity::from_bytes(
181                &hex::decode("cccccccccccccccccccccccccccccccccccccccc").unwrap()[..],
182            )
183            .unwrap(),
184        ];
185        assert_eq!(f.0, v);
186        Ok(())
187    }
188
189    #[test]
190    fn test_contains() -> Result<()> {
191        let family =
192            "ffffffffffffffffffffffffffffffffffffffff eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
193                .parse::<RelayFamily>()?;
194        let in_family = RsaIdentity::from_bytes(
195            &hex::decode("ffffffffffffffffffffffffffffffffffffffff").unwrap()[..],
196        )
197        .unwrap();
198        let not_in_family = RsaIdentity::from_bytes(
199            &hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap()[..],
200        )
201        .unwrap();
202        assert!(family.contains(&in_family), "Relay not found in family");
203        assert!(
204            !family.contains(&not_in_family),
205            "Extra relay found in family"
206        );
207        Ok(())
208    }
209
210    #[test]
211    fn mutable() {
212        let mut family = RelayFamily::default();
213        let key = RsaIdentity::from_hex("ffffffffffffffffffffffffffffffffffffffff").unwrap();
214        assert!(!family.contains(&key));
215        family.push(key);
216        assert!(family.contains(&key));
217    }
218
219    #[test]
220    fn family_ids() {
221        let ed_str_rep = "ed25519:7sToQRuge1bU2hS0CG0ViMndc4m82JhO4B4kdrQey80";
222        let ed_id = RelayFamilyId::from_str(ed_str_rep).unwrap();
223        assert!(matches!(ed_id, RelayFamilyId::Ed25519(_)));
224        assert_eq!(ed_id.to_string().as_str(), ed_str_rep);
225
226        let other_str_rep = "hello-world";
227        let other_id = RelayFamilyId::from_str(other_str_rep).unwrap();
228        assert!(matches!(other_id, RelayFamilyId::Unrecognized(_)));
229        assert_eq!(other_id.to_string().as_str(), other_str_rep);
230
231        assert_eq!(ed_id, ed_id);
232        assert_ne!(ed_id, other_id);
233    }
234}