1
//! consensus documents - items that vary by consensus flavor
2
//!
3
//! **This file is reincluded multiple times**,
4
//! by the macros in [`crate::doc::ns_variety_definition_macros`],
5
//! once for votes, and once for each consensus flavour.
6
//! It is *not* a module `crate::doc::netstatus::rs::each_flavor`.
7
//!
8
//! Each time this file is included by one of the macros mentioned above,
9
//! the `ns_***` macros (such as `ns_const_name!`) may expand to different values.
10
//!
11
//! See [`crate::doc::ns_variety_definition_macros`].
12

            
13
use super::*;
14

            
15
ns_use_this_variety! {
16
    use [crate::doc::netstatus::rs]::?::{RouterStatus};
17
}
18
#[cfg(feature = "build_docs")]
19
ns_use_this_variety! {
20
    pub(crate) use [crate::doc::netstatus::build]::?::{ConsensusBuilder};
21
    pub use [crate::doc::netstatus::rs::build]::?::{RouterStatusBuilder};
22
}
23

            
24
/// A single consensus netstatus, as produced by the old parser.
25
#[derive(Debug, Clone)]
26
#[non_exhaustive]
27
pub struct Consensus {
28
    /// What kind of consensus document is this?  Absent in votes and
29
    /// in ns-flavored consensuses.
30
    pub flavor: ConsensusFlavor,
31
    /// The preamble, except for the intro item.
32
    pub preamble: Preamble,
33
    /// List of voters whose votes contributed to this consensus.
34
    pub voters: Vec<ConsensusAuthorityEntry>,
35
    /// A list of routerstatus entries for the relays on the network,
36
    /// with one entry per relay.
37
    ///
38
    /// These are currently ordered by the router's RSA identity, but this is not
39
    /// to be relied on, since we may want to even abolish RSA at some point!
40
    pub relays: Vec<RouterStatus>,
41
    /// Footer for the consensus object.
42
    pub footer: ConsensusFooterFields,
43
}
44

            
45
impl Consensus {
46
    /// Return the Lifetime for this consensus.
47
75525
    pub fn lifetime(&self) -> &Lifetime {
48
75525
        &self.preamble.lifetime
49
75525
    }
50

            
51
    /// Return a slice of all the routerstatus entries in this consensus.
52
73585944
    pub fn relays(&self) -> &[RouterStatus] {
53
73585944
        &self.relays[..]
54
73585944
    }
55

            
56
    /// Return a mapping from keywords to integers representing how
57
    /// to weight different kinds of relays in different path positions.
58
12084
    pub fn bandwidth_weights(&self) -> &NetParams<i32> {
59
12084
        &self.footer.bandwidth_weights
60
12084
    }
61

            
62
    /// Return the map of network parameters that this consensus advertises.
63
12137
    pub fn params(&self) -> &NetParams<i32> {
64
12137
        &self.preamble.params
65
12137
    }
66

            
67
    /// Return the latest shared random value, if the consensus
68
    /// contains one.
69
48283
    pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
70
48283
        self.preamble.shared_rand.shared_rand_current_value.as_ref()
71
48283
    }
72

            
73
    /// Return the previous shared random value, if the consensus
74
    /// contains one.
75
48283
    pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
76
48283
        self.preamble.shared_rand.shared_rand_previous_value.as_ref()
77
48283
    }
78

            
79
    /// Return a [`ProtoStatus`] that lists the network's current requirements and
80
    /// recommendations for the list of protocols that every relay must implement.  
81
2385
    pub fn relay_protocol_status(&self) -> &ProtoStatus {
82
2385
        &self.preamble.proto_statuses.relay
83
2385
    }
84

            
85
    /// Return a [`ProtoStatus`] that lists the network's current requirements and
86
    /// recommendations for the list of protocols that every client must implement.
87
    pub fn client_protocol_status(&self) -> &ProtoStatus {
88
        &self.preamble.proto_statuses.client
89
    }
90

            
91
    /// Return a set of all known [`ProtoStatus`] values.
92
53
    pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
93
53
        &self.preamble.proto_statuses
94
53
    }
