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::result::Result as StdResult;
39
use std::time::SystemTime;
40

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

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

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

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

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

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

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

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

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

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

            
110
    /// A list of offered proof-of-work parameters, at most one per type.
111
    pow_params: pow::PowParamSet,
112
    // /// A list of recognized CREATE handshakes that this onion service supports.
113
    //
114
    // TODO:  When someday we add a "create2 format" other than "hs-ntor", we
115
    // should turn this into a caret enum, record this info, and expose it.
116
    // create2_formats: Vec<u32>,
117
}
118

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

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

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

            
151
    /// The key to be used to identify the onion service at this introduction point.
152
    /// (`KP_hs_ipt_sid`)
153
    #[builder(setter(name = "kp_hs_ipt_sid"))] // TODO rename the internal variable too
154
    ipt_sid_key: HsIntroPtSessionIdKey,
155

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

            
166
/// An onion service after it has been parsed by the client, but not yet decrypted.
167
pub struct EncryptedHsDesc {
168
    /// The un-decoded outer document of our onion service descriptor.
169
    outer_doc: outer::HsDescOuter,
170
}
171

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

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

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

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

            
248
712
        Ok(result)
249
714
    }
250

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

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

            
291
432
            let new_bounds = unchecked_desc
292
432
                .intersect(&inner_timerangebound)
293
440
                .map(|(b1, b2)| (b1.cloned(), b2.cloned()));
294

            
295
432
            (inner_timerangebound, new_bounds)
296
        };
297

            
298
432
        let hsdesc = inner_desc
299
432
            .check_valid_at(&valid_at)
300
432
            .map_err(|e| E::InnerValidation(e.into()))?
301
432
            .check_signature()
302
432
            .map_err(|e| E::InnerValidation(e.into()))?;
303

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

            
310
432
        Ok(TimerangeBound::new(hsdesc, new_bounds))
311
432
    }
312

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

            
329
    /// Return true if this onion service claims to be a non-anonymous "single
330
    /// onion service".
331
    ///
332
    /// (We should always anonymize our own connection to an onion service.)
333
4590
    pub fn is_single_onion_service(&self) -> bool {
334
4590
        self.is_single_onion_service
335
4590
    }
336

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

            
345
    /// Get a list of offered proof-of-work parameters, at most one per type.
346
810
    pub fn pow_params(&self) -> &[pow::PowParams] {
347
810
        self.pow_params.slice()
348
810
    }
349

            
350
    /// Return the revision counter of this descriptor
351
1080
    pub fn revision(&self) -> RevisionCounter {
352
1080
        self.idx_info.revision
353
1080
    }
