1
//! Parsing implementation for Tor microdescriptors.
2
//!
3
//! A "microdescriptor" is an incomplete, infrequently-changing
4
//! summary of a relay's information that is generated by
5
//! the directory authorities.
6
//!
7
//! Microdescriptors are much smaller than router descriptors, and
8
//! change less frequently. For this reason, they're currently used
9
//! for building circuits by all relays and clients.
10
//!
11
//! Microdescriptors can't be used on their own: you need to know
12
//! which relay they are for, which requires a valid consensus
13
//! directory.
14

            
15
use crate::parse::keyword::Keyword;
16
use crate::parse::parser::SectionRules;
17
use crate::parse::tokenize::{ItemResult, NetDocReader};
18
use crate::types::family::RelayFamily;
19
use crate::types::misc::*;
20
use crate::types::policy::PortPolicy;
21
use crate::util;
22
use crate::util::str::Extent;
23
use crate::{AllowAnnotations, Error, ParseErrorKind as EK, Result};
24
use tor_error::internal;
25
use tor_llcrypto::d;
26
use tor_llcrypto::pk::{curve25519, ed25519, rsa};
27

            
28
use digest::Digest;
29
use once_cell::sync::Lazy;
30
use std::sync::Arc;
31

            
32
use std::time;
33

            
34
#[cfg(feature = "build_docs")]
35
mod build;
36

            
37
#[cfg(feature = "build_docs")]
38
pub use build::MicrodescBuilder;
39

            
40
/// Annotations prepended to a microdescriptor that has been stored to
41
/// disk.
42
#[allow(dead_code)]
43
119
#[derive(Clone, Debug, Default)]
44
pub struct MicrodescAnnotation {
45
    /// A time at which this microdescriptor was last listed in some
46
    /// consensus document.
47
    last_listed: Option<time::SystemTime>,
48
}
49

            
50
/// The digest of a microdescriptor as used in microdesc consensuses
51
pub type MdDigest = [u8; 32];
52

            
53
/// A single microdescriptor.
54
#[allow(dead_code)]
55
152
#[derive(Clone, Debug)]
56
pub struct Microdesc {
57
    /// The SHA256 digest of the text of this microdescriptor.  This
58
    /// value is used to identify the microdescriptor when downloading
59
    /// it, and when listing it in a consensus document.
60
    // TODO: maybe this belongs somewhere else. Once it's used to store
61
    // correlate the microdesc to a consensus, it's never used again.
62
    sha256: MdDigest,
63
    /// Public key used for the ntor circuit extension protocol.
64
    ntor_onion_key: curve25519::PublicKey,
65
    /// Declared family for this relay.
66
    family: RelayFamily,
67
    /// List of IPv4 ports to which this relay will exit
68
    ipv4_policy: Arc<PortPolicy>,
69
    /// List of IPv6 ports to which this relay will exit
70
    ipv6_policy: Arc<PortPolicy>,
71
    /// Ed25519 identity for this relay
72
    ed25519_id: ed25519::Ed25519Identity,
73
    // addr is obsolete and doesn't go here any more
74
    // pr is obsolete and doesn't go here any more.
75
    // The legacy "tap" onion-key is obsolete, and though we parse it, we don't
76
    // save it.
77
}
78

            
79
impl Microdesc {
80
    /// Create a new MicrodescBuilder that can be used to construct
81
    /// microdescriptors.
82
    ///
83
    /// This function is only available when the crate is built with the
84
    /// `build_docs` feature.
85
    ///
86
    /// # Limitations
87
    ///
88
    /// The generated microdescriptors cannot yet be encoded, and do
89
    /// not yet have correct sha256 digests. As such they are only
90
    /// useful for testing.
91
    #[cfg(feature = "build_docs")]
92
37879
    pub fn builder() -> MicrodescBuilder {
93
37879
        MicrodescBuilder::new()
94
37879
    }
95

            
96
    /// Return the sha256 digest of this microdesc.
97
11190727
    pub fn digest(&self) -> &MdDigest {
98
11190727
        &self.sha256
99
11190727
    }
100
    /// Return the ntor onion key for this microdesc
101
174048
    pub fn ntor_key(&self) -> &curve25519::PublicKey {
102
174048
        &self.ntor_onion_key
103
174048
    }
104
    /// Return the ipv4 exit policy for this microdesc
105
5790834
    pub fn ipv4_policy(&self) -> &Arc<PortPolicy> {
106
5790834
        &self.ipv4_policy
107
5790834
    }
108
    /// Return the ipv6 exit policy for this microdesc
109
342708
    pub fn ipv6_policy(&self) -> &Arc<PortPolicy> {
110
342708
        &self.ipv6_policy
111
342708
    }
112
    /// Return the relay family for this microdesc
113
5279693
    pub fn family(&self) -> &RelayFamily {
114
5279693
        &self.family
115
5279693
    }
116
    /// Return the ed25519 identity for this microdesc, if its
117
    /// Ed25519 identity is well-formed.
118
11419055
    pub fn ed25519_id(&self) -> &ed25519::Ed25519Identity {
119
11419055
        &self.ed25519_id
120
11419055
    }
121
}
122

            
123
/// A microdescriptor annotated with additional data
124
///
125
/// TODO: rename this.
126
#[allow(dead_code)]
127
#[derive(Clone, Debug)]
128
pub struct AnnotatedMicrodesc {
129
    /// The microdescriptor
130
    md: Microdesc,
131
    /// The annotations for the microdescriptor
132
    ann: MicrodescAnnotation,
133
    /// Where did we find the microdescriptor with the originally parsed
134
    /// string?
135
    location: Option<Extent>,
136
}
137

            
138
impl AnnotatedMicrodesc {
139
    /// Consume this annotated microdesc and discard its annotations.
140
119
    pub fn into_microdesc(self) -> Microdesc {
141
119
        self.md
142
119
    }
143

            
144
    /// Return a reference to the microdescriptor within this annotated
145
    /// microdescriptor.
146
7
    pub fn md(&self) -> &Microdesc {
147
7
        &self.md
148
7
    }
149

            
150
    /// If this Microdesc was parsed from `s`, return its original text.
151
119
    pub fn within<'a>(&self, s: &'a str) -> Option<&'a str> {
