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

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

            
12
#[cfg(feature = "hs-client")]
13
use tor_hscrypto::pk::HsBlindId;
14

            
15
/// Alias for a result with a `RequestError`.
16
type Result<T> = std::result::Result<T, crate::err::RequestError>;
17

            
18
use base64ct::{Base64Unpadded, Encoding as _};
19
use std::borrow::Cow;
20
use std::future::Future;
21
use std::iter::FromIterator;
22
use std::pin::Pin;
23
use std::sync::Arc;
24
use std::time::{Duration, SystemTime};
25

            
26
use itertools::Itertools;
27

            
28
use crate::AnonymizedRequest;
29
use crate::body::RequestBody;
30
use crate::err::RequestError;
31

            
32
/// Declare an inaccessible public type.
33
pub(crate) mod sealed {
34
    use tor_circmgr::ClientDirTunnel;
35

            
36
    use crate::body::RequestBody;
37

            
38
    use super::{AnonymizedRequest, Result};
39

            
40
    use std::future::Future;
41
    use std::pin::Pin;
42

            
43
    /// Sealed trait to help implement [`Requestable`](super::Requestable): not
44
    /// visible outside this crate, so we can change its methods however we like.
45
    pub trait RequestableInner: Send + Sync {
46
        /// Build an [`http::Request`] from this Requestable, if
47
        /// it is well-formed.
48
        fn make_request(&self) -> Result<http::Request<RequestBody>>;
49

            
50
        /// Return true if partial response bodies are potentially useful.
51
        ///
52
        /// This is true for request types where we're going to be downloading
53
        /// multiple documents, and we know how to parse out the ones we wanted
54
        /// if the answer is truncated.
55
        fn partial_response_body_ok(&self) -> bool;
56

            
57
        /// Return the maximum allowable response length we'll accept for this
58
        /// request.
59
        fn max_response_len(&self) -> usize;
60

            
61
        /// Optimization: return an error if there is some problem with the provided circuit that
62
        /// makes it unlikely to be useful for this request.
63
        ///
64
        /// *Do not* use this method to check for security properties.
65
        fn check_circuit_skip_optimization<'a>(
66
            &self,
67
            tunnel: &'a ClientDirTunnel,
68
        ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
69
            let _ = tunnel;
70
            Box::pin(async { Ok(()) })
71
        }
72

            
73
        /// Return a value to say whether this request must be anonymized.
74
        fn anonymized(&self) -> AnonymizedRequest;
75
    }
76
}
77

            
78
/// A request for an object that can be served over the Tor directory system.
79
pub trait Requestable: sealed::RequestableInner {
80
    /// Return a wrapper around this [`Requestable`] that implements `Debug`,
81
    /// and whose output shows the actual HTTP request that will be generated.
82
    ///
83
    /// The format is not guaranteed to  be stable.
84
7
    fn debug_request(&self) -> DisplayRequestable<'_, Self>
85
7
    where
86
7
        Self: Sized,
87
    {
88
7
        DisplayRequestable(self)
89
7
    }
90
}
91
impl<T: sealed::RequestableInner> Requestable for T {}
92

            
93
/// A wrapper to implement [`Requestable::debug_request`].
94
pub struct DisplayRequestable<'a, R: Requestable>(&'a R);
95

            
96
impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> {
97
7
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98
7
        write!(f, "{:?}", self.0.make_request())
99
7
    }
100
}
101

            
102
impl sealed::RequestableInner for Arc<dyn Requestable> {
103
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
104
        let r: &dyn Requestable = self.as_ref();
105
        r.make_request()
106
    }
107

            
108
    fn partial_response_body_ok(&self) -> bool {
109
        let r: &dyn Requestable = self.as_ref();
110
        r.partial_response_body_ok()
111
    }
112

            
113
    fn anonymized(&self) -> AnonymizedRequest {
114
        let r: &dyn Requestable = self.as_ref();
115
        r.anonymized()
116
    }
117

            
118
    fn max_response_len(&self) -> usize {
119
        let r: &dyn Requestable = self.as_ref();
120
        r.max_response_len()
121
    }
122

            
123
    fn check_circuit_skip_optimization<'a>(
124
        &self,
125
        tunnel: &'a ClientDirTunnel,
126
    ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
127
        let r: &dyn Requestable = self.as_ref();
128
        r.check_circuit_skip_optimization(tunnel)
129
    }
