1
//! Descriptions objects for different kinds of directory requests
2
//! that we can make.
3

            
4
use tor_llcrypto::pk::rsa::RsaIdentity;
5
use tor_netdoc::doc::authcert::AuthCertKeyIds;
6
use tor_netdoc::doc::microdesc::MdDigest;
7
use tor_netdoc::doc::netstatus::ConsensusFlavor;
8
#[cfg(feature = "routerdesc")]
9
use tor_netdoc::doc::routerdesc::RdDigest;
10

            
11
use crate::Result;
12

            
13
use std::iter::FromIterator;
14
use std::time::SystemTime;
15

            
16
/// A request for an object that can be served over the Tor directory system.
17
pub trait Requestable {
18
    /// Build an [`http::Request`] from this Requestable, if
19
    /// it is well-formed.
20
    fn make_request(&self) -> Result<http::Request<()>>;
21

            
22
    /// Return true if partial downloads are potentially useful.  This
23
    /// is true for request types where we're going to be downloading
24
    /// multiple documents.
25
    fn partial_docs_ok(&self) -> bool;
26

            
27
    /// Return the maximum allowable response length we'll accept for this
28
    /// request.
29
1
    fn max_response_len(&self) -> usize {
30
1
        (16 * 1024 * 1024) - 1
31
1
    }
32
}
33

            
34
/// A Request for a consensus directory.
35
#[derive(Debug, Clone)]
36
pub struct ConsensusRequest {
37
    /// What flavor of consensus are we asking for?  Right now, only
38
    /// "microdesc" and "ns" are supported.
39
    flavor: ConsensusFlavor,
40
    /// A list of the authority identities that we believe in.  We tell the
41
    /// directory cache only to give us a consensus if it is signed by enough
42
    /// of these authorities.
43
    authority_ids: Vec<RsaIdentity>,
44
    /// The publication time of the most recent consensus we have.  Used to
45
    /// generate an If-Modified-Since header so that we don't get a document
46
    /// we already have.
47
    last_consensus_published: Option<SystemTime>,
48
    /// A set of SHA3-256 digests of the _signed portion_ of consensuses we have.
49
    /// Used to declare what diffs we would accept.
50
    ///
51
    /// (Currently we don't send this, since we can't handle diffs.)
52
    last_consensus_sha3_256: Vec<[u8; 32]>,
53
}
54

            
55
impl ConsensusRequest {
56
    /// Create a new request for a consensus directory document.
57
78
    pub fn new(flavor: ConsensusFlavor) -> Self {
58
78
        ConsensusRequest {
59
78
            flavor,
60
78
            authority_ids: Vec::new(),
61
78
            last_consensus_published: None,
62
78
            last_consensus_sha3_256: Vec::new(),
63
78
        }
64
78
    }
65

            
66
    /// Add `id` to the list of authorities that this request should
67
    /// say we believe in.
68
1
    pub fn push_authority_id(&mut self, id: RsaIdentity) {
69
1
        self.authority_ids.push(id);
70
1
    }
71

            
72
    /// Add `d` to the list of consensus digests this request should
73
    /// say we already have.
74
23
    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
75
23
        self.last_consensus_sha3_256.push(d);
76
23
    }
77

            
78
    /// Set the publication time we should say we have for our last
79
    /// consensus to `when`.
80
23
    pub fn set_last_consensus_date(&mut self, when: SystemTime) {
81
23
        self.last_consensus_published = Some(when);
82
23
    }
83

            
84
    /// Return a slice of the consensus digests that we're saying we
85
    /// already have.
86
45
    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
87
45
        self.last_consensus_sha3_256.iter()
88
45
    }
89

            
90
    /// Return an iterator of the authority identities that this request
91
    /// is saying we believe in.
92
1
    pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
93
1
        self.authority_ids.iter()
94
1
    }
95

            
96
    /// Return the date we're reporting for our most recent consensus.
97
24
    pub fn last_consensus_date(&self) -> Option<SystemTime> {
98
24
        self.last_consensus_published
99
24
    }
