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

            
6
use std::sync::Arc;
7

            
8
use crate::types::misc::LongIdent;
9
use crate::{Error, NetdocErrorKind, Pos, Result};
10
use base64ct::Encoding;
11
use tor_basic_utils::intern::InternCache;
12
use tor_llcrypto::pk::ed25519::{ED25519_ID_LEN, Ed25519Identity};
13
use 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)]
28
pub 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.
34
static FAMILY_CACHE: InternCache<RelayFamily> = InternCache::new();
35

            
36
impl RelayFamily {
37
    /// Return a new empty RelayFamily.
38
435640
    pub fn new() -> Self {
39
435640
        RelayFamily::default()
40
435640
    }
41

            
42
    /// Add `rsa_id` to this family.
43
6
    pub fn push(&mut self, rsa_id: RsaIdentity) {
44
6
        self.0.push(rsa_id);
45
6
    }
46

            
47
    /// Convert this family to a standard format (with all IDs sorted and de-duplicated).
48
445448
    fn normalize(&mut self) {
49
445448
        self.0.sort();
50
445448
        self.0.dedup();
51
445448
    }
52

            
53
    /// Consume this family, and return a new canonical interned representation
54
    /// of the family.
55
445448
    pub fn intern(mut self) -> Arc<Self> {
56
445448
        self.normalize();
57
445448
        FAMILY_CACHE.intern(self)
58
445448
    }
59

            
60
    /// Does this family include the given relay?
61
53180298
    pub fn contains(&self, rsa_id: &RsaIdentity) -> bool {
62
53180298
        self.0.contains(rsa_id)
63
53180298
    }
64

            
65
    /// Return an iterator over the RSA identity keys listed in this
66
    /// family.
67
102
    pub fn members(&self) -> impl Iterator<Item = &RsaIdentity> {
68
102
        self.0.iter()
69
102
    }
70

            
71
    /// Return true if this family has no members.
72
2068
    pub fn is_empty(&self) -> bool {
73
2068
        self.0.is_empty()
74
2068
    }
75
}
76

            
77
impl std::str::FromStr for RelayFamily {
78
    type Err = Error;
79
443911
    fn from_str(s: &str) -> Result<Self> {
80
443911
        let v: Result<Vec<RsaIdentity>> = s
81
443911
            .split(crate::parse::tokenize::is_sp)
82
749449
            .map(|e| e.parse::<LongIdent>().map(|v| v.into()))
83
443911
            .filter(Result::is_ok)
84
443911
            .collect();
85
443911
        Ok(RelayFamily(v?))
86
443911
    }
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]
98
pub 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`.
106
const ED25519_ID_PREFIX: &str = "ed25519:";
107

            
108
impl std::str::FromStr for RelayFamilyId {
109
    type Err = Error;
110

            
111
1976
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
112
1976
        let mut buf = [0_u8; ED25519_ID_LEN];
113
1976
        if let Some(s) = s.strip_prefix(ED25519_ID_PREFIX) {
114
10
            if let Ok(decoded) = base64ct::Base64Unpadded::decode(s, &mut buf) {
115
10
                if let Some(ed_id) = Ed25519Identity::from_bytes(decoded) {
116
10
                    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
1966
        }
123
1966
        Ok(RelayFamilyId::Unrecognized(s.to_string()))
124
1976
    }
125
}
126

            
127
impl std::fmt::Display for RelayFamilyId {
128
8
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129
8
        match self {
130
6
            RelayFamilyId::Ed25519(id) => write!(f, "{}{}", ED25519_ID_PREFIX, id),
131
2
            RelayFamilyId::Unrecognized(s) => write!(f, "{}", s),
132
        }
133
8
    }
134
}
135

            
136
impl PartialOrd for RelayFamilyId {
137
2
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
138
2
        Some(Ord::cmp(self, other))
139
2
    }
140
}
141
impl Ord for RelayFamilyId {
142
2
    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
2
        Ord::cmp(&self.to_string(), &other.to_string())
146
2
    }
147
}
148

            
149
#[cfg(test)]
150
mod 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_time_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
}