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, ContentEncoding, Sha1, Sha3_256, Sha256, Timestamp, sql, store_insert,
56
};
57

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

            
61
/// Post tolerance, 1 day.
62
const POST_TOLERANCE: Duration = Duration::from_secs(60 * 60 * 24);
63

            
64
/// Returns a consensus that can be used as a test vector.
65
///
66
/// We assume the consensus is valid to avoid circular dependencies with other
67
/// functions here.  This should be okay because these things are not intended
68
/// to test tor-netdoc itself.
69
562
pub(crate) fn current_consensus_ns() -> (plain::NetworkStatus, &'static str) {
70
562
    let raw = include_str!("../testdata2/cached-consensus");
71
562
    current_consensus::<plain::NetworkStatusUnverified>(raw)
72
562
}
73

            
74
/// [`current_consensus_ns()`] but for microdescriptor consensuses.
75
// TODO: Merge with current_consensus_ns() because it is repetitive.
76
532
pub(crate) fn current_consensus_md() -> (md::NetworkStatus, &'static str) {
77
532
    let raw = include_str!("../testdata2/cached-microdesc-consensus");
78
532
    current_consensus::<md::NetworkStatusUnverified>(raw)
79
532
}
80

            
81
/// Internal function for obtaining a consensus from a text file.
82
1094
fn current_consensus<T: NetdocParseableUnverified + NetdocParseable>(
83
1094
    raw: &'static str,
84
1094
) -> (T::Body, &'static str) {
85
1094
    let consensus = parse2::parse_netdoc::<T>(&ParseInput::new(raw, "consensus")).unwrap();
86
1094
    (consensus.unwrap_unverified().0, raw)
87
1094
}
88

            
89
/// Returns the unsigned SHA-3 of a given consensus as a string.
90
///
91
/// Required for consensus diffs.
92
// TODO DIRMIRROR: We *need* a method for this in tor-netdoc.
93
36
pub(crate) fn consensus_sha3(data: &str) -> Sha3_256 {
94
36
    Sha3_256::digest(
95
36
        data.split_inclusive("\ndirectory-signature ")
96
36
            .next()
97
36
            .unwrap()
98
36
            .as_bytes(),
99
    )
100
36
}
101

            
102
/// Returns the acknowledged fingerprints of the authority certificates.
103
///
104
/// Extracts them ad-hoc from testdata2/cached-certs; we acknowledge
105
/// all fingerprints listed their as trusted.
106
136
pub(crate) fn current_auth_cert_ids() -> Vec<RsaIdentity> {
107
    // Quick ad-hoc parser: Iterate line by line, use rest of line when line
108
    // starts with "fingerprint ".
109
136
    let raw = include_str!("../testdata2/cached-certs");
110
136
    let mut res = Vec::new();
111
25024
    for line in raw.lines() {
112
25024
        if let Some(fp) = line.strip_prefix("fingerprint ") {
113
544
            // Remove trailing \n.
114
544
            let fp = fp.trim();
115
544
            res.push(RsaIdentity::from_hex(fp).expect("invalid fingerprint in cached-certs?"));
116
24480
        }
117
    }
118
136
    res
119
136
}
120

            
121
/// Returns the current and verified authority certificates.
122
32
pub(crate) fn current_auth_certs() -> Vec<(AuthCert, String)> {
123
32
    let raw = include_str!("../testdata2/cached-certs");
124
32
    let auth_certs = parse2::parse_netdoc_multiple_with_offsets::<AuthCertUnverified>(
125
32
        &ParseInput::new(raw, "cached-certs"),
126
    )
127
32
    .unwrap();
128

            
129
32
    let mut res = Vec::new();
130
128
    for (cert, start, end) in auth_certs {
131
128
        let cert = cert
132
128
            .verify(&current_auth_cert_ids())
133
128
            .unwrap()
134
128
            .if_valid_at(&valid_system_time())
135
128
            .unwrap();
136
128
        res.push((cert, raw[start..end].to_string()));
137
128
    }
138
32
    res
139
32
}
140

            
141
/// Returns the current [`AuthorityContacts`].
142
///
143
/// Right now, all of this is empty but it is requried at a few places regardless.
144
6
pub(crate) fn current_auth_cert_contacts() -> AuthorityContacts {
145
6
    let mut authorities = AuthorityContactsBuilder::default();
146
6
    authorities.set_v3idents(current_auth_cert_ids());
147
6
    authorities.set_downloads(vec![]);
148
6
    authorities.set_uploads(vec![]);
149
6
    authorities.set_votes(vec![]);
150
6
    authorities.build().unwrap()
151
6
}
152

            
153
/// Returns the current verified router descriptors, as well as their signatures.
154
///
155
/// Their signatures matter because we need their SHA-1/SHA-256.
156
18
pub(crate) fn current_router_descs()
157
18
-> Vec<(RouterDesc, SignaturesData<RouterDescUnverified>, String)> {
158
18
    let raw = include_str!("../testdata2/cached-descriptors.new");
159
18
    let routers = parse2::parse_netdoc_multiple_with_offsets::<RouterDescUnverified>(
160
18
        &ParseInput::new(raw, "cached-descriptors.new"),
161
    )
162
18
    .unwrap();
163

            
164
18
    let mut res = Vec::new();
165
360
    for (rd, start, end) in routers {
166
360
        // We need a tolerance here because the cached-descriptors may contain
167
360
        // descriptors not included in the current consensus, hence potentially
168
360
        // outside our current time.
169
360
        rd.clone()
170
360
            .verify()
171
360
            .unwrap()
172
360
            .extend_start_bound(PRE_TOLERANCE)
173
360
            .extend_end_bound(POST_TOLERANCE)
174
360
            .if_valid_at(&valid_system_time())
175
360
            .unwrap();
176
360
        let (body, sigs) = rd.unwrap_unverified();
177
360
        res.push((body, sigs, raw[start..end].to_string()));
178
360
    }
179
18
    res
180
18
}
181

            
182
/// Returns the current micro descriptors.
183
18
pub(crate) fn current_micro_descs() -> Vec<(Microdesc, String)> {
184
18
    let raw = include_str!("../testdata2/cached-microdescs.new");
185
18
    parse2::parse_netdoc_multiple_with_offsets::<Microdesc>(&ParseInput::new(
186
18
        raw,
187
18
        "cached-microdescs.new",
188
18
    ))
189
18
    .unwrap()
190
18
    .into_iter()
191
135
    .map(|(md, start, end)| (md, raw[start..end].to_string()))
192
18
    .collect()
193
18
}
194

            
195
/// Returns a [`SystemTime`] where the test data is valid.
196
///
197
/// It picks the middle of `fresh-until` and `valid-after` from
198
/// [`current_consensus_ns()`].
199
///
200
/// In other words: `valid_after + ((fresh_until - valid_after) / 2)`
201
///
202
/// Note that we still need to apply a tolerance sometimes, because the testdata
203
/// also contains data that is not yet published, hence not yet valid.
204
512
pub(crate) fn valid_system_time() -> SystemTime {
205
512
    let lifetime = current_consensus_ns().0.preamble.lifetime;
206
512
    let lifetime_md = current_consensus_md().0.preamble.lifetime;
207
512
    assert_eq!(lifetime, lifetime_md);
208
512
    lifetime.valid_after.0
209
512
        + lifetime
210
512
            .fresh_until
211
512
            .0
212
512
            .duration_since(lifetime.valid_after.0)
213
512
            .expect("invalid SystemTime?")
214
512
            / 2
215
512
}
216

            
217
/// Returns a [`SystemTime`] where the test data is not valid.
218
///
219
/// It picks [`valid_system_time()`] plus two years to ensure that even the
220
/// authority certificates are expired.
221
2
pub(crate) fn invalid_system_time() -> SystemTime {
222
2
    valid_system_time() + Duration::from_secs(60 * 60 * 24 * 365 * 2)
223
2
}
224

            
225
/// Creates a database based on data from test vectors.
226
///
227
/// The database is initialized with the following data:
228
/// * The database schema.
229
/// * [`current_consensus_ns()`] and [`current_consensus_md()`].
230
/// * [`current_auth_certs()`]
231
/// * [`current_router_descs()`].
232
/// * [`current_micro_descs()`].
233
///
234
/// It expects the database primitives to work properly and is intended to test
235
/// the actual operational aspect.
236
16
pub(crate) fn test_db() -> Pool<SqliteConnectionManager> {
237
16
    let pool = db::open("").unwrap();
238
16
    let mut conn = pool.get().unwrap();
239
16
    let tx = conn.transaction().unwrap();
240

            
241
    // Insert the authority certificates.
242
64
    for (cert, raw) in current_auth_certs() {
243
64
        AuthCertMeta::insert(&tx, iter::once(ContentEncoding::Identity), &cert, &raw).unwrap();
244
64
    }
245

            
246
    // TODO DIRMIRROR: Everything below here is very boilerplate and C&P.
247
    // This is because we currently lack proper insertion methods for
248
    // documents beside AuthCert.  Once we have them, we can replace this
249
    // monster with those.
250

            
251
    // Insert the plain consensus.
252
16
    let ns = current_consensus_ns();
253
16
    let docid = store_insert(&tx, ns.1.as_bytes(), iter::once(ContentEncoding::Identity)).unwrap();
254
16
    let unsigned_sha3_256 = consensus_sha3(ns.1);
255
16
    tx.execute(
256
16
        sql!(
257
16
            "
258
16
            INSERT INTO consensus
259
16
            (docid, unsigned_sha3_256, flavor, valid_after, fresh_until, valid_until)
260
16
            VALUES
261
16
            (:docid, :unsigned_sha3_256, :flavor, :valid_after, :fresh_until, :valid_until)
262
16
            "
263
16
        ),
264
16
        named_params! {
265
16
            ":docid": docid,
266
16
            ":unsigned_sha3_256": unsigned_sha3_256,
267
16
            ":flavor": ConsensusFlavor::Plain.name(),
268
16
            ":valid_after": Timestamp::from(ns.0.preamble.lifetime.valid_after.0),
269
16
            ":fresh_until": Timestamp::from(ns.0.preamble.lifetime.fresh_until.0),
270
16
            ":valid_until": Timestamp::from(ns.0.preamble.lifetime.valid_until.0),
271
16
        },
272
16
    )
273
16
    .unwrap();
274

            
275
    // Insert the consensus/desc relationship.
276
112
    for relay in &ns.0.routers {
277
112
        tx.execute(
278
112
            sql!(
279
112
                "
280
112
                INSERT INTO consensus_router_descriptor_member
281
112
                (consensus_docid, unsigned_sha1, unsigned_sha2)
282
112
                VALUES
283
112
                (:consensus_docid, :unsigned_sha1, NULL)
284
112
                "
285
112
            ),
286
112
            named_params! {
287
112
                ":consensus_docid": docid,
288
112
                ":unsigned_sha1": Sha1::from(relay.doc_digest().clone())
289
112
            },
290
112
        )
291
112
        .unwrap();
292
112
    }
293

            
294
    // Insert the router descriptors.
295
320
    for (rd, rd_sigs, raw) in current_router_descs() {
296
320
        let docid =
297
320
            store_insert(&tx, raw.as_bytes(), iter::once(ContentEncoding::Identity)).unwrap();
298
320
        let sha1 = Sha1::from(rd_sigs.hashes.sha1.unwrap());
299
320
        let sha2 = Sha256::from(rd_sigs.hashes.sha256.unwrap());
300
320

            
301
320
        tx.execute(
302
320
            sql!(
303
320
                "
304
320
                INSERT INTO router_descriptor
305
320
                (docid, unsigned_sha1, unsigned_sha2, kp_relay_id_rsa_sha1, flavor, extra_unsigned_sha1)
306
320
                VALUES
307
320
                -- TODO DIRMIRROR: Support extra-info.
308
320
                (:docid, :sha1, :sha2, :fingerprint, :flavor, NULL)
309
320
                "
310
320
            ),
311
320
            named_params! {
312
320
                ":docid": docid,
313
320
                ":sha1": sha1,
314
320
                ":sha2": sha2,
315
320
                ":fingerprint": Sha1::from(rd.signing_key.to_rsa_identity().to_bytes()),
316
320
                ":flavor": ConsensusFlavor::Plain.name()
317
320
            },
318
320
        )
319
320
        .unwrap();
320
320
    }
321

            
322
    // Insert the microdesc consensus (mostly a copy of the above code).
323
    // Yes, this is not super nice but will hopefully be solved when we have
324
    // a ConsensusMeta::insert() method.
325
16
    let md = current_consensus_md();
326
16
    let docid = store_insert(&tx, md.1.as_bytes(), iter::once(ContentEncoding::Identity)).unwrap();
327
16
    let unsigned_sha3_256 = consensus_sha3(md.1);
328
16
    tx.execute(
329
16
        sql!(
330
16
            "
331
16
            INSERT INTO consensus
332
16
            (docid, unsigned_sha3_256, flavor, valid_after, fresh_until, valid_until)
333
16
            VALUES
334
16
            (:docid, :unsigned_sha3_256, :flavor, :valid_after, :fresh_until, :valid_until)
335
16
            "
336
16
        ),
337
16
        named_params! {
338
16
            ":docid": docid,
339
16
            ":unsigned_sha3_256": unsigned_sha3_256,
340
16
            ":flavor": ConsensusFlavor::Microdesc.name(),
341
16
            ":valid_after": Timestamp::from(md.0.preamble.lifetime.valid_after.0),
342
16
            ":fresh_until": Timestamp::from(md.0.preamble.lifetime.fresh_until.0),
343
16
            ":valid_until": Timestamp::from(md.0.preamble.lifetime.valid_until.0),
344
16
        },
345
16
    )
346
16
    .unwrap();
347

            
348
    // Insert the consensus-md/desc relationship.
349
112
    for relay in &md.0.routers {
350
112
        tx.execute(
351
112
            sql!(
352
112
                "
353
112
                -- TODO DIRMIRROR: Change table name to descriptor only, as it
354
112
                -- obviously also contains microdesc relationships.
355
112
                INSERT INTO consensus_router_descriptor_member
356
112
                (consensus_docid, unsigned_sha1, unsigned_sha2)
357
112
                VALUES
358
112
                (:consensus_docid, NULL, :unsigned_sha2)
359
112
                "
360
112
            ),
361
112
            named_params! {
362
112
                ":consensus_docid": docid,
363
112
                ":unsigned_sha2": Sha256::from(relay.doc_digest().clone())
364
112
            },
365
112
        )
366
112
        .unwrap();
367
112
    }
368

            
369
    // Insert the actual micro descriptors.
370
112
    for (_md, raw) in current_micro_descs() {
371
112
        let docid =
372
112
            store_insert(&tx, raw.as_bytes(), iter::once(ContentEncoding::Identity)).unwrap();
373
112
        // Microdescs contain no signature, so the hash goes over everything.
374
112
        let sha1 = Sha1::digest(raw.as_bytes());
375
112
        let sha2 = Sha256::digest(raw.as_bytes());
376
112
        tx.execute(
377
112
            sql!(
378
112
                "
379
112
                -- TODO DIRMIRROR: Same naming issue here.
380
112
                INSERT INTO router_descriptor
381
112
                (docid, unsigned_sha1, unsigned_sha2, kp_relay_id_rsa_sha1, flavor, extra_unsigned_sha1)
382
112
                VALUES
383
112
                (:docid, :sha1, :sha2, NULL, :flavor, NULL)
384
112
                "
385
112
            ),
386
112
            named_params! {
387
112
                ":docid": docid,
388
112
                ":sha1": sha1,
389
112
                ":sha2": sha2,
390
112
                ":flavor": ConsensusFlavor::Microdesc.name(),
391
112
            }
392
112
        ).unwrap();
393
112
    }
394

            
395
16
    tx.commit().unwrap();
396
16
    pool
397
16
}