130
}
131

            
132
/// How much clock skew do we allow in the distance between the directory
133
/// cache's clock and our own?
134
///
135
///  If we find more skew than this, we end the
136
/// request early, on the theory that the directory will not tell us any
137
/// information we'd accept.
138
#[derive(Clone, Debug)]
139
struct SkewLimit {
140
    /// We refuse to proceed if the directory says we are more fast than this.
141
    ///
142
    /// (This is equivalent to deciding that, from our perspective, the
143
    /// directory is at least this slow.)
144
    max_fast: Duration,
145

            
146
    /// We refuse to proceed if the directory says that we are more slow than
147
    /// this.
148
    ///
149
    /// (This is equivalent to deciding that, from our perspective, the
150
    /// directory is at least this fast.)
151
    max_slow: Duration,
152
}
153

            
154
/// A Request for a consensus directory.
155
#[derive(Debug, Clone)]
156
pub struct ConsensusRequest {
157
    /// What flavor of consensus are we asking for?  Right now, only
158
    /// "microdesc" and "ns" are supported.
159
    flavor: ConsensusFlavor,
160
    /// A list of the authority identities that we believe in.  We tell the
161
    /// directory cache only to give us a consensus if it is signed by enough
162
    /// of these authorities.
163
    authority_ids: Vec<RsaIdentity>,
164
    /// The publication time of the most recent consensus we have.  Used to
165
    /// generate an If-Modified-Since header so that we don't get a document
166
    /// we already have.
167
    last_consensus_published: Option<SystemTime>,
168
    /// A set of SHA3-256 digests of the _signed portion_ of consensuses we have.
169
    /// Used to declare what diffs we would accept.
170
    last_consensus_sha3_256: Vec<[u8; 32]>,
171
    /// If present, the largest amount of clock skew to allow between ourself and a directory cache.
172
    skew_limit: Option<SkewLimit>,
173
}
174

            
175
impl ConsensusRequest {
176
    /// Create a new request for a consensus directory document.
177
338
    pub fn new(flavor: ConsensusFlavor) -> Self {
178
338
        ConsensusRequest {
179
338
            flavor,
180
338
            authority_ids: Vec::new(),
181
338
            last_consensus_published: None,
182
338
            last_consensus_sha3_256: Vec::new(),
183
338
            skew_limit: None,
184
338
        }
185
338
    }
186

            
187
    /// Add `id` to the list of authorities that this request should
188
    /// say we believe in.
189
1
    pub fn push_authority_id(&mut self, id: RsaIdentity) {
190
1
        self.authority_ids.push(id);
191
1
    }
192

            
193
    /// Add `d` to the list of consensus digests this request should
194
    /// say we already have.
195
85
    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
196
85
        self.last_consensus_sha3_256.push(d);
197
85
    }
198

            
199
    /// Set the publication time we should say we have for our last
200
    /// consensus to `when`.
201
169
    pub fn set_last_consensus_date(&mut self, when: SystemTime) {
202
169
        self.last_consensus_published = Some(when);
203
169
    }
204

            
205
    /// Return a slice of the consensus digests that we're saying we
206
    /// already have.
207
169
    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
208
169
        self.last_consensus_sha3_256.iter()
209
169
    }
210

            
211
    /// Return an iterator of the authority identities that this request
212
    /// is saying we believe in.
213
1
    pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
214
1
        self.authority_ids.iter()
215
1
    }
216

            
217
    /// Return the date we're reporting for our most recent consensus.
218
339
    pub fn last_consensus_date(&self) -> Option<SystemTime> {
219
339
        self.last_consensus_published
220
339
    }
221

            
222
    /// Tell the directory client that we should abort the request early if the
223
    /// directory's clock skew exceeds certain limits.
224
    ///
225
    /// The `max_fast` parameter is the most fast that we're willing to be with
226
    /// respect to the directory (or in other words, the most slow that we're
227
    /// willing to let the directory be with respect to us).
228
    ///
229
    /// The `max_slow` parameter is the most _slow_ that we're willing to be with
230
    /// respect to the directory ((or in other words, the most slow that we're
231
    /// willing to let the directory be with respect to us).
232
168
    pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
233
168
        self.skew_limit = Some(SkewLimit { max_fast, max_slow });
234
168
    }
235
}
236

            
237
/// Convert a list of digests in some format to a string, for use in a request
238
///
239
/// The digests `DL` will be sorted, converted to strings with `EF`,
240
/// separated with `sep`, and returned as an fresh `String`.
241
///
242
/// If the digests list is empty, returns None instead.
243
//
244
// In principle this ought to be doable with much less allocating,
245
// starting with hex::encode etc.
246
101
fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
247
101
where
248
101
    DL: IntoIterator<Item = &'d D> + 'd,
249
101
    D: PartialOrd + Ord + 'd,