100
}
101

            
102
impl Default for ConsensusRequest {
103
1
    fn default() -> Self {
104
1
        Self::new(ConsensusFlavor::Microdesc)
105
1
    }
106
}
107

            
108
impl Requestable for ConsensusRequest {
109
1
    fn make_request(&self) -> Result<http::Request<()>> {
110
1
        // Build the URL.
111
1
        let mut uri = "/tor/status-vote/current/consensus".to_string();
112
1
        match self.flavor {
113
            ConsensusFlavor::Ns => {}
114
1
            flav => {
115
1
                uri.push('-');
116
1
                uri.push_str(flav.name());
117
1
            }
118
        }
119
1
        if !self.authority_ids.is_empty() {
120
1
            let mut ids = self.authority_ids.clone();
121
1
            ids.sort_unstable();
122
1
            uri.push('/');
123
1
            let ids: Vec<String> = ids.iter().map(|id| hex::encode(id.as_bytes())).collect();
124
1
            uri.push_str(&ids.join("+"));
125
1
        }
126
1
        uri.push_str(".z");
127
1

            
128
1
        let mut req = http::Request::builder().method("GET").uri(uri);
129
1
        req = add_common_headers(req);
130

            
131
        // Possibly, add an if-modified-since header.
132
1
        if let Some(when) = self.last_consensus_date() {
133
1
            req = req.header(
134
1
                http::header::IF_MODIFIED_SINCE,
135
1
                httpdate::fmt_http_date(when),
136
1
            );
137
1
        }
138

            
139
        // Possibly, add an X-Or-Diff-From-Consensus header.
140
1
        if !self.last_consensus_sha3_256.is_empty() {
141
1
            let mut digests = self.last_consensus_sha3_256.clone();
142
1
            digests.sort_unstable();
143
1
            let digests: Vec<String> = digests.iter().map(hex::encode).collect();
144
1
            req = req.header("X-Or-Diff-From-Consensus", &digests.join(", "));
145
1
        }
146

            
147
1
        Ok(req.body(())?)
148
1
    }
149

            
150
1
    fn partial_docs_ok(&self) -> bool {
151
1
        false
152
1
    }
153
}
154

            
155
/// A request for one or more authority certificates.
156
35
#[derive(Debug, Clone, Default)]
157
pub struct AuthCertRequest {
158
    /// The identity/signing keys of the certificates we want.
159
    ids: Vec<AuthCertKeyIds>,
160
}
161

            
162
impl AuthCertRequest {
163
    /// Create a new request, asking for no authority certificates.
164
34
    pub fn new() -> Self {
165
34
        AuthCertRequest::default()
166
34
    }
167

            
168
    /// Add `ids` to the list of certificates we're asking for.
169
37
    pub fn push(&mut self, ids: AuthCertKeyIds) {
170
37
        self.ids.push(ids);
171
37
    }
172

            
173
    /// Return a list of the keys that we're asking for.
174
34
    pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
175
34
        self.ids.iter()
176
34
    }
177
}
178

            
179
impl Requestable for AuthCertRequest {
180
2
    fn make_request(&self) -> Result<http::Request<()>> {
181
2
        let mut ids = self.ids.clone();
182
2
        ids.sort_unstable();
183
2

            
184
2
        let ids: Vec<String> = ids
185
2
            .iter()
186
4
            .map(|id| {
187
4
                format!(
188
4
                    "{}-{}",
189
4
                    hex::encode(id.id_fingerprint.as_bytes()),
190
4
                    hex::encode(id.sk_fingerprint.as_bytes())
191
4
                )
192
4
            })
193
2
            .collect();
194
2

            
195
2
        let uri = format!("/tor/keys/fp-sk/{}.z", &ids.join("+"));
196
2

            
197
2
        let req = http::Request::builder().method("GET").uri(uri);
198
2
        let req = add_common_headers(req);
199
2

            
200
2
        Ok(req.body(())?)
201
2
    }
202

            
203
2
    fn partial_docs_ok(&self) -> bool {
204
2
        self.ids.len() > 1
205
2
    }
206

            
207
1
    fn max_response_len(&self) -> usize {
208
1
        // TODO: Pick a more principled number; I just made this one up.
209
1
        self.ids.len().saturating_mul(16 * 1024)
210
1
    }
211
}
212

            
213
impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
214
2
    fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
