1
//! Extra-Info Document Implementation.
2
//!
3
//! Historically, there were no microdescriptors and bootstrapping
4
//! involved downloading all router descriptors.  In order to save
5
//! space, various information not required for bootstrapping was
6
//! outsourced into an external document called extra-info, which
7
//! itself would then be referred to by original router descriptor.
8
//!
9
//! In times of microdescriptors, this has become meaningless, yet it
10
//! still continues to be a part of the protocol.
11
//!
12
//! <https://spec.torproject.org/dir-spec/extra-info-document-format.html>
13

            
14
use derive_deftly::Deftly;
15
use tor_cert::KeyUnknownCert;
16
use tor_llcrypto::pk::ed25519;
17

            
18
use crate::{
19
    doc::routerdesc::RouterDesc,
20
    parse2::VerifyFailed,
21
    types::{descriptor::*, *},
22
};
23

            
24
/// Additional information about a relay not contained in it's router
25
/// descriptor.
26
///
27
/// See the module documentation for more information.
28
///
29
/// <https://spec.torproject.org/dir-spec/extra-info-document-format.html>
30
#[derive(Debug, Clone, PartialEq, Deftly)]
31
#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
32
#[non_exhaustive]
33
pub struct ExtraInfo {
34
    /// `extra-info` — Introduce a server's extra-info
35
    ///
36
    /// <https://spec.torproject.org/dir-spec/extra-info-document-format.html#extra-info>
37
    pub extra_info: ExtraInfoIntroItem,
38

            
39
    /// `identity-ed25519` --- Specify the router's ed25519 identity.
40
    ///
41
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:identity-ed25519>
42
    pub identity_ed25519: EmbeddedCert<Ed25519IdentityCert, KeyUnknownCert>,
43

            
44
    /// `published` --- Time this descriptor (and extra-info) was generated.
45
    ///
46
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:published>
47
    #[deftly(netdoc(single_arg))]
48
    pub published: Iso8601TimeSp,
49
}
50

            
51
/// Signatures for an [`ExtraInfo`] document.
52
///
53
/// The signature logic is the same as with router descriptors.
54
///
55
/// Technically redundant because the hash is referenced by the router
56
/// descriptor in a signed fashion already.
57
#[derive(Debug, Clone, PartialEq, Deftly)]
58
#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
59
#[deftly(netdoc(signatures(hashes_accu = "RouterHashAccu")))]
60
#[non_exhaustive]
61
pub struct ExtraInfoSignatures {
62
    /// `router-sig-ed25519` --- Ed25519 signature
63
    ///
64
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:router-sig-ed25519>
65
    pub router_sig_ed25519: RouterSigEd25519,
66

            
67
    /// `router-signature` --- RSA signature
68
    ///
69
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:router-signature>
70
    pub router_signature: RouterSignature,
71
}
72

            
73
/// Introduction line of an extra-info document.
74
///
75
/// <https://spec.torproject.org/dir-spec/extra-info-document-format.html#extra-info--introduce-a-servers-extra-info>
76
#[derive(Debug, Clone, PartialEq, Deftly)]
77
#[derive_deftly(ItemValueParseable, ItemValueEncodable)]
78
#[non_exhaustive]
79
pub struct ExtraInfoIntroItem {
80
    /// A valid router [`Nickname`].
81
    pub nickname: Nickname,
82
    /// Fingerprint of the router's RSA identity.
83
    pub fingerprint: Fingerprint,
84
}
85

            
86
impl ExtraInfoUnverified {
87
    /// Verifies an extra-info document.
88
    ///
89
    /// This verification *requires* an already verified [`RouterDesc`].
90
    ///
91
    /// We do not work with time bounded values here, because all respective
92
    /// time bounded values in extra-info documents match up with the respective
93
    /// values in their associated [`RouterDesc`].  Instead, we simply require
94
    /// [`RouterDesc`] to be timely for this to be timely as well.
95
    ///
96
    /// For an extra-info document to be valid, the following constraints apply:
97
    /// * [`RouterDesc::router`] implies [`ExtraInfoIntroItem::nickname`]
98
    /// * [`RouterDesc::signing_key`] implies [`ExtraInfoIntroItem::fingerprint`]
99
    /// * [`RouterDesc::identity_ed25519`] == [`ExtraInfo::identity_ed25519`]
100
    /// * [`RouterDesc::published`] == [`ExtraInfo::published`]
101
    /// * [`RouterDesc::extra_info_digest`] implies this document.
102
    /// * This document has valid signatures.
103
40
    pub fn verify(self, rd: &RouterDesc) -> Result<ExtraInfo, VerifyFailed> {
104
40
        let (mut body, sigs): (ExtraInfo, _) = (self.body, self.sigs);
105

            
106
        // Check whether the nicknames match.
107
40
        if rd.router.nickname != body.extra_info.nickname {
108
            return Err(VerifyFailed::Inconsistent);
109
40
        }
110

            
111
        // Check whether the fingerprint is as expected.
112
        // Keep in mind, that we cannot use the fingerprint field in the router
113
        // descriptor, as it is optional.
114
40
        if rd.signing_key.to_rsa_identity() != body.extra_info.fingerprint.0 {
115
            return Err(VerifyFailed::Inconsistent);
116
40
        }
117

            
118
        // Check whether the Ed25519 signing key certificate is the same.
119
40
        if rd.identity_ed25519.raw_unverified() != body.identity_ed25519.raw_unverified() {
120
            return Err(VerifyFailed::Inconsistent);
121
40
        }
122
        // We do not re-verify the certificate but rather clone it.
123
        // This is okay, because we just verified that the unverified form in
124
        // this document is equal to the unverified form in the already verified
125
        // router descriptor, which implies that verifying it would lead to the
126
        // same (verified) result again.
127
40
        body.identity_ed25519 = rd.identity_ed25519.clone();
128
40
        let sign_ed25519 = body
129
40
            .identity_ed25519
130
40
            .get()
131
40
            .expect("rd must contain verified identity_ed25519")
132
40
            .sign_ed25519;
133

            
134
        // Check whether the published timestamp is the same.
135
40
        if body.published != rd.published {
136
            return Err(VerifyFailed::Inconsistent);
137
40
        }
138

            
139
        // Already obtain the hashes we got by having hashed the document.
140
        // We need to do that now for the next check but need it later on too.
141
        // A failure of this accumulation is a full verification failure.
142
40
        let this_sha1 = sigs.hashes.sha1.ok_or(VerifyFailed::VerifyFailed)?;
143
40
        let this_sha256 = sigs.hashes.sha256.ok_or(VerifyFailed::VerifyFailed)?;
144

            
145
        // Check whether the router descriptor implies this document.
146
        // If this field is not set, we should have not obtained this document
147
        // in the first place ...
148
40
        let Some(expected_digests) = rd.extra_info_digest.as_ref() else {
149
            return Err(VerifyFailed::Inconsistent);
150
        };
151
        // TODO DIRMIRROR: IMPORTANT, we MUST also verify the SHA-256 hash.
152
        // However, we cannot use the normal hash accumulator due to a
153
        // long-standig bug in the ExtraInfoDigests value, where the indicated
154
        // value there refers to the full document (including the signature
155
        // bytes) and not up until "router-sig-ed25519 ".  The solution would
156
        // most likely involve writing a different accumulator for this and/or
157
        // extending RouterHashAccu with an additional field?
158
40
        if *expected_digests.sha1 != this_sha1 {
159
            return Err(VerifyFailed::Inconsistent);
160
40
        }
161

            
162
        // Verify the actual outer document signatures.
163
40
        ed25519::PublicKey::try_from(sign_ed25519)
164
40
            .map_err(|_| VerifyFailed::Other)?
165
40
            .verify(&this_sha256, &sigs.sigs.router_sig_ed25519.0)?;
166
40
        rd.signing_key
167
40
            .verify(&this_sha1, &sigs.sigs.router_signature.0)?;
168

            
169
40
        Ok(body)
170
40
    }
171
}
172

            
173
#[cfg(test)]
174
mod test {
175
    // @@ begin test lint list maintained by maint/add_warning @@
176
    #![allow(clippy::bool_assert_comparison)]
177
    #![allow(clippy::clone_on_copy)]
178
    #![allow(clippy::dbg_macro)]
179
    #![allow(clippy::mixed_attributes_style)]
180
    #![allow(clippy::print_stderr)]
181
    #![allow(clippy::print_stdout)]
182
    #![allow(clippy::single_char_pattern)]
183
    #![allow(clippy::unwrap_used)]
184
    #![allow(clippy::unchecked_time_subtraction)]
185
    #![allow(clippy::useless_vec)]
186
    #![allow(clippy::needless_pass_by_value)]
187
    #![allow(clippy::string_slice)] // See arti#2571
188
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
189

            
190
    use std::collections::HashMap;
191

            
192
    use tor_checkable::TimeBound;
193

            
194
    use super::*;
195
    use crate::doc::routerdesc::RouterDescUnverified;
196
    use crate::parse2::{self, NetdocParseableUnverified, ParseInput};
197

            
198
    /// Simple test that just validates all extra-infos in testdata2/.
199
    #[test]
200
    fn simple() {
201
        // Obtain the accompanying router descriptors.
202
        let routers = parse2::parse_netdoc_multiple::<RouterDescUnverified>(&ParseInput::new(
203
            include_str!("../../testdata2/cached-descriptors.new"),
204
            "cached-descriptors",
205
        ))
206
        .unwrap()
207
        .into_iter()
208
        // We need to verify because we make use of the embedded certificats.
209
        // However, we skip the time verification because it is out of scope
210
        // for this module.
211
        .map(|rd| rd.verify().unwrap().dangerously_assume_timely())
212
        .collect::<Vec<RouterDesc>>();
213

            
214
        // Now, parse all extra info documents and store them by their hash.
215
        let extras_list = parse2::parse_netdoc_multiple::<ExtraInfoUnverified>(&ParseInput::new(
216
            include_str!("../../testdata2/cached-extrainfo.new"),
217
            "cached-extrainfo",
218
        ))
219
        .unwrap();
220
        let mut extras = HashMap::new();
221
        for extra in extras_list {
222
            // We need to use the sha1 due to a long-lived bug in the sha256,
223
            // whereas the field contains the signature over the entire document,
224
            // rather than just up until router-sig-ed25519 ...
225
            let sha1 = extra.inspect_unverified().1.hashes.sha1.unwrap();
226
            // Ensure there are no duplicates.
227
            assert!(extras.insert(sha1, extra).is_none());
228
        }
229

            
230
        // We now have all router descriptors and all extra infos in a
231
        // content-addressable store.  Now we iterate over every router
232
        // descriptor, look up the extra-info and take it from the hash
233
        // map, followed by an actual verification.  In the end, all
234
        // verification calls should have been successful and the HashMap
235
        // should be empty.
236
        for router in routers {
237
            let sha1 = router.extra_info_digest.clone().unwrap().sha1.0;
238
            let extra = extras.remove(&sha1).unwrap();
239
            extra.verify(&router).unwrap();
240
        }
241
        assert!(extras.is_empty());
242
    }
243
}