95
}
96

            
97
impl Consensus {
98
    /// Return a new ConsensusBuilder for building test consensus objects.
99
    ///
100
    /// This function is only available when the `build_docs` feature has
101
    /// been enabled.
102
    #[cfg(feature = "build_docs")]
103
14895
    pub fn builder() -> ConsensusBuilder {
104
14895
        ConsensusBuilder::new(RouterStatus::flavor())
105
14895
    }
106

            
107
    /// Try to parse a single networkstatus document from a string.
108
433
    pub fn parse(s: &str) -> crate::Result<(&str, &str, UncheckedConsensus)> {
109
433
        let mut reader = NetDocReader::new(s)?;
110
433
        Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
111
433
    }
112
    /// Extract a voter-info section from the reader; return
113
    /// Ok(None) when we are out of voter-info sections.
114
1513
    fn take_voterinfo(
115
1513
        r: &mut NetDocReader<'_, NetstatusKwd>,
116
1513
    ) -> crate::Result<Option<ConsensusAuthorityEntry>> {
117
        use NetstatusKwd::*;
118

            
119
1513
        match r.peek() {
120
            None => return Ok(None),
121
1513
            Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
122
1135
            _ => (),
123
        };
124

            
125
1135
        let mut first_dir_source = true;
126
        // TODO: Extract this pattern into a "pause at second"???
127
        // Pause at the first 'r', or the second 'dir-source'.
128
4540
        let mut p = r.pause_at(|i| match i {
129
            Err(_) => false,
130
4540
            Ok(item) => {
131
4540
                item.kwd() == RS_R
132
4162
                    || if item.kwd() == DIR_SOURCE {
133
1892
                        let was_first = first_dir_source;
134
1892
                        first_dir_source = false;
135
1892
                        !was_first
136
                    } else {
137
2270
                        false
138
                    }
139
            }
140
4540
        });
141

            
142
1135
        let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
143
1135
        let voter = ConsensusAuthorityEntry::from_section(&voter_sec)?;
144

            
145
1135
        Ok(Some(voter))
146
1513
    }
147

            
148
    /// Extract the footer (but not signatures) from the reader.
149
374
    fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> crate::Result<ConsensusFooterFields> {
150
        use NetstatusKwd::*;
151
1122
        let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
152
374
        let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
153
374
        let footer = ConsensusFooterFields::from_section(&footer_sec)?;
154
373
        Ok(footer)
155
374
    }
156

            
157
    /// Extract a routerstatus from the reader.  Return Ok(None) if we're
158
    /// out of routerstatus entries.
159
2420
    fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> crate::Result<Option<(Pos, RouterStatus)>> {
160
        use NetstatusKwd::*;
161
2420
        match r.peek() {
162
            None => return Ok(None),
163
2420
            Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
164
2046
            _ => (),
165
        };
166

            
167
2046
        let pos = r.pos();
168

            
169
2046
        let mut first_r = true;
170
15937
        let mut p = r.pause_at(|i| match i {
171
            Err(_) => false,
172
15937
            Ok(item) => {
173
15937
                item.kwd() == DIRECTORY_FOOTER
174
15562
                    || if item.kwd() == RS_R {
175
3717
                        let was_first = first_r;
176
3717
                        first_r = false;
177
3717
                        !was_first
178
                    } else {
179
11845
                        false
180
                    }
181
            }
182
15937
        });
183

            
184
2046
        let rules = match RouterStatus::flavor() {
185
2039
            ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
186
7
            ConsensusFlavor::Plain => &NS_ROUTERSTATUS_RULES_PLAIN,
187
        };
188

            
189
2046
        let rs_sec = rules.parse(&mut p)?;
190
2046
        let rs = RouterStatus::from_section(&rs_sec)?;
191
2043
        Ok(Some((pos, rs)))
192
2420
    }
193

            
194
    /// Extract an entire UncheckedConsensus from a reader.
195
    ///
196
    /// Returns the signed portion of the string, the remainder of the
197
    /// string, and an UncheckedConsensus.
198
433
    fn parse_from_reader<'a>(
199
433
        r: &mut NetDocReader<'a, NetstatusKwd>,
200
433
    ) -> crate::Result<(&'a str, &'a str, UncheckedConsensus)> {
201
        use NetstatusKwd::*;
202
378
        let ((flavor, preamble), start_pos) = {
203
5754
            let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
204
433
            let preamble_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
205
            // Unwrapping should be safe because above `.parse` would have
206
            // returned an Error
207
            #[allow(clippy::unwrap_used)]
208
380
            let pos = preamble_sec.first_item().unwrap().offset_in(r.str()).unwrap();
209
380
            (Preamble::from_section(&preamble_sec)?, pos)
210
        };
211
378
        if RouterStatus::flavor() != flavor {
212
            return Err(EK::BadDocumentType.with_msg(format!(
213
                "Expected {:?}, got {:?}",
214
                RouterStatus::flavor(),
215
                flavor
216
            )));
217
378
        }
218

            
219
378
        let mut voters = Vec::new();
220

            
221
1513
        while let Some(voter) = Self::take_voterinfo(r)? {
222
1135
            voters.push(voter);
223
1135
        }
224

            
225
378
        let mut relays: Vec<RouterStatus> = Vec::new();
226
2420
        while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
227
2043
            if let Some(prev) = relays.last() {
228
1666
                if prev.rsa_identity() >= routerstatus.rsa_identity() {
229
1
                    return Err(EK::WrongSortOrder.at_pos(pos));
230
1665
                }
231
377
            }
232
2042
            relays.push(routerstatus);
233
        }