215
2
        let mut req = Self::new();
216
5
        for i in iter {
217
3
            req.push(i);
218
3
        }
219
2
        req
220
2
    }
221
}
222

            
223
/// A request for one or more microdescriptors
224
64
#[derive(Debug, Clone, Default)]
225
pub struct MicrodescRequest {
226
    /// The SHA256 digests of the microdescriptors we want.
227
    digests: Vec<MdDigest>,
228
}
229

            
230
impl MicrodescRequest {
231
    /// Construct a request for no microdescriptors.
232
63
    pub fn new() -> Self {
233
63
        MicrodescRequest::default()
234
63
    }
235
    /// Add `d` to the list of microdescriptors we want to request.
236
11078
    pub fn push(&mut self, d: MdDigest) {
237
11078
        self.digests.push(d);
238
11078
    }
239

            
240
    /// Return a list of the microdescriptor digests that we're asking for.
241
12
    pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
242
12
        self.digests.iter()
243
12
    }
244
}
245

            
246
impl Requestable for MicrodescRequest {
247
9
    fn make_request(&self) -> Result<http::Request<()>> {
248
9
        // TODO: require that self.digests is nonempty.
249
9
        let mut digests = self.digests.clone();
250
9
        digests.sort_unstable();
251
9

            
252
9
        let ids: Vec<String> = digests
253
9
            .iter()
254
12
            .map(|d| base64::encode_config(d, base64::STANDARD_NO_PAD))
255
9
            .collect();
256
9
        let uri = format!("/tor/micro/d/{}.z", &ids.join("-"));
257
9
        let req = http::Request::builder().method("GET").uri(uri);
258
9

            
259
9
        let req = add_common_headers(req);
260
9

            
261
9
        Ok(req.body(())?)
262
9
    }
263

            
264
9
    fn partial_docs_ok(&self) -> bool {
265
9
        self.digests.len() > 1
266
9
    }
267

            
268
8
    fn max_response_len(&self) -> usize {
269
8
        // TODO: Pick a more principled number; I just made this one up.
270
8
        self.digests.len().saturating_mul(8 * 1024)
271
8
    }
272
}
273

            
274
impl FromIterator<MdDigest> for MicrodescRequest {
275
12
    fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
276
12
        let mut req = Self::new();
277
1025
        for i in iter {
278
1013
            req.push(i);
279
1013
        }
280
12
        req
281
12
    }