152
119
        self.location.as_ref().and_then(|ext| ext.reconstruct(s))
153
119
    }
154
}
155

            
156
decl_keyword! {
157
    /// Keyword type for recognized objects in microdescriptors.
158
    MicrodescKwd {
159
        annotation "@last-listed" => ANN_LAST_LISTED,
160
        "onion-key" => ONION_KEY,
161
        "ntor-onion-key" => NTOR_ONION_KEY,
162
        "family" => FAMILY,
163
        "p" => P,
164
        "p6" => P6,
165
        "id" => ID,
166
    }
167
}
168

            
169
/// Rules about annotations that can appear before a Microdescriptor
170
1
static MICRODESC_ANNOTATIONS: Lazy<SectionRules<MicrodescKwd>> = Lazy::new(|| {
171
1
    use MicrodescKwd::*;
172
1
    let mut rules = SectionRules::new();
173
1
    rules.add(ANN_LAST_LISTED.rule().args(1..));
174
1
    rules.add(ANN_UNRECOGNIZED.rule().may_repeat().obj_optional());
175
1
    rules
176
1
});
177
/// Rules about entries that must appear in an Microdesc, and how they must
178
/// be formed.
179
18
static MICRODESC_RULES: Lazy<SectionRules<MicrodescKwd>> = Lazy::new(|| {
180
18
    use MicrodescKwd::*;
181
18

            
182
18
    let mut rules = SectionRules::new();
183
18
    rules.add(ONION_KEY.rule().required().no_args().obj_required());
184
18
    rules.add(NTOR_ONION_KEY.rule().required().args(1..));
185
18
    rules.add(FAMILY.rule().args(1..));
186
18
    rules.add(P.rule().args(2..));
187
18
    rules.add(P6.rule().args(2..));
188
18
    rules.add(ID.rule().may_repeat().args(2..));
189
18
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
190
18
    rules
191
18
});
192

            
193
impl MicrodescAnnotation {
194
    /// Extract a (possibly empty) microdescriptor annotation from a
195
    /// reader.
196
    #[allow(dead_code)]
197
6
    fn parse_from_reader(
198
6
        reader: &mut NetDocReader<'_, MicrodescKwd>,
199
6
    ) -> Result<MicrodescAnnotation> {
200
6
        use MicrodescKwd::*;
201
6

            
202
10
        let mut items = reader.pause_at(|item| item.is_ok_with_non_annotation());
203
6
        let body = MICRODESC_ANNOTATIONS.parse(&mut items)?;
204

            
205
6
        let last_listed = match body.get(ANN_LAST_LISTED) {
206
2
            None => None,
207
4
            Some(item) => Some(item.args_as_str().parse::<Iso8601TimeSp>()?.into()),
208
        };
209

            
210
6
        Ok(MicrodescAnnotation { last_listed })
211
6
    }
212
}
213

            
214
impl Microdesc {
215
    /// Parse a string into a new microdescriptor.
216
20
    pub fn parse(s: &str) -> Result<Microdesc> {
217
20
        let mut items = crate::parse::tokenize::NetDocReader::new(s);
218
20
        let (result, _) = Self::parse_from_reader(&mut items).map_err(|e| e.within(s))?;
219
18
        items.should_be_exhausted()?;
220
18
        Ok(result)
221
20
    }
222

            
223
    /// Extract a single microdescriptor from a NetDocReader.
224
145
    fn parse_from_reader(
225
145
        reader: &mut NetDocReader<'_, MicrodescKwd>,
226
145
    ) -> Result<(Microdesc, Option<Extent>)> {
227
145
        use MicrodescKwd::*;
228
145
        let s = reader.str();
229
145

            
230
145
        let mut first_onion_key = true;
231
145
        // We'll pause at the next annotation, or at the _second_ onion key.
232
575
        let mut items = reader.pause_at(|item| match item {
233
            Err(_) => false,
234
575
            Ok(item) => {
235
575
                item.kwd().is_annotation()
236
572
                    || if item.kwd() == ONION_KEY {
237
231
                        let was_first = first_onion_key;
238
231
                        first_onion_key = false;
239
231
                        !was_first
240
                    } else {
241
341
                        false
242
                    }
243
            }
244
575
        });
245

            
246
145
        let body = MICRODESC_RULES.parse(&mut items)?;
247

            
248
        // We have to start with onion-key
249
143
        let start_pos = {
250
            // unwrap here is safe because parsing would have failed
251
            // had there not been at least one item.
252
            #[allow(clippy::unwrap_used)]
253
145
            let first = body.first_item().unwrap();
254
145
            if first.kwd() != ONION_KEY {
255
2
                return Err(EK::WrongStartingToken
256
2
                    .with_msg(first.kwd_str().to_string())
257
2
                    .at_pos(first.pos()));
258
143
            }
259
143
            // Unwrap is safe here because we are parsing these strings from s
260
143
            #[allow(clippy::unwrap_used)]
261
143
            util::str::str_offset(s, first.kwd_str()).unwrap()
262
        };
263

            
264
        // Legacy (tap) onion key.  We parse this to make sure it's well-formed,
265
        // but then we discard it immediately, since we never want to use it.
266
143
        let _: rsa::PublicKey = body
267
143
            .required(ONION_KEY)?
268
143
            .parse_obj::<RsaPublic>("RSA PUBLIC KEY")?
269
143
            .check_len_eq(1024)?
270
143
            .check_exponent(65537)?
271
143
            .into();
272

            
273
        // Ntor onion key
274
143
        let ntor_onion_key = body
275
143
            .required(NTOR_ONION_KEY)?
276
143
            .parse_arg::<Curve25519Public>(0)?
277
143
            .into();
278

            
279
        // family
280
143
        let family = body
281
143
            .maybe(FAMILY)
282
143
            .parse_args_as_str::<RelayFamily>()?
283
143
            .unwrap_or_else(RelayFamily::new);
284

            
285
        // exit policies.
286
143
        let ipv4_policy = body
287
143
            .maybe(P)
288
143
            .parse_args_as_str::<PortPolicy>()?
289
143
            .unwrap_or_else(PortPolicy::new_reject_all);
290
143
        let ipv6_policy = body
291
143
            .maybe(P6)
292
143
            .parse_args_as_str::<PortPolicy>()?
293
142
            .unwrap_or_else(PortPolicy::new_reject_all);
294

            
295
        // ed25519 identity
296
142
        let ed25519_id = {
297
142
            let id_tok = body
298
142
                .slice(ID)
299
142
                .iter()
300
142
                .find(|item| item.arg(0) == Some("ed25519"));
301
142
            match id_tok {
302
                None => {
303
                    return Err(EK::MissingToken.with_msg("id ed25519"));
304
                }
305
142
                Some(tok) => tok.parse_arg::<Ed25519Public>(1)?.into(),
306
            }
307
        };
308

            
309
142
        let end_pos = {
310
            // unwrap here is safe because parsing would have failed
311
            // had there not been at least one item.
312
            #[allow(clippy::unwrap_used)]
313
142
            let last_item = body.last_item().unwrap();
314
142
            last_item.offset_after(s).ok_or_else(|| {
315
                Error::from(internal!("last item was not within source string"))
316
                    .at_pos(last_item.end_pos())
317
142
            })?
318
        };
319

            
320
142
        let text = &s[start_pos..end_pos];
321
142
        let sha256 = d::Sha256::digest(text.as_bytes()).into();
322
142

            
323
142
        let location = Extent::new(s, text);
324
142

            
325
142
        let md = Microdesc {
326
142
            sha256,
327
142
            ntor_onion_key,
328
142
            family,
329
142
            ipv4_policy: ipv4_policy.intern(),
330
142
            ipv6_policy: ipv6_policy.intern(),
331
142
            ed25519_id,
332
142
        };
333
142
        Ok((md, location))
334
145
    }
335
}
336

            
337
/// Consume tokens from 'reader' until the next token is the beginning
338
/// of a microdescriptor: an annotation or an ONION_KEY.  If no such
339
/// token exists, advance to the end of the reader.
340
1
fn advance_to_next_microdesc(reader: &mut NetDocReader<'_, MicrodescKwd>, annotated: bool) {
341
1
    use MicrodescKwd::*;
342
1
    let iter = reader.iter();
343
    loop {
344
1
        let item = iter.peek();
345
1
        match item {
346
1
            Some(Ok(t)) => {
347
1
                let kwd = t.kwd();
348
1
                if (annotated && kwd.is_annotation()) || kwd == ONION_KEY {
349
1
                    return;
350
                }
351
            }
352
            Some(Err(_)) => {
353
                // We skip over broken tokens here.
354
            }
355
            None => {
356
                return;
357
            }
358
        };
359
        let _ = iter.next();
360
    }
361
1
}
362

            
363
/// An iterator that parses one or more (possibly annotated)
364
/// microdescriptors from a string.
365
#[derive(Debug)]
366
pub struct MicrodescReader<'a> {
367
    /// True if we accept annotations; false otherwise.
