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

            
3
use super::*;
4

            
5
use crate::doc::authcert;
6
use crate::types;
7
use authcert::AuthCert as DirAuthKeyCert;
8

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

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

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

            
25
impl NormalItemArgument for NdaNetworkStatusVersion {}
26

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

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

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

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

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

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

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

            
136
/// `directory-signature` hash algorithm argument
137
#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::EnumString, Deftly)]
138
#[derive_deftly(DirectorySignatureHashesAccu)]
139
#[non_exhaustive]
140
pub enum DirectorySignatureHashAlgo {
141
    /// SHA-1
142
    #[deftly(hash_len = "20")]
143
    Sha1,
144
    /// SHA-256
145
    #[deftly(hash_len = "32")]
146
    Sha256,
147
}
148

            
149
/// Unsupported `vote-status` value
150
///
151
/// This message is not normally actually shown since our `ErrorProblem` doesn't contain it.
152
#[derive(Clone, Debug, Error)]
153
#[non_exhaustive]
154
#[error("invalid value for vote-status in network status document")]
155
pub struct InvalidNetworkStatusVoteStatus {}
156

            
157
impl SignatureItemParseable for NdiDirectorySignature {
158
    type HashAccu = DirectorySignaturesHashesAccu;
159

            
160
    // TODO torspec#350.  That's why this manual impl is needed
161
1926
    fn from_unparsed_and_body<'s>(
162
1926
        mut input: UnparsedItem<'s>,
163
1926
        document_body: &SignatureHashInputs<'_>,
164
1926
        hashes: &mut DirectorySignaturesHashesAccu,
165
1926
    ) -> Result<Self, EP> {
166
1926
        let object = input.object();
167
1926
        let args = input.args_mut();
168
1926
        let maybe_algorithm = args.clone().next().ok_or(EP::MissingArgument {
169
1926
            field: "algorithm/h_kp_auth_id_rsa",
170
1926
        })?;
171

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

            
189
1926
        let rsa_signature = object.ok_or(EP::MissingObject)?.decode_data()?;
190

            
191
3897
        let mut fingerprint_arg = |field: &'static str| {
192
3852
            (|| {
193
3852
                args.next()
194
3852
                    .ok_or(AE::Missing)?
195
3852
                    .parse::<types::Fingerprint>()
196
3852
                    .map_err(|_e| AE::Invalid)
197
3852
                    .map(pk::rsa::RsaIdentity::from)
198
            })()
199
3852
            .map_err(args.error_handler(field))
200
3852
        };
201

            
202
        Ok(NdiDirectorySignature::Known {
203
1926
            hash_algo,
204
1926
            rsa_signature,
205
1926
            h_kp_auth_id_rsa: fingerprint_arg("h_kp_auth_id_rsa")?,
206
1926
            h_kp_auth_sign_rsa: fingerprint_arg("h_kp_auth_sign_rsa")?,
207
        })
208
1926
    }
209
}
210

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

            
227
8
    for sig in signatures {
228
8
        match sig {
229
            NdiDirectorySignature::Known {
230
8
                hash_algo,
231
8
                h_kp_auth_id_rsa,
232
8
                h_kp_auth_sign_rsa,
233
8
                rsa_signature,
234
            } => {
235
8
                let Some(authority) = ({
236
8
                    trusted
237
8
                        .iter()
238
24
                        .find(|trusted| **trusted == *h_kp_auth_id_rsa)
239
                }) else {
240
                    // unknown kp_auth_id_rsa, ignore it
241
                    continue;
242
                };
243
8
                let Some(cert) = ({
244
8
                    certs
245
8
                        .iter()
246
24
                        .find(|cert| cert.dir_signing_key.to_rsa_identity() == *h_kp_auth_sign_rsa)
247
                }) else {
248
                    // no cert for this kp_auth_sign_rsa, ignore it
249
                    continue;
250
                };
251

            
252
8
                let h = hashes
253
8
                    .hash_slice_for_verification(*hash_algo)
254
8
                    .ok_or(VF::Bug)?;
255

            
256
8
                let () = cert.dir_signing_key.verify(h, rsa_signature)?;
257

            
258
8
                ok.insert(*authority);
259
            }
260
            NdiDirectorySignature::Unknown { .. } => {}
261
        }
262
    }
263

            
264
2
    if ok.len() < threshold {
265
        return Err(VF::InsufficientTrustedSigners);
266
2
    }
267

            
268
2
    Ok(())
269
2
}