250
101
    EF: Fn(&'d D) -> String,
251
{
252
101
    let mut digests = digests.into_iter().collect_vec();
253
101
    if digests.is_empty() {
254
86
        return None;
255
15
    }
256
15
    digests.sort_unstable();
257
15
    let ids = digests.into_iter().map(encode).map(Cow::Owned);
258
    // name collision with unstable Iterator::intersperse
259
    // https://github.com/rust-lang/rust/issues/48919
260
15
    let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
261
15
    Some(ids)
262
101
}
263

            
264
impl Default for ConsensusRequest {
265
2
    fn default() -> Self {
266
2
        Self::new(ConsensusFlavor::Microdesc)
267
2
    }
268
}
269

            
270
impl sealed::RequestableInner for ConsensusRequest {
271
44
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
272
        // Build the URL.
273
44
        let mut uri = "/tor/status-vote/current/consensus".to_string();
274
44
        match self.flavor {
275
42
            ConsensusFlavor::Plain => {}
276
2
            flav => {
277
2
                uri.push('-');
278
2
                uri.push_str(flav.name());
279
2
            }
280
        }
281
44
        let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
282
44
        if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
283
1
            // With authorities, "../consensus/<F1>+<F2>+<F3>"
284
1
            uri.push('/');
285
1
            uri.push_str(&ids);
286
43
        }
287
        // Without authorities, "../consensus-microdesc"
288

            
289
44
        let mut req = http::Request::builder().method("GET").uri(uri);
290
44
        req = add_common_headers(req, self.anonymized());
291

            
292
        // Possibly, add an if-modified-since header.
293
44
        if let Some(when) = self.last_consensus_date() {
294
1
            req = req.header(
295
1
                http::header::IF_MODIFIED_SINCE,
296
1
                httpdate::fmt_http_date(when),
297
1
            );
298
43
        }
299

            
300
        // Possibly, add an X-Or-Diff-From-Consensus header.
301
44
        if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
302
1
            req = req.header("X-Or-Diff-From-Consensus", &ids);
303
43
        }
304

            
305
44
        Ok(req.body(RequestBody::default())?)
306
44
    }
307

            
308
43
    fn partial_response_body_ok(&self) -> bool {
309
43
        false
310
43
    }
311

            
312
43
    fn max_response_len(&self) -> usize {
313
43
        (16 * 1024 * 1024) - 1
314
43
    }
315

            
316
    fn check_circuit_skip_optimization<'a>(
317
        &self,
318
        tunnel: &'a ClientDirTunnel,
319
    ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
320
        let skew_limit = self.skew_limit.clone();
321
        Box::pin(async move {
322
            use tor_proto::ClockSkew::*;
323
            // This is the clock skew _according to the directory_.
324
            let skew = tunnel.first_hop_clock_skew().await?;
325
            match (&skew_limit, &skew) {
326
                (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
327
                    Err(RequestError::TooMuchClockSkew)
328
                }
329
                (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
330
                    Err(RequestError::TooMuchClockSkew)
331
                }
332
                (_, _) => Ok(()),
333
            }
334
        })
335
    }
336

            
337
86
    fn anonymized(&self) -> AnonymizedRequest {
338
86
        AnonymizedRequest::Direct
339
86
    }
340
}
341

            
342
/// A request for one or more authority certificates.
343
#[derive(Debug, Clone, Default)]
344
pub struct AuthCertRequest {
345
    /// The identity/signing keys of the certificates we want.
346
    ids: Vec<AuthCertKeyIds>,
347
}
348

            
349
impl AuthCertRequest {
350
    /// Create a new request, asking for no authority certificates.
351
169
    pub fn new() -> Self {
352
169
        AuthCertRequest::default()
353
169
    }
354

            
355
    /// Add `ids` to the list of certificates we're asking for.
356
298
    pub fn push(&mut self, ids: AuthCertKeyIds) {
357
298
        self.ids.push(ids);
358
298
    }
359

            
360
    /// Return a list of the keys that we're asking for.
361
127
    pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
362
127
        self.ids.iter()
363
127
    }
364
}
365

            
366
impl sealed::RequestableInner for AuthCertRequest {
367
44
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
368
44
        if self.ids.is_empty() {
369
            return Err(RequestError::EmptyRequest);
370
44
        }
371
44
        let mut ids = self.ids.clone();
372
44
        ids.sort_unstable();
373

            
374
44
        let ids: Vec<String> = ids
375
44
            .iter()
376
172
            .map(|id| {
377
172
                format!(
378
                    "{}-{}",
379
172
                    hex::encode(id.id_fingerprint.as_bytes()),
380
172
                    hex::encode(id.sk_fingerprint.as_bytes())
381
                )
382
172
            })
383
44
            .collect();
384

            
385
44
        let uri = format!("/tor/keys/fp-sk/{}", ids.join("+"));
386

            
387
44
        let req = http::Request::builder().method("GET").uri(uri);
388
44
        let req = add_common_headers(req, self.anonymized());
389

            
390
44
        Ok(req.body(RequestBody::default())?)
391
44
    }
392

            
393
44
    fn partial_response_body_ok(&self) -> bool {
394
44
        self.ids.len() > 1
395
44
    }
396

            
397
43
    fn max_response_len(&self) -> usize {
398
        // TODO: Pick a more principled number; I just made this one up.
399
43
        self.ids.len().saturating_mul(16 * 1024)
400
43
    }
401

            
402
86
    fn anonymized(&self) -> AnonymizedRequest {
403
86
        AnonymizedRequest::Direct
404
86
    }
405
}
406

            
407
impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
408
2
    fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
