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

            
22
use crate::{NetdocErrorKind as EK, Result};
23

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

            
33
use derive_builder::Builder;
34
use smallvec::SmallVec;
35

            
36
use std::num::NonZeroU8;
37
use std::result::Result as StdResult;
38
use std::time::SystemTime;
39

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
251
699
        Ok(result)
252
701
    }
253

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

            
281
432
        TimeRangeBound::build_intersect(|bounds| {
282
424
            let inner_timerangebound = unchecked_desc
283
424
                .unwrap_with(bounds)
284
424
                .decrypt(subcredential, hsc_desc_enc)?;
285

            
286
424
            let hsdesc = inner_timerangebound
287
424
                .unwrap_with(bounds)
288
424
                .check_signature()
289
424
                .map_err(|e| E::InnerValidation(e.into()))?;
290

            
291
424
            Ok(hsdesc)
292
424
        })
293
424
    }
294

            
295
    /// One or more introduction points used to contact the onion service.
296
    ///
297
    /// Always returns at least one introduction point,
298
    /// and never more than [`NUM_INTRO_POINT_MAX`](tor_hscrypto::NUM_INTRO_POINT_MAX).
299
    /// (Descriptors which have fewer or more are dealt with during parsing.)
300
    ///
301
    /// Accessor function.
302
    //
303
    // TODO: We'd like to derive this, but amplify::Getters  would give us &Vec<>,
304
    // not &[].
305
    //
306
    // Perhaps someday we can use derive_deftly, or add as_ref() support?
307
1062
    pub fn intro_points(&self) -> &[IntroPointDesc] {
308
1062
        &self.intro_points
309
1062
    }
310

            
311
    /// Return true if this onion service claims to be a non-anonymous "single
312
    /// onion service".
313
    ///
314
    /// (We should always anonymize our own connection to an onion service.)
315
4505
    pub fn is_single_onion_service(&self) -> bool {
316
4505
        self.is_single_onion_service
317
4505
    }
318

            
319
    /// Return true if this onion service claims that it needs user authentication
320
    /// of some kind in its INTRODUCE messages.
321
    ///
322
    /// (Arti does not currently support sending this kind of authentication.)
323
    pub fn requires_intro_authentication(&self) -> bool {
324
        self.auth_required.is_some()
325
    }
326

            
327
    /// Get a list of offered proof-of-work parameters, at most one per type.
328
795
    pub fn pow_params(&self) -> &[pow::PowParams] {
329
795
        self.pow_params.slice()
330
795
    }
331

            
332
    /// Return the revision counter of this descriptor
333
1060
    pub fn revision(&self) -> RevisionCounter {
334
1060
        self.idx_info.revision
335
1060
    }
336

            
337
    /// Return the set of protocol capabilities declared in this descriptor.
338
4505
    pub fn declared_capabilities(&self) -> &tor_protover::Protocols {
339
4505
        &self.protos
340
4505
    }
341

            
342
    /// Return the flow control protocols,
343
    /// and the `sendme_inc` value declared for congestion control in this descriptor,
344
    /// if they were present.
345
4876
    pub fn flow_control(&self) -> Option<(tor_protover::Protocols, NonZeroU8)> {
346
4968
        self.flow_control.as_ref().map(|(p, inc)| (p.clone(), *inc))
347
4876
    }
348
}
349

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

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

            
376
    /// Decrypting the inner layer failed because we need to have a decryption key,
377
    /// but we didn't provide one.
378
    ///
379
    /// This is probably our fault.
380
    #[error("Decryption failure on onion service descriptor: missing decryption key")]
381
    MissingDecryptionKey,
382

            
383
    /// Decrypting the inner layer failed because, although we provided a key,
384
    /// we did not provide the key we need to decrypt it.
385
    ///
386
    /// This is probably our fault.
387
    #[error("Decryption failure on onion service descriptor: incorrect decryption key")]
388
    WrongDecryptionKey,
389

            
390
    /// Decrypting the inner or middle layer failed because of an issue with the
391
    /// decryption itself.
392
    ///
393
    /// This is the onion service's fault.
394
    #[error("Decryption failure on onion service descriptor: could not decrypt")]
395
    DecryptionFailed,
396

            
397
    /// We failed to parse something cryptographic in an inner layer of the
398
    /// onion service descriptor.
399
    ///
400
    /// This is definitely the onion service's fault.
401
    #[error("Parsing failure on inner layer of an onion service descriptor")]
