1
//! Implementation for onion service descriptors.
2
//!
3
//! An onion service descriptor is a document generated by an onion service and
4
//! uploaded to one or more HsDir nodes for clients to later download.  It tells
5
//! the onion service client where to find the current introduction points for
6
//! the onion service, and how to connect to them.
7
//!
8
//! An onion service descriptor is more complicated than most other
9
//! documentation types, because it is partially encrypted.
10

            
11
mod desc_enc;
12

            
13
#[cfg(feature = "hs-service")]
14
mod build;
15
mod inner;
16
mod middle;
17
mod outer;
18
pub mod pow;
19

            
20
pub use desc_enc::DecryptionError;
21
use tor_basic_utils::rangebounds::RangeBoundsExt;
22
use tor_error::internal;
23

            
24
use crate::{NetdocErrorKind as EK, Result};
25

            
26
use tor_checkable::signed::{self, SignatureGated};
27
use tor_checkable::timed::{self, TimerangeBound};
28
use tor_checkable::{SelfSigned, Timebound};
29
use tor_hscrypto::pk::{HsBlindId, HsClientDescEncKeypair, HsIntroPtSessionIdKey, HsSvcNtorKey};
30
use tor_hscrypto::{RevisionCounter, Subcredential};
31
use tor_linkspec::EncodedLinkSpec;
32
use tor_llcrypto::pk::curve25519;
33
use tor_units::IntegerMinutes;
34

            
35
use derive_builder::Builder;
36
use smallvec::SmallVec;
37

            
38
use std::num::NonZeroU8;
39
use std::result::Result as StdResult;
40
use std::time::SystemTime;
41

            
42
pub use {inner::HsDescInner, middle::HsDescMiddle, outer::HsDescOuter};
43

            
44
#[cfg(feature = "hs-service")]
45
pub use build::{HsDescBuilder, create_desc_sign_key_cert};
46

            
47
/// Metadata about an onion service descriptor, as stored at an HsDir.
48
///
49
/// This object is parsed from the outermost document of an onion service
50
/// descriptor, and used on the HsDir to maintain its index.  It does not
51
/// include the inner documents' information about introduction points, since the
52
/// HsDir cannot decrypt those without knowing the onion service's un-blinded
53
/// identity.
54
///
55
/// The HsDir caches this value, along with the original text of the descriptor.
56
#[cfg(feature = "hs-dir")]
57
#[allow(dead_code)] // TODO RELAY: Remove this.
58
pub struct StoredHsDescMeta {
59
    /// The blinded onion identity for this descriptor.  (This is the only
60
    /// identity that the HsDir knows.)
61
    blinded_id: HsBlindId,
62

            
63
    /// Information about the expiration and revision counter for this
64
    /// descriptor.
65
    idx_info: IndexInfo,
66
}
67

            
68
/// An unchecked StoredHsDescMeta: parsed, but not checked for liveness or validity.
69
#[cfg(feature = "hs-dir")]
70
pub type UncheckedStoredHsDescMeta =
71
    signed::SignatureGated<timed::TimerangeBound<StoredHsDescMeta>>;
