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)]
17#[non_exhaustive]
18pub enum NdaNetworkStatusVersion {
19    /// The currently supported version, `3`
20    #[strum(serialize = "3")]
21    V3,
22}
23
24/// `params` value
25#[derive(Clone, Debug, Default, Deftly)]
26#[derive_deftly(ItemValueParseable)]
27#[non_exhaustive]
28pub struct NdiParams {
29    // Not implemented.
30}
31
32/// `r` sub-document
33#[derive(Deftly, Clone, Debug)]
34#[derive_deftly(ItemValueParseable)]
35#[non_exhaustive]
36pub struct NdiR {
37    /// nickname
38    pub nickname: types::Nickname,
39    /// identity
40    pub identity: String, // In non-demo, use a better type
41}
42
43/// `directory-signature` value
44#[derive(Debug, Clone)]
45#[non_exhaustive]
46pub enum NdiDirectorySignature {
47    /// Known "hash function" name
48    Known {
49        /// H(KP\_auth\_id\_rsa)
50        h_kp_auth_id_rsa: pk::rsa::RsaIdentity,
51        /// H(kp\_auth\_sign\_rsa)
52        h_kp_auth_sign_rsa: pk::rsa::RsaIdentity,
53        /// RSA signature
54        rsa_signature: Vec<u8>,
55        /// Hash of the covered text
56        hash: DirectorySignatureHash,
57    },
58    /// Unknown "hash function" name
59    ///
60    /// TODO torspec#350;
61    /// might have been an unknown algorithm, or might be invalid hex, or soemthing.
62    Unknown {},
63}
64define_derive_deftly! {
65    /// Ad-hoc derives for [`DirectorySignatureHash`] impls, avoiding copypasta bugs
66    DirectorySignatureHash expect items, beta_deftly:
67
68    impl $ttype {
69        /// If `algorithm` is an algorithm name, calculate the hash
70        fn parse_keyword_and_hash(algorithm: &str, body: &SignatureHashInputs) -> Option<Self> {
71            Some(match algorithm {
72              $(
73                ${concat ${kebab_case $vname}} => {
74                    let mut h = tor_llcrypto::d::$vname::new();
75                    h.update(body.body().body());
76                    h.update(body.signature_item_kw_spc);
77                    Self::$vname(h.finalize().into())
78                }
79              )
80                _ => return None,
81            })
82        }
83
84        fn hash_slice_for_verification(&self) -> &[u8] {
85            match self { $(
86                $vpat => f_0,
87            ) }
88        }
89    }
90}
91
92/// `directory-signature` hash algorithm argument
93#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::EnumString, Deftly)]
94#[derive_deftly(DirectorySignatureHash)]
95#[non_exhaustive]
96pub enum DirectorySignatureHash {
97    /// SHA-1
98    Sha1([u8; 20]),
99    /// SHA-256
100    Sha256([u8; 32]),
101}
102
103/// Unsupported `vote-status` value
104///
105/// This message is not normally actually shown since our `ErrorProblem` doesn't contain it.
106#[derive(Clone, Debug, Error)]
107#[non_exhaustive]
108#[error("invalid value for vote-status in network status document")]
109pub struct InvalidNetworkStatusVoteStatus {}
110
111impl SignatureItemParseable for NdiDirectorySignature {
112    // TODO torspec#350.  That's why this manual impl is needed
113    fn from_unparsed_and_body<'s>(
114        mut input: UnparsedItem<'s>,
115        document_body: &SignatureHashInputs<'_>,
116    ) -> Result<Self, EP> {
117        let object = input.object();
118        let args = input.args_mut();
119        let maybe_algorithm = args.clone().next().ok_or(EP::MissingArgument {
120            field: "algorithm/h_kp_auth_id_rsa",
121        })?;
122
123        let hash = if let Some(hash) =
124            DirectorySignatureHash::parse_keyword_and_hash(maybe_algorithm, document_body)
125        {
126            let _: &str = args.next().expect("we just peeked");
127            hash
128        } else if maybe_algorithm
129            .find(|c: char| !c.is_ascii_hexdigit())
130            .is_some()
131        {
132            // Not hex.  Must be some unknown algorithm.
133            // There might be Object, but don't worry if not.
134            return Ok(NdiDirectorySignature::Unknown {});
135        } else {
136            DirectorySignatureHash::parse_keyword_and_hash("sha1", document_body)
137                .expect("sha1 is not valid?")
138        };
139
140        let rsa_signature = object.ok_or(EP::MissingObject)?.decode_data()?;
141
142        let mut fingerprint_arg = |field: &'static str| {
143            args.next()
144                .ok_or(EP::MissingArgument { field })?
145                .parse::<types::Fingerprint>()
146                .map_err(|_e| EP::InvalidArgument { field })
147                .map(pk::rsa::RsaIdentity::from)
148        };
149
150        Ok(NdiDirectorySignature::Known {
151            rsa_signature,
152            h_kp_auth_id_rsa: fingerprint_arg("h_kp_auth_id_rsa")?,
153            h_kp_auth_sign_rsa: fingerprint_arg("h_kp_auth_sign_rsa")?,
154            hash,
155        })
156    }
157}
158
159/// Meat of the verification functions for network documents
160///
161/// Checks that at least `threshold` members of `trusted`
162/// have signed this document (in `signatures`),
163/// via some cert(s) in `certs`.
164///
165/// Does not check validity time.
166fn verify_general_timeless(
167    signatures: &[NdiDirectorySignature],
168    trusted: &[pk::rsa::RsaIdentity],
169    certs: &[&DirAuthKeyCert],
170    threshold: usize,
171) -> Result<(), VF> {
172    let mut ok = HashSet::<pk::rsa::RsaIdentity>::new();
173
174    for sig in signatures {
175        match sig {
176            NdiDirectorySignature::Known {
177                hash,
178                h_kp_auth_id_rsa,
179                h_kp_auth_sign_rsa,
180                rsa_signature,
181            } => {
182                let Some(authority) = ({
183                    trusted
184                        .iter()
185                        .find(|trusted| **trusted == *h_kp_auth_id_rsa)
186                }) else {
187                    // unknown kp_auth_id_rsa, ignore it
188                    continue;
189                };
190                let Some(cert) = ({
191                    certs
192                        .iter()
193                        .find(|cert| cert.kp_auth_sign_rsa.to_rsa_identity() == *h_kp_auth_sign_rsa)
194                }) else {
195                    // no cert for this kp_auth_sign_rsa, ignore it
196                    continue;
197                };
198
199                let h = hash.hash_slice_for_verification();
200
201                let () = cert.kp_auth_sign_rsa.verify(h, rsa_signature)?;
202
203                ok.insert(*authority);
204            }
205            NdiDirectorySignature::Unknown { .. } => {}
206        }
207    }
208
209    if ok.len() < threshold {
210        return Err(VF::InsufficientTrustedSigners);
211    }
212
213    Ok(())
214}