234
374
        relays.shrink_to_fit();
235

            
236
374
        let footer = Self::take_footer(r)?;
237

            
238
373
        let consensus = Consensus {
239
373
            flavor,
240
373
            preamble,
241
373
            voters,
242
373
            relays,
243
373
            footer,
244
373
        };
245

            
246
        // Find the signatures.
247
373
        let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
248
373
        let mut signatures = Vec::new();
249
1120
        for item in &mut *r {
250
1120
            let item = item?;
251
1120
            if item.kwd() != DIRECTORY_SIGNATURE {
252
                return Err(EK::UnexpectedToken
253
                    .with_msg(item.kwd().to_str())
254
                    .at_pos(item.pos()));
255
1120
            }
256

            
257
1120
            let sig = Signature::from_item(&item)?;
258
1120
            if first_sig.is_none() {
259
373
                first_sig = Some(item);
260
747
            }
261
1120
            signatures.push(sig);
262
        }
263

            
264
373
        let end_pos = match first_sig {
265
            None => return Err(EK::MissingToken.with_msg("directory-signature")),
266
            // Unwrap should be safe because `first_sig` was parsed from `r`
267
            #[allow(clippy::unwrap_used)]
268
373
            Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
269
        };
270

            
271
        // Find the appropriate digest.
272
373
        let signed_str = r
273
373
            .str()
274
373
            .get(start_pos..end_pos)
275
373
            .ok_or_else(|| internal!("chopped utf8"))?;
276
373
        let remainder = r
277
373
            .str()
278
373
            .get(end_pos..)
279
373
            .ok_or_else(|| internal!("chopped utf8"))?;
280
373
        let (sha256, sha1) = match RouterStatus::flavor() {
281
1
            ConsensusFlavor::Plain => (
282
1
                None,
283
1
                Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
284
1
            ),
285
372
            ConsensusFlavor::Microdesc => (
286
372
                Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
287
372
                None,
288
372
            ),
289
        };
290
373
        let hashes = DirectorySignaturesHashesAccu {
291
373
            sha256,
292
373
            sha1,
293
373
        };
294
373
        let siggroup = SignatureGroup {
295
373
            hashes,
296
373
            signatures,
297
373
        };
298

            
299
373
        let unval = UnvalidatedConsensus {
300
373
            consensus,
301
373
            siggroup,
302
373
            n_authorities: None,
303
373
        };
304
373
        let timebound_range = unval.consensus.preamble.validity_time_range();
305
373
        let timebound = TimeRangeBound::new(unval, timebound_range);
306
373
        Ok((signed_str, remainder, timebound))
307
433
    }
