tor_netdoc/doc/netstatus/
each_flavor.rs1use 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#[derive(Debug, Clone)]
26#[non_exhaustive]
27pub struct Consensus {
28 pub flavor: ConsensusFlavor,
31 pub preamble: Preamble,
33 pub voters: Vec<ConsensusAuthorityEntry>,
35 pub relays: Vec<RouterStatus>,
41 pub footer: ConsensusFooterFields,
43}
44
45impl Consensus {
46 pub fn lifetime(&self) -> &Lifetime {
48 &self.preamble.lifetime
49 }
50
51 pub fn relays(&self) -> &[RouterStatus] {
53 &self.relays[..]
54 }
55
56 pub fn bandwidth_weights(&self) -> &NetParams<i32> {
59 &self.footer.bandwidth_weights
60 }
61
62 pub fn params(&self) -> &NetParams<i32> {
64 &self.preamble.params
65 }
66
67 pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
70 self.preamble.shared_rand.shared_rand_current_value.as_ref()
71 }
72
73 pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
76 self.preamble.shared_rand.shared_rand_previous_value.as_ref()
77 }
78
79 pub fn relay_protocol_status(&self) -> &ProtoStatus {
82 &self.preamble.proto_statuses.relay
83 }
84
85 pub fn client_protocol_status(&self) -> &ProtoStatus {
88 &self.preamble.proto_statuses.client
89 }
90
91 pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
93 &self.preamble.proto_statuses
94 }
95}
96
97impl Consensus {
98 #[cfg(feature = "build_docs")]
103 pub fn builder() -> ConsensusBuilder {
104 ConsensusBuilder::new(RouterStatus::flavor())
105 }
106
107 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 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 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 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 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 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 #[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 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 #[allow(clippy::unwrap_used)]
268 Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
269 };
270
271 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 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 fn from_section(sec: &Section<'_, NetstatusKwd>) -> crate::Result<(ConsensusFlavor, Preamble)> {
309 use NetstatusKwd::*;
310
311 {
312 #[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 .trim();
356 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 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#[derive(Debug, Clone)]
444#[non_exhaustive]
445pub struct UnvalidatedConsensus {
446 pub consensus: Consensus,
449 pub siggroup: SignatureGroup,
452 pub n_authorities: Option<usize>,
456}
457
458impl UnvalidatedConsensus {
459 #[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 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 pub fn peek_lifetime(&self) -> &Lifetime {
482 self.consensus.lifetime()
483 }
484
485 pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
492 self.siggroup.could_validate(authorities)
493 }
494
495 #[cfg(feature = "experimental-api")]
500 pub fn n_relays(&self) -> usize {
501 self.consensus.relays.len()
502 }
503
504 #[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
549pub type UncheckedConsensus = TimerangeBound<UnvalidatedConsensus>;
552
553#[cfg(feature = "incomplete")] impl NetworkStatusUnverified {
555 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 Ok(SignatureVerifiedIfIntended {})
575 },
576 )?;
577 Ok(())
578 }
579
580 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 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}