282
}
283

            
284
/// A request for one, many or all router descriptors.
285
24
#[derive(Debug, Clone, Default)]
286
#[cfg(feature = "routerdesc")]
287
pub struct RouterDescRequest {
288
    /// If this is set, we just ask for all the descriptors.
289
    // TODO: maybe this should be an enum, or maybe this case should
290
    // be a different type.
291
    all_descriptors: bool,
292
    /// A list of digests to download.
293
    digests: Vec<RdDigest>,
294
}
295

            
296
#[cfg(feature = "routerdesc")]
297
impl RouterDescRequest {
298
    /// Construct a request for all router descriptors.
299
1
    pub fn all() -> Self {
300
1
        RouterDescRequest {
301
1
            all_descriptors: true,
302
1
            digests: Vec::new(),
303
1
        }
304
1
    }
305
    /// Construct a new empty request.
306
23
    pub fn new() -> Self {
307
23
        RouterDescRequest::default()
308
23
    }
309
    /// Add `d` to the list of digests we want to request.
310
11004
    pub fn push(&mut self, d: RdDigest) {
311
11004
        if !self.all_descriptors {
312
11004
            self.digests.push(d);
313
11004
        }
314
11004
    }
315

            
316
    /// Return an iterator over the descriptor digests that we're asking for.
317
1
    pub fn digests(&self) -> impl Iterator<Item = &RdDigest> {
318
1
        self.digests.iter()
319
1
    }
320
}
321

            
322
#[cfg(feature = "routerdesc")]
323
impl Requestable for RouterDescRequest {
324
3
    fn make_request(&self) -> Result<http::Request<()>> {
325
3
        let mut uri = "/tor/server/".to_string();
326
3

            
327
3
        if self.all_descriptors {
328
1
            uri.push_str("all");
329
2
        } else {
330
2
            uri.push_str("d/");
331
2
            // TODO: require that self.digests is nonempty.
332
2
            let mut digests = self.digests.clone();
333
2
            digests.sort_unstable();
334
2
            let ids: Vec<String> = digests.iter().map(hex::encode).collect();
335
2
            uri.push_str(&ids.join("+"));
336
2
        }
337
3
        uri.push_str(".z");
338
3

            
339
3
        let req = http::Request::builder().method("GET").uri(uri);
340
3
        let req = add_common_headers(req);
341
3

            
342
3
        Ok(req.body(())?)
343
3
    }
344

            
345
3
    fn partial_docs_ok(&self) -> bool {
346
3
        self.digests.len() > 1 || self.all_descriptors
347
3
    }
348

            
349
2
    fn max_response_len(&self) -> usize {
350
2
        // TODO: Pick a more principled number; I just made these up.
351
2
        if self.all_descriptors {
352
1
            64 * 1024 * 1024 // big but not impossible
353
        } else {
354
1
            self.digests.len().saturating_mul(8 * 1024)
355
        }
356
2
    }
357
}
358

            
359
#[cfg(feature = "routerdesc")]
360
impl FromIterator<RdDigest> for RouterDescRequest {
361
3
    fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
362
3
        let mut req = Self::new();
363
1005
        for i in iter {
364
1002
            req.push(i);
365
1002
        }
366
3
        req
367
3
    }