308
}
309

            
310
impl Preamble {
311
    /// Extract the CommonPreamble members from a single preamble section.
312
380
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> crate::Result<(ConsensusFlavor, Preamble)> {
313
        use NetstatusKwd::*;
314

            
315
        {
316
            // this unwrap is safe because if there is not at least one
317
            // token in the section, the section is unparsable.
318
            #[allow(clippy::unwrap_used)]
319
380
            let first = sec.first_item().unwrap();
320
380
            if first.kwd() != NETWORK_STATUS_VERSION {
321
1
                return Err(EK::UnexpectedToken
322
1
                    .with_msg(first.kwd().to_str())
323
1
                    .at_pos(first.pos()));
324
379
            }
325
        }
326

            
327
379
        let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
328

            
329
379
        let version: u32 = ver_item.parse_arg(0)?;
330
379
        if version != 3 {
331
1
            return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
332
378
        }
333
378
        let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
334

            
335
378
        let valid_after = sec
336
378
            .required(VALID_AFTER)?
337
378
            .args_as_str()
338
378
            .parse::<Iso8601TimeSp>()?
339
378
            .into();
340
378
        let fresh_until = sec
341
378
            .required(FRESH_UNTIL)?
342
378
            .args_as_str()
343
378
            .parse::<Iso8601TimeSp>()?
344
378
            .into();
345
378
        let valid_until = sec
346
378
            .required(VALID_UNTIL)?
347
378
            .args_as_str()
348
378
            .parse::<Iso8601TimeSp>()?
349
378
            .into();
350
378
        let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
351

            
352
756
        let parse_rec_versions = |item| {
353
756
            let item = sec
354
756
                .maybe(item);
355
756
            let args = item
356
756
                .args_as_str()
357
756
                .unwrap_or("")
358
                // C Tor emits an item with trailing whitespace which we must ignore
359
756
                .trim();
360
            // We want only the first arg, according to the spec.
361
            // We could want to use MaybeItem::parse_arg, but it treats absence of the
362
            // argument as an error.  There is no parse_optional_arg on `MaybeItem`.
363
            // We could add that, but I am trying to avoid adding code to the old parser.
364
            // So instead we reimplement argument splitting (again).
365
756
            args
366
756
                .split_once(|c: char| c.is_ascii_whitespace()).map(|(l, _r)| l).unwrap_or(args)
367
756
                .parse()
368
756
                .map_err(|_e| EK::BadArgument.at_pos(item.pos()))
369
756
        };
370
378
        let client_versions = parse_rec_versions(CLIENT_VERSIONS)?;
371
378
        let server_versions = parse_rec_versions(SERVER_VERSIONS)?;
372

            
373
378
        let proto_statuses = {
374
378
            let client = ProtoStatus::from_section(
375
378
                sec,
376
378
                RECOMMENDED_CLIENT_PROTOCOLS,
377
378
                REQUIRED_CLIENT_PROTOCOLS,
378
            )?;
379
378
            let relay = ProtoStatus::from_section(
380
378
                sec,
381
378
                RECOMMENDED_RELAY_PROTOCOLS,
382
378
                REQUIRED_RELAY_PROTOCOLS,
383
            )?;
384
378
            Arc::new(ProtoStatuses { client, relay })
385
        };
386

            
387
378
        let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
388

            
389
378
        let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
390
378
        if status != "consensus" {
391
            return Err(EK::BadDocumentType.err());
392
378
        }
393

            
394
        // We're ignoring KNOWN_FLAGS in the consensus.
395

            
396
378
        let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
397

            
398
378
        let shared_rand_previous_value = sec
399
378
            .get(SHARED_RAND_PREVIOUS_VALUE)
400
378
            .map(SharedRandStatus::from_item)
401
378
            .transpose()?;
402

            
403
378
        let shared_rand_current_value = sec
404
378
            .get(SHARED_RAND_CURRENT_VALUE)
405
378
            .map(SharedRandStatus::from_item)
406
378
            .transpose()?;
407

            
408
378
        let voting_delay = {
409
378
            let tok = sec.required(VOTING_DELAY)?;
410
378
            let vote_seconds = tok.parse_arg(0)?;
411
378
            let dist_seconds = tok.parse_arg(1)?;
412
378
            VotingDelayConstructor { vote_seconds, dist_seconds }.construct()
413
        };
414

            
415
378
        let shared_rand = SharedRandStatuses {
416
378
            shared_rand_previous_value,
417
378
            shared_rand_current_value,
418
378
            __non_exhaustive: (),
419
378
        };
420

            
421
378
        let preamble = Preamble {
422
378
            lifetime,
423
378
            client_versions,
424
378
            server_versions,
425
378
            proto_statuses,
426
378
            params,
427
378
            voting_delay,
428
378
            consensus_method: (consensus_method,),
429
378
            published: NotPresent,
430
378
            consensus_methods: NotPresent,
431
378
            known_flags: DocRelayFlags::new_empty_unknown_discarded(),
432
378
            shared_rand,
433
378
            __non_exhaustive: (),
434
378
        };
435

            
436
378
        Ok((flavor, preamble))
437
380
    }
438
}
439

            
440
/// A Microdesc consensus whose signatures have not yet been checked.
441
///
442
/// To validate this object, call set_n_authorities() on it, then call
443
/// check_signature() on that result with the set of certs that you
444
/// have.  Make sure only to provide authority certificates representing
445
/// real authorities!
446
#[derive(Debug, Clone)]
447
#[non_exhaustive]
448
pub struct UnvalidatedConsensus {
449
    /// The consensus object. We don't want to expose this until it's
450
    /// validated.
451
    pub consensus: Consensus,
452
    /// The signatures that need to be validated before we can call
453
    /// this consensus valid.
454
    pub siggroup: SignatureGroup,
455
    /// The total number of authorities that we believe in.  We need
456
    /// this information in order to validate the signatures, since it
457
    /// determines how many signatures we need to find valid in `siggroup`.
458
    pub n_authorities: Option<usize>,
459
}
460

            
461
impl UnvalidatedConsensus {
462
    /// Tell the unvalidated consensus how many authorities we believe in.
463
    ///
464
    /// Without knowing this number, we can't validate the signature.
465
    #[must_use]
466
267
    pub fn set_n_authorities(self, n_authorities: usize) -> Self {
467
267
        UnvalidatedConsensus {
468
267
            n_authorities: Some(n_authorities),
469
267
            ..self
470
267
        }
471
267
    }
472

            
473
    /// Return an iterator of all the certificate IDs that we might use
474
    /// to validate this consensus.
475
212
    pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
476
212
        match self.key_is_correct(&[]) {
477
            Ok(()) => Vec::new(),
478
212
            Err(missing) => missing,
479
        }
480
212
        .into_iter()
481
212
    }