354
}
355

            
356
/// An error returned by [`HsDesc::parse_decrypt_validate`], indicating what
357
/// kind of failure prevented us from validating an onion service descriptor.
358
///
359
/// This is distinct from [`tor_netdoc::Error`](crate::Error) so that we can
360
/// tell errors that could be the HsDir's fault from those that are definitely
361
/// protocol violations by the onion service.
362
#[derive(Clone, Debug, thiserror::Error)]
363
#[non_exhaustive]
364
pub enum HsDescError {
365
    /// An outer object failed parsing: the HsDir should probably have
366
    /// caught this, and not given us this HsDesc.
367
    ///
368
    /// (This can be an innocent error if we happen to know about restrictions
369
    /// that the HsDir does not).
370
    #[error("Parsing failure on outer layer of an onion service descriptor.")]
371
    OuterParsing(#[source] crate::Error),
372

            
373
    /// An outer object failed validation: the HsDir should probably have
374
    /// caught this, and not given us this HsDesc.
375
    ///
376
    /// (This can happen erroneously if we think that something is untimely but
377
    /// the HSDir's clock is slightly different, or _was_ different when it
378
    /// decided to give us this object.)
379
    #[error("Validation failure on outer layer of an onion service descriptor.")]
380
    OuterValidation(#[source] crate::Error),
381

            
382
    /// Decrypting the inner layer failed because we need to have a decryption key,
383
    /// but we didn't provide one.
384
    ///
385
    /// This is probably our fault.
386
    #[error("Decryption failure on onion service descriptor: missing decryption key")]
387
    MissingDecryptionKey,
388

            
389
    /// Decrypting the inner layer failed because, although we provided a key,
390
    /// we did not provide the key we need to decrypt it.
391
    ///
392
    /// This is probably our fault.
393
    #[error("Decryption failure on onion service descriptor: incorrect decryption key")]
394
    WrongDecryptionKey,
395

            
396
    /// Decrypting the inner or middle layer failed because of an issue with the
397
    /// decryption itself.
398
    ///
399
    /// This is the onion service's fault.
400
    #[error("Decryption failure on onion service descriptor: could not decrypt")]
401
    DecryptionFailed,
402

            
403
    /// We failed to parse something cryptographic in an inner layer of the
404
    /// onion service descriptor.
405
    ///
406
    /// This is definitely the onion service's fault.
407
    #[error("Parsing failure on inner layer of an onion service descriptor")]
408
    InnerParsing(#[source] crate::Error),
409

            
410
    /// We failed to validate something cryptographic in an inner layer of the
411
    /// onion service descriptor.
412
    ///
413
    /// This is definitely the onion service's fault.
414
    #[error("Validation failure on inner layer of an onion service descriptor")]
415
    InnerValidation(#[source] crate::Error),
416

            
417
    /// We encountered an internal error.
418
    #[error("Internal error: {0}")]
419
    Bug(#[from] tor_error::Bug),
420
}
421

            
422
impl tor_error::HasKind for HsDescError {
423
    fn kind(&self) -> tor_error::ErrorKind {
424
        use HsDescError as E;
425
        use tor_error::ErrorKind as EK;
426
        match self {
427
            E::OuterParsing(_) | E::OuterValidation(_) => EK::TorProtocolViolation,
428
            E::MissingDecryptionKey => EK::OnionServiceMissingClientAuth,
429
            E::WrongDecryptionKey => EK::OnionServiceWrongClientAuth,
430
            E::DecryptionFailed | E::InnerParsing(_) | E::InnerValidation(_) => {
431
                EK::OnionServiceProtocolViolation
432
            }
433
            E::Bug(e) => e.kind(),
434
        }
435
    }
436
}
437

            
438
impl HsDescError {
439
    /// Return true if this error is one that we should report as a suspicious event.
440
    ///
441
    /// Note that this is a defense-in-depth check
442
    /// for resisting descriptor-length inflation attacks:
443
    /// Our limits on total download size and/or total cell counts are the defense
444
    /// that really matters.
445
    /// (See prop360 for more information.)
446
    pub fn should_report_as_suspicious(&self) -> bool {
447
        use crate::NetdocErrorKind as EK;
448
        use HsDescError as E;
449
        #[allow(clippy::match_like_matches_macro)]
450
        match self {
451
            E::OuterParsing(e) => match e.netdoc_error_kind() {
452
                EK::ExtraneousSpace => true,
453
                EK::WrongEndingToken => true,
454
                EK::MissingKeyword => true,
455
                _ => false,
456
            },
457
            E::OuterValidation(e) => match e.netdoc_error_kind() {
458
                EK::BadSignature => true,
459
                _ => false,
460
            },
461
            E::MissingDecryptionKey => false,
462
            E::WrongDecryptionKey => false,
463
            E::DecryptionFailed => false,
464
            E::InnerParsing(_) => false,
465
            E::InnerValidation(_) => false,
466
            E::Bug(_) => false,
467
        }
468
    }
469
}
470

            
471
impl IntroPointDesc {
472
    /// Start building a description of an intro point
473
648
    pub fn builder() -> IntroPointDescBuilder {
474
648
        IntroPointDescBuilder::default()
475
648
    }
476

            
477
    /// The list of link specifiers needed to extend a circuit to the introduction point.
478
    ///
479
    /// These can include public keys and network addresses.
480
    ///
481
    /// Accessor function.
482
    //
483
    // TODO: It would be better to derive this too, but this accessor needs to
484
    // return a slice; Getters can only give us a &Vec<> in this case.
485
2430
    pub fn link_specifiers(&self) -> &[EncodedLinkSpec] {
486
2430
        &self.link_specifiers
487
2430
    }
488
}
489

            
490
impl EncryptedHsDesc {
491
    /// Attempt to decrypt both layers of encryption in this onion service
492
    /// descriptor.
493
    ///
494
    /// If `hsc_desc_enc` is provided, we use it to decrypt the inner encryption layer;
495
    /// otherwise, we require that the inner document is encrypted using the "no
496
    /// restricted discovery" method.
497
    //
498
    // TODO: Someday we _might_ want to allow a list of keypairs in place of
499
    // `hs_desc_enc`.  For now, though, we always know a single key that we want
500
    // to try using, and we don't want to leak any extra information by
501
    // providing other keys that _might_ work.  We certainly don't want to
502
    // encourage people to provide every key they know.
503
712
    pub fn decrypt(
504
712
        &self,
505
712
        subcredential: &Subcredential,
506
712
        hsc_desc_enc: Option<&HsClientDescEncKeypair>,
507
712
    ) -> StdResult<TimerangeBound<SignatureGated<HsDesc>>, HsDescError> {
508
        use HsDescError as E;
509
712
        let blinded_id = self.outer_doc.blinded_id();
510
712
        let revision_counter = self.outer_doc.revision_counter();
511
712
        let kp_desc_sign = self.outer_doc.desc_sign_key_id();
512

            
513
        // Decrypt the superencryption layer; parse the middle document.
514
712
        let middle = self
515
712
            .outer_doc
516
712
            .decrypt_body(subcredential)
517
712
            .map_err(|_| E::DecryptionFailed)?;
518
712
        let middle = std::str::from_utf8(&middle[..]).map_err(|_| {
519
            E::InnerParsing(EK::BadObjectVal.with_msg("Bad utf-8 in middle document"))
520
        })?;
521
712
        let middle = middle::HsDescMiddle::parse(middle).map_err(E::InnerParsing)?;
522

            
523
        // Decrypt the encryption layer and parse the inner document.
524
712
        let inner = middle.decrypt_inner(
525
712
            &blinded_id,
526
712
            revision_counter,
527
712
            subcredential,
528
722
            hsc_desc_enc.map(|keys| keys.secret()),
529
2
        )?;
530
710
        let inner = std::str::from_utf8(&inner[..]).map_err(|_| {
531
            E::InnerParsing(EK::BadObjectVal.with_msg("Bad utf-8 in inner document"))
532
        })?;
533
710
        let (cert_signing_key, time_bound) =
534
710
            inner::HsDescInner::parse(inner).map_err(E::InnerParsing)?;
535

            
536
710
        if cert_signing_key.as_ref() != Some(kp_desc_sign) {
537
            return Err(E::InnerValidation(EK::BadObjectVal.with_msg(
538
                "Signing keys in inner document did not match those in outer document",
539
            )));
540
710
        }
541

            
542
        // Construct the HsDesc!
543
727
        let time_bound = time_bound.dangerously_map(|sig_bound| {
544
710
            sig_bound.dangerously_map(|inner| HsDesc {
545
710
                idx_info: IndexInfo::from_outer_doc(&self.outer_doc),
546
710
                auth_required: inner.intro_auth_types,
547
710
                is_single_onion_service: inner.single_onion_service,
548
710
                intro_points: inner.intro_points,
549
710
                pow_params: inner.pow_params,
550
710
            })
551
710
        });
552
710
        Ok(time_bound)
553
712
    }
554

            
555
    /// Create a new `IndexInfo` from the outer part of an onion service descriptor.
556
714
    fn from_outer_doc(outer_layer: outer::HsDescOuter) -> Self {
557
714
        EncryptedHsDesc {
558
714
            outer_doc: outer_layer,
559
714
        }
560
714
    }
561
}
562

            
563
impl IndexInfo {
564
    /// Create a new `IndexInfo` from the outer part of an onion service descriptor.
565
712
    fn from_outer_doc(outer: &outer::HsDescOuter) -> Self {
566
712
        IndexInfo {
567
712
            lifetime: outer.lifetime,
568
712
            signing_cert_expires: outer.desc_signing_key_cert.expiry(),
569
712
            revision: outer.revision_counter(),
570
712
        }
571
712
    }
572
}
573

            
574
#[cfg(feature = "hs-dir")]
575
impl StoredHsDescMeta {
576
    /// Create a new `StoredHsDescMeta` from the outer part of an onion service descriptor.
577
2
    fn from_outer_doc(outer: &outer::HsDescOuter) -> Self {
578
2
        let blinded_id = outer.blinded_id();
579
2
        let idx_info = IndexInfo::from_outer_doc(outer);
580
2
        StoredHsDescMeta {
581
2
            blinded_id,
582
2
            idx_info,
583
2
        }
584
2
    }
585
}
586

            
587
/// Test data
588
#[cfg(any(test, feature = "testing"))]
589
#[allow(missing_docs)]
590
#[allow(clippy::missing_docs_in_private_items)]
591
#[allow(clippy::unwrap_used)]
592
pub mod test_data {
593
    use super::*;
594
    use hex_literal::hex;
595

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

            
598
    pub const TEST_SUBCREDENTIAL: [u8; 32] =
599
        hex!("78210A0D2C72BB7A0CAF606BCD938B9A3696894FDDDBC3B87D424753A7E3DF37");
600

            
601
    // This HsDesc uses DescEnc authentication.
602
    pub const TEST_DATA_2: &str = include_str!("../../testdata/hsdesc2.txt");
603
    pub const TEST_DATA_TIMEPERIOD_2: u64 = 19397;
604
    // paozpdhgz2okvc6kgbxvh2bnfsmt4xergrtcl4obkhopyvwxkpjzvoad.onion
605
    pub const TEST_HSID_2: [u8; 32] =
606
        hex!("781D978CE6CE9CAA8BCA306F53E82D2C993E5C91346625F1C151DCFC56D753D3");
607
    pub const TEST_SUBCREDENTIAL_2: [u8; 32] =
608
        hex!("24A133E905102BDA9A6AFE57F901366A1B8281865A91F1FE0853E4B50CC8B070");
609
    // SACGOAEODFGCYY22NYZV45ZESFPFLDGLMBWFACKEO34XGHASSAMQ (base32)
610
    pub const TEST_PUBKEY_2: [u8; 32] =
611
        hex!("900467008E194C2C635A6E335E7724915E558CCB606C50094476F9731C129019");
612
    // SDZNMD4RP4SCH4EYTTUZPFRZINNFWAOPPKZ6BINZAC7LREV24RBQ (base32)
613
    pub const TEST_SECKEY_2: [u8; 32] =
614
        hex!("90F2D60F917F2423F0989CE9979639435A5B01CF7AB3E0A1B900BEB892BAE443");
615

            
616
    /// K_hs_blind_id that can be used to parse [`TEST_DATA`]
617
    ///
618
    /// `pub(crate)` mostly because it's difficult to describe what TP it's for.
619
    pub(crate) const TEST_DATA_HS_BLIND_ID: [u8; 32] =
620
        hex!("43cc0d62fc6252f578705ca645a46109e265290343b1137e90189744b20b3f2d");
621

            
622
    /// Obtain a testing [`HsDesc`]
623
    pub fn test_parsed_hsdesc() -> Result<HsDesc> {
624
        let blinded_id = TEST_DATA_HS_BLIND_ID.into();
625

            
626
        let desc = HsDesc::parse(TEST_DATA, &blinded_id)?
627
            .check_signature()?
628
            .check_valid_at(&humantime::parse_rfc3339("2023-01-23T15:00:00Z").unwrap())
629
            .unwrap()
630
            .decrypt(&TEST_SUBCREDENTIAL.into(), None)
631
            .unwrap();
632
        let desc = desc
633
            .check_valid_at(&humantime::parse_rfc3339("2023-01-24T03:00:00Z").unwrap())
634
            .unwrap();
635
        let desc = desc.check_signature().unwrap();
636
        Ok(desc)
637
    }
638
}
639

            
640
#[cfg(test)]
641
mod test {
642
    // @@ begin test lint list maintained by maint/add_warning @@
643
    #![allow(clippy::bool_assert_comparison)]
644
    #![allow(clippy::clone_on_copy)]
645
    #![allow(clippy::dbg_macro)]
646
    #![allow(clippy::mixed_attributes_style)]
647
    #![allow(clippy::print_stderr)]
648
    #![allow(clippy::print_stdout)]
649
    #![allow(clippy::single_char_pattern)]
650
    #![allow(clippy::unwrap_used)]
651
    #![allow(clippy::unchecked_time_subtraction)]
652
    #![allow(clippy::useless_vec)]
653
    #![allow(clippy::needless_pass_by_value)]
654
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
655
    use std::time::Duration;
656

            
657
    use super::test_data::*;
658
    use super::*;
659
    use hex_literal::hex;
660
    use tor_hscrypto::{pk::HsIdKey, time::TimePeriod};
661
    use tor_llcrypto::pk::ed25519;
662

            
663
    #[test]
664
    #[cfg(feature = "hs-dir")]
665
    fn parse_meta_good() -> Result<()> {
666
        let meta = StoredHsDescMeta::parse(TEST_DATA)?
667
            .check_signature()?
668
            .check_valid_at(&humantime::parse_rfc3339("2023-01-23T15:00:00Z").unwrap())
669
            .unwrap();
670

            
671
        assert_eq!(meta.blinded_id.as_ref(), &TEST_DATA_HS_BLIND_ID);
672
        assert_eq!(
673
            Duration::try_from(meta.idx_info.lifetime).unwrap(),
674
            Duration::from_secs(60 * 180)
675
        );
676
        assert_eq!(
677
            meta.idx_info.signing_cert_expires,
678
            humantime::parse_rfc3339("2023-01-26T03:00:00Z").unwrap()
679
        );
680
        assert_eq!(meta.idx_info.revision, RevisionCounter::from(19655750));
681

            
682
        Ok(())
683
    }
684

            
685
    #[test]
686
    fn parse_desc_good() -> Result<()> {
687
        let wrong_blinded_id = [12; 32].into();
688
        let desc = HsDesc::parse(TEST_DATA, &wrong_blinded_id);
689
        assert!(desc.is_err());
690
        let desc = test_parsed_hsdesc()?;
691

            
692
        assert_eq!(
693
            Duration::try_from(desc.idx_info.lifetime).unwrap(),
694
            Duration::from_secs(60 * 180)
695
        );
696
        assert_eq!(
697
            desc.idx_info.signing_cert_expires,
698
            humantime::parse_rfc3339("2023-01-26T03:00:00Z").unwrap()
699
        );
700
        assert_eq!(desc.idx_info.revision, RevisionCounter::from(19655750));
701
        assert!(desc.auth_required.is_none());
702
        assert_eq!(desc.is_single_onion_service, false);
703
        assert_eq!(desc.intro_points.len(), 3);
704

            
705
        let ipt0 = &desc.intro_points()[0];
706
        assert_eq!(
707
            ipt0.ipt_ntor_key().as_bytes(),
708
            &hex!("553BF9F9E1979D6F5D5D7D20BB3FE7272E32E22B6E86E35C76A7CA8A377E402F")
709
        );
710
        // TODO TEST: Perhaps add tests for other intro point fields.
711

            
712
        Ok(())
713
    }
714

            
715
    /// Get an EncryptedHsDesc corresponding to `TEST_DATA_2`.
716
    fn get_test2_encrypted() -> EncryptedHsDesc {
717
        let id: HsIdKey = ed25519::PublicKey::from_bytes(&TEST_HSID_2).unwrap().into();
718
        let period = TimePeriod::new(
719
            humantime::parse_duration("24 hours").unwrap(),
720
            humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
721
            humantime::parse_duration("12 hours").unwrap(),
722
        )
723
        .unwrap();
724
        assert_eq!(period.interval_num(), TEST_DATA_TIMEPERIOD_2);
725
        let (blind_id, subcredential) = id.compute_blinded_key(period).unwrap();
726

            
727
        assert_eq!(
728
            blind_id.as_bytes(),
729
            &hex!("706628758208395D461AA0F460A5E76E7B828C66B5E794768592B451302E961D")
730
        );
731

            
732
        assert_eq!(subcredential.as_ref(), &TEST_SUBCREDENTIAL_2);
733

            
734
        HsDesc::parse(TEST_DATA_2, &blind_id.into())
735
            .unwrap()
736
            .check_signature()
737
            .unwrap()
738
            .check_valid_at(&humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap())
739
            .unwrap()
740
    }
741

            
742
    #[test]
743
    fn parse_desc_auth_missing() {
744
        // If we try to decrypt TEST_DATA_2 with no ClientDescEncKey, we get a
745
        // failure.
746
        let encrypted = get_test2_encrypted();
747
        let subcredential = TEST_SUBCREDENTIAL_2.into();
748
        let with_no_auth = encrypted.decrypt(&subcredential, None);
749
        assert!(with_no_auth.is_err());
750
    }
751

            
752
    #[test]
753
    fn parse_desc_auth_good() {
754
        // But if we try to decrypt TEST_DATA_2 with the correct ClientDescEncKey, we get a
755
        // the data inside!
756

            
757
        let encrypted = get_test2_encrypted();
758
        let subcredential = TEST_SUBCREDENTIAL_2.into();
759
        let pk = curve25519::PublicKey::from(TEST_PUBKEY_2).into();
760
        let sk = curve25519::StaticSecret::from(TEST_SECKEY_2).into();
761
        let desc = encrypted
762
            .decrypt(&subcredential, Some(&HsClientDescEncKeypair::new(pk, sk)))
763
            .unwrap();
764
        let desc = desc
765
            .check_valid_at(&humantime::parse_rfc3339("2023-01-24T03:00:00Z").unwrap())
766
            .unwrap();
767
        let desc = desc.check_signature().unwrap();
768
        assert_eq!(desc.intro_points.len(), 3);
769
    }
770
}