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

            
14
use super::super::*;
15

            
16
/// Toplevel document string for error reporting
17
const TOPLEVEL_DOCTYPE_FOR_ERROR: &str =
18
    ns_expr!("NetworkStatusVote", "NetworkStatusNs", "NetworkStatusMd",);
19

            
20
/// The real router status entry type.
21
pub 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]
35
pub 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]
76
pub 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]
89
pub 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]
97
pub struct NdaVoteStatus {}
98

            
99
/// `network-status-version` _flavour_ value
100
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
101
#[non_exhaustive]
102
pub struct NdaNetworkStatusVersionFlavour {}
103

            
104
/// The argument in `network-status-version` that is there iff it's a microdesc consensus.
105
const NDA_NETWORK_STATUS_VERSION_FLAVOUR: Option<&str> = ns_expr!(None, None, Some("microdesc"));
106

            
107
impl ItemArgumentParseable for NdaNetworkStatusVersionFlavour {
108
222
    fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, AE> {
109
222
        let exp: Option<&str> = NDA_NETWORK_STATUS_VERSION_FLAVOUR;
110
222
        if let Some(exp) = exp {
111
2
            let got = args.next().ok_or(AE::Missing)?;
112
2
            if got != exp {
113
                return Err(AE::Invalid);
114
2
            };
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
220
            args.reject_extra_args()?;
120
        }
121
222
        Ok(Self {})
122
222
    }
123
}
124

            
125
/// The document type argument in `vote-status`
126
const NDA_VOTE_STATUS: &str = ns_expr!("vote", "consensus", "consensus");
127

            
128
impl FromStr for NdaVoteStatus {
129
    type Err = InvalidNetworkStatusVoteStatus;
130
222
    fn from_str(s: &str) -> Result<Self, InvalidNetworkStatusVoteStatus> {
131
222
        if s == NDA_VOTE_STATUS {
132
222
            Ok(Self {})
133
        } else {
134
            Err(InvalidNetworkStatusVoteStatus {})
135
        }
136
222
    }
137
}
138

            
139
impl Display for NdaVoteStatus {
140
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141
        Display::fmt(NDA_VOTE_STATUS, f)
142
    }
143
}
144

            
145
impl NormalItemArgument for NdaVoteStatus {}
146

            
147
/// `voting-delay` value
148
#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
149
#[derive_deftly(ItemValueParseable)]
150
#[non_exhaustive]
151
pub 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]
162
pub 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]
171
pub struct NdiAuthorityDirSource {
172
    /// nickname
173
    pub nickname: types::Nickname,
174
    /// fingerprint
175
    pub h_p_auth_id_rsa: types::Fingerprint,
176
}
177

            
178
ns_choose! { (
179
    use VoteAuthoritySection as NddAuthoritySection;
180
)(
181
    use ConsensusAuthoritySection as NddAuthoritySection;
182
)}
183

            
184
ns_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
2
        pub fn verify(
261
2
            self,
262
2
            now: SystemTime,
263
2
            authorities: &[pk::rsa::RsaIdentity],
264
2
            certs: &[&DirAuthKeyCert],
265
2
        ) -> Result<(NetworkStatus, SignaturesData<NetworkStatusUnverified>), VF> {
266
2
            let threshold = authorities.len() / 2 + 1; // strict majority
267
2
            let validity_start = self.body.valid_after.0
268
2
                .checked_sub(Duration::from_secs(self.body.voting_delay.dist_seconds.into()))
269
2
                .ok_or(VF::Other)?;
270
2
            check_validity_time(now, validity_start..= *self.body.valid_until.0)?;
271

            
272
2
            netstatus::verify_general_timeless(
273
2
                &self.sigs.hashes,
274
2
                &self.sigs.sigs.directory_signature,
275
2
                authorities,
276
2
                certs,
277
2
                threshold,
278
            )?;
279

            
280
2
            Ok(self.unwrap_unverified())
281
2
        }
282
    }
283
)}