409
2
        let mut req = Self::new();
410
3
        for i in iter {
411
3
            req.push(i);
412
3
        }
413
2
        req
414
2
    }
415
}
416

            
417
/// A request for one or more microdescriptors
418
#[derive(Debug, Clone, Default)]
419
pub struct MicrodescRequest {
420
    /// The SHA256 digests of the microdescriptors we want.
421
    digests: Vec<MdDigest>,
422
}
423

            
424
impl MicrodescRequest {
425
    /// Construct a request for no microdescriptors.
426
219
    pub fn new() -> Self {
427
219
        MicrodescRequest::default()
428
219
    }
429
    /// Add `d` to the list of microdescriptors we want to request.
430
42265
    pub fn push(&mut self, d: MdDigest) {
431
42265
        self.digests.push(d);
432
42265
    }
433

            
434
    /// Return a list of the microdescriptor digests that we're asking for.
435
43
    pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
436
43
        self.digests.iter()
437
43
    }
438
}
439

            
440
impl sealed::RequestableInner for MicrodescRequest {
441
10
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
442
13
        let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
443
10
        let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
444
10
            .ok_or(RequestError::EmptyRequest)?;
445
10
        let uri = format!("/tor/micro/d/{}", ids);
446
10
        let req = http::Request::builder().method("GET").uri(uri);
447

            
448
10
        let req = add_common_headers(req, self.anonymized());
449

            
450
10
        Ok(req.body(RequestBody::default())?)
451
10
    }
452

            
453
10
    fn partial_response_body_ok(&self) -> bool {
454
10
        self.digests.len() > 1
455
10
    }
456

            
457
9
    fn max_response_len(&self) -> usize {
458
        // TODO: Pick a more principled number; I just made this one up.
459
9
        self.digests.len().saturating_mul(8 * 1024)
460
9
    }
461

            
462
18
    fn anonymized(&self) -> AnonymizedRequest {
463
18
        AnonymizedRequest::Direct
464
18
    }
465
}
466

            
467
impl FromIterator<MdDigest> for MicrodescRequest {
468
13
    fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
469
13
        let mut req = Self::new();
470
1014
        for i in iter {
471
1014
            req.push(i);
472
1014
        }
473
13
        req
474
13
    }
475
}
476

            
477
/// A request for one, many or all router descriptors.
478
#[derive(Debug, Clone)]
479
#[cfg(feature = "routerdesc")]
480
pub struct RouterDescRequest {
481
    /// The descriptors to request.
482
    requested_descriptors: RequestedDescs,
483
}
484

            
485
/// Tracks the different router descriptor types.
486
#[derive(Debug, Clone)]
487
#[cfg(feature = "routerdesc")]
488
enum RequestedDescs {
489
    /// If this is set, we just ask for all the descriptors.
490
    All,
491
    /// A list of digests to download.
492
    Digests(Vec<RdDigest>),
493
}
494

            
495
#[cfg(feature = "routerdesc")]
496
// TODO: This is probably not a reasonable default.
497
impl Default for RouterDescRequest {
498
1
    fn default() -> Self {
499
1
        RouterDescRequest {
500
1
            requested_descriptors: RequestedDescs::Digests(Vec::new()),
501
1
        }
502
1
    }
503
}
504

            
505
#[cfg(feature = "routerdesc")]
506
impl RouterDescRequest {
507
    /// Construct a request for all router descriptors.
508
1
    pub fn all() -> Self {
509
1
        RouterDescRequest {
510
1
            requested_descriptors: RequestedDescs::All,
511
1
        }
512
1
    }
513
    /// Construct a new empty request.
514
    pub fn new() -> Self {
515
        RouterDescRequest::default()
516
    }
517
}
518

            
519
#[cfg(feature = "routerdesc")]
520
impl sealed::RequestableInner for RouterDescRequest {
521
3
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
522
3
        let mut uri = "/tor/server/".to_string();
523

            
524
3
        match self.requested_descriptors {
525
2
            RequestedDescs::Digests(ref digests) => {
526
2
                uri.push_str("d/");
527
2
                let ids = digest_list_stringify(digests, hex::encode, "+")
528
2
                    .ok_or(RequestError::EmptyRequest)?;
529
2
                uri.push_str(&ids);
530
            }
531
1
            RequestedDescs::All => {
532
1
                uri.push_str("all");
533
1
            }
534
        }
535

            
536
3
        let req = http::Request::builder().method("GET").uri(uri);
537
3
        let req = add_common_headers(req, self.anonymized());
538

            
539
3
        Ok(req.body(RequestBody::default())?)
540
3
    }
541

            
542
3
    fn partial_response_body_ok(&self) -> bool {
543
3
        match self.requested_descriptors {
544
2
            RequestedDescs::Digests(ref digests) => digests.len() > 1,
545
1
            RequestedDescs::All => true,
546
        }
547
3
    }
