1
//! Test vectors for use in unit tests.
2
//!
3
//! This module is backed by `#[cfg(test)]`, meaning it will not be present in
4
//! production code.
5
//!
6
//! The purpose is to provide a shared set of functions/constants/etc. for use
7
//! in dirserver unit tests.
8
//!
9
//! These helpers assume that [`tor_netdoc`] works properly.
10
//!
11
//! TODO DIRMIRROR Remove the symlink into `crates/tor-netdoc`
12
//! instead use constants exported by `tor-netdoc` with `testing` feature,
13
//! adding consts to tor-netdoc as needed.
14
//!
15
//! TODO DIRMIRROR consider moving many of these "get the test ..." functions to tor-netdoc
16
//! (exposed only with `testing` feature enabled)
17
//! and maybe unify with existing code there, as applicable.
18
// @@ begin test lint list maintained by maint/add_warning @@
19
#![allow(clippy::bool_assert_comparison)]
20
#![allow(clippy::clone_on_copy)]
21
#![allow(clippy::dbg_macro)]
22
#![allow(clippy::mixed_attributes_style)]
23
#![allow(clippy::print_stderr)]
24
#![allow(clippy::print_stdout)]
25
#![allow(clippy::single_char_pattern)]
26
#![allow(clippy::unwrap_used)]
27
#![allow(clippy::unchecked_time_subtraction)]
28
#![allow(clippy::useless_vec)]
29
#![allow(clippy::needless_pass_by_value)]
30
#![allow(clippy::string_slice)] // See arti#2571
31
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
32

            
33
use std::{
34
    iter,
35
    time::{Duration, SystemTime},
36
};
37

            
38
use r2d2::Pool;
39
use r2d2_sqlite::SqliteConnectionManager;
40
use rusqlite::named_params;
41
use tor_checkable::TimeBound;
42
use tor_dircommon::authority::{AuthorityContacts, AuthorityContactsBuilder};
43
use tor_llcrypto::pk::rsa::RsaIdentity;
44
use tor_netdoc::{
45
    doc::{
46
        authcert::{AuthCert, AuthCertUnverified},
47
        microdesc::Microdesc,
48
        netstatus::{ConsensusFlavor, md, plain},
49
        routerdesc::{RouterDesc, RouterDescUnverified},
50
    },
51
    parse2::{self, NetdocParseable, NetdocParseableUnverified, ParseInput, SignaturesData},
52
};
53

            
54
use crate::database::{
55
    self as db, AuthCertMeta, ConsensusMeta, ContentEncoding, Sha1, Sha3_256, Sha256, sql,
56
    store_insert,
57
};
58

            
59
/// Plain unverified consensus.
60
type Plain = plain::NetworkStatusUnverified;
61

            
62
/// Microdesc unverified consensus.
63
type Md = md::NetworkStatusUnverified;
64

            
65
/// Pre-tolerance, 3 days.
66
const PRE_TOLERANCE: Duration = Duration::from_secs(60 * 60 * 72);
67

            
68
/// Post tolerance, 1 day.
69
const POST_TOLERANCE: Duration = Duration::from_secs(60 * 60 * 24);
70

            
71
/// Returns a consensus that can be used as a test vector.
72
///
73
/// We assume the consensus is valid to avoid circular dependencies with other
74
/// functions here.  This should be okay because these things are not intended
75
/// to test tor-netdoc itself.
76
301
pub(crate) fn current_consensus_ns() -> (
77
301
    plain::NetworkStatus,
78
301
    plain::NetworkStatusSignatures,
79
301
    &'static str,
80
301
) {
81
301
    let raw = include_str!("../testdata2/cached-consensus");
82
301
    current_consensus::<plain::NetworkStatusUnverified>(raw)
83
301
}
84

            
85
/// [`current_consensus_ns()`] but for microdescriptor consensuses.
86
// TODO: Merge with current_consensus_ns() because it is repetitive.
87
287
pub(crate) fn current_consensus_md()
88
287
-> (md::NetworkStatus, md::NetworkStatusSignatures, &'static str) {
89
287
    let raw = include_str!("../testdata2/cached-microdesc-consensus");
90
287
    current_consensus::<md::NetworkStatusUnverified>(raw)
91
287
}
92

            
93
/// Internal function for obtaining a consensus from a text file.
94
588
fn current_consensus<T: NetdocParseableUnverified + NetdocParseable>(
95
588
    raw: &'static str,
96
588
) -> (T::Body, T::Signatures, &'static str) {
97
588
    let consensus = parse2::parse_netdoc::<T>(&ParseInput::new(raw, "consensus")).unwrap();
98
588
    let (body, sigs) = consensus.unwrap_unverified();
99
588
    (body, sigs.sigs, raw)
100
588
}
101

            
102
/// Returns the unsigned SHA-3 of a given consensus as a string.
103
///
104
/// Required for consensus diffs.
105
// TODO DIRMIRROR: We *need* a method for this in tor-netdoc.
106
2
pub(crate) fn consensus_sha3(data: &str) -> Sha3_256 {
107
2
    Sha3_256::digest(
108
2
        data.split_inclusive("\ndirectory-signature ")
109
2
            .next()
110
2
            .unwrap()
111
2
            .as_bytes(),
112
    )
113
2
}
114

            
115
/// Returns the acknowledged fingerprints of the authority certificates.
116
///
117
/// Extracts them ad-hoc from testdata2/cached-certs; we acknowledge
118
/// all fingerprints listed their as trusted.
119
50
pub(crate) fn current_auth_cert_ids() -> Vec<RsaIdentity> {
120
    // Quick ad-hoc parser: Iterate line by line, use rest of line when line
121
    // starts with "fingerprint ".
122
50
    let raw = include_str!("../testdata2/cached-certs");
123
50
    let mut res = Vec::new();
124
9200
    for line in raw.lines() {
125
9200
        if let Some(fp) = line.strip_prefix("fingerprint ") {
126
200
            // Remove trailing \n.
127
200
            let fp = fp.trim();
128
200
            res.push(RsaIdentity::from_hex(fp).expect("invalid fingerprint in cached-certs?"));
129
9000
        }
130
    }
131
50
    res
132
50
}
133

            
134
/// Returns the current and verified authority certificates.
135
11
pub(crate) fn current_auth_certs() -> Vec<(AuthCert, String)> {
136
11
    let raw = include_str!("../testdata2/cached-certs");
137
11
    let auth_certs = parse2::parse_netdoc_multiple_with_offsets::<AuthCertUnverified>(
138
11
        &ParseInput::new(raw, "cached-certs"),
139
    )
140
11
    .unwrap();
141

            
142
11
    let mut res = Vec::new();
143
44
    for (cert, start, end) in auth_certs {
144
44
        let cert = cert
145
44
            .verify(&current_auth_cert_ids())
146
44
            .unwrap()
147
44
            .if_valid_at(&valid_system_time())
148
44
            .unwrap();
149
44
        res.push((cert, raw[start..end].to_string()));
150
44
    }
151
11
    res
152
11
}
153

            
154
/// Returns the current [`AuthorityContacts`].
155
///
156
/// Right now, all of this is empty but it is requried at a few places regardless.
157
5
pub(crate) fn current_auth_cert_contacts() -> AuthorityContacts {
158
5
    let mut authorities = AuthorityContactsBuilder::default();
159
5
    authorities.set_v3idents(current_auth_cert_ids());
160
5
    authorities.set_downloads(vec![]);
161
5
    authorities.set_uploads(vec![]);
162
5
    authorities.set_votes(vec![]);
163
5
    authorities.build().unwrap()
164
5
}
165

            
166
/// Returns the current verified router descriptors, as well as their signatures.
167
///
168
/// Their signatures matter because we need their SHA-1/SHA-256.
169
11
pub(crate) fn current_router_descs()
170
11
-> Vec<(RouterDesc, SignaturesData<RouterDescUnverified>, String)> {
171
11
    let raw = include_str!("../testdata2/cached-descriptors.new");
172
11
    let routers = parse2::parse_netdoc_multiple_with_offsets::<RouterDescUnverified>(
173
11
        &ParseInput::new(raw, "cached-descriptors.new"),
174
    )
175
11
    .unwrap();
176

            
177
11
    let mut res = Vec::new();
178
220
    for (rd, start, end) in routers {
179
220
        // We need a tolerance here because the cached-descriptors may contain
180
220
        // descriptors not included in the current consensus, hence potentially
181
220
        // outside our current time.
182
220
        rd.clone()
183
220
            .verify()
184
220
            .unwrap()
185
220
            .extend_start_bound(PRE_TOLERANCE)
186
220
            .extend_end_bound(POST_TOLERANCE)
187
220
            .if_valid_at(&valid_system_time())
188
220
            .unwrap();
189
220
        let (body, sigs) = rd.unwrap_unverified();
190
220
        res.push((body, sigs, raw[start..end].to_string()));
191
220
    }
192
11
    res
193
11
}
194

            
195
/// Returns the current micro descriptors.
196
11
pub(crate) fn current_micro_descs() -> Vec<(Microdesc, String)> {
197
11
    let raw = include_str!("../testdata2/cached-microdescs.new");
198
11
    parse2::parse_netdoc_multiple_with_offsets::<Microdesc>(&ParseInput::new(
199
11
        raw,
200
11
        "cached-microdescs.new",
201
11
    ))
202
11
    .unwrap()
203
11
    .into_iter()
204
77
    .map(|(md, start, end)| (md, raw[start..end].to_string()))
205
11
    .collect()
206
11
}
207

            
208
/// Returns a [`SystemTime`] where the test data is valid.
209
///
210
/// It picks the middle of `fresh-until` and `valid-after` from
211
/// [`current_consensus_ns()`].
212
///
213
/// In other words: `valid_after + ((fresh_until - valid_after) / 2)`
214
///
215
/// Note that we still need to apply a tolerance sometimes, because the testdata
216
/// also contains data that is not yet published, hence not yet valid.
217
275
pub(crate) fn valid_system_time() -> SystemTime {
218
275
    let lifetime = current_consensus_ns().0.preamble.lifetime;
219
275
    let lifetime_md = current_consensus_md().0.preamble.lifetime;
220
275
    assert_eq!(lifetime, lifetime_md);
221
275
    lifetime.valid_after.0
222
275
        + lifetime
223
275
            .fresh_until
224
275
            .0
225
275
            .duration_since(lifetime.valid_after.0)
226
275
            .expect("invalid SystemTime?")
227
275
            / 2
228
275
}
229

            
230
/// Returns a [`SystemTime`] where the test data is not valid.
231
///
232
/// It picks [`valid_system_time()`] plus two years to ensure that even the
233
/// authority certificates are expired.
234
2
pub(crate) fn invalid_system_time() -> SystemTime {
235
2
    valid_system_time() + Duration::from_secs(60 * 60 * 24 * 365 * 2)
236
2
}
237

            
238
/// Creates a database based on data from test vectors.
239
///
240
/// The database is initialized with the following data:
241
/// * The database schema.
242
/// * [`current_consensus_ns()`] and [`current_consensus_md()`].
243
/// * [`current_auth_certs()`]
244
/// * [`current_router_descs()`].
245
/// * [`current_micro_descs()`].
246
///
247
/// It expects the database primitives to work properly and is intended to test
248
/// the actual operational aspect.
249
10
pub(crate) fn test_db() -> Pool<SqliteConnectionManager> {
250
10
    let pool = db::open("").unwrap();
251
10
    let mut conn = pool.get().unwrap();
252
10
    let tx = conn.transaction().unwrap();
253

            
254
    // Insert the authority certificates.
255
40
    for (cert, raw) in current_auth_certs() {
256
40
        AuthCertMeta::insert(&tx, iter::once(ContentEncoding::Identity), &cert, &raw).unwrap();
257
40
    }
258

            
259
10
    let (plain_body, plain_sigs, plain_data) = current_consensus_ns();
260
10
    ConsensusMeta::<Plain>::insert(
261
10
        &tx,
262
10
        iter::once(ContentEncoding::Identity),
263
10
        (&plain_body, &plain_sigs),
264
10
        plain_data,
265
    )
266
10
    .unwrap();
267

            
268
    // Insert the router descriptors.
269
200
    for (rd, rd_sigs, raw) in current_router_descs() {
270
200
        let docid =
271
200
            store_insert(&tx, raw.as_bytes(), iter::once(ContentEncoding::Identity)).unwrap();
272
200
        let sha1 = Sha1::from(rd_sigs.hashes.sha1.unwrap());
273
200
        let sha2 = Sha256::from(rd_sigs.hashes.sha256.unwrap());
274
200

            
275
200
        tx.execute(
276
200
            sql!(
277
200
                "
278
200
                INSERT INTO descriptor
279
200
                (docid, sha1, sha2, kp_relay_id_rsa_sha1, flavor, extra_sha1)
280
200
                VALUES
281
200
                -- TODO DIRMIRROR: Support extra-info.
282
200
                (:docid, :sha1, :sha2, :fingerprint, :flavor, NULL)
283
200
                "
284
200
            ),
285
200
            named_params! {
286
200
                ":docid": docid,
287
200
                ":sha1": sha1,
288
200
                ":sha2": sha2,
289
200
                ":fingerprint": Sha1::from(rd.signing_key.to_rsa_identity().to_bytes()),
290
200
                ":flavor": ConsensusFlavor::Plain.name()
291
200
            },
292
200
        )
293
200
        .unwrap();
294
200
    }
295

            
296
10
    let (md_body, md_sigs, md_data) = current_consensus_md();
297
10
    ConsensusMeta::<Md>::insert(
298
10
        &tx,
299
10
        iter::once(ContentEncoding::Identity),
300
10
        (&md_body, &md_sigs),
301
10
        md_data,
302
    )
303
10
    .unwrap();
304

            
305
    // Insert the actual micro descriptors.
306
70
    for (_md, raw) in current_micro_descs() {
307
70
        let docid =
308
70
            store_insert(&tx, raw.as_bytes(), iter::once(ContentEncoding::Identity)).unwrap();
309
70
        // Microdescs contain no signature, so the hash goes over everything.
310
70
        let sha1 = Sha1::digest(raw.as_bytes());
311
70
        let sha2 = Sha256::digest(raw.as_bytes());
312
70
        tx.execute(
313
70
            sql!(
314
70
                "
315
70
                -- TODO DIRMIRROR: Same naming issue here.
316
70
                INSERT INTO descriptor
317
70
                (docid, sha1, sha2, kp_relay_id_rsa_sha1, flavor, extra_sha1)
318
70
                VALUES
319
70
                (:docid, :sha1, :sha2, NULL, :flavor, NULL)
320
70
                "
321
70
            ),
322
70
            named_params! {
323
70
                ":docid": docid,
324
70
                ":sha1": sha1,
325
70
                ":sha2": sha2,
326
70
                ":flavor": ConsensusFlavor::Microdesc.name(),
327
70
            },
328
70
        )
329
70
        .unwrap();
330
70
    }
331

            
332
10
    tx.commit().unwrap();
333
10
    pool
334
10
}