tor_netdoc/parse2/poc/
netstatus.rs

1//! network status documents: shared between votes, consensuses and md consensuses
2
3use super::*;
4
5use crate::types;
6use authcert::DirAuthKeyCert;
7
8mod ns_per_flavour_macros;
9pub use ns_per_flavour_macros::*;
10
11ns_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]
18pub enum NdaNetworkStatusVersion {
19    /// The currently supported version, `3`
20    #[strum(serialize = "3")]
21    V3,
22}
23
24impl NormalItemArgument for NdaNetworkStatusVersion {}
25
26/// `params` value
27#[derive(Clone, Debug, Default, Deftly)]
28#[derive_deftly(ItemValueParseable)]
29#[non_exhaustive]
30pub struct NdiParams {
31    // Not implemented.
32}
33
34/// `r` sub-document
35#[derive(Deftly, Clone, Debug)]
36#[derive_deftly(ItemValueParseable)]
37#[non_exhaustive]
38pub 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]
48pub 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}
66define_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        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        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]
98pub 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")]
111pub struct InvalidNetworkStatusVoteStatus {}
112
113impl SignatureItemParseable for NdiDirectorySignature {
114    // TODO torspec#350.  That's why this manual impl is needed
115    fn from_unparsed_and_body<'s>(
116        mut input: UnparsedItem<'s>,
117        document_body: &SignatureHashInputs<'_>,
118    ) -> Result<Self, EP> {
119        let object = input.object();
120        let args = input.args_mut();
121        let maybe_algorithm = args.clone().next().ok_or(EP::MissingArgument {
122            field: "algorithm/h_kp_auth_id_rsa",
123        })?;
124
125        let hash = if let Some(hash) =
126            DirectorySignatureHash::parse_keyword_and_hash(maybe_algorithm, document_body)
127        {
128            let _: &str = args.next().expect("we just peeked");
129            hash
130        } else if maybe_algorithm
131            .find(|c: char| !c.is_ascii_hexdigit())
132            .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            DirectorySignatureHash::parse_keyword_and_hash("sha1", document_body)
139                .expect("sha1 is not valid?")
140        };
141
142        let rsa_signature = object.ok_or(EP::MissingObject)?.decode_data()?;
143
144        let mut fingerprint_arg = |field: &'static str| {
145            (|| {
146                args.next()
147                    .ok_or(AE::Missing)?
148                    .parse::<types::Fingerprint>()
149                    .map_err(|_e| AE::Invalid)
150                    .map(pk::rsa::RsaIdentity::from)
151            })()
152            .map_err(args.error_handler(field))
153        };
154
155        Ok(NdiDirectorySignature::Known {
156            rsa_signature,
157            h_kp_auth_id_rsa: fingerprint_arg("h_kp_auth_id_rsa")?,
158            h_kp_auth_sign_rsa: fingerprint_arg("h_kp_auth_sign_rsa")?,
159            hash,
160        })
161    }
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.
171fn verify_general_timeless(
172    signatures: &[NdiDirectorySignature],
173    trusted: &[pk::rsa::RsaIdentity],
174    certs: &[&DirAuthKeyCert],
175    threshold: usize,
176) -> Result<(), VF> {
177    let mut ok = HashSet::<pk::rsa::RsaIdentity>::new();
178
179    for sig in signatures {
180        match sig {
181            NdiDirectorySignature::Known {
182                hash,
183                h_kp_auth_id_rsa,
184                h_kp_auth_sign_rsa,
185                rsa_signature,
186            } => {
187                let Some(authority) = ({
188                    trusted
189                        .iter()
190                        .find(|trusted| **trusted == *h_kp_auth_id_rsa)
191                }) else {
192                    // unknown kp_auth_id_rsa, ignore it
193                    continue;
194                };
195                let Some(cert) = ({
196                    certs
197                        .iter()
198                        .find(|cert| cert.kp_auth_sign_rsa.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                let h = hash.hash_slice_for_verification();
205
206                let () = cert.kp_auth_sign_rsa.verify(h, rsa_signature)?;
207
208                ok.insert(*authority);
209            }
210            NdiDirectorySignature::Unknown { .. } => {}
211        }
212    }
213
214    if ok.len() < threshold {
215        return Err(VF::InsufficientTrustedSigners);
216    }
217
218    Ok(())
219}