368
    annotated: bool,
369
    /// An underlying reader to give us Items for the microdescriptors
370
    reader: NetDocReader<'a, MicrodescKwd>,
371
}
372

            
373
impl<'a> MicrodescReader<'a> {
374
    /// Construct a MicrodescReader to take microdescriptors from a string
375
    /// 's'.
376
36
    pub fn new(s: &'a str, allow: &AllowAnnotations) -> Self {
377
36
        let reader = NetDocReader::new(s);
378
36
        let annotated = allow == &AllowAnnotations::AnnotationsAllowed;
379
36
        MicrodescReader { annotated, reader }
380
36
    }
381

            
382
    /// If we're annotated, parse an annotation from the reader. Otherwise
383
    /// return a default annotation.
384
125
    fn take_annotation(&mut self) -> Result<MicrodescAnnotation> {
385
125
        if self.annotated {
386
6
            MicrodescAnnotation::parse_from_reader(&mut self.reader)
387
        } else {
388
119
            Ok(MicrodescAnnotation::default())
389
        }
390
125
    }
391

            
392
    /// Parse a (possibly annotated) microdescriptor from the reader.
393
    ///
394
    /// On error, parsing stops after the first failure.
395
125
    fn take_annotated_microdesc_raw(&mut self) -> Result<AnnotatedMicrodesc> {
396
125
        let ann = self.take_annotation()?;
397
125
        let (md, location) = Microdesc::parse_from_reader(&mut self.reader)?;
398
124
        Ok(AnnotatedMicrodesc { md, ann, location })
399
125
    }
400

            
401
    /// Parse a (possibly annotated) microdescriptor from the reader.
402
    ///
403
    /// On error, advance the reader to the start of the next microdescriptor.
404
125
    fn take_annotated_microdesc(&mut self) -> Result<AnnotatedMicrodesc> {
405
125
        let pos_orig = self.reader.pos();
406
125
        let result = self.take_annotated_microdesc_raw();
407
125
        if result.is_err() {
408
1
            if self.reader.pos() == pos_orig {
409
                // No tokens were consumed from the reader.  We need to
410
                // drop at least one token to ensure we aren't looping.
411
                //
412
                // (This might not be able to happen, but it's easier to
413
                // explicitly catch this case than it is to prove that
414
                // it's impossible.)
415
                let _ = self.reader.iter().next();
416
1
            }
417
1
            advance_to_next_microdesc(&mut self.reader, self.annotated);
418
124
        }
419
125
        result
420
125
    }
421
}
422

            
423
impl<'a> Iterator for MicrodescReader<'a> {
424
    type Item = Result<AnnotatedMicrodesc>;
425
    fn next(&mut self) -> Option<Self::Item> {
426
        // If there is no next token, we're at the end.
427
161
        self.reader.iter().peek()?;
428

            
429
125
        Some(
430
125
            self.take_annotated_microdesc()
431
125
                .map_err(|e| e.within(self.reader.str())),
432
125
        )
433
161
    }
434
}
435

            
436
#[cfg(test)]
437
mod test {
438
    #![allow(clippy::unwrap_used)]
439
    use super::*;
440
    use hex_literal::hex;
441
    const TESTDATA: &str = include_str!("../../testdata/microdesc1.txt");
442
    const TESTDATA2: &str = include_str!("../../testdata/microdesc2.txt");
443

            
444
    fn read_bad(fname: &str) -> String {
445
        use std::fs;
446
        use std::path::PathBuf;
447
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
448
        path.push("testdata");
449
        path.push("bad-mds");
450
        path.push(fname);
451

            
452
        fs::read_to_string(path).unwrap()
453
    }
454

            
455
    #[test]
456
    fn parse_single() -> Result<()> {
457
        let _md = Microdesc::parse(TESTDATA)?;
458
        Ok(())
459
    }
460

            
461
    #[test]
462
    fn parse_multi() -> Result<()> {
463
        use std::time::{Duration, SystemTime};
464
        let mds: Result<Vec<_>> =
465
            MicrodescReader::new(TESTDATA2, &AllowAnnotations::AnnotationsAllowed).collect();
466
        let mds = mds?;
467
        assert_eq!(mds.len(), 4);
468

            
469
        assert_eq!(
470
            mds[0].ann.last_listed.unwrap(),
471
            SystemTime::UNIX_EPOCH + Duration::new(1580151129, 0)
472
        );
473
        assert_eq!(
474
            mds[0].md().digest(),
475
            &hex!("38c71329a87098cb341c46c9c62bd646622b4445f7eb985a0e6adb23a22ccf4f")
476
        );
477
        assert_eq!(
478
            mds[0].md().ntor_key().as_bytes(),
479
            &hex!("5e895d65304a3a1894616660143f7af5757fe08bc18045c7855ee8debb9e6c47")
480
        );
481
        assert!(mds[0].md().ipv4_policy().allows_port(993));
482
        assert!(mds[0].md().ipv6_policy().allows_port(993));
483
        assert!(!mds[0].md().ipv4_policy().allows_port(25));
484
        assert!(!mds[0].md().ipv6_policy().allows_port(25));
485
        assert_eq!(
486
            mds[0].md().ed25519_id().as_bytes(),
487
            &hex!("2d85fdc88e6c1bcfb46897fca1dba6d1354f93261d68a79e0b5bc170dd923084")
488
        );
489

            
490
        Ok(())
491
    }
492

            
493
    #[test]
494
    fn test_bad() {
495
        use crate::types::policy::PolicyError;
496
        use crate::Pos;
497
        fn check(fname: &str, e: &Error) {
498
            let content = read_bad(fname);
499
            let res = Microdesc::parse(&content);
500
            assert!(res.is_err());
501
            assert_eq!(&res.err().unwrap(), e);
502
        }
503

            
504
        check(
505
            "wrong-start",
506
            &EK::WrongStartingToken
507
                .with_msg("family")
508
                .at_pos(Pos::from_line(1, 1)),
509
        );
510
        check(
511
            "bogus-policy",
512
            &EK::BadPolicy
513
                .at_pos(Pos::from_line(9, 1))
514
                .with_source(PolicyError::InvalidPort),
515
        );
516
    }
517

            
518
    #[test]
519
    fn test_recover() {
520
        let mut data = read_bad("wrong-start");
521
        data += TESTDATA;
522

            
523
        let res: Vec<Result<_>> =
524
            MicrodescReader::new(&data, &AllowAnnotations::AnnotationsAllowed).collect();
525

            
526
        assert_eq!(res.len(), 2);
527
        assert!(res[0].is_err());
528
        assert!(res[1].is_ok());
529
    }
530
}