368
}
369

            
370
/// List the encodings we accept
371
20
fn encodings() -> String {
372
20
    let mut encodings = "deflate, identity".to_string();
373
20
    #[cfg(feature = "xz")]
374
20
    {
375
20
        encodings += ", x-tor-lzma";
376
20
    }
377
20
    #[cfg(feature = "zstd")]
378
20
    {
379
20
        encodings += ", x-zstd";
380
20
    }
381
20

            
382
20
    encodings
383
20
}
384

            
385
/// Add commonly used headers to the HTTP request.
386
///
387
/// (Right now, this is only Accept-Encoding.)
388
15
fn add_common_headers(req: http::request::Builder) -> http::request::Builder {
389
15
    // TODO: gzip, brotli
390
15
    req.header(http::header::ACCEPT_ENCODING, encodings())
391
15
}
392

            
393
#[cfg(test)]
394
mod test {
395
    #![allow(clippy::unwrap_used)]
396
    use super::*;
397

            
398
    #[test]
399
    fn test_md_request() -> Result<()> {
400
        let d1 = b"This is a testing digest. it isn";
401
        let d2 = b"'t actually SHA-256.............";
402

            
403
        let mut req = MicrodescRequest::default();
404
        req.push(*d1);
405
        assert!(!req.partial_docs_ok());
406
        req.push(*d2);
407
        assert!(req.partial_docs_ok());
408
        assert_eq!(req.max_response_len(), 16 << 10);
409

            
410
        let req = crate::util::encode_request(&req.make_request()?);
411

            
412
        assert_eq!(req,
413
                   format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", encodings()));
414

            
415
        // Try it with FromIterator, and use some accessors.
416
        let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
417
        let ds: Vec<_> = req2.digests().collect();
418
        assert_eq!(ds, vec![d1, d2]);
419
        let req2 = crate::util::encode_request(&req2.make_request()?);
420
        assert_eq!(req, req2);
421

            
422
        Ok(())
423
    }
424

            
425
    #[test]
426
    fn test_cert_request() -> Result<()> {
427
        let d1 = b"This is a testing dn";
428
        let d2 = b"'t actually SHA-256.";
429
        let key1 = AuthCertKeyIds {
430
            id_fingerprint: (*d1).into(),
431
            sk_fingerprint: (*d2).into(),
432
        };
433

            
434
        let d3 = b"blah blah blah 1 2 3";
435
        let d4 = b"I like pizza from Na";
436
        let key2 = AuthCertKeyIds {
437
            id_fingerprint: (*d3).into(),
438
            sk_fingerprint: (*d4).into(),
439
        };
440

            
441
        let mut req = AuthCertRequest::default();
442
        req.push(key1);
443
        assert!(!req.partial_docs_ok());
444
        req.push(key2);
445
        assert!(req.partial_docs_ok());
446
        assert_eq!(req.max_response_len(), 32 << 10);
447

            
448
        let keys: Vec<_> = req.keys().collect();
449
        assert_eq!(keys, vec![&key1, &key2]);
450

            
451
        let req = crate::util::encode_request(&req.make_request()?);
452

            
453
        assert_eq!(req,
454
                   format!("GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", encodings()));
455

            
456
        let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
457
        let req2 = crate::util::encode_request(&req2.make_request()?);
458
        assert_eq!(req, req2);
459

            
460
        Ok(())
461
    }
462

            
463
    #[test]
464
    fn test_consensus_request() -> Result<()> {
465
        let d1 = RsaIdentity::from_bytes(
466
            &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
467
        )
468
        .unwrap();
469

            
470
        let d2 = b"blah blah blah 12 blah blah blah";
471
        let d3 = SystemTime::now();
472
        let mut req = ConsensusRequest::default();
473

            
474
        let when = httpdate::fmt_http_date(d3);
475

            
476
        req.push_authority_id(d1);
477
        req.push_old_consensus_digest(*d2);
478
        req.set_last_consensus_date(d3);
479
        assert!(!req.partial_docs_ok());
480
        assert_eq!(req.max_response_len(), (16 << 20) - 1);
481
        assert_eq!(req.old_consensus_digests().next(), Some(d2));
482
        assert_eq!(req.authority_ids().next(), Some(&d1));
483
        assert_eq!(req.last_consensus_date(), Some(d3));
484

            
485
        let req = crate::util::encode_request(&req.make_request()?);
486

            
487
        assert_eq!(req,
488
                   format!("GET /tor/status-vote/current/consensus-microdesc/03479e93ebf3ff2c58c1c9dbf2de9de9c2801b3e.z HTTP/1.0\r\naccept-encoding: {}\r\nif-modified-since: {}\r\nx-or-diff-from-consensus: 626c616820626c616820626c616820313220626c616820626c616820626c6168\r\n\r\n", encodings(), when));
489

            
490
        Ok(())
491
    }
492

            
493
    #[test]
494
    #[cfg(feature = "routerdesc")]
495
    fn test_rd_request_all() -> Result<()> {
496
        let req = RouterDescRequest::all();
497
        assert!(req.partial_docs_ok());
498
        assert_eq!(req.max_response_len(), 1 << 26);
499

            
500
        let req = crate::util::encode_request(&req.make_request()?);
501

            
502
        assert_eq!(
503
            req,
504
            format!(
505
                "GET /tor/server/all.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
506
                encodings()
507
            )
508
        );
509

            
510
        Ok(())
511
    }
512

            
513
    #[test]
514
    #[cfg(feature = "routerdesc")]
515
    fn test_rd_request() -> Result<()> {
516
        let d1 = b"at some point I got ";
517
        let d2 = b"of writing in hex...";
518

            
519
        let mut req = RouterDescRequest::default();
520
        req.push(*d1);
521
        assert!(!req.partial_docs_ok());
522
        req.push(*d2);
523
        assert!(req.partial_docs_ok());
524
        assert_eq!(req.max_response_len(), 16 << 10);
525

            
526
        let req = crate::util::encode_request(&req.make_request()?);
527

            
528
        assert_eq!(req,
529
                   format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", encodings()));
530

            
531
        // Try it with FromIterator, and use some accessors.
532
        let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
533
        let ds: Vec<_> = req2.digests().collect();
534
        assert_eq!(ds, vec![d1, d2]);
535
        let req2 = crate::util::encode_request(&req2.make_request()?);
536
        assert_eq!(req, req2);
537
        Ok(())
538
    }
539
}