Skip to main content

tor_netdoc/parse2/poc/
netstatus.rs

1//! network status documents: shared between votes, consensuses and md consensuses
2
3use super::*;
4
5use crate::doc::{self, authcert};
6use crate::types;
7use authcert::AuthCert as DirAuthKeyCert;
8use doc::netstatus::{ConsensusAuthoritySection, VoteAuthoritySection};
9
10mod ns_per_flavour_macros;
11pub use ns_per_flavour_macros::*;
12
13ns_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]
20pub enum NdaNetworkStatusVersion {
21    /// The currently supported version, `3`
22    #[strum(serialize = "3")]
23    V3,
24}
25
26impl NormalItemArgument for NdaNetworkStatusVersion {}
27
28/// `params` value
29#[derive(Clone, Debug, Default, Deftly)]
30#[derive_deftly(ItemValueParseable)]
31#[non_exhaustive]
32pub struct NdiParams {
33    // Not implemented.
34}
35
36/// `r` sub-document
37#[derive(Deftly, Clone, Debug)]
38#[derive_deftly(ItemValueParseable)]
39#[non_exhaustive]
40pub 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]
50pub 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}
68define_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        fn parse_keyword_and_hash(
107            &mut self,
108            algorithm: &str,
109            body: &SignatureHashInputs,
110        ) -> 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        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
137define_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")]
147pub struct InvalidNetworkStatusVoteStatus {}
148
149impl SignatureItemParseable for NdiDirectorySignature {
150    type HashAccu = DirectorySignaturesHashesAccu;
151
152    // TODO torspec#350.  That's why this manual impl is needed
153    fn from_unparsed_and_body<'s>(
154        mut input: UnparsedItem<'s>,
155        document_body: &SignatureHashInputs<'_>,
156        hashes: &mut DirectorySignaturesHashesAccu,
157    ) -> Result<Self, EP> {
158        let object = input.object();
159        let args = input.args_mut();
160        let maybe_algorithm = args.clone().next().ok_or(EP::MissingArgument {
161            field: "algorithm/h_kp_auth_id_rsa",
162        })?;
163
164        let hash_algo =
165            if let Some(algo) = hashes.parse_keyword_and_hash(maybe_algorithm, document_body) {
166                let _: &str = args.next().expect("we just peeked");
167                algo
168            } else if maybe_algorithm
169                .find(|c: char| !c.is_ascii_hexdigit())
170                .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                hashes
177                    .parse_keyword_and_hash("sha1", document_body)
178                    .expect("sha1 is not valid?")
179            };
180
181        let rsa_signature = object.ok_or(EP::MissingObject)?.decode_data()?;
182
183        let mut fingerprint_arg = |field: &'static str| {
184            (|| {
185                args.next()
186                    .ok_or(AE::Missing)?
187                    .parse::<types::Fingerprint>()
188                    .map_err(|_e| AE::Invalid)
189                    .map(pk::rsa::RsaIdentity::from)
190            })()
191            .map_err(args.error_handler(field))
192        };
193
194        Ok(NdiDirectorySignature::Known {
195            hash_algo,
196            rsa_signature,
197            h_kp_auth_id_rsa: fingerprint_arg("h_kp_auth_id_rsa")?,
198            h_kp_auth_sign_rsa: fingerprint_arg("h_kp_auth_sign_rsa")?,
199        })
200    }
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.
210fn verify_general_timeless(
211    hashes: &DirectorySignaturesHashesAccu,
212    signatures: &[NdiDirectorySignature],
213    trusted: &[pk::rsa::RsaIdentity],
214    certs: &[&DirAuthKeyCert],
215    threshold: usize,
216) -> Result<(), VF> {
217    let mut ok = HashSet::<pk::rsa::RsaIdentity>::new();
218
219    for sig in signatures {
220        match sig {
221            NdiDirectorySignature::Known {
222                hash_algo,
223                h_kp_auth_id_rsa,
224                h_kp_auth_sign_rsa,
225                rsa_signature,
226            } => {
227                let Some(authority) = ({
228                    trusted
229                        .iter()
230                        .find(|trusted| **trusted == *h_kp_auth_id_rsa)
231                }) else {
232                    // unknown kp_auth_id_rsa, ignore it
233                    continue;
234                };
235                let Some(cert) = ({
236                    certs
237                        .iter()
238                        .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                let h = hashes
245                    .hash_slice_for_verification(*hash_algo)
246                    .ok_or(VF::Bug)?;
247
248                let () = cert.dir_signing_key.verify(h, rsa_signature)?;
249
250                ok.insert(*authority);
251            }
252            NdiDirectorySignature::Unknown { .. } => {}
253        }
254    }
255
256    if ok.len() < threshold {
257        return Err(VF::InsufficientTrustedSigners);
258    }
259
260    Ok(())
261}