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

            
3
use super::*;
4

            
5
use crate::types;
6
use authcert::DirAuthKeyCert;
7

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

            
11
ns_per_flavour_macros::ns_export_flavoured_types! {
12
    NetworkStatus, NetworkStatusSigned, Router,
13
}
14

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

            
24
impl NormalItemArgument for NdaNetworkStatusVersion {}
25

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

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

            
45
/// `directory-signature` value
46
#[derive(Debug, Clone)]
47
#[non_exhaustive]
48
pub enum NdiDirectorySignature {
49
    /// Known "hash function" name
50
    Known {
51
        /// H(KP\_auth\_id\_rsa)
52
        h_kp_auth_id_rsa: pk::rsa::RsaIdentity,
53
        /// H(kp\_auth\_sign\_rsa)
54
        h_kp_auth_sign_rsa: pk::rsa::RsaIdentity,
55
        /// RSA signature
56
        rsa_signature: Vec<u8>,
57
        /// Hash of the covered text
58
        hash: DirectorySignatureHash,
59
    },
60
    /// Unknown "hash function" name
61
    ///
62
    /// TODO torspec#350;
63
    /// might have been an unknown algorithm, or might be invalid hex, or soemthing.
64
    Unknown {},
65
}
66
define_derive_deftly! {
67
    /// Ad-hoc derives for [`DirectorySignatureHash`] impls, avoiding copypasta bugs
68
    DirectorySignatureHash expect items, beta_deftly:
69

            
70
    impl $ttype {
71
        /// If `algorithm` is an algorithm name, calculate the hash
72
28
        fn parse_keyword_and_hash(algorithm: &str, body: &SignatureHashInputs) -> Option<Self> {
73
            Some(match algorithm {
74
              $(
75
                ${concat ${kebab_case $vname}} => {
76
                    let mut h = tor_llcrypto::d::$vname::new();
77
                    h.update(body.body().body());
78
                    h.update(body.signature_item_kw_spc);
79
                    Self::$vname(h.finalize().into())
80
                }
81
              )
82
                _ => return None,
83
            })
84
        }
85

            
86
8
        fn hash_slice_for_verification(&self) -> &[u8] {
87
            match self { $(
88
                $vpat => f_0,
89
            ) }
90
        }
91
    }
92
}
93

            
94
/// `directory-signature` hash algorithm argument
95
#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::EnumString, Deftly)]
96
#[derive_deftly(DirectorySignatureHash)]
97
#[non_exhaustive]
98
pub enum DirectorySignatureHash {
99
    /// SHA-1
100
    Sha1([u8; 20]),
101
    /// SHA-256
102
    Sha256([u8; 32]),
103
}
104

            
105
/// Unsupported `vote-status` value
106
///
107
/// This message is not normally actually shown since our `ErrorProblem` doesn't contain it.
108
#[derive(Clone, Debug, Error)]
109
#[non_exhaustive]
110
#[error("invalid value for vote-status in network status document")]
111
pub struct InvalidNetworkStatusVoteStatus {}
112

            
113
impl SignatureItemParseable for NdiDirectorySignature {
114
    // TODO torspec#350.  That's why this manual impl is needed
115
18
    fn from_unparsed_and_body<'s>(
116
18
        mut input: UnparsedItem<'s>,
117
18
        document_body: &SignatureHashInputs<'_>,
118
18
    ) -> Result<Self, EP> {
119
18
        let object = input.object();
120
18
        let args = input.args_mut();
121
18
        let maybe_algorithm = args.clone().next().ok_or(EP::MissingArgument {
122
18
            field: "algorithm/h_kp_auth_id_rsa",
123
18
        })?;
124

            
125
18
        let hash = if let Some(hash) =
126
18
            DirectorySignatureHash::parse_keyword_and_hash(maybe_algorithm, document_body)
127
        {
128
8
            let _: &str = args.next().expect("we just peeked");
129
8
            hash
130
10
        } else if maybe_algorithm
131
405
            .find(|c: char| !c.is_ascii_hexdigit())
132
10
            .is_some()
133
        {
134
            // Not hex.  Must be some unknown algorithm.
135
            // There might be Object, but don't worry if not.
136
            return Ok(NdiDirectorySignature::Unknown {});
137
        } else {
138
10
            DirectorySignatureHash::parse_keyword_and_hash("sha1", document_body)
139
10
                .expect("sha1 is not valid?")
140
        };
141

            
142
18
        let rsa_signature = object.ok_or(EP::MissingObject)?.decode_data()?;
143

            
144
45
        let mut fingerprint_arg = |field: &'static str| {
145
36
            (|| {
146
36
                args.next()
147
36
                    .ok_or(AE::Missing)?
148
36
                    .parse::<types::Fingerprint>()
149
36
                    .map_err(|_e| AE::Invalid)
150
36
                    .map(pk::rsa::RsaIdentity::from)
151
            })()
152
36
            .map_err(args.error_handler(field))
153
36
        };
154

            
155
        Ok(NdiDirectorySignature::Known {
156
18
            rsa_signature,
157
18
            h_kp_auth_id_rsa: fingerprint_arg("h_kp_auth_id_rsa")?,
158
18
            h_kp_auth_sign_rsa: fingerprint_arg("h_kp_auth_sign_rsa")?,
159
18
            hash,
160
        })
161
18
    }
162
}
163

            
164
/// Meat of the verification functions for network documents
165
///
166
/// Checks that at least `threshold` members of `trusted`
167
/// have signed this document (in `signatures`),
168
/// via some cert(s) in `certs`.
169
///
170
/// Does not check validity time.
171
2
fn verify_general_timeless(
172
2
    signatures: &[NdiDirectorySignature],
173
2
    trusted: &[pk::rsa::RsaIdentity],
174
2
    certs: &[&DirAuthKeyCert],
175
2
    threshold: usize,
176
2
) -> Result<(), VF> {
177
2
    let mut ok = HashSet::<pk::rsa::RsaIdentity>::new();
178

            
179
10
    for sig in signatures {
180
8
        match sig {
181
            NdiDirectorySignature::Known {
182
8
                hash,
183
8
                h_kp_auth_id_rsa,
184
8
                h_kp_auth_sign_rsa,
185
8
                rsa_signature,
186
            } => {
187
8
                let Some(authority) = ({
188
8
                    trusted
189
8
                        .iter()
190
24
                        .find(|trusted| **trusted == *h_kp_auth_id_rsa)
191
                }) else {
192
                    // unknown kp_auth_id_rsa, ignore it
193
                    continue;
194
                };
195
8
                let Some(cert) = ({
196
8
                    certs
197
8
                        .iter()
198
24
                        .find(|cert| cert.dir_signing_key.to_rsa_identity() == *h_kp_auth_sign_rsa)
199
                }) else {
200
                    // no cert for this kp_auth_sign_rsa, ignore it
201
                    continue;
202
                };
203

            
204
8
                let h = hash.hash_slice_for_verification();
205

            
206
8
                let () = cert.dir_signing_key.verify(h, rsa_signature)?;
207

            
208
8
                ok.insert(*authority);
209
            }
210
            NdiDirectorySignature::Unknown { .. } => {}
211
        }
212
    }
213

            
214
2
    if ok.len() < threshold {
215
        return Err(VF::InsufficientTrustedSigners);
216
2
    }
217

            
218
2
    Ok(())
219
2
}