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
72675
    pub fn lifetime(&self) -> &Lifetime {
48
72675
        &self.preamble.lifetime
49
72675
    }
50

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

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

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

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

            
73
    /// Return the previous shared random value, if the consensus
74
    /// contains one.
75
46461
    pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
76
46461
        self.preamble.shared_rand.shared_rand_previous_value.as_ref()
77
46461
    }
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
2295
    pub fn relay_protocol_status(&self) -> &ProtoStatus {
82
2295
        &self.preamble.proto_statuses.relay
83
2295
    }
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
102
    pub fn client_protocol_status(&self) -> &ProtoStatus {
88
102
        &self.preamble.proto_statuses.client
89
102
    }
90

            
91
    /// Return a set of all known [`ProtoStatus`] values.
92
51
    pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
93
51
        &self.preamble.proto_statuses
94
51
    }
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
14335
    pub fn builder() -> ConsensusBuilder {
104
14335
        ConsensusBuilder::new(RouterStatus::flavor())
105
14335
    }
106

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

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

            
125
1115
        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
4755
        let mut p = r.pause_at(|i| match i {
129
            Err(_) => false,
130
4712
            Ok(item) => {
131
4712
                item.kwd() == RS_R
132
4320
                    || if item.kwd() == DIR_SOURCE {
133
1964
                        let was_first = first_dir_source;
134
1964
                        first_dir_source = false;
135
1964
                        !was_first
136
                    } else {
137
2356
                        false
138
                    }
139
            }
140
4712
        });
141

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

            
145
1115
        Ok(Some(voter))
146
1486
    }
147

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

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

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

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

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

            
189
2002
        let rs_sec = rules.parse(&mut p)?;
190
2002
        let rs = RouterStatus::from_section(&rs_sec)?;
191
1996
        Ok(Some((pos, rs)))
192
2365
    }
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
    #[allow(clippy::string_slice)] // TODO
199
426
    fn parse_from_reader<'a>(
200
426
        r: &mut NetDocReader<'a, NetstatusKwd>,
201
426
    ) -> crate::Result<(&'a str, &'a str, UncheckedConsensus)> {
202
        use NetstatusKwd::*;
203
371
        let ((flavor, preamble), start_pos) = {
204
6013
            let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
205
426
            let preamble_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
206
            // Unwrapping should be safe because above `.parse` would have
207
            // returned an Error
208
            #[allow(clippy::unwrap_used)]
209
375
            let pos = preamble_sec.first_item().unwrap().offset_in(r.str()).unwrap();
210
375
            (Preamble::from_section(&preamble_sec)?, pos)
211
        };
212
371
        if RouterStatus::flavor() != flavor {
213
            return Err(EK::BadDocumentType.with_msg(format!(
214
                "Expected {:?}, got {:?}",
215
                RouterStatus::flavor(),
216
                flavor
217
            )));
218
371
        }
219

            
220
371
        let mut voters = Vec::new();
221

            
222
1486
        while let Some(voter) = Self::take_voterinfo(r)? {
223
1115
            voters.push(voter);
224
1115
        }
225

            
226
371
        let mut relays: Vec<RouterStatus> = Vec::new();
227
2365
        while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
228
1996
            if let Some(prev) = relays.last() {
229
1627
                if prev.rsa_identity() >= routerstatus.rsa_identity() {
230
2
                    return Err(EK::WrongSortOrder.at_pos(pos));
231
1625
                }
232
369
            }
233
1994
            relays.push(routerstatus);
234
        }
235
363
        relays.shrink_to_fit();
236

            
237
363
        let footer = Self::take_footer(r)?;
238

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

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

            
258
1085
            let sig = Signature::from_item(&item)?;
259
1085
            if first_sig.is_none() {
260
361
                first_sig = Some(item);
261
724
            }
262
1085
            signatures.push(sig);
263
        }
264

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

            
272
        // Find the appropriate digest.
273
361
        let signed_str = &r.str()[start_pos..end_pos];
274
361
        let remainder = &r.str()[end_pos..];
275
361
        let (sha256, sha1) = match RouterStatus::flavor() {
276
2
            ConsensusFlavor::Plain => (
277
2
                None,
278
2
                Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
279
2
            ),
280
359
            ConsensusFlavor::Microdesc => (
281
359
                Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
282
359
                None,
283
359
            ),
284
        };
285
361
        let hashes = DirectorySignaturesHashesAccu {
286
361
            sha256,
287
361
            sha1,
288
361
            // TODO #2530 This is wrong.  There isn't one hash, there's two.
289
361
            sha1_unnamed: sha1,
290
361
        };
291
361
        let siggroup = SignatureGroup {
292
361
            hashes,
293
361
            signatures,
294
361
        };
295

            
296
361
        let unval = UnvalidatedConsensus {
297
361
            consensus,
298
361
            siggroup,
299
361
            n_authorities: None,
300
361
        };
301
361
        let timebound_range = unval.consensus.preamble.validity_time_range();
302
361
        let timebound = TimerangeBound::new(unval, timebound_range);
303
361
        Ok((signed_str, remainder, timebound))
304
426
    }
305
}
306

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

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

            
324
394
        let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
325

            
326
394
        let version: u32 = ver_item.parse_arg(0)?;
327
394
        if version != 3 {
328
2
            return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
329
392
        }
330
392
        let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
331

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

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

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

            
384
392
        let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
385

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

            
391
        // We're ignoring KNOWN_FLAGS in the consensus.
392

            
393
392
        let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
394

            
395
392
        let shared_rand_previous_value = sec
396
392
            .get(SHARED_RAND_PREVIOUS_VALUE)
397
392
            .map(SharedRandStatus::from_item)
398
392
            .transpose()?;
399

            
400
392
        let shared_rand_current_value = sec
401
392
            .get(SHARED_RAND_CURRENT_VALUE)
402
392
            .map(SharedRandStatus::from_item)
403
392
            .transpose()?;
404

            
405
392
        let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
406
392
            let n1 = tok.parse_arg(0)?;
407
392
            let n2 = tok.parse_arg(1)?;
408
392
            Some((n1, n2))
409
        } else {
410
            None
411
        };
412

            
413
392
        let shared_rand = SharedRandStatuses {
414
392
            shared_rand_previous_value,
415
392
            shared_rand_current_value,
416
392
            __non_exhaustive: (),
417
392
        };
418

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

            
434
392
        Ok((flavor, preamble))
435
396
    }
436
}
437

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

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

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

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

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

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

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

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

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

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

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

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

            
593
4
        Self::verify_general(
594
4
            &sigs,
595
4
            trusted_authorities,
596
4
            certs,
597
16
            |tv| tv.verify().map_err(ConsensusVerifyFailed::InvalidSignature),
598
        )?;
599

            
600
4
        let time_range = body.preamble.validity_time_range();
601
4
        Ok(TimerangeBound::new(
602
4
            body,
603
4
            time_range,
604
4
        ))
605
4
    }
606

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