548

            
549
2
    fn max_response_len(&self) -> usize {
550
        // TODO: Pick a more principled number; I just made these up.
551
2
        match self.requested_descriptors {
552
1
            RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
553
1
            RequestedDescs::All => 64 * 1024 * 1024, // big but not impossible
554
        }
555
2
    }
556

            
557
3
    fn anonymized(&self) -> AnonymizedRequest {
558
3
        AnonymizedRequest::Direct
559
3
    }
560
}
561

            
562
#[cfg(feature = "routerdesc")]
563
impl FromIterator<RdDigest> for RouterDescRequest {
564
3
    fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
565
3
        let digests = iter.into_iter().collect();
566

            
567
3
        RouterDescRequest {
568
3
            requested_descriptors: RequestedDescs::Digests(digests),
569
3
        }
570
3
    }
571
}
572

            
573
/// A request for the descriptor of whatever relay we are making the request to
574
#[derive(Debug, Clone, Default)]
575
#[cfg(feature = "routerdesc")]
576
#[non_exhaustive]
577
pub struct RoutersOwnDescRequest {}
578

            
579
#[cfg(feature = "routerdesc")]
580
impl RoutersOwnDescRequest {
581
    /// Construct a new request.
582
    pub fn new() -> Self {
583
        RoutersOwnDescRequest::default()
584
    }
585
}
586

            
587
#[cfg(feature = "routerdesc")]
588
impl sealed::RequestableInner for RoutersOwnDescRequest {
589
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
590
        let uri = "/tor/server/authority";
591
        let req = http::Request::builder().method("GET").uri(uri);
592
        let req = add_common_headers(req, self.anonymized());
593

            
594
        Ok(req.body(RequestBody::default())?)
595
    }
596

            
597
    fn partial_response_body_ok(&self) -> bool {
598
        false
599
    }
600

            
601
    fn max_response_len(&self) -> usize {
602
        // As of 2026, directory authorities don't accept a routerdesc longer than
603
        // 20000 bytes.  This should be plenty for the foreseeable future.
604
        128 * 1024
605
    }
606

            
607
    fn anonymized(&self) -> AnonymizedRequest {
608
        AnonymizedRequest::Direct
609
    }
610
}
611

            
612
/// A request for one or many extra-infos.
613
///
614
/// <https://spec.torproject.org/dir-spec/general-use-http-urls.html>
615
/// (search in page for "extra-info")
616
#[derive(Debug, Clone)]
617
#[cfg(feature = "routerdesc")]
618
pub struct ExtraInfoRequest {
619
    /// The extra-infos to request.
620
    requested_extra_infos: RequestedExtraInfos,
621
}
622

            
623
/// Which extra-info documents to download.
624
///
625
/// Currently only a subset of the available URLs are supported.
626
#[derive(Debug, Clone)]
627
#[cfg(feature = "routerdesc")]
628
#[non_exhaustive]
629
enum RequestedExtraInfos {
630
    /// Just ask for all the extra-infos.
631
    ///
632
    /// `http://<hostname>/tor/extra/all`
633
    All,
634
    /// Download extra-infos with these SHA-1 digests.
635
    ///
636
    /// `http://<hostname>/tor/extra/d/...`
637
    Digests(Vec<ExtraInfoDigest>),
638
}
639

            
640
#[cfg(feature = "routerdesc")]
641
impl Default for ExtraInfoRequest {
642
    // TODO: This is probably not a reasonable default.
643
    fn default() -> Self {
644
        Self {
645
            requested_extra_infos: RequestedExtraInfos::Digests(Vec::new()),
646
        }
647
    }
648
}
649

            
650
#[cfg(feature = "routerdesc")]
651
impl ExtraInfoRequest {
652
    /// Construct a request for all extra-infos.
653
1
    pub fn all() -> Self {
654
1
        Self {
655
1
            requested_extra_infos: RequestedExtraInfos::All,
656
1
        }
657
1
    }
658
    /// Construct a new empty request.
659
    pub fn new() -> Self {
660
        Self::default()
661
    }
662
}
663

            
664
#[cfg(feature = "routerdesc")]
665
impl sealed::RequestableInner for ExtraInfoRequest {
666
2
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
667
2
        let mut uri = "/tor/extra/".to_string();
668

            
669
2
        match &self.requested_extra_infos {
670
1
            RequestedExtraInfos::All => uri.push_str("all"),
671
1
            RequestedExtraInfos::Digests(digests) => {
672
1
                uri.push_str("d/");
673
1
                let ids = digest_list_stringify(digests, hex::encode_upper, "+")
674
1
                    .ok_or(RequestError::EmptyRequest)?;
675
1
                uri.push_str(&ids);
676
            }
677
        }
678

            
679
2
        let req = http::Request::builder().method("GET").uri(uri);
680
2
        let req = add_common_headers(req, self.anonymized());
681
2
        Ok(req.body(RequestBody::default())?)
682
2
    }