402
    InnerParsing(#[source] crate::Error),
403

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

            
411
    /// We encountered an internal error.
412
    #[error("Internal error: {0}")]
413
    Bug(#[from] tor_error::Bug),
414
}
415

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

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

            
465
impl IntroPointDesc {
466
    /// Start building a description of an intro point
467
636
    pub fn builder() -> IntroPointDescBuilder {
468
636
        IntroPointDescBuilder::default()
469
636
    }
470

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

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

            
507
        // Decrypt the superencryption layer; parse the middle document.
508
699
        let middle = self
509
699
            .outer_doc
510
699
            .decrypt_body(subcredential)
511
699
            .map_err(|_| E::DecryptionFailed)?;
512
699
        let middle = std::str::from_utf8(&middle[..]).map_err(|_| {
513
            E::InnerParsing(EK::BadObjectVal.with_msg("Bad utf-8 in middle document"))
514
        })?;
515
699
        let middle = middle::HsDescMiddle::parse(middle).map_err(E::InnerParsing)?;
516

            
517
        // Decrypt the encryption layer and parse the inner document.
518
699
        let inner = middle.decrypt_inner(
519
699
            &blinded_id,
520
699
            revision_counter,
521
699
            subcredential,
522
709
            hsc_desc_enc.map(|keys| keys.secret()),
523
2
        )?;
524
697
        let inner = std::str::from_utf8(&inner[..]).map_err(|_| {
525
            E::InnerParsing(EK::BadObjectVal.with_msg("Bad utf-8 in inner document"))
526
        })?;
527
697
        let (cert_signing_key, time_bound) =
528
697
            inner::HsDescInner::parse(inner).map_err(E::InnerParsing)?;
529

            
530
697
        if cert_signing_key.as_ref() != Some(kp_desc_sign) {
531
            return Err(E::InnerValidation(EK::BadObjectVal.with_msg(
532
                "Signing keys in inner document did not match those in outer document",
533
            )));
534
697
        }
535

            
536
        // Construct the HsDesc!
537
714
        let time_bound = time_bound.dangerously_map(|sig_bound| {
538
697
            sig_bound.dangerously_map(|inner| HsDesc {
539
697
                idx_info: IndexInfo::from_outer_doc(&self.outer_doc),
540
697
                auth_required: inner.intro_auth_types,
541
697
                is_single_onion_service: inner.single_onion_service,
542
697
                intro_points: inner.intro_points,
543
697
                pow_params: inner.pow_params,
544
697
                flow_control: inner.flow_control.clone(),
545
697
                protos: inner.protos,
546
697
            })
547
697
        });
548
697
        Ok(time_bound)
549
699
    }
550

            
551
    /// Create a new `IndexInfo` from the outer part of an onion service descriptor.
552
701
    fn from_outer_doc(outer_layer: outer::HsDescOuter) -> Self {
553
701
        EncryptedHsDesc {
554
701
            outer_doc: outer_layer,
555
701
        }
556
701
    }
557
}
558

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

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

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

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

            
594
    pub const TEST_SUBCREDENTIAL: [u8; 32] =
595
        hex!("78210A0D2C72BB7A0CAF606BCD938B9A3696894FDDDBC3B87D424753A7E3DF37");
596

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

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

            
618
    /// Obtain a testing [`HsDesc`]
619
    pub fn test_parsed_hsdesc() -> Result<HsDesc> {
620
        let blinded_id = TEST_DATA_HS_BLIND_ID.into();
621

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

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

            
654
    use super::test_data::*;
655
    use super::*;
656
    use hex_literal::hex;
657
    use tor_hscrypto::{pk::HsIdKey, time::TimePeriod};
658
    use tor_llcrypto::pk::ed25519;
659

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

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

            
679
        Ok(())
680
    }
681

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

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

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

            
709
        Ok(())
710
    }
711

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

            
724
        assert_eq!(
725
            blind_id.as_bytes(),
726
            &hex!("706628758208395D461AA0F460A5E76E7B828C66B5E794768592B451302E961D")
727
        );
728

            
729
        assert_eq!(subcredential.as_ref(), &TEST_SUBCREDENTIAL_2);
730

            
731
        HsDesc::parse(TEST_DATA_2, &blind_id.into())
732
            .unwrap()
733
            .check_signature()
734
            .unwrap()
735
            .if_valid_at(&humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap())
736
            .unwrap()
737
    }
738

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

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

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