Skip to main content

tor_netdoc/parse2/poc/netstatus/
flavoured.rs

1//! network status documents - types that vary by flavour
2//!
3//! **This file is reincluded multiple times**,
4//! once for each consensus flavour, and once for votes.
5//!
6//! Each time, with different behaviour for the macros `ns_***`.
7//!
8//! Thus, this file generates (for example) all three of:
9//! `ns::NetworkStatus` aka `NetworkStatusNs`,
10//! `NetworkStatusMd` and `NetworkStatusVote`.
11//!
12//! (We treat votes as a "flavour".)
13
14use super::super::*;
15
16/// Toplevel document string for error reporting
17const TOPLEVEL_DOCTYPE_FOR_ERROR: &str =
18    ns_expr!("NetworkStatusVote", "NetworkStatusNs", "NetworkStatusMd",);
19
20/// The real router status entry type.
21pub type Router = ns_type!(
22    crate::doc::netstatus::VoteRouterStatus,
23    crate::doc::netstatus::PlainRouterStatus,
24    crate::doc::netstatus::MdRouterStatus,
25);
26
27/// Network status document (vote, consensus, or microdescriptor consensus) - body
28///
29/// The preamble items are members of this struct.
30/// The rest are handled as sub-documents.
31#[derive(Deftly, Clone, Debug)]
32#[derive_deftly(NetdocParseableUnverified)]
33#[deftly(netdoc(doctype_for_error = TOPLEVEL_DOCTYPE_FOR_ERROR))]
34#[non_exhaustive]
35pub struct NetworkStatus {
36    /// `network-status-version`
37    pub network_status_version: (NdaNetworkStatusVersion, NdaNetworkStatusVersionFlavour),
38
39    /// `vote-status`
40    pub vote_status: NdiVoteStatus,
41
42    /// `published`
43    pub published: ns_type!((NdaSystemTimeDeprecatedSyntax,), Option<Void>,),
44
45    /// `valid-after`
46    pub valid_after: (NdaSystemTimeDeprecatedSyntax,),
47
48    /// `valid-until`
49    pub valid_until: (NdaSystemTimeDeprecatedSyntax,),
50
51    /// `voting-delay`
52    pub voting_delay: NdiVotingDelay,
53
54    /// `params`
55    #[deftly(netdoc(default))]
56    pub params: NdiParams,
57
58    /// Authority section
59    #[deftly(netdoc(subdoc))]
60    pub authority: NddAuthoritySection,
61
62    /// `r` subdocuments
63    #[deftly(netdoc(subdoc))]
64    pub r: Vec<Router>,
65
66    /// `directory-footer` section (which we handle as a sub-document)
67    #[deftly(netdoc(subdoc))]
68    pub directory_footer: Option<NddDirectoryFooter>,
69}
70
71/// Signatures on a network status document
72#[derive(Deftly, Clone, Debug)]
73#[derive_deftly(NetdocParseableSignatures)]
74#[deftly(netdoc(signatures(hashes_accu = "DirectorySignaturesHashesAccu")))]
75#[non_exhaustive]
76pub struct NetworkStatusSignatures {
77    /// `directory-signature`s
78    pub directory_signature: ns_type!(NdiDirectorySignature, Vec<NdiDirectorySignature>),
79}
80
81/// `vote-status` value
82///
83/// In a non-demo we'd probably abolish this,
84/// using `NdaStatus` directly in `NddNetworkStatus`
85/// impl of `ItemValueParseable` for tuples.
86#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
87#[derive_deftly(ItemValueParseable)]
88#[non_exhaustive]
89pub struct NdiVoteStatus {
90    /// status
91    pub status: NdaVoteStatus,
92}
93
94/// `vote-status` status argument (for a specific flavour)
95#[derive(Clone, Debug, Hash, Eq, PartialEq)]
96#[non_exhaustive]
97pub struct NdaVoteStatus {}
98
99/// `network-status-version` _flavour_ value
100#[derive(Clone, Debug, Hash, Eq, PartialEq)]
101#[non_exhaustive]
102pub struct NdaNetworkStatusVersionFlavour {}
103
104/// The argument in `network-status-version` that is there iff it's a microdesc consensus.
105const NDA_NETWORK_STATUS_VERSION_FLAVOUR: Option<&str> = ns_expr!(None, None, Some("microdesc"));
106
107impl ItemArgumentParseable for NdaNetworkStatusVersionFlavour {
108    fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, AE> {
109        let exp: Option<&str> = NDA_NETWORK_STATUS_VERSION_FLAVOUR;
110        if let Some(exp) = exp {
111            let got = args.next().ok_or(AE::Missing)?;
112            if got != exp {
113                return Err(AE::Invalid);
114            };
115        } else {
116            // NS consensus, or vote.  Reject additional arguments, since they
117            // might be an unknown flavour.  See
118            //   https://gitlab.torproject.org/tpo/core/torspec/-/issues/359
119            args.reject_extra_args()?;
120        }
121        Ok(Self {})
122    }
123}
124
125/// The document type argument in `vote-status`
126const NDA_VOTE_STATUS: &str = ns_expr!("vote", "consensus", "consensus");
127
128impl FromStr for NdaVoteStatus {
129    type Err = InvalidNetworkStatusVoteStatus;
130    fn from_str(s: &str) -> Result<Self, InvalidNetworkStatusVoteStatus> {
131        if s == NDA_VOTE_STATUS {
132            Ok(Self {})
133        } else {
134            Err(InvalidNetworkStatusVoteStatus {})
135        }
136    }
137}
138
139impl Display for NdaVoteStatus {
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        Display::fmt(NDA_VOTE_STATUS, f)
142    }
143}
144
145impl NormalItemArgument for NdaVoteStatus {}
146
147/// `voting-delay` value
148#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
149#[derive_deftly(ItemValueParseable)]
150#[non_exhaustive]
151pub struct NdiVotingDelay {
152    /// VoteSeconds
153    pub vote_seconds: u32,
154    /// DistSeconds
155    pub dist_seconds: u32,
156}
157
158/// `directory-footer` section
159#[derive(Deftly, Clone, Debug)]
160#[derive_deftly(NetdocParseable)]
161#[non_exhaustive]
162pub struct NddDirectoryFooter {
163    /// `directory-footer`
164    pub directory_footer: (),
165}
166
167/// `dir-source`
168#[derive(Deftly, Clone, Debug)]
169#[derive_deftly(ItemValueParseable)]
170#[non_exhaustive]
171pub struct NdiAuthorityDirSource {
172    /// nickname
173    pub nickname: types::Nickname,
174    /// fingerprint
175    pub h_p_auth_id_rsa: types::Fingerprint,
176}
177
178ns_choose! { (
179    use VoteAuthoritySection as NddAuthoritySection;
180)(
181    use ConsensusAuthoritySection as NddAuthoritySection;
182)}
183
184ns_choose! { (
185    impl NetworkStatusUnverified {
186        /// Verify this vote's signatures using the embedded certificate
187        ///
188        /// # Security considerations
189        ///
190        /// The caller should use `NetworkStatus::h_kp_auth_id_rsa`
191        /// to find out which voter's vote this is.
192        pub fn verify_selfcert(
193            self,
194            now: SystemTime,
195        ) -> Result<(NetworkStatus, SignaturesData<NetworkStatusUnverified>), VF> {
196            let validity = *self.body.published.0 ..= *self.body.valid_until.0;
197            check_validity_time(now, validity)?;
198
199            let cert = self.body.parse_authcert()?.verify_selfcert(now)?;
200
201            netstatus::verify_general_timeless(
202                &self.sigs.hashes,
203                slice::from_ref(&self.sigs.sigs.directory_signature),
204                &[*cert.fingerprint],
205                &[&cert],
206                1,
207            )?;
208
209            Ok(self.unwrap_unverified())
210        }
211    }
212
213    impl NetworkStatus {
214        /// Parse the embedded authcert
215        fn parse_authcert(&self) -> Result<crate::doc::authcert::AuthCertUnverified, EP> {
216            let cert_input = ParseInput::new(
217                self.authority.cert.as_str(),
218                "<embedded auth cert>",
219            );
220            parse_netdoc(&cert_input).map_err(|e| e.problem)
221        }
222
223        /// Voter identity
224        ///
225        /// # Security considerations
226        ///
227        /// The returned identity has been confirmed to have properly certified
228        /// this vote at this time.
229        ///
230        /// It is up to the caller to decide whether this identity is actually
231        /// a voter, count up votes, etc.
232        pub fn h_kp_auth_id_rsa(&self) -> pk::rsa::RsaIdentity {
233            *self.parse_authcert()
234                // SECURITY: if the user calls this function, they have a bare
235                // NetworkStatus, not a NetworkStatusUnverified, so parsing
236                // and verification has already been done in verify_selfcert above.
237                .expect("was verified already!")
238                .inspect_unverified()
239                .0
240                .fingerprint
241        }
242    }
243) (
244    impl NetworkStatusUnverified {
245        /// Verify this consensus document
246        ///
247        /// # Security considerations
248        ///
249        /// The timeliness verification is relaxed, and incorporates the `DistSeconds` skew.
250        /// The caller **must not use** the returned consensus before its `valid_after`,
251        /// and must handle `fresh_until`.
252        ///
253        /// `authorities` should be a list of the authorities
254        /// that the caller trusts.
255        ///
256        /// `certs` is a list of dir auth key certificates to use to try to link
257        /// the signed consensus to those authorities.
258        /// Extra certificates in `certs`, that don't come from anyone in `authorities`,
259        /// are ignored.
260        pub fn verify(
261            self,
262            now: SystemTime,
263            authorities: &[pk::rsa::RsaIdentity],
264            certs: &[&DirAuthKeyCert],
265        ) -> Result<(NetworkStatus, SignaturesData<NetworkStatusUnverified>), VF> {
266            let threshold = authorities.len() / 2 + 1; // strict majority
267            let validity_start = self.body.valid_after.0
268                .checked_sub(Duration::from_secs(self.body.voting_delay.dist_seconds.into()))
269                .ok_or(VF::Other)?;
270            check_validity_time(now, validity_start..= *self.body.valid_until.0)?;
271
272            netstatus::verify_general_timeless(
273                &self.sigs.hashes,
274                &self.sigs.sigs.directory_signature,
275                authorities,
276                certs,
277                threshold,
278            )?;
279
280            Ok(self.unwrap_unverified())
281        }
282    }
283)}