683

            
684
    fn partial_response_body_ok(&self) -> bool {
685
        match &self.requested_extra_infos {
686
            RequestedExtraInfos::Digests(digests) => digests.len() > 1,
687
            RequestedExtraInfos::All => true,
688
        }
689
    }
690

            
691
    fn max_response_len(&self) -> usize {
692
        // TODO torspec#392: Pick more principled size limits.
693
        // These were copied from the RouterDescRequest impl and doubled.
694
        match &self.requested_extra_infos {
695
            RequestedExtraInfos::Digests(digests) => digests.len().saturating_mul(16 * 1024),
696
            RequestedExtraInfos::All => 128 * 1024 * 1024,
697
        }
698
    }
699

            
700
2
    fn anonymized(&self) -> AnonymizedRequest {
701
2
        AnonymizedRequest::Direct
702
2
    }
703
}
704

            
705
#[cfg(feature = "routerdesc")]
706
impl FromIterator<ExtraInfoDigest> for ExtraInfoRequest {
707
1
    fn from_iter<T: IntoIterator<Item = ExtraInfoDigest>>(iter: T) -> Self {
708
1
        Self {
709
1
            requested_extra_infos: RequestedExtraInfos::Digests(iter.into_iter().collect()),
710
1
        }
711
1
    }
712
}
713

            
714
/// A request to download a hidden service descriptor
715
///
716
/// rend-spec-v3 2.2.6
717
#[derive(Debug, Clone)]
718
#[cfg(feature = "hs-client")]
719
pub struct HsDescDownloadRequest {
720
    /// What hidden service?
721
    hsid: HsBlindId,
722
    /// What's the largest acceptable response length?
723
    max_len: usize,
724
}
725

            
726
#[cfg(feature = "hs-client")]
727
impl HsDescDownloadRequest {
728
    /// Construct a request for a single onion service descriptor by its
729
    /// blinded ID.
730
295
    pub fn new(hsid: HsBlindId) -> Self {
731
        /// Default maximum length to use when we have no other information.
732
        const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
733
295
        HsDescDownloadRequest {
734
295
            hsid,
735
295
            max_len: DEFAULT_HSDESC_MAX_LEN,
736
295
        }
737
295
    }
738

            
739
    /// Set the maximum acceptable response length.
740
294
    pub fn set_max_len(&mut self, max_len: usize) {
741
294
        self.max_len = max_len;
742
294
    }
743
}
744

            
745
#[cfg(feature = "hs-client")]
746
impl sealed::RequestableInner for HsDescDownloadRequest {
747
589
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
748
589
        let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
749
        // We hardcode version 3 here; if we ever have a v4 onion service
750
        // descriptor, it will need a different kind of Request.
751
589
        let uri = format!("/tor/hs/3/{}", hsid);
752
589
        let req = http::Request::builder().method("GET").uri(uri);
753
589
        let req = add_common_headers(req, self.anonymized());
754
589
        Ok(req.body(RequestBody::default())?)
755
589
    }
756

            
757
295
    fn partial_response_body_ok(&self) -> bool {
758
295
        false
759
295
    }
760

            
761
295
    fn max_response_len(&self) -> usize {
762
295
        self.max_len
763
295
    }
764

            
765
883
    fn anonymized(&self) -> AnonymizedRequest {
766
883
        AnonymizedRequest::Anonymized
767
883
    }
