1
//! network status documents: shared between votes, consensuses and md consensuses
2

            
3
use super::*;
4

            
5
use crate::doc::{self, authcert};
6
use crate::types;
7
use authcert::AuthCert as DirAuthKeyCert;
8
use doc::netstatus::{ConsensusAuthoritySection, VoteAuthoritySection};
9

            
10
mod ns_per_flavour_macros;
11
pub use ns_per_flavour_macros::*;
12

            
13
ns_per_flavour_macros::ns_export_flavoured_types! {
14
    NetworkStatus, NetworkStatusUnverified, Router,
15
}
16

            
17
/// `network-status-version` version value
18
#[derive(Debug, Clone, Copy, Eq, PartialEq, strum::EnumString, strum::Display)]
19
#[non_exhaustive]
20
pub enum NdaNetworkStatusVersion {
21
    /// The currently supported version, `3`
22
    #[strum(serialize = "3")]
23
    V3,
24
}
25

            
26
impl NormalItemArgument for NdaNetworkStatusVersion {}
27

            
28
/// `params` value
29
#[derive(Clone, Debug, Default, Deftly)]
30
#[derive_deftly(ItemValueParseable)]
31
#[non_exhaustive]
32
pub struct NdiParams {
33
    // Not implemented.
34
}
35

            
36
/// `r` sub-document
37
#[derive(Deftly, Clone, Debug)]
38
#[derive_deftly(ItemValueParseable)]
39
#[non_exhaustive]
40
pub struct NdiR {
41
    /// nickname
42
    pub nickname: types::Nickname,
43
    /// identity
44
    pub identity: String, // In non-demo, use a better type
45
}
46

            
47
/// `directory-signature` value
48
#[derive(Debug, Clone)]
49
#[non_exhaustive]
50
pub enum NdiDirectorySignature {
51
    /// Known "hash function" name
52
    Known {
53
        /// Hash algorithm
54
        hash_algo: DirectorySignatureHashAlgo,
55
        /// H(KP\_auth\_id\_rsa)
56
        h_kp_auth_id_rsa: pk::rsa::RsaIdentity,
57
        /// H(kp\_auth\_sign\_rsa)
58
        h_kp_auth_sign_rsa: pk::rsa::RsaIdentity,
59
        /// RSA signature
60
        rsa_signature: Vec<u8>,
61
    },
62
    /// Unknown "hash function" name
63
    ///
64
    /// TODO torspec#350;
65
    /// might have been an unknown algorithm, or might be invalid hex, or something.
66
    Unknown {},
67
}
68
define_derive_deftly! {
69
    /// Ad-hoc derives for [`DirectorySignatureHash`] impls, avoiding copypasta bugs
70
    ///
71
    /// # Input
72
    ///
73
    ///  * `pub enum DirectorySignatureHashAlgo`
74
    ///  * Unit variants
75
    ///  * Each variant with `#[deftly(hash_len = "N")]`
76
    ///    where `N` is the digest length in bytes.
77
    ///
78
    /// # Generated code
79
    ///
80
    ///  * `pub enum DirectorySignaturesHashesAccu`,
81
    ///    with each variant a 1-tuple containing `Option<[u8; N]>`.
82
    ///    (These are `None` if this hash has not been computed yet.)
83
    ///
84
    ///  * `DirectorySignaturesHashesAccu::parse_keyword_and_hash`
85
    ///
86
    ///  * `DirectorySignaturesHashesAccu::hash_slice_for_verification`
87
    DirectorySignatureHashesAccu expect items, beta_deftly:
88

            
89
    ${define FNAME ${paste ${snake_case $vname}} }
90

            
91
    /// `directory-signature`a hash algorithm argument
92
    #[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Deftly)]
93
    #[derive_deftly(AsMutSelf)]
94
    #[non_exhaustive]
95
    pub struct DirectorySignaturesHashesAccu {
96
      $(
97
        ${vattrs doc}
98
        $FNAME: Option<[u8; ${vmeta(hash_len) as expr}]>,
99
      )
100
    }
101

            
102
    impl DirectorySignaturesHashesAccu {
103
        /// If `algorithm` is an algorithm name, calculate the hash
104
        ///
105
        /// Otherwise, return `None`.
106
3844
        fn parse_keyword_and_hash(
107
3844
            &mut self,
108
3844
            algorithm: &str,
109
3844
            body: &SignatureHashInputs,
110
3844
        ) -> Option<$ttype> {
111
            Some(match algorithm {
112
              $(
113
                ${concat $FNAME} => {
114
                    let mut h = tor_llcrypto::d::$vname::new();
115
                    h.update(body.body().body());
116
                    h.update(body.signature_item_kw_spc);
117
                    self.$FNAME = Some(h.finalize().into());
118
                    $vtype
119
                }
120
              )
121
                _ => return None,
122
            })
123
        }
124

            
125
        /// Return the hash value for this algorithm, as a slice
126
        ///
127
        /// `None` if the value wasn't computed.
128
        /// That shouldn't happen.
129
8
        fn hash_slice_for_verification(&self, algo: $ttype) -> Option<&[u8]> {
130
            match algo { $(
131
                $vtype => Some(self.$FNAME.as_ref()?),
132
            ) }
133
        }
134
    }
135
}
136

            
137
define_directory_signature_hash_algo! {
138
    #[derive_deftly(DirectorySignatureHashesAccu)]
139
}
140

            
141
/// Unsupported `vote-status` value
142
///
143
/// This message is not normally actually shown since our `ErrorProblem` doesn't contain it.
144
#[derive(Clone, Debug, Error)]
145
#[non_exhaustive]
146
#[error("invalid value for vote-status in network status document")]
147
pub struct InvalidNetworkStatusVoteStatus {}
148

            
149
impl SignatureItemParseable for NdiDirectorySignature {
150
    type HashAccu = DirectorySignaturesHashesAccu;
151

            
152
    // TODO torspec#350.  That's why this manual impl is needed
153
1926
    fn from_unparsed_and_body<'s>(
154
1926
        mut input: UnparsedItem<'s>,
155
1926
        document_body: &SignatureHashInputs<'_>,
156
1926
        hashes: &mut DirectorySignaturesHashesAccu,
157
1926
    ) -> Result<Self, EP> {
158
1926
        let object = input.object();
159
1926
        let args = input.args_mut();
160
1926
        let maybe_algorithm = args.clone().next().ok_or(EP::MissingArgument {
161
1926
            field: "algorithm/h_kp_auth_id_rsa",
162
1926
        })?;
163

            
164
1926
        let hash_algo =
165
1926
            if let Some(algo) = hashes.parse_keyword_and_hash(maybe_algorithm, document_body) {
166
8
                let _: &str = args.next().expect("we just peeked");
167
8
                algo
168
1918
            } else if maybe_algorithm
169
76761
                .find(|c: char| !c.is_ascii_hexdigit())
170
1918
                .is_some()
171
            {
172
                // Not hex.  Must be some unknown algorithm.
173
                // There might be Object, but don't worry if not.
174
                return Ok(NdiDirectorySignature::Unknown {});
175
            } else {
176
1918
                hashes
177
1918
                    .parse_keyword_and_hash("sha1", document_body)
178
1918
                    .expect("sha1 is not valid?")
179
            };
180

            
181
1926
        let rsa_signature = object.ok_or(EP::MissingObject)?.decode_data()?;
182

            
183
3897
        let mut fingerprint_arg = |field: &'static str| {
184
3852
            (|| {
185
3852
                args.next()
186
3852
                    .ok_or(AE::Missing)?
187
3852
                    .parse::<types::Fingerprint>()
188
3852
                    .map_err(|_e| AE::Invalid)
189
3852
                    .map(pk::rsa::RsaIdentity::from)
190
            })()
191
3852
            .map_err(args.error_handler(field))
192
3852
        };