72

            
73
/// Information about how long to hold a given onion service descriptor, and
74
/// when to replace it.
75
#[derive(Debug, Clone)]
76
struct IndexInfo {
77
    /// The lifetime in minutes that this descriptor should be held after it is
78
    /// received.
79
    #[allow(dead_code)] // TODO RELAY: Remove this if there turns out to be no need for it.
80
    lifetime: IntegerMinutes<u16>,
81
    /// The expiration time on the `descriptor-signing-key-cert` included in this
82
    /// descriptor.
83
    #[allow(dead_code)] // TODO RELAY: Remove this if there turns out to be no need for it.
84
    signing_cert_expires: SystemTime,
85
    /// The revision counter on this descriptor: higher values should replace
86
    /// older ones.
87
    revision: RevisionCounter,
88
}
89

            
90
/// A decrypted, decoded onion service descriptor.
91
///
92
/// This object includes information from both the outer (plaintext) document of
93
/// the descriptor, and the inner (encrypted) documents.  It tells the client the
94
/// information it needs to contact the onion service, including necessary
95
/// introduction points and public keys.
96
#[derive(Debug, Clone)]
97
pub struct HsDesc {
98
    /// Information about the expiration and revision counter for this
99
    /// descriptor.
100
    idx_info: IndexInfo,
101

            
102
    /// The list of authentication types that this onion service supports.
103
    auth_required: Option<SmallVec<[IntroAuthType; 2]>>,
104

            
105
    /// If true, this a "single onion service" and is not trying to keep its own location private.
106
    is_single_onion_service: bool,
107

            
108
    /// One or more introduction points used to contact the onion service.
109
    intro_points: Vec<IntroPointDesc>,
110

            
111
    /// A list of offered proof-of-work parameters, at most one per type.
112
    pow_params: pow::PowParamSet,
113

            
114
    /// A specified sendme increment and sub protocol capability list, if they were provided.
115
    ///
116
    /// Note that for historical reasons the protocol capabilities here are treated separately
117
    /// from those in `protos`.
118
    pub(super) flow_control: Option<(tor_protover::Protocols, NonZeroU8)>,
119

            
120
    /// A list of subprotocol capabilities advertised by the onion service.
121
    protos: tor_protover::Protocols,
122
}
123

            
124
/// A type of authentication that is required when introducing to an onion
125
/// service.
126
#[non_exhaustive]
127
#[derive(Debug, Clone, Copy, Eq, PartialEq, derive_more::Display)]
128
pub enum IntroAuthType {
129
    /// Ed25519 authentication is required.
130
    #[display("ed25519")]
131
    Ed25519,
132
}
133

            
134
/// Information in an onion service descriptor about a single
135
/// introduction point.
136
#[derive(Debug, Clone, amplify::Getters, Builder)]
137
#[builder(pattern = "owned")] // mirrors HsDescBuilder
138
pub struct IntroPointDesc {
139
    /// The list of link specifiers needed to extend a circuit to the introduction point.
140
    ///
141
    /// These can include public keys and network addresses.
142
    ///
143
    /// Note that we do not enforce the presence of any link specifiers here;
144
    /// this means that you can't assume that an `IntroPointDesc` is a meaningful
145
    /// `ChanTarget` without some processing.
146
    //
147
    // The builder setter takes a `Vec` directly.  This seems fine.
148
    #[getter(skip)]
149
    link_specifiers: Vec<EncodedLinkSpec>,
150

            
151
    /// The key to be used to extend a circuit _to the introduction point_, using the
152
    /// ntor or ntor3 handshakes.  (`KP_ntor`)
153
    #[builder(setter(name = "ipt_kp_ntor"))] // TODO rename the internal variable too
154
    ipt_ntor_key: curve25519::PublicKey,
155

            
156
    /// The key to be used to identify the onion service at this introduction point.
157
    /// (`KP_hs_ipt_sid`)
158
    #[builder(setter(name = "kp_hs_ipt_sid"))] // TODO rename the internal variable too
159
    ipt_sid_key: HsIntroPtSessionIdKey,
160

            
161
    /// `KP_hss_ntor`, the key used to encrypt a handshake _to the onion
162
    /// service_ when using this introduction point.
163
    ///
164
    /// The onion service uses a separate key of this type with each
165
    /// introduction point as part of its strategy for preventing replay
166
    /// attacks.
167
    #[builder(setter(name = "kp_hss_ntor"))] // TODO rename the internal variable too
168
    svc_ntor_key: HsSvcNtorKey,
169
}
170

            
171
/// An onion service after it has been parsed by the client, but not yet decrypted.
172
pub struct EncryptedHsDesc {
173
    /// The un-decoded outer document of our onion service descriptor.
174
    outer_doc: outer::HsDescOuter,
175
}
176

            
177
/// An unchecked HsDesc: parsed, but not checked for liveness or validity.
178
pub type UncheckedEncryptedHsDesc = signed::SignatureGated<timed::TimerangeBound<EncryptedHsDesc>>;
179

            
180
#[cfg(feature = "hs-dir")]
181
impl StoredHsDescMeta {
182
    // TODO relay: needs accessor functions too.  (Let's not use public fields; we
183
    // are likely to want to mess with the repr of these types.)
184

            
185
    /// Parse the outermost layer of the descriptor in `input`, and return the
186
    /// resulting metadata (if possible).
187
2
    pub fn parse(input: &str) -> Result<UncheckedStoredHsDescMeta> {
188
2
        let outer = outer::HsDescOuter::parse(input)?;
189
3
        Ok(outer.dangerously_map(|timebound| {
190
2
            timebound.dangerously_map(|outer| StoredHsDescMeta::from_outer_doc(&outer))
191
2
        }))
192
2
    }
193
}
194

            
195
impl HsDesc {
196
    /// Parse the outermost document of the descriptor in `input`, and validate
197
    /// that its identity is consistent with `blinded_onion_id`.
198
    ///
199
    /// On success, the caller will get a wrapped object which they must
200
    /// validate and then decrypt.
201
    ///
202
    /// Use [`HsDesc::parse_decrypt_validate`] if you just need an [`HsDesc`] and don't want to
203
    /// handle the validation/decryption of the wrapped object yourself.
204
    ///
205
    /// # Example
206
    /// ```
207
    /// # use hex_literal::hex;
208
    /// # use tor_checkable::{SelfSigned, Timebound};
209
    /// # use tor_netdoc::doc::hsdesc::HsDesc;
210
    /// # use tor_netdoc::Error;
211
    /// #
212
    /// # let unparsed_desc: &str = include_str!("../../testdata/hsdesc1.txt");
213
    /// # let blinded_id =
214
    /// #    hex!("43cc0d62fc6252f578705ca645a46109e265290343b1137e90189744b20b3f2d").into();
215
    /// # let subcredential =
216
    /// #    hex!("78210A0D2C72BB7A0CAF606BCD938B9A3696894FDDDBC3B87D424753A7E3DF37").into();
217
    /// # let timestamp = humantime::parse_rfc3339("2023-01-23T15:00:00Z").unwrap();
218
    /// #
219
    /// // Parse the descriptor
220
    /// let unchecked_desc = HsDesc::parse(unparsed_desc, &blinded_id)?;
221
    /// // Validate the signature and timeliness of the outer document
222
    /// let checked_desc = unchecked_desc
223
    ///     .check_signature()?
224
    ///     .check_valid_at(&timestamp)?;
225
    /// // Decrypt the outer and inner layers of the descriptor
226
    /// let unchecked_decrypted_desc = checked_desc.decrypt(&subcredential, None)?;
227
    /// // Validate the signature and timeliness of the inner document
228
    /// let hsdesc = unchecked_decrypted_desc
229
    ///     .check_valid_at(&timestamp)?
230
    ///     .check_signature()?;
231
    /// # Ok::<(), anyhow::Error>(())
232
    /// ```
233
688
    pub fn parse(
234
688
        input: &str,
235
688
        // We don't actually need this to parse the HsDesc, but we _do_ need it to prevent
236
688
        // a nasty pattern where we forget to check that we got the right one.
237
688
        blinded_onion_id: &HsBlindId,
238
688
    ) -> Result<UncheckedEncryptedHsDesc> {
239
688
        let outer = outer::HsDescOuter::parse(input)?;
240
688
        let mut id_matches = false;
241
707
        let result = outer.dangerously_map(|timebound| {
242
688
            timebound.dangerously_map(|outer| {
243
688
                id_matches = blinded_onion_id == &outer.blinded_id();
244
688
                EncryptedHsDesc::from_outer_doc(outer)
245
688
            })
246
688
        });
247
688
        if !id_matches {
248
2
            return Err(
249
2
                EK::BadObjectVal.with_msg("onion service descriptor did not have the expected ID")
250
2
            );
251
686
        }
252

            
253
686
        Ok(result)
254
688
    }
255

            
256
    /// A convenience function for parsing, decrypting and validating HS descriptors.
257
    ///
258
    /// This function:
259
    ///   * parses the outermost document of the descriptor in `input`, and validates that its
260
    ///     identity is consistent with `blinded_onion_id`.
261
    ///   * decrypts both layers of encryption in the onion service descriptor. If `hsc_desc_enc`
262
    ///     is provided, we use it to decrypt the inner encryption layer;
263
    ///     otherwise, we require that
264
    ///     the inner document is encrypted using the "no restricted discovery" method.
265
    ///   * checks if both layers are valid at the `valid_at` timestamp
266
    ///   * validates the signatures on both layers
267
    ///
268
    /// Returns an error if the descriptor cannot be parsed, or if one of the validation steps
269
    /// fails.
270
416
    pub fn parse_decrypt_validate(
271
416
        input: &str,
272
416
        blinded_onion_id: &HsBlindId,
273
416
        valid_at: SystemTime,
274
416
        subcredential: &Subcredential,
275
416
        hsc_desc_enc: Option<&HsClientDescEncKeypair>,
276
416
    ) -> StdResult<TimerangeBound<Self>, HsDescError> {
277
        use HsDescError as E;
278
416
        let unchecked_desc = Self::parse(input, blinded_onion_id)
279
416
            .map_err(E::OuterParsing)?
280
416
            .check_signature()
281
416
            .map_err(|e| E::OuterValidation(e.into()))?;
282

            
283
416
        let (inner_desc, new_bounds) = {
284
            // We use is_valid_at and dangerously_into_parts instead of check_valid_at because we
285
            // need the time bounds of the outer layer (for computing the intersection with the
286
            // time bounds of the inner layer).
287
416
            unchecked_desc
288
416
                .is_valid_at(&valid_at)
289
416
                .map_err(|e| E::OuterValidation(e.into()))?;
290
            // It's safe to use dangerously_peek() as we've just checked if unchecked_desc is
291
            // valid at the current time
292
416
            let inner_timerangebound = unchecked_desc
293
416
                .dangerously_peek()
294
416
                .decrypt(subcredential, hsc_desc_enc)?;
295

            
296
416
            let new_bounds = unchecked_desc
297
416
                .intersect(&inner_timerangebound)
298
424
                .map(|(b1, b2)| (b1.cloned(), b2.cloned()));
299

            
300
416
            (inner_timerangebound, new_bounds)
301
        };
302

            
303
416
        let hsdesc = inner_desc
304
416
            .check_valid_at(&valid_at)
305
416
            .map_err(|e| E::InnerValidation(e.into()))?
306
416
            .check_signature()
307
416
            .map_err(|e| E::InnerValidation(e.into()))?;
308

            
309
        // If we've reached this point, it means the descriptor is valid at specified time. This
310
        // means the time bounds of the two layers definitely intersect, so new_bounds **must** be
311
        // Some. It is a bug if new_bounds is None.
312
416
        let new_bounds = new_bounds
313
416
            .ok_or_else(|| internal!("failed to compute TimerangeBounds for a valid descriptor"))?;
314

            
315
416
        Ok(TimerangeBound::new(hsdesc, new_bounds))
316
416
    }
317

            
318
    /// One or more introduction points used to contact the onion service.
319
    ///
320
    /// Always returns at least one introduction point,
321
    /// and never more than [`NUM_INTRO_POINT_MAX`](tor_hscrypto::NUM_INTRO_POINT_MAX).
322
    /// (Descriptors which have fewer or more are dealt with during parsing.)
323
    ///
324
    /// Accessor function.
325
    //
326
    // TODO: We'd like to derive this, but amplify::Getters  would give us &Vec<>,
327
    // not &[].
328
    //
329
    // Perhaps someday we can use derive_deftly, or add as_ref() support?
330
1042
    pub fn intro_points(&self) -> &[IntroPointDesc] {
331
1042
        &self.intro_points
332
1042
    }
333

            
334
    /// Return true if this onion service claims to be a non-anonymous "single
335
    /// onion service".
336
    ///
337
    /// (We should always anonymize our own connection to an onion service.)
338
4420
    pub fn is_single_onion_service(&self) -> bool {
339
4420
        self.is_single_onion_service
340
4420
    }
341

            
342
    /// Return true if this onion service claims that it needs user authentication
343
    /// of some kind in its INTRODUCE messages.
344
    ///
345
    /// (Arti does not currently support sending this kind of authentication.)
346
    pub fn requires_intro_authentication(&self) -> bool {
347
        self.auth_required.is_some()
348
    }
349

            
350
    /// Get a list of offered proof-of-work parameters, at most one per type.
351
780
    pub fn pow_params(&self) -> &[pow::PowParams] {
352
780
        self.pow_params.slice()
353
780
    }
354

            
355
    /// Return the revision counter of this descriptor
356
1040
    pub fn revision(&self) -> RevisionCounter {
357
1040
        self.idx_info.revision
358
1040
    }
359

            
360
    /// Return the set of protocol capabilities declared in this descriptor.
361
4420
    pub fn declared_capabilities(&self) -> &tor_protover::Protocols {
362
4420
        &self.protos
363
4420
    }
364

            
365
    /// Return the flow control protocols,
366
    /// and the `sendme_inc` value declared for congestion control in this descriptor,
367
    /// if they were present.
368
4784
    pub fn flow_control(&self) -> Option<(tor_protover::Protocols, NonZeroU8)> {
369
4876
        self.flow_control.as_ref().map(|(p, inc)| (p.clone(), *inc))
370
4784
    }
371
}
372

            
373
/// An error returned by [`HsDesc::parse_decrypt_validate`], indicating what
374
/// kind of failure prevented us from validating an onion service descriptor.
375
///
376
/// This is distinct from [`tor_netdoc::Error`](crate::Error) so that we can
377
/// tell errors that could be the HsDir's fault from those that are definitely
378
/// protocol violations by the onion service.
379
#[derive(Clone, Debug, thiserror::Error)]
380
#[non_exhaustive]
381
pub enum HsDescError {
382
    /// An outer object failed parsing: the HsDir should probably have
383
    /// caught this, and not given us this HsDesc.
384
    ///
385
    /// (This can be an innocent error if we happen to know about restrictions
386
    /// that the HsDir does not).
387
    #[error("Parsing failure on outer layer of an onion service descriptor.")]
388
    OuterParsing(#[source] crate::Error),
389

            
390
    /// An outer object failed validation: the HsDir should probably have
391
    /// caught this, and not given us this HsDesc.
392
    ///
393
    /// (This can happen erroneously if we think that something is untimely but
394
    /// the HSDir's clock is slightly different, or _was_ different when it
395
    /// decided to give us this object.)
396
    #[error("Validation failure on outer layer of an onion service descriptor.")]
397
    OuterValidation(#[source] crate::Error),
398

            
399
    /// Decrypting the inner layer failed because we need to have a decryption key,
400
    /// but we didn't provide one.
401
    ///
402
    /// This is probably our fault.
403
    #[error("Decryption failure on onion service descriptor: missing decryption key")]
404
    MissingDecryptionKey,
405

            
406
    /// Decrypting the inner layer failed because, although we provided a key,
407
    /// we did not provide the key we need to decrypt it.
408
    ///
409
    /// This is probably our fault.
410
    #[error("Decryption failure on onion service descriptor: incorrect decryption key")]
411
    WrongDecryptionKey,
412

            
413
    /// Decrypting the inner or middle layer failed because of an issue with the
414
    /// decryption itself.
415
    ///
416
    /// This is the onion service's fault.
417
    #[error("Decryption failure on onion service descriptor: could not decrypt")]
418
    DecryptionFailed,
419

            
420
    /// We failed to parse something cryptographic in an inner layer of the
421
    /// onion service descriptor.
422
    ///
423
    /// This is definitely the onion service's fault.
424
    #[error("Parsing failure on inner layer of an onion service descriptor")]
425
    InnerParsing(#[source] crate::Error),
426

            
427
    /// We failed to validate something cryptographic in an inner layer of the
428
    /// onion service descriptor.
429
    ///
430
    /// This is definitely the onion service's fault.
431
    #[error("Validation failure on inner layer of an onion service descriptor")]
432
    InnerValidation(#[source] crate::Error),
433

            
434
    /// We encountered an internal error.
435
    #[error("Internal error: {0}")]
436
    Bug(#[from] tor_error::Bug),
437
}
438

            
439
impl tor_error::HasKind for HsDescError {
440
    fn kind(&self) -> tor_error::ErrorKind {
441
        use HsDescError as E;
442
        use tor_error::ErrorKind as EK;
443
        match self {
444
            E::OuterParsing(_) | E::OuterValidation(_) => EK::TorProtocolViolation,
445
            E::MissingDecryptionKey => EK::OnionServiceMissingClientAuth,
446
            E::WrongDecryptionKey => EK::OnionServiceWrongClientAuth,
447
            E::DecryptionFailed | E::InnerParsing(_) | E::InnerValidation(_) => {
448
                EK::OnionServiceProtocolViolation
449
            }
450
            E::Bug(e) => e.kind(),
451
        }
452
    }
453
}
454

            
455
impl HsDescError {
456
    /// Return true if this error is one that we should report as a suspicious event.
457
    ///
458
    /// Note that this is a defense-in-depth check
459
    /// for resisting descriptor-length inflation attacks:
460
    /// Our limits on total download size and/or total cell counts are the defense
461
    /// that really matters.
462
    /// (See prop360 for more information.)
463
    pub fn should_report_as_suspicious(&self) -> bool {
464
        use crate::NetdocErrorKind as EK;
465
        use HsDescError as E;
466
        #[allow(clippy::match_like_matches_macro)]
467
        match self {
468
            E::OuterParsing(e) => match e.netdoc_error_kind() {
469
                EK::ExtraneousSpace => true,
470
                EK::WrongEndingToken => true,
471
                EK::MissingKeyword => true,
472
                _ => false,
473
            },
474
            E::OuterValidation(e) => match e.netdoc_error_kind() {
475
                EK::BadSignature => true,
476
                _ => false,
477
            },
478
            E::MissingDecryptionKey => false,
479
            E::WrongDecryptionKey => false,
480
            E::DecryptionFailed => false,
481
            E::InnerParsing(_) => false,
482
            E::InnerValidation(_) => false,
483
            E::Bug(_) => false,
484
        }
485
    }
486
}
487

            
488
impl IntroPointDesc {
489
    /// Start building a description of an intro point
490
624
    pub fn builder() -> IntroPointDescBuilder {
491
624
        IntroPointDescBuilder::default()
492
624
    }
493

            
494
    /// The list of link specifiers needed to extend a circuit to the introduction point.
495
    ///
496
    /// These can include public keys and network addresses.
497
    ///
498
    /// Accessor function.
499
    //
500
    // TODO: It would be better to derive this too, but this accessor needs to
501
    // return a slice; Getters can only give us a &Vec<> in this case.
502
2340
    pub fn link_specifiers(&self) -> &[EncodedLinkSpec] {
503
2340
        &self.link_specifiers
504
2340
    }
505
}
506

            
507
impl EncryptedHsDesc {
508
    /// Attempt to decrypt both layers of encryption in this onion service
509
    /// descriptor.
510
    ///
511
    /// If `hsc_desc_enc` is provided, we use it to decrypt the inner encryption layer;
512
    /// otherwise, we require that the inner document is encrypted using the "no
513
    /// restricted discovery" method.
514
    //
515
    // TODO: Someday we _might_ want to allow a list of keypairs in place of
516
    // `hs_desc_enc`.  For now, though, we always know a single key that we want
517
    // to try using, and we don't want to leak any extra information by
518
    // providing other keys that _might_ work.  We certainly don't want to
519
    // encourage people to provide every key they know.
520
686
    pub fn decrypt(
521
686
        &self,
522
686
        subcredential: &Subcredential,
523
686
        hsc_desc_enc: Option<&HsClientDescEncKeypair>,
524
686
    ) -> StdResult<TimerangeBound<SignatureGated<HsDesc>>, HsDescError> {
525
        use HsDescError as E;
526
686
        let blinded_id = self.outer_doc.blinded_id();
527
686
        let revision_counter = self.outer_doc.revision_counter();
528
686
        let kp_desc_sign = self.outer_doc.desc_sign_key_id();
529

            
530
        // Decrypt the superencryption layer; parse the middle document.
531
686
        let middle = self
532
686
            .outer_doc
533
686
            .decrypt_body(subcredential)
534
686
            .map_err(|_| E::DecryptionFailed)?;
535
686
        let middle = std::str::from_utf8(&middle[..]).map_err(|_| {
536
            E::InnerParsing(EK::BadObjectVal.with_msg("Bad utf-8 in middle document"))
537
        })?;
538
686
        let middle = middle::HsDescMiddle::parse(middle).map_err(E::InnerParsing)?;
539

            
540
        // Decrypt the encryption layer and parse the inner document.
541
686
        let inner = middle.decrypt_inner(
542
686
            &blinded_id,
543
686
            revision_counter,
544
686
            subcredential,
545
696
            hsc_desc_enc.map(|keys| keys.secret()),
546
2
        )?;
547
684
        let inner = std::str::from_utf8(&inner[..]).map_err(|_| {
548
            E::InnerParsing(EK::BadObjectVal.with_msg("Bad utf-8 in inner document"))
549
        })?;
550
684
        let (cert_signing_key, time_bound) =
551
684
            inner::HsDescInner::parse(inner).map_err(E::InnerParsing)?;
552

            
553
684
        if cert_signing_key.as_ref() != Some(kp_desc_sign) {
554
            return Err(E::InnerValidation(EK::BadObjectVal.with_msg(
555
                "Signing keys in inner document did not match those in outer document",
556
            )));
557
684
        }
558

            
559
        // Construct the HsDesc!
560
701
        let time_bound = time_bound.dangerously_map(|sig_bound| {
561
684
            sig_bound.dangerously_map(|inner| HsDesc {
562
684
                idx_info: IndexInfo::from_outer_doc(&self.outer_doc),
563
684
                auth_required: inner.intro_auth_types,
564
684
                is_single_onion_service: inner.single_onion_service,
565
684
                intro_points: inner.intro_points,
566
684
                pow_params: inner.pow_params,
567
684
                flow_control: inner.flow_control.clone(),
568
684
                protos: inner.protos,
569
684
            })
570
684
        });
571
684
        Ok(time_bound)
572
686
    }
573

            
574
    /// Create a new `IndexInfo` from the outer part of an onion service descriptor.
575
688
    fn from_outer_doc(outer_layer: outer::HsDescOuter) -> Self {
576
688
        EncryptedHsDesc {
577
688
            outer_doc: outer_layer,
578
688
        }
579
688
    }
580
}
581

            
582
impl IndexInfo {
583
    /// Create a new `IndexInfo` from the outer part of an onion service descriptor.
584
686
    fn from_outer_doc(outer: &outer::HsDescOuter) -> Self {
585
686
        IndexInfo {
586
686
            lifetime: outer.lifetime,
587
686
            signing_cert_expires: outer.desc_signing_key_cert.expiry(),
588
686
            revision: outer.revision_counter(),
589
686
        }
590
686
    }
591
}
592

            
593
#[cfg(feature = "hs-dir")]
594
impl StoredHsDescMeta {
595
    /// Create a new `StoredHsDescMeta` from the outer part of an onion service descriptor.
596
2
    fn from_outer_doc(outer: &outer::HsDescOuter) -> Self {
597
2
        let blinded_id = outer.blinded_id();
598
2
        let idx_info = IndexInfo::from_outer_doc(outer);
599
2
        StoredHsDescMeta {
600
2
            blinded_id,
601
2
            idx_info,
602
2
        }
603
2
    }
604
}
605

            
606
/// Test data
607
#[cfg(any(test, feature = "testing"))]
608
#[allow(missing_docs)]
609
#[allow(clippy::missing_docs_in_private_items)]
610
#[allow(clippy::unwrap_used)]
611
pub mod test_data {
612
    use super::*;
613
    use hex_literal::hex;
614

            
615
    pub const TEST_DATA: &str = include_str!("../../testdata/hsdesc1.txt");
616

            
617
    pub const TEST_SUBCREDENTIAL: [u8; 32] =
618
        hex!("78210A0D2C72BB7A0CAF606BCD938B9A3696894FDDDBC3B87D424753A7E3DF37");
619

            
620
    // This HsDesc uses DescEnc authentication.
621
    pub const TEST_DATA_2: &str = include_str!("../../testdata/hsdesc2.txt");
622
    pub const TEST_DATA_TIMEPERIOD_2: u64 = 19397;
623
    // paozpdhgz2okvc6kgbxvh2bnfsmt4xergrtcl4obkhopyvwxkpjzvoad.onion
624
    pub const TEST_HSID_2: [u8; 32] =
625
        hex!("781D978CE6CE9CAA8BCA306F53E82D2C993E5C91346625F1C151DCFC56D753D3");
626
    pub const TEST_SUBCREDENTIAL_2: [u8; 32] =
627
        hex!("24A133E905102BDA9A6AFE57F901366A1B8281865A91F1FE0853E4B50CC8B070");
628
    // SACGOAEODFGCYY22NYZV45ZESFPFLDGLMBWFACKEO34XGHASSAMQ (base32)
629
    pub const TEST_PUBKEY_2: [u8; 32] =
630
        hex!("900467008E194C2C635A6E335E7724915E558CCB606C50094476F9731C129019");
631
    // SDZNMD4RP4SCH4EYTTUZPFRZINNFWAOPPKZ6BINZAC7LREV24RBQ (base32)
632
    pub const TEST_SECKEY_2: [u8; 32] =
633
        hex!("90F2D60F917F2423F0989CE9979639435A5B01CF7AB3E0A1B900BEB892BAE443");
634

            
635
    /// K_hs_blind_id that can be used to parse [`TEST_DATA`]
636
    ///
637
    /// `pub(crate)` mostly because it's difficult to describe what TP it's for.
638
    pub(crate) const TEST_DATA_HS_BLIND_ID: [u8; 32] =
639
        hex!("43cc0d62fc6252f578705ca645a46109e265290343b1137e90189744b20b3f2d");
640

            
641
    /// Obtain a testing [`HsDesc`]
642
    pub fn test_parsed_hsdesc() -> Result<HsDesc> {
643
        let blinded_id = TEST_DATA_HS_BLIND_ID.into();
644

            
645
        let desc = HsDesc::parse(TEST_DATA, &blinded_id)?
646
            .check_signature()?
647
            .check_valid_at(&humantime::parse_rfc3339("2023-01-23T15:00:00Z").unwrap())
648
            .unwrap()
649
            .decrypt(&TEST_SUBCREDENTIAL.into(), None)
650
            .unwrap();
651
        let desc = desc
652
            .check_valid_at(&humantime::parse_rfc3339("2023-01-24T03:00:00Z").unwrap())
653
            .unwrap();
654
        let desc = desc.check_signature().unwrap();
655
        Ok(desc)
656
    }
657
}
658

            
659
#[cfg(test)]
660
mod test {
661
    // @@ begin test lint list maintained by maint/add_warning @@
662
    #![allow(clippy::bool_assert_comparison)]
663
    #![allow(clippy::clone_on_copy)]
664
    #![allow(clippy::dbg_macro)]
665
    #![allow(clippy::mixed_attributes_style)]
666
    #![allow(clippy::print_stderr)]
667
    #![allow(clippy::print_stdout)]
668
    #![allow(clippy::single_char_pattern)]
669
    #![allow(clippy::unwrap_used)]
670
    #![allow(clippy::unchecked_time_subtraction)]
671
    #![allow(clippy::useless_vec)]
672
    #![allow(clippy::needless_pass_by_value)]
673
    #![allow(clippy::string_slice)] // See arti#2571
674
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
675
    use std::time::Duration;
676

            
677
    use super::test_data::*;
678
    use super::*;
679
    use hex_literal::hex;
680
    use tor_hscrypto::{pk::HsIdKey, time::TimePeriod};
681
    use tor_llcrypto::pk::ed25519;
682

            
683
    #[test]
684
    #[cfg(feature = "hs-dir")]
685
    fn parse_meta_good() -> Result<()> {
686
        let meta = StoredHsDescMeta::parse(TEST_DATA)?
687
            .check_signature()?
688
            .check_valid_at(&humantime::parse_rfc3339("2023-01-23T15:00:00Z").unwrap())
689
            .unwrap();
690

            
691
        assert_eq!(meta.blinded_id.as_ref(), &TEST_DATA_HS_BLIND_ID);
692
        assert_eq!(
693
            Duration::try_from(meta.idx_info.lifetime).unwrap(),
694
            Duration::from_secs(60 * 180)
695
        );
696
        assert_eq!(
697
            meta.idx_info.signing_cert_expires,
698
            humantime::parse_rfc3339("2023-01-26T03:00:00Z").unwrap()
699
        );
700
        assert_eq!(meta.idx_info.revision, RevisionCounter::from(19655750));
701

            
702
        Ok(())
703
    }
704

            
705
    #[test]
706
    fn parse_desc_good() -> Result<()> {
707
        let wrong_blinded_id = [12; 32].into();
708
        let desc = HsDesc::parse(TEST_DATA, &wrong_blinded_id);
709
        assert!(desc.is_err());
710
        let desc = test_parsed_hsdesc()?;
711

            
712
        assert_eq!(
713
            Duration::try_from(desc.idx_info.lifetime).unwrap(),
714
            Duration::from_secs(60 * 180)
715
        );
716
        assert_eq!(
717
            desc.idx_info.signing_cert_expires,
718
            humantime::parse_rfc3339("2023-01-26T03:00:00Z").unwrap()
719
        );
720
        assert_eq!(desc.idx_info.revision, RevisionCounter::from(19655750));
721
        assert!(desc.auth_required.is_none());
722
        assert_eq!(desc.is_single_onion_service, false);
723
        assert_eq!(desc.intro_points.len(), 3);
724

            
725
        let ipt0 = &desc.intro_points()[0];
726
        assert_eq!(
727
            ipt0.ipt_ntor_key().as_bytes(),
728
            &hex!("553BF9F9E1979D6F5D5D7D20BB3FE7272E32E22B6E86E35C76A7CA8A377E402F")
729
        );
730
        // TODO TEST: Perhaps add tests for other intro point fields.
731

            
732
        Ok(())
733
    }
734

            
735
    /// Get an EncryptedHsDesc corresponding to `TEST_DATA_2`.
736
    fn get_test2_encrypted() -> EncryptedHsDesc {
737
        let id: HsIdKey = ed25519::PublicKey::from_bytes(&TEST_HSID_2).unwrap().into();
738
        let period = TimePeriod::new(
739
            humantime::parse_duration("24 hours").unwrap(),
740
            humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
741
            humantime::parse_duration("12 hours").unwrap(),
742
        )
743
        .unwrap();
744
        assert_eq!(period.interval_num(), TEST_DATA_TIMEPERIOD_2);
745
        let (blind_id, subcredential) = id.compute_blinded_key(period).unwrap();
746

            
747
        assert_eq!(
748
            blind_id.as_bytes(),
749
            &hex!("706628758208395D461AA0F460A5E76E7B828C66B5E794768592B451302E961D")
750
        );
751

            
752
        assert_eq!(subcredential.as_ref(), &TEST_SUBCREDENTIAL_2);
753

            
754
        HsDesc::parse(TEST_DATA_2, &blind_id.into())
755
            .unwrap()
756
            .check_signature()
757
            .unwrap()
758
            .check_valid_at(&humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap())
759
            .unwrap()
760
    }
761

            
762
    #[test]
763
    fn parse_desc_auth_missing() {
764
        // If we try to decrypt TEST_DATA_2 with no ClientDescEncKey, we get a
765
        // failure.
766
        let encrypted = get_test2_encrypted();
767
        let subcredential = TEST_SUBCREDENTIAL_2.into();
768
        let with_no_auth = encrypted.decrypt(&subcredential, None);
769
        assert!(with_no_auth.is_err());
770
    }
771

            
772
    #[test]
773
    fn parse_desc_auth_good() {
774
        // But if we try to decrypt TEST_DATA_2 with the correct ClientDescEncKey, we get a
775
        // the data inside!
776

            
777
        let encrypted = get_test2_encrypted();
778
        let subcredential = TEST_SUBCREDENTIAL_2.into();
779
        let pk = curve25519::PublicKey::from(TEST_PUBKEY_2).into();
780
        let sk = curve25519::StaticSecret::from(TEST_SECKEY_2).into();
781
        let desc = encrypted
782
            .decrypt(&subcredential, Some(&HsClientDescEncKeypair::new(pk, sk)))
783
            .unwrap();
784
        let desc = desc
785
            .check_valid_at(&humantime::parse_rfc3339("2023-01-24T03:00:00Z").unwrap())
786
            .unwrap();
787
        let desc = desc.check_signature().unwrap();
788
        assert_eq!(desc.intro_points.len(), 3);
789
    }
790
}