482

            
483
    /// Return the lifetime of this unvalidated consensus
484
265
    pub fn peek_lifetime(&self) -> &Lifetime {
485
265
        self.consensus.lifetime()
486
265
    }
487

            
488
    /// Return true if a client who believes in exactly the provided
489
    /// set of authority IDs might consider this consensus to be
490
    /// well-signed.
491
    ///
492
    /// (This is the case if the consensus claims to be signed by more than
493
    /// half of the authorities in the list.)
494
270
    pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
495
270
        self.siggroup.could_validate(authorities)
496
270
    }
497

            
498
    /// Return the number of relays in this unvalidated consensus.
499
    ///
500
    /// This function is unstable. It is only enabled if the crate was
501
    /// built with the `experimental-api` feature.
502
    #[cfg(feature = "experimental-api")]
503
    pub fn n_relays(&self) -> usize {
504
        self.consensus.relays.len()
505
    }
506

            
507
    /// Modify the list of relays in this unvalidated consensus.
508
    ///
509
    /// A use case for this is long-lasting custom directories. To ensure Arti can still quickly
510
    /// build circuits when the directory gets old, a tiny churn file can be regularly obtained,
511
    /// listing no longer available Tor nodes, which can then be removed from the consensus.
512
    ///
513
    /// This function is unstable. It is only enabled if the crate was
514
    /// built with the `experimental-api` feature.
515
    #[cfg(feature = "experimental-api")]