768
}
769

            
770
/// Define a request type for uploading a document.
771
#[allow(unused)]
772
macro_rules! upload_request {
773
    {
774
        $(
775
            $(#[$m:meta])*
776
            pub struct $t:ident (
777
                // Does this request require anonymity?
778
                // This should be a variant of AnonymizedRequest.
779
                $anonymity:ident,
780
                // To what URI at the server should the document be posted?
781
                // This should begin with "/tor">
782
                $uri:expr,
783
                // Total maximum length of the _response_ that we'll accept.
784
                // If the response is larger than this, we'll abort the request.
785
                //
786
                // Note that expected response body for a POST is typically _empty_,
787
                // but this needs to be nonzero in order to account for
788
                // the status line and headers.
789
                $max_response_len:expr
790
            )
791
        );*
792
        $(;)?
793
    } => {
794
        $(
795
            $(#[$m])*
796
            #[derive(Clone, Debug)]
797
            pub struct $t(Arc<str>);
798

            
799
            impl $t {
800
                /// Create a new upload request
801
3696
                pub fn new(document: Arc<str>) -> Self {
802
3696
                    Self(document)
803
3696
                }
804
            }
805

            
806
            impl sealed::RequestableInner for $t {
807
3696
                fn make_request(&self) -> Result<http::Request<RequestBody>> {
808
                    /// The upload URI.
809
                    const URI: &str = $uri;
810

            
811
3696
                    let req = http::Request::builder().method("POST").uri(URI);
812
3696
                    let req = add_common_headers(req, self.anonymized());
813
3696
                    Ok(req.body(RequestBody::from(Arc::clone(&self.0)))?)
814
3696
                }
815

            
816
3696
                fn partial_response_body_ok(&self) -> bool {
817
3696
                    false
818
3696
                }
819

            
820
3696
                fn max_response_len(&self) -> usize {
821
                    $max_response_len
822
3696
                }
823

            
824
7392
                fn anonymized(&self) -> AnonymizedRequest {
825
7392
                    AnonymizedRequest::$anonymity
826
7392
                }
827
            }
828
         )*
829
    }
830
}
831

            
832
#[cfg(feature = "hs-service")]
833
upload_request! {
834

            
835
    /// A request to upload a hidden service descriptor
836
    ///
837
    /// rend-spec-v3 2.2.6
838
    pub struct HsDescUploadRequest(
839
        Anonymized,
840
        "/tor/hs/3/publish",
841
        // A real Tor POST _response_ will always be less than this length, which
842
        // will fit into 3 DATA messages at most. (The reply will be a single
843
        // HTTP line, followed by a Date header.)
844
        // Do not increase this limit without thinking about side channels!
845
        //
846
        // (Note that the body will be empty, but we need to allow some space
847
        // to account for the status line and headers.)
848
        1024
849
    )
850
}
851

            
852
#[cfg(feature = "relay")]
853
upload_request! {
854
    /// A request to upload a router descriptor and optional extra-info document.
855
    pub struct UploadRouterDesc(Direct, "/tor/", 4096);
856

            
857
}
858

            
859
/// Encodings that all Tor clients support.
860
const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
861

            
862
/// List all the encodings we accept
863
111
fn all_encodings() -> String {
864
    #[allow(unused_mut)]
865
111
    let mut encodings = UNIVERSAL_ENCODINGS.to_string();
866
    #[cfg(feature = "xz")]
867
111
    {
868
111
        encodings += ", x-tor-lzma";
869
111
    }
870
    #[cfg(feature = "zstd")]
871
111
    {
872
111
        encodings += ", x-zstd";
873
111
    }
874

            
875
111
    encodings
876
111
}
877

            
878
/// Add commonly used headers to the HTTP request.
879
///
880
/// (Right now, this is only Accept-Encoding.)
881
4388
fn add_common_headers(
882
4388
    req: http::request::Builder,
883
4388
    anon: AnonymizedRequest,
884
4388
) -> http::request::Builder {
885
    // TODO: gzip, brotli
886
4388
    match anon {
887
        AnonymizedRequest::Anonymized => {
888
            // In an anonymized request, we do not admit to supporting any
889
            // encoding besides those that are always available.
890
4285
            req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
891
        }
892
103
        AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
893
    }
894
4388
}
895

            
896
#[cfg(test)]
897
mod test {
898
    // @@ begin test lint list maintained by maint/add_warning @@
899
    #![allow(clippy::bool_assert_comparison)]
900
    #![allow(clippy::clone_on_copy)]
901
    #![allow(clippy::dbg_macro)]
902
    #![allow(clippy::mixed_attributes_style)]
903
    #![allow(clippy::print_stderr)]
904
    #![allow(clippy::print_stdout)]
905
    #![allow(clippy::single_char_pattern)]
906
    #![allow(clippy::unwrap_used)]
907
    #![allow(clippy::unchecked_time_subtraction)]
908
    #![allow(clippy::useless_vec)]
909
    #![allow(clippy::needless_pass_by_value)]
910
    #![allow(clippy::string_slice)] // See arti#2571
911
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
912
    use super::sealed::RequestableInner;
913
    use super::*;
914
    use web_time_compat::SystemTimeExt;
915

            
916
    #[test]
917
    fn test_md_request() -> Result<()> {
918
        let d1 = b"This is a testing digest. it isn";
919
        let d2 = b"'t actually SHA-256.............";
920

            
921
        let mut req = MicrodescRequest::default();
922
        req.push(*d1);
923
        assert!(!req.partial_response_body_ok());
924
        req.push(*d2);
925
        assert!(req.partial_response_body_ok());
926
        assert_eq!(req.max_response_len(), 16 << 10);
927

            
928
        let req = crate::util::request_to_string(&req.make_request()?);
929

            
930
        assert_eq!(
931
            req,
932
            format!(
933
                "GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
934
                all_encodings()
935
            )
936
        );
937

            
938
        // Try it with FromIterator, and use some accessors.
939
        let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
940
        let ds: Vec<_> = req2.digests().collect();
941
        assert_eq!(ds, vec![d1, d2]);
942
        let req2 = crate::util::request_to_string(&req2.make_request()?);
943
        assert_eq!(req, req2);
944

            
945
        Ok(())
946
    }
947

            
948
    #[test]
949
    fn test_cert_request() -> Result<()> {
950
        let d1 = b"This is a testing dn";
951
        let d2 = b"'t actually SHA-256.";
952
        let key1 = AuthCertKeyIds {
953
            id_fingerprint: (*d1).into(),
954
            sk_fingerprint: (*d2).into(),
955
        };
956

            
957
        let d3 = b"blah blah blah 1 2 3";
958
        let d4 = b"I like pizza from Na";
959
        let key2 = AuthCertKeyIds {
960
            id_fingerprint: (*d3).into(),
961
            sk_fingerprint: (*d4).into(),
962
        };
963

            
964
        let mut req = AuthCertRequest::default();
965
        req.push(key1);
966
        assert!(!req.partial_response_body_ok());
967
        req.push(key2);
968
        assert!(req.partial_response_body_ok());
969
        assert_eq!(req.max_response_len(), 32 << 10);
970

            
971
        let keys: Vec<_> = req.keys().collect();
972
        assert_eq!(keys, vec![&key1, &key2]);
973

            
974
        let req = crate::util::request_to_string(&req.make_request()?);
975

            
976
        assert_eq!(
977
            req,
978
            format!(
979
                "GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
980
                all_encodings()
981
            )
982
        );
983

            
984
        let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
985
        let req2 = crate::util::request_to_string(&req2.make_request()?);
986
        assert_eq!(req, req2);
987

            
988
        Ok(())
989
    }
990

            
991
    #[test]
992
    fn test_consensus_request() -> Result<()> {
993
        let d1 = RsaIdentity::from_bytes(
994
            &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
995
        )
996
        .unwrap();
997

            
998
        let d2 = b"blah blah blah 12 blah blah blah";
999
        let d3 = SystemTime::get();
        let mut req = ConsensusRequest::default();
        let when = httpdate::fmt_http_date(d3);
        req.push_authority_id(d1);
        req.push_old_consensus_digest(*d2);
        req.set_last_consensus_date(d3);
        assert!(!req.partial_response_body_ok());
        assert_eq!(req.max_response_len(), (16 << 20) - 1);
        assert_eq!(req.old_consensus_digests().next(), Some(d2));
        assert_eq!(req.authority_ids().next(), Some(&d1));
        assert_eq!(req.last_consensus_date(), Some(d3));
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/status-vote/current/consensus-microdesc/03479e93ebf3ff2c58c1c9dbf2de9de9c2801b3e HTTP/1.0\r\naccept-encoding: {}\r\nif-modified-since: {}\r\nx-or-diff-from-consensus: 626c616820626c616820626c616820313220626c616820626c616820626c6168\r\n\r\n",
                all_encodings(),
                when
            )
        );
        // Request without authorities
        let req = ConsensusRequest::default();
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        Ok(())
    }
    #[test]
    #[cfg(feature = "routerdesc")]
    fn test_rd_request_all() -> Result<()> {
        let req = RouterDescRequest::all();
        assert!(req.partial_response_body_ok());
        assert_eq!(req.max_response_len(), 1 << 26);
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        Ok(())
    }
    #[test]
    #[cfg(feature = "routerdesc")]
    fn test_rd_request() -> Result<()> {
        let d1 = b"at some point I got ";
        let d2 = b"of writing in hex...";
        let mut req = RouterDescRequest::default();
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
            digests.push(*d1);
        }
        assert!(!req.partial_response_body_ok());
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
            digests.push(*d2);
        }
        assert!(req.partial_response_body_ok());
        assert_eq!(req.max_response_len(), 16 << 10);
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        // Try it with FromIterator, and use some accessors.
        let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
        let ds: Vec<_> = match req2.requested_descriptors {
            RequestedDescs::Digests(ref digests) => digests.iter().collect(),
            RequestedDescs::All => Vec::new(),
        };
        assert_eq!(ds, vec![d1, d2]);
        let req2 = crate::util::request_to_string(&req2.make_request()?);
        assert_eq!(req, req2);
        Ok(())
    }
    #[test]
    #[cfg(feature = "routerdesc")]
    fn test_extra_info_request() -> Result<()> {
        let req = ExtraInfoRequest::from_iter([[0; 20], [1; 20], [2; 20]]);
        assert_eq!(
            crate::util::request_to_string(&req.make_request()?),
            format!(
                "GET /tor/extra/d/{}+{}+{} HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                hex::encode_upper([0; 20]),
                hex::encode_upper([1; 20]),
                hex::encode_upper([2; 20]),
                all_encodings()
            )
        );
        let req = ExtraInfoRequest::all();
        assert_eq!(
            crate::util::request_to_string(&req.make_request()?),
            format!(
                "GET /tor/extra/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        Ok(())
    }
    #[test]
    #[cfg(feature = "hs-client")]
    fn test_hs_desc_download_request() -> Result<()> {
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
        let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
        let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
        let hsid = HsBlindId::from(hsid);
        let req = HsDescDownloadRequest::new(hsid);
        assert!(!req.partial_response_body_ok());
        assert_eq!(req.max_response_len(), 50 * 1000);
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                UNIVERSAL_ENCODINGS
            )
        );
        Ok(())
    }
}