193

            
194
        Ok(NdiDirectorySignature::Known {
195
1926
            hash_algo,
196
1926
            rsa_signature,
197
1926
            h_kp_auth_id_rsa: fingerprint_arg("h_kp_auth_id_rsa")?,
198
1926
            h_kp_auth_sign_rsa: fingerprint_arg("h_kp_auth_sign_rsa")?,
199
        })
200
1926
    }
201
}
202

            
203
/// Meat of the verification functions for network documents
204
///
205
/// Checks that at least `threshold` members of `trusted`
206
/// have signed this document (in `signatures`),
207
/// via some cert(s) in `certs`.
208
///
209
/// Does not check validity time.
210
2
fn verify_general_timeless(
211
2
    hashes: &DirectorySignaturesHashesAccu,
212
2
    signatures: &[NdiDirectorySignature],
213
2
    trusted: &[pk::rsa::RsaIdentity],
214
2
    certs: &[&DirAuthKeyCert],
215
2
    threshold: usize,
216
2
) -> Result<(), VF> {
217
2
    let mut ok = HashSet::<pk::rsa::RsaIdentity>::new();
218

            
219
8
    for sig in signatures {
220
8
        match sig {
221
            NdiDirectorySignature::Known {
222
8
                hash_algo,
223
8
                h_kp_auth_id_rsa,
224
8
                h_kp_auth_sign_rsa,
225
8
                rsa_signature,
226
            } => {
227
8
                let Some(authority) = ({
228
8
                    trusted
229
8
                        .iter()
230
24
                        .find(|trusted| **trusted == *h_kp_auth_id_rsa)
231
                }) else {
232
                    // unknown kp_auth_id_rsa, ignore it
233
                    continue;
234
                };
235
8
                let Some(cert) = ({
236
8
                    certs
237
8
                        .iter()
238
24
                        .find(|cert| cert.dir_signing_key.to_rsa_identity() == *h_kp_auth_sign_rsa)
239
                }) else {
240
                    // no cert for this kp_auth_sign_rsa, ignore it
241
                    continue;
242
                };
243

            
244
8
                let h = hashes
245
8
                    .hash_slice_for_verification(*hash_algo)
246
8
                    .ok_or(VF::Bug)?;
247

            
248
8
                let () = cert.dir_signing_key.verify(h, rsa_signature)?;
249

            
250
8
                ok.insert(*authority);
251
            }
252
            NdiDirectorySignature::Unknown { .. } => {}
253
        }
254
    }
255

            
256
2
    if ok.len() < threshold {
257
        return Err(VF::InsufficientTrustedSigners);
258
2
    }
259

            
260
2
    Ok(())
261
2
}