516
    pub fn modify_relays<F>(&mut self, func: F)
517
    where
518
        F: FnOnce(&mut Vec<RouterStatus>),
519
    {
520
        func(&mut self.consensus.relays);
521
    }
522
}
523

            
524
impl ExternallySigned<Consensus> for UnvalidatedConsensus {
525
    type Key = [AuthCert];
526
    type KeyHint = Vec<AuthCertKeyIds>;
527
    type Error = Error;
528

            
529
324
    fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
530
324
        let (n_ok, missing) = self.siggroup.list_missing(k);
531
324
        match self.n_authorities {
532
324
            Some(n) if consensus_threshold(n).contains(&n_ok) => Ok(()),
533
268
            _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
534
        }
535
324
    }
536
56
    fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
537
56
        match self.n_authorities {
538
            None => Err(Error::from(internal!(
539
                "Didn't set authorities on consensus"
540
            ))),
541
56
            Some(authority) => {
542
56
                self.siggroup.validate(authority, k)
543
56
                    .map_err(|_: VerifyFailed| EK::BadSignature.err())
544
            }
545
        }
546
56
    }
547
161
    fn dangerously_assume_wellsigned(self) -> Consensus {
548
161
        self.consensus
549
161
    }
550
}
551

            
552
/// A Consensus object that has been parsed, but not checked for
553
/// signatures and timeliness.
554
pub type UncheckedConsensus = TimeRangeBound<UnvalidatedConsensus>;
555

            
556
impl NetworkStatusUnverified {
557
    /// Could we verify this consensus or do we need more authcerts?
558
    ///
559
    /// `Ok` means that we have enough authcerts to verify the signature.
560
    ///
561
    /// `Err` means that we have not enough authcerts,
562
    /// or the consensus has not enough signatures.
563
    /// The [`ConsensusVerifiabilityError`] error gives the details.
564
275
    pub fn can_verify(
565
275
        &self,
566
275
        trusted_authorities: &[RsaIdentity],
567
275
        certs_already: &[AuthCert],
568
275
    ) -> Result<(), ConsensusVerifiabilityError> {
569
275
        let sigs = self.inspect_unverified().1;
570
275
        Self::verify_general(
571
275
            sigs,
572
275
            trusted_authorities,
573
275
            certs_already,
574
448
            |_signature| {
575
                // indeed, we don't actuaally verify, so this is a no-op
576
448
                Ok(SignatureVerifiedIfIntended {})
577
448
            },
578
165
        )?;
579
110
        Ok(())
580
275
    }
581

            
582
    /// Verify the signatures
583
    ///
584
    /// Doesn't check the validity period:
585
    /// the document is wrapped in [`TimeRangeBound`],
586
    /// ensuring that the caller does that check.
587
277
    pub fn verify(
588
277
        self,
589
277
        trusted_authorities: &[RsaIdentity],
590
277
        certs: &[AuthCert],
591
277
    ) -> Result<TimeRangeBound<NetworkStatus>, ConsensusVerifyFailed> {
592
277
        let (body, sigs) = self.unwrap_unverified();
593

            
594
277
        Self::verify_general(
595
277
            &sigs,
596
277
            trusted_authorities,
597
277
            certs,
598
880
            |tv| tv.verify().map_err(ConsensusVerifyFailed::InvalidSignature),
599
61
        )?;
600

            
601
216
        let time_range = body.preamble.validity_time_range();
602
216
        Ok(TimeRangeBound::new(
603
216
            body,
604
216
            time_range,
605
216
        ))
606
277
    }
607

            
608
    /// Glue to call `SignatureGroup::verify_general` given our `SignaturesData`
609
    ///
610
    /// [`SignatureGroup::verify_general`] contains the actual verification code,
611
    /// shared between the old parser and the new.
612
552
    fn verify_general<E>(
613
552
        sigs: &parse2::SignaturesData<Self>,
614
552
        trusted: &[RsaIdentity],
615
552
        certs: &[AuthCert],
616
552
        do_verify: impl Fn(ConsensusSignatureToVerify) -> Result<SignatureVerifiedIfIntended, E>,
617
552
    ) -> Result<(), E>
618
552
    where ConsensusVerifiabilityError: Into<E>,
619
    {
620
552
        SignatureGroup {
621
552
            hashes: sigs.hashes,
622
552
            signatures: sigs.sigs.directory_signature.clone(),
623
552
        }.verify_general(
624
552
            VerifyGeneralTrustedAuthorities::TrustThese { trusted },
625
552
            certs,
626
552
            do_verify,
627
        )
628
552
    }
629
}
630

            
631
#[cfg(feature = "retain-unknown")]
632
#[test]
633
2
fn verify_error_netstatus() -> Result<(), anyhow::Error> {
634
    use assert_matches::assert_matches;
635
    use ConsensusVerifiabilityError as CVE;
636

            
637
2
    let file = ns_expr!(
638
1
        "testdata2/cached-consensus",
639
1
        "testdata2/cached-microdesc-consensus",
640
        unreachable(),
641
    );
642
2
    let (mut doc, _text, certs, authorities, _now) =
643
2
        super::test::prep_netstatus_verify::<NetworkStatusUnverified>(file)?;
644

            
645
    macro_rules! assert_consensus_verifiability_error { {
646
        ( $($verify_args:tt)* ),
647
        $($assert_matches_rhs:tt)*
648
    } => {
649
        assert_matches!(
650
            doc.can_verify($($verify_args)*),
651
            Err(e)
652
                => assert_matches!(e, $($assert_matches_rhs)*)
653
        );
654
        assert_matches!(
655
            doc.clone().verify($($verify_args)*),
656
            Err(ConsensusVerifyFailed::CertificationInsufficient(e))
657
                => assert_matches!(e, $($assert_matches_rhs)*)
658
        );
659
    } }
660

            
661
    // missing authcerts
662

            
663
2
    assert_consensus_verifiability_error! {
664
2
        (&authorities, &certs[..1]),
665
2
        ConsensusVerifiabilityError::MissingAuthCerts { deficit, missing } => {
666
2
            assert_eq!(deficit, authorities.len() / 2); // one short of strict majority
667
2
            itertools::assert_equal(
668
2
                missing.into_iter().sorted(),
669
12
                certs[1..].iter().map(|a| a.key_ids()).sorted(),
670
            );
671
        }
672
    }
673

            
674
    // wrong signers
675

            
676
2
    let wrong_authorities = authorities
677
2
        .iter()
678
2
        .cloned()
679
2
        .enumerate()
680
8
        .map(|(i, a)| {
681
8
            if i < 2 {
682
4
                [i as u8; _].into()
683
            } else {
684
4
                a
685
            }
686
8
        })
687
2
        .collect_vec();
688

            
689
2
    assert_consensus_verifiability_error! {
690
2
        (&wrong_authorities, &certs),
691
        CVE::InsufficientTrustedSigners
692
    }
693

            
694
    // one broken signature, but enough others
695

            
696
2
    doc.sigs.sigs.directory_signature[0].signature.fill(0xff);
697

            
698
    assert_matches!(
699
2
        doc.can_verify(&authorities, &certs),
700
        Ok(())
701
    );
702
    assert_matches!(
703
2
        doc.clone().verify(&authorities, &certs),
704
        Ok(_)
705
    );
706

            
707
    // too few signatories, and one broken signature
708

            
709
2
    doc.sigs.sigs.directory_signature.truncate(authorities.len() / 2 + 1);
710

            
711
    assert_matches!(
712
2
        doc.can_verify(&authorities, &certs),
713
        Ok(())
714
    );
715
    assert_matches!(
716
2
        doc.clone().verify(&authorities, &certs),
717
        Err(ConsensusVerifyFailed::InvalidSignature(VerifyFailed::VerifyFailed))
718
    );
719

            
720
    // too few signatures, no broken signatures
721

            
722
2
    doc.sigs.sigs.directory_signature.remove(0);
723

            
724
2
    assert_consensus_verifiability_error! {
725
2
        (&authorities, &certs),
726
        CVE::InsufficientTrustedSigners
727
    }
728

            
729
2
    Ok(())
730
2
}