Skip to main content

tor_netdoc/doc/netstatus/
each_flavor.rs

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
13use super::*;
14
15ns_use_this_variety! {
16    use [crate::doc::netstatus::rs]::?::{RouterStatus};
17}
18#[cfg(feature = "build_docs")]
19ns_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]
27pub 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
45impl Consensus {
46    /// Return the Lifetime for this consensus.
47    pub fn lifetime(&self) -> &Lifetime {
48        &self.preamble.lifetime
49    }
50
51    /// Return a slice of all the routerstatus entries in this consensus.
52    pub fn relays(&self) -> &[RouterStatus] {
53        &self.relays[..]
54    }
55
56    /// Return a mapping from keywords to integers representing how
57    /// to weight different kinds of relays in different path positions.
58    pub fn bandwidth_weights(&self) -> &NetParams<i32> {
59        &self.footer.bandwidth_weights
60    }
61
62    /// Return the map of network parameters that this consensus advertises.
63    pub fn params(&self) -> &NetParams<i32> {
64        &self.preamble.params
65    }
66
67    /// Return the latest shared random value, if the consensus
68    /// contains one.
69    pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
70        self.preamble.shared_rand.shared_rand_current_value.as_ref()
71    }
72
73    /// Return the previous shared random value, if the consensus
74    /// contains one.
75    pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
76        self.preamble.shared_rand.shared_rand_previous_value.as_ref()
77    }
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    pub fn relay_protocol_status(&self) -> &ProtoStatus {
82        &self.preamble.proto_statuses.relay
83    }
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    pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
93        &self.preamble.proto_statuses
94    }
95}
96
97impl 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    pub fn builder() -> ConsensusBuilder {
104        ConsensusBuilder::new(RouterStatus::flavor())
105    }
106
107    /// Try to parse a single networkstatus document from a string.
108    pub fn parse(s: &str) -> crate::Result<(&str, &str, UncheckedConsensus)> {
109        let mut reader = NetDocReader::new(s)?;
110        Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
111    }
112    /// Extract a voter-info section from the reader; return
113    /// Ok(None) when we are out of voter-info sections.
114    fn take_voterinfo(
115        r: &mut NetDocReader<'_, NetstatusKwd>,
116    ) -> crate::Result<Option<ConsensusAuthorityEntry>> {
117        use NetstatusKwd::*;
118
119        match r.peek() {
120            None => return Ok(None),
121            Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
122            _ => (),
123        };
124
125        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        let mut p = r.pause_at(|i| match i {
129            Err(_) => false,
130            Ok(item) => {
131                item.kwd() == RS_R
132                    || if item.kwd() == DIR_SOURCE {
133                        let was_first = first_dir_source;
134                        first_dir_source = false;
135                        !was_first
136                    } else {
137                        false
138                    }
139            }
140        });
141
142        let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
143        let voter = ConsensusAuthorityEntry::from_section(&voter_sec)?;
144
145        Ok(Some(voter))
146    }
147
148    /// Extract the footer (but not signatures) from the reader.
149    fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> crate::Result<ConsensusFooterFields> {
150        use NetstatusKwd::*;
151        let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
152        let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
153        let footer = ConsensusFooterFields::from_section(&footer_sec)?;
154        Ok(footer)
155    }
156
157    /// Extract a routerstatus from the reader.  Return Ok(None) if we're
158    /// out of routerstatus entries.
159    fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> crate::Result<Option<(Pos, RouterStatus)>> {
160        use NetstatusKwd::*;
161        match r.peek() {
162            None => return Ok(None),
163            Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
164            _ => (),
165        };
166
167        let pos = r.pos();
168
169        let mut first_r = true;
170        let mut p = r.pause_at(|i| match i {
171            Err(_) => false,
172            Ok(item) => {
173                item.kwd() == DIRECTORY_FOOTER
174                    || if item.kwd() == RS_R {
175                        let was_first = first_r;
176                        first_r = false;
177                        !was_first
178                    } else {
179                        false
180                    }
181            }
182        });
183
184        let rules = match RouterStatus::flavor() {
185            ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
186            ConsensusFlavor::Plain => &NS_ROUTERSTATUS_RULES_PLAIN,
187        };
188
189        let rs_sec = rules.parse(&mut p)?;
190        let rs = RouterStatus::from_section(&rs_sec)?;
191        Ok(Some((pos, rs)))
192    }
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    fn parse_from_reader<'a>(
199        r: &mut NetDocReader<'a, NetstatusKwd>,
200    ) -> crate::Result<(&'a str, &'a str, UncheckedConsensus)> {
201        use NetstatusKwd::*;
202        let ((flavor, preamble), start_pos) = {
203            let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
204            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            let pos = preamble_sec.first_item().unwrap().offset_in(r.str()).unwrap();
209            (Preamble::from_section(&preamble_sec)?, pos)
210        };
211        if RouterStatus::flavor() != flavor {
212            return Err(EK::BadDocumentType.with_msg(format!(
213                "Expected {:?}, got {:?}",
214                RouterStatus::flavor(),
215                flavor
216            )));
217        }
218
219        let mut voters = Vec::new();
220
221        while let Some(voter) = Self::take_voterinfo(r)? {
222            voters.push(voter);
223        }
224
225        let mut relays: Vec<RouterStatus> = Vec::new();
226        while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
227            if let Some(prev) = relays.last() {
228                if prev.rsa_identity() >= routerstatus.rsa_identity() {
229                    return Err(EK::WrongSortOrder.at_pos(pos));
230                }
231            }
232            relays.push(routerstatus);
233        }
234        relays.shrink_to_fit();
235
236        let footer = Self::take_footer(r)?;
237
238        let consensus = Consensus {
239            flavor,
240            preamble,
241            voters,
242            relays,
243            footer,
244        };
245
246        // Find the signatures.
247        let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
248        let mut signatures = Vec::new();
249        for item in &mut *r {
250            let item = item?;
251            if item.kwd() != DIRECTORY_SIGNATURE {
252                return Err(EK::UnexpectedToken
253                    .with_msg(item.kwd().to_str())
254                    .at_pos(item.pos()));
255            }
256
257            let sig = Signature::from_item(&item)?;
258            if first_sig.is_none() {
259                first_sig = Some(item);
260            }
261            signatures.push(sig);
262        }
263
264        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            Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
269        };
270
271        // Find the appropriate digest.
272        let signed_str = r.str().get(start_pos..end_pos).ok_or(internal!("chopped utf8"))?;
273        let remainder = r.str().get(end_pos..).ok_or(internal!("chopped utf8"))?;
274        let (sha256, sha1) = match RouterStatus::flavor() {
275            ConsensusFlavor::Plain => (
276                None,
277                Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
278            ),
279            ConsensusFlavor::Microdesc => (
280                Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
281                None,
282            ),
283        };
284        let hashes = DirectorySignaturesHashesAccu {
285            sha256,
286            sha1,
287            // TODO #2530 This is wrong.  There isn't one hash, there's two.
288            sha1_unnamed: sha1,
289        };
290        let siggroup = SignatureGroup {
291            hashes,
292            signatures,
293        };
294
295        let unval = UnvalidatedConsensus {
296            consensus,
297            siggroup,
298            n_authorities: None,
299        };
300        let timebound_range = unval.consensus.preamble.validity_time_range();
301        let timebound = TimerangeBound::new(unval, timebound_range);
302        Ok((signed_str, remainder, timebound))
303    }
304}
305
306impl Preamble {
307    /// Extract the CommonPreamble members from a single preamble section.
308    fn from_section(sec: &Section<'_, NetstatusKwd>) -> crate::Result<(ConsensusFlavor, Preamble)> {
309        use NetstatusKwd::*;
310
311        {
312            // this unwrap is safe because if there is not at least one
313            // token in the section, the section is unparsable.
314            #[allow(clippy::unwrap_used)]
315            let first = sec.first_item().unwrap();
316            if first.kwd() != NETWORK_STATUS_VERSION {
317                return Err(EK::UnexpectedToken
318                    .with_msg(first.kwd().to_str())
319                    .at_pos(first.pos()));
320            }
321        }
322
323        let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
324
325        let version: u32 = ver_item.parse_arg(0)?;
326        if version != 3 {
327            return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
328        }
329        let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
330
331        let valid_after = sec
332            .required(VALID_AFTER)?
333            .args_as_str()
334            .parse::<Iso8601TimeSp>()?
335            .into();
336        let fresh_until = sec
337            .required(FRESH_UNTIL)?
338            .args_as_str()
339            .parse::<Iso8601TimeSp>()?
340            .into();
341        let valid_until = sec
342            .required(VALID_UNTIL)?
343            .args_as_str()
344            .parse::<Iso8601TimeSp>()?
345            .into();
346        let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
347
348        let parse_rec_versions = |item| {
349            let item = sec
350                .maybe(item);
351            let args = item
352                .args_as_str()
353                .unwrap_or("")
354                // C Tor emits an item with trailing whitespace which we must ignore
355                .trim();
356            // We want only the first arg, according to the spec.
357            // We could want to use MaybeItem::parse_arg, but it treats absence of the
358            // argument as an error.  There is no parse_optional_arg on `MaybeItem`.
359            // We could add that, but I am trying to avoid adding code to the old parser.
360            // So instead we reimplement argument splitting (again).
361            args
362                .split_once(|c: char| c.is_ascii_whitespace()).map(|(l, _r)| l).unwrap_or(args)
363                .parse()
364                .map_err(|_e| EK::BadArgument.at_pos(item.pos()))
365        };
366        let client_versions = parse_rec_versions(CLIENT_VERSIONS)?;
367        let server_versions = parse_rec_versions(SERVER_VERSIONS)?;
368
369        let proto_statuses = {
370            let client = ProtoStatus::from_section(
371                sec,
372                RECOMMENDED_CLIENT_PROTOCOLS,
373                REQUIRED_CLIENT_PROTOCOLS,
374            )?;
375            let relay = ProtoStatus::from_section(
376                sec,
377                RECOMMENDED_RELAY_PROTOCOLS,
378                REQUIRED_RELAY_PROTOCOLS,
379            )?;
380            Arc::new(ProtoStatuses { client, relay })
381        };
382
383        let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
384
385        let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
386        if status != "consensus" {
387            return Err(EK::BadDocumentType.err());
388        }
389
390        // We're ignoring KNOWN_FLAGS in the consensus.
391
392        let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
393
394        let shared_rand_previous_value = sec
395            .get(SHARED_RAND_PREVIOUS_VALUE)
396            .map(SharedRandStatus::from_item)
397            .transpose()?;
398
399        let shared_rand_current_value = sec
400            .get(SHARED_RAND_CURRENT_VALUE)
401            .map(SharedRandStatus::from_item)
402            .transpose()?;
403
404        let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
405            let n1 = tok.parse_arg(0)?;
406            let n2 = tok.parse_arg(1)?;
407            Some((n1, n2))
408        } else {
409            None
410        };
411
412        let shared_rand = SharedRandStatuses {
413            shared_rand_previous_value,
414            shared_rand_current_value,
415            __non_exhaustive: (),
416        };
417
418        let preamble = Preamble {
419            lifetime,
420            client_versions,
421            server_versions,
422            proto_statuses,
423            params,
424            voting_delay,
425            consensus_method: (consensus_method,),
426            published: NotPresent,
427            consensus_methods: NotPresent,
428            known_flags: DocRelayFlags::new_empty_unknown_discarded(),
429            shared_rand,
430            __non_exhaustive: (),
431        };
432
433        Ok((flavor, preamble))
434    }
435}
436
437/// A Microdesc consensus whose signatures have not yet been checked.
438///
439/// To validate this object, call set_n_authorities() on it, then call
440/// check_signature() on that result with the set of certs that you
441/// have.  Make sure only to provide authority certificates representing
442/// real authorities!
443#[derive(Debug, Clone)]
444#[non_exhaustive]
445pub struct UnvalidatedConsensus {
446    /// The consensus object. We don't want to expose this until it's
447    /// validated.
448    pub consensus: Consensus,
449    /// The signatures that need to be validated before we can call
450    /// this consensus valid.
451    pub siggroup: SignatureGroup,
452    /// The total number of authorities that we believe in.  We need
453    /// this information in order to validate the signatures, since it
454    /// determines how many signatures we need to find valid in `siggroup`.
455    pub n_authorities: Option<usize>,
456}
457
458impl UnvalidatedConsensus {
459    /// Tell the unvalidated consensus how many authorities we believe in.
460    ///
461    /// Without knowing this number, we can't validate the signature.
462    #[must_use]
463    pub fn set_n_authorities(self, n_authorities: usize) -> Self {
464        UnvalidatedConsensus {
465            n_authorities: Some(n_authorities),
466            ..self
467        }
468    }
469
470    /// Return an iterator of all the certificate IDs that we might use
471    /// to validate this consensus.
472    pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
473        match self.key_is_correct(&[]) {
474            Ok(()) => Vec::new(),
475            Err(missing) => missing,
476        }
477        .into_iter()
478    }
479
480    /// Return the lifetime of this unvalidated consensus
481    pub fn peek_lifetime(&self) -> &Lifetime {
482        self.consensus.lifetime()
483    }
484
485    /// Return true if a client who believes in exactly the provided
486    /// set of authority IDs might consider this consensus to be
487    /// well-signed.
488    ///
489    /// (This is the case if the consensus claims to be signed by more than
490    /// half of the authorities in the list.)
491    pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
492        self.siggroup.could_validate(authorities)
493    }
494
495    /// Return the number of relays in this unvalidated consensus.
496    ///
497    /// This function is unstable. It is only enabled if the crate was
498    /// built with the `experimental-api` feature.
499    #[cfg(feature = "experimental-api")]
500    pub fn n_relays(&self) -> usize {
501        self.consensus.relays.len()
502    }
503
504    /// Modify the list of relays in this unvalidated consensus.
505    ///
506    /// A use case for this is long-lasting custom directories. To ensure Arti can still quickly
507    /// build circuits when the directory gets old, a tiny churn file can be regularly obtained,
508    /// listing no longer available Tor nodes, which can then be removed from the consensus.
509    ///
510    /// This function is unstable. It is only enabled if the crate was
511    /// built with the `experimental-api` feature.
512    #[cfg(feature = "experimental-api")]
513    pub fn modify_relays<F>(&mut self, func: F)
514    where
515        F: FnOnce(&mut Vec<RouterStatus>),
516    {
517        func(&mut self.consensus.relays);
518    }
519}
520
521impl ExternallySigned<Consensus> for UnvalidatedConsensus {
522    type Key = [AuthCert];
523    type KeyHint = Vec<AuthCertKeyIds>;
524    type Error = Error;
525
526    fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
527        let (n_ok, missing) = self.siggroup.list_missing(k);
528        match self.n_authorities {
529            Some(n) if consensus_threshold(n).contains(&n_ok) => Ok(()),
530            _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
531        }
532    }
533    fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
534        match self.n_authorities {
535            None => Err(Error::from(internal!(
536                "Didn't set authorities on consensus"
537            ))),
538            Some(authority) => {
539                self.siggroup.validate(authority, k)
540                    .map_err(|_: VerifyFailed| EK::BadSignature.err())
541            }
542        }
543    }
544    fn dangerously_assume_wellsigned(self) -> Consensus {
545        self.consensus
546    }
547}
548
549/// A Consensus object that has been parsed, but not checked for
550/// signatures and timeliness.
551pub type UncheckedConsensus = TimerangeBound<UnvalidatedConsensus>;
552
553#[cfg(feature = "incomplete")] // untested
554impl NetworkStatusUnverified {
555    /// Could we verify this consensus or do we need more authcerts?
556    ///
557    /// `Ok` means that we have enough authcerts to verify the signature.
558    ///
559    /// `Err` means that we have not enough authcerts,
560    /// or the consensus has not enough signatures.
561    /// The [`ConsensusVerifiabilityError`] error gives the details.
562    pub fn can_verify(
563        &self,
564        trusted_authorities: &[RsaIdentity],
565        certs_already: &[AuthCert],
566    ) -> Result<(), ConsensusVerifiabilityError> {
567        let sigs = self.inspect_unverified().1;
568        Self::verify_general(
569            sigs,
570            trusted_authorities,
571            certs_already,
572            |_signature| {
573                // indeed, we don't actuaally verify, so this is a no-op
574                Ok(SignatureVerifiedIfIntended {})
575            },
576        )?;
577        Ok(())
578    }
579
580    /// Verify the signatures
581    ///
582    /// Doesn't check the validity period:
583    /// the document is wrapped in [`TimerangeBound`],
584    /// ensuring that the caller does that check.
585    pub fn verify(
586        self,
587        trusted_authorities: &[RsaIdentity],
588        certs: &[AuthCert],
589    ) -> Result<TimerangeBound<NetworkStatus>, ConsensusVerifyFailed> {
590        let (body, sigs) = self.unwrap_unverified();
591
592        Self::verify_general(
593            &sigs,
594            trusted_authorities,
595            certs,
596            |tv| tv.verify().map_err(ConsensusVerifyFailed::InvalidSignature),
597        )?;
598
599        let time_range = body.preamble.validity_time_range();
600        Ok(TimerangeBound::new(
601            body,
602            time_range,
603        ))
604    }
605
606    /// Glue to call `SignatureGroup::verify_general` given our `SignaturesData`
607    ///
608    /// [`SignatureGroup::verify_general`] contains the actual verification code,
609    /// shared between the old parser and the new.
610    fn verify_general<E>(
611        sigs: &parse2::SignaturesData<Self>,
612        trusted: &[RsaIdentity],
613        certs: &[AuthCert],
614        do_verify: impl Fn(ConsensusSignatureToVerify) -> Result<SignatureVerifiedIfIntended, E>,
615    ) -> Result<(), E>
616    where ConsensusVerifiabilityError: Into<E>,
617    {
618        SignatureGroup {
619            hashes: sigs.hashes,
620            signatures: sigs.sigs.directory_signature.clone(),
621        }.verify_general(
622            VerifyGeneralTrustedAuthorities::TrustThese { trusted },
623            certs,
624            do_verify,
625        )
626    }
627}