1
//! Hidden service descriptor encoding.
2

            
3
mod inner;
4
mod middle;
5
mod outer;
6

            
7
use crate::NetdocBuilder;
8
use crate::doc::hsdesc::{IntroAuthType, IntroPointDesc};
9
use rand::{CryptoRng, Rng};
10
use tor_bytes::EncodeError;
11
use tor_cell::chancell::msg::HandshakeType;
12
use tor_cert::{CertEncodeError, CertType, CertifiedKey, Ed25519Cert, EncodedEd25519Cert};
13
use tor_error::into_bad_api_usage;
14
use tor_hscrypto::pk::{HsBlindIdKey, HsBlindIdKeypair, HsSvcDescEncKeypair};
15
use tor_hscrypto::{RevisionCounter, Subcredential};
16
use tor_llcrypto::pk::curve25519;
17
use tor_llcrypto::pk::ed25519;
18
use tor_units::IntegerMinutes;
19

            
20
use derive_builder::Builder;
21
use smallvec::SmallVec;
22

            
23
use std::borrow::{Borrow, Cow};
24
use std::time::SystemTime;
25

            
26
use self::inner::HsDescInner;
27
use self::middle::HsDescMiddle;
28
use self::outer::HsDescOuter;
29

            
30
use super::desc_enc::{HS_DESC_ENC_NONCE_LEN, HsDescEncNonce, HsDescEncryption};
31
use super::pow::PowParams;
32

            
33
/// An intermediary type for encoding hidden service descriptors.
34
///
35
/// This object is constructed via [`HsDescBuilder`], and then turned into a
36
/// signed document using [`HsDescBuilder::build_sign()`].
37
///
38
/// TODO: Add an example for using this API.
39
#[derive(Builder)]
40
#[builder(public, derive(Debug, Clone), pattern = "owned", build_fn(vis = ""))]
41
struct HsDesc<'a> {
42
    /// The blinded hidden service public key used for the first half of the "SECRET_DATA" field.
43
    ///
44
    /// (See rend-spec v3 2.5.1.1 and 2.5.2.1.)
45
    blinded_id: &'a HsBlindIdKey,
46
    /// The short-term descriptor signing key (KP_hs_desc_sign, KS_hs_desc_sign).
47
    hs_desc_sign: &'a ed25519::Keypair,
48
    /// The descriptor signing key certificate.
49
    ///
50
    /// This certificate can be created using [`create_desc_sign_key_cert`].
51
    hs_desc_sign_cert: EncodedEd25519Cert,
52
    /// A list of recognized CREATE handshakes that this onion service supports.
53
    create2_formats: &'a [HandshakeType],
54
    /// A list of authentication types that this onion service supports.
55
    auth_required: Option<SmallVec<[IntroAuthType; 2]>>,
56
    /// If true, this a "single onion service" and is not trying to keep its own location private.
57
    is_single_onion_service: bool,
58
    /// One or more introduction points used to contact the onion service.
59
    intro_points: &'a [IntroPointDesc],
60
    /// The expiration time of an introduction point authentication key certificate.
61
    intro_auth_key_cert_expiry: SystemTime,
62
    /// The expiration time of an introduction point encryption key certificate.
63
    intro_enc_key_cert_expiry: SystemTime,
64
    /// Proof-of-work parameters.
65
    #[builder(default)]
66
    #[cfg(feature = "hs-pow-full")]
67
    pow_params: Option<&'a PowParams>,
68

            
69
    /// The list of clients authorized to discover the hidden service.
70
    ///
71
    /// If `None`, restricted discovery is disabled.
72
    /// If `Some(&[])`, restricted discovery is enabled,
73
    /// but there will be no authorized clients.
74
    ///
75
    /// If restricted discovery is disabled, the resulting middle document will contain a single
76
    /// `auth-client` line populated with random values.
77
    ///
78
    /// Restricted discovery is disabled by default.
79
    #[builder(default)]
80
    auth_clients: Option<&'a [curve25519::PublicKey]>,
81
    /// The lifetime of this descriptor, in minutes.
82
    ///
83
    /// This doesn't actually list the starting time or the end time for the
84
    /// descriptor: presumably, because we didn't want to leak the onion
85
    /// service's view of the wallclock.
86
    lifetime: IntegerMinutes<u16>,
87
    /// A revision counter to tell whether this descriptor is more or less recent
88
    /// than another one for the same blinded ID.
89
    revision_counter: RevisionCounter,
90
    /// The "subcredential" of the onion service.
91
    subcredential: Subcredential,
92

            
93
    /// Maximum length of generated HsDesc.
94
    ///
95
    /// If the generated descriptor is larger than this, `build_sign` will return an error.
96
    #[builder(field(type = "Option<usize>", build = "()"), setter(strip_option))]
97
    #[allow(dead_code)]
98
    max_generated_len: (),
99
}
100

            
101
/// Restricted discovery parameters.
102
#[derive(Debug)]
103
pub(super) struct ClientAuth<'a> {
104
    /// An ephemeral x25519 keypair generated by the hidden service (`KP_hss_desc_enc`).
105
    ///
106
    /// A new keypair MUST be generated every time a descriptor is encoded, or the descriptor
107
    /// encryption will not be secure.
108
    ephemeral_key: HsSvcDescEncKeypair,
109
    /// The list of clients authorized to discover this service.
110
    auth_clients: &'a [curve25519::PublicKey],
111
    /// The `N_hs_desc_enc` descriptor_cookie key generated by the hidden service.
112
    ///
113
    /// A new descriptor cookie is randomly generated for each descriptor.
114
    descriptor_cookie: [u8; HS_DESC_ENC_NONCE_LEN],
115
}
116

            
117
impl<'a> ClientAuth<'a> {
118
    /// Create a new `ClientAuth` using the specified authorized clients.
119
    ///
120
    /// If `auth_clients` is empty list, there will be no authorized clients.
121
    ///
122
    /// This returns `None` if the list of `auth_clients` is `None`.
123
136
    fn new<R: Rng + CryptoRng>(
124
136
        auth_clients: Option<&'a [curve25519::PublicKey]>,
125
136
        rng: &mut R,
126
136
    ) -> Option<ClientAuth<'a>> {
127
136
        let Some(auth_clients) = auth_clients else {
128
            // Restricted discovery is disabled
129
132
            return None;
130
        };
131

            
132
        // Generate a new `N_hs_desc_enc` descriptor_cookie key for this descriptor.
133
4
        let descriptor_cookie = rand::RngExt::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(rng);
134

            
135
4
        let secret = curve25519::StaticSecret::random_from_rng(rng);
136
4
        let ephemeral_key = HsSvcDescEncKeypair {
137
4
            public: curve25519::PublicKey::from(&secret).into(),
138
4
            secret: secret.into(),
139
4
        };
140

            
141
4
        Some(ClientAuth {
142
4
            ephemeral_key,
143
4
            auth_clients,
144
4
            descriptor_cookie,
145
4
        })
146
136
    }
147
}
148

            
149
impl<'a> NetdocBuilder for HsDescBuilder<'a> {
150
136
    fn build_sign<R: Rng + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
151
        /// The superencrypted field must be padded to the nearest multiple of 10k bytes
152
        ///
153
        /// rend-spec-v3 2.5.1.1
154
        const SUPERENCRYPTED_ALIGN: usize = 10 * (1 << 10);
155

            
156
136
        let max_generated_len = self.max_generated_len.unwrap_or(usize::MAX);
157

            
158
136
        let hs_desc = self
159
136
            .build()
160
136
            .map_err(into_bad_api_usage!("the HsDesc could not be built"))?;
161

            
162
136
        let client_auth = ClientAuth::new(hs_desc.auth_clients, rng);
163

            
164
        // Construct the inner (second layer) plaintext. This is the unencrypted value of the
165
        // "encrypted" field.
166
136
        let inner_plaintext = HsDescInner {
167
136
            hs_desc_sign: hs_desc.hs_desc_sign,
168
136
            create2_formats: hs_desc.create2_formats,
169
136
            auth_required: hs_desc.auth_required.as_ref(),
170
136
            is_single_onion_service: hs_desc.is_single_onion_service,
171
136
            intro_points: hs_desc.intro_points,
172
136
            intro_auth_key_cert_expiry: hs_desc.intro_auth_key_cert_expiry,
173
136
            intro_enc_key_cert_expiry: hs_desc.intro_enc_key_cert_expiry,
174
136
            #[cfg(feature = "hs-pow-full")]
175
136
            pow_params: hs_desc.pow_params,
176
136
        }
177
136
        .build_sign(rng)?;
178

            
179
136
        let desc_enc_nonce = client_auth
180
136
            .as_ref()
181
136
            .map(|client_auth| client_auth.descriptor_cookie.into());
182

            
183
        // Encrypt the inner document. The encrypted blob is the ciphertext contained in the
184
        // "encrypted" field described in section 2.5.1.2. of rend-spec-v3.
185
136
        let inner_encrypted = hs_desc.encrypt_field(
186
136
            rng,
187
136
            inner_plaintext.as_bytes(),
188
136
            desc_enc_nonce.as_ref(),
189
136
            b"hsdir-encrypted-data",
190
        );
191

            
192
        // Construct the middle (first player) plaintext. This is the unencrypted value of the
193
        // "superencrypted" field.
194
136
        let middle_plaintext = HsDescMiddle {
195
136
            client_auth: client_auth.as_ref(),
196
136
            subcredential: hs_desc.subcredential,
197
136
            encrypted: inner_encrypted,
198
136
        }
199
136
        .build_sign(rng)?;
200

            
201
        // Section 2.5.1.1. of rend-spec-v3: before encryption, pad the plaintext to the nearest
202
        // multiple of 10k bytes
203
136
        let middle_plaintext =
204
136
            pad_with_zero_to_align(middle_plaintext.as_bytes(), SUPERENCRYPTED_ALIGN);
205

            
206
        // Encrypt the middle document. The encrypted blob is the ciphertext contained in the
207
        // "superencrypted" field described in section 2.5.1.1. of rend-spec-v3.
208
136
        let middle_encrypted = hs_desc.encrypt_field(
209
136
            rng,
210
136
            middle_plaintext.borrow(),
211
            // desc_enc_nonce is absent when handling the superencryption layer (2.5.1.1).
212
136
            None,
213
136
            b"hsdir-superencrypted-data",
214
        );
215

            
216
        // Finally, build the hidden service descriptor.
217
136
        let hsdesc = HsDescOuter {
218
136
            hs_desc_sign: hs_desc.hs_desc_sign,
219
136
            hs_desc_sign_cert: hs_desc.hs_desc_sign_cert,
220
136
            lifetime: hs_desc.lifetime,
221
136
            revision_counter: hs_desc.revision_counter,
222
136
            superencrypted: middle_encrypted,
223
136
        }
224
136
        .build_sign(rng)?;
225

            
226
136
        if hsdesc.len() > max_generated_len {
227
            return Err(EncodeError::BadLengthValue);
228
136
        }
229
136
        Ok(hsdesc)
230
136
    }
231
}
232

            
233
/// Create the descriptor signing key certificate.
234
///
235
/// Returns the encoded representation of the certificate
236
/// obtained by signing the descriptor signing key `hs_desc_sign`
237
/// with the blinded id key `blind_id`.
238
///
239
/// This certificate is meant to be passed to [`HsDescBuilder::hs_desc_sign_cert`].
240
3464
pub fn create_desc_sign_key_cert(
241
3464
    hs_desc_sign: &ed25519::PublicKey,
242
3464
    blind_id: &HsBlindIdKeypair,
243
3464
    expiry: SystemTime,
244
3464
) -> Result<EncodedEd25519Cert, CertEncodeError> {
245
    // "The certificate cross-certifies the short-term descriptor signing key with the blinded
246
    // public key.  The certificate type must be [08], and the blinded public key must be
247
    // present as the signing-key extension."
248
3464
    Ed25519Cert::builder()
249
3464
        .cert_type(CertType::HS_BLINDED_ID_V_SIGNING)
250
3464
        .expiration(expiry)
251
3464
        .signing_key(ed25519::Ed25519Identity::from(blind_id.as_ref().public()))
252
3464
        .cert_key(CertifiedKey::Ed25519(hs_desc_sign.into()))
253
3464
        .encode_and_sign(blind_id)
254
3464
}
255

            
256
impl<'a> HsDesc<'a> {
257
    /// Encrypt the specified plaintext using the algorithm described in section
258
    /// `[HS-DESC-ENCRYPTION-KEYS]` of rend-spec-v3.txt.
259
272
    fn encrypt_field<R: Rng + CryptoRng>(
260
272
        &self,
261
272
        rng: &mut R,
262
272
        plaintext: &[u8],
263
272
        desc_enc_nonce: Option<&HsDescEncNonce>,
264
272
        string_const: &[u8],
265
272
    ) -> Vec<u8> {
266
272
        let encrypt = HsDescEncryption {
267
272
            blinded_id: &ed25519::Ed25519Identity::from(self.blinded_id.as_ref()).into(),
268
272
            desc_enc_nonce,
269
272
            subcredential: &self.subcredential,
270
272
            revision: self.revision_counter,
271
272
            string_const,
272
272
        };
273

            
274
272
        encrypt.encrypt(rng, plaintext)
275
272
    }
276
}
277

            
278
/// Pad `v` with zeroes to the next multiple of `alignment`.
279
3464
fn pad_with_zero_to_align(v: &[u8], alignment: usize) -> Cow<[u8]> {
280
3464
    let padding = (alignment - (v.len() % alignment)) % alignment;
281

            
282
3464
    if padding > 0 {
283
3464
        let padded = v
284
3464
            .iter()
285
3464
            .copied()
286
3464
            .chain(std::iter::repeat_n(0, padding))
287
3464
            .collect::<Vec<_>>();
288

            
289
3464
        Cow::Owned(padded)
290
    } else {
291
        // No need to pad.
292
        Cow::Borrowed(v)
293
    }
294
3464
}
295

            
296
#[cfg(test)]
297
mod test {
298
    // @@ begin test lint list maintained by maint/add_warning @@
299
    #![allow(clippy::bool_assert_comparison)]
300
    #![allow(clippy::clone_on_copy)]
301
    #![allow(clippy::dbg_macro)]
302
    #![allow(clippy::mixed_attributes_style)]
303
    #![allow(clippy::print_stderr)]
304
    #![allow(clippy::print_stdout)]
305
    #![allow(clippy::single_char_pattern)]
306
    #![allow(clippy::unwrap_used)]
307
    #![allow(clippy::unchecked_time_subtraction)]
308
    #![allow(clippy::useless_vec)]
309
    #![allow(clippy::needless_pass_by_value)]
310
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
311

            
312
    use std::net::Ipv4Addr;
313
    use std::time::Duration;
314

            
315
    use super::*;
316
    use crate::doc::hsdesc::{EncryptedHsDesc, HsDesc as ParsedHsDesc};
317
    use tor_basic_utils::test_rng::Config;
318
    use tor_checkable::{SelfSigned, Timebound};
319
    use tor_hscrypto::pk::{HsClientDescEncKeypair, HsIdKeypair};
320
    use tor_hscrypto::time::TimePeriod;
321
    use tor_linkspec::LinkSpec;
322
    use tor_llcrypto::pk::{curve25519, ed25519::ExpandedKeypair};
323
    use web_time_compat::SystemTimeExt;
324

            
325
    // TODO: move the test helpers to a separate module and make them more broadly available if
326
    // necessary.
327

            
328
    /// Expect `err` to be a `Bug`, and return its string representation.
329
    ///
330
    /// # Panics
331
    ///
332
    /// Panics if `err` is not a `Bug`.
333
    pub(super) fn expect_bug(err: EncodeError) -> String {
334
        match err {
335
            EncodeError::Bug(b) => b.to_string(),
336
            EncodeError::BadLengthValue => panic!("expected Bug, got BadLengthValue"),
337
            _ => panic!("expected Bug, got unknown error"),
338
        }
339
    }
340

            
341
    pub(super) fn create_intro_point_descriptor<R: Rng + CryptoRng>(
342
        rng: &mut R,
343
        link_specifiers: &[LinkSpec],
344
    ) -> IntroPointDesc {
345
        let link_specifiers = link_specifiers
346
            .iter()
347
            .map(|link_spec| link_spec.encode())
348
            .collect::<Result<Vec<_>, _>>()
349
            .unwrap();
350

            
351
        IntroPointDesc {
352
            link_specifiers,
353
            ipt_ntor_key: create_curve25519_pk(rng),
354
            ipt_sid_key: ed25519::Keypair::generate(rng).verifying_key().into(),
355
            svc_ntor_key: create_curve25519_pk(rng).into(),
356
        }
357
    }
358

            
359
    /// Create a new curve25519 public key.
360
    pub(super) fn create_curve25519_pk<R: Rng + CryptoRng>(rng: &mut R) -> curve25519::PublicKey {
361
        let ephemeral_key = curve25519::EphemeralSecret::random_from_rng(rng);
362
        (&ephemeral_key).into()
363
    }
364

            
365
    /// Parse the specified hidden service descriptor.
366
    fn parse_hsdesc(
367
        unparsed_desc: &str,
368
        blinded_pk: ed25519::PublicKey,
369
        subcredential: &Subcredential,
370
        hsc_desc_enc: Option<&HsClientDescEncKeypair>,
371
    ) -> ParsedHsDesc {
372
        const TIMESTAMP: &str = "2023-01-23T15:00:00Z";
373

            
374
        let id = ed25519::Ed25519Identity::from(blinded_pk);
375
        let enc_desc: EncryptedHsDesc = ParsedHsDesc::parse(unparsed_desc, &id.into())
376
            .unwrap()
377
            .check_signature()
378
            .unwrap()
379
            .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
380
            .unwrap();
381

            
382
        enc_desc
383
            .decrypt(subcredential, hsc_desc_enc)
384
            .unwrap()
385
            .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
386
            .unwrap()
387
            .check_signature()
388
            .unwrap()
389
    }
390

            
391
    #[test]
392
    fn encode_decode() {
393
        const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::TAP, HandshakeType::NTOR];
394
        const LIFETIME_MINS: u16 = 100;
395
        const REVISION_COUNT: u64 = 2;
396
        const CERT_EXPIRY_SECS: u64 = 60 * 60;
397

            
398
        let mut rng = Config::Deterministic.into_rng();
399
        // The identity keypair of the hidden service.
400
        let hs_id = ed25519::Keypair::generate(&mut rng);
401
        let hs_desc_sign = ed25519::Keypair::generate(&mut rng);
402
        let period = TimePeriod::new(
403
            humantime::parse_duration("24 hours").unwrap(),
404
            humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
405
            humantime::parse_duration("12 hours").unwrap(),
406
        )
407
        .unwrap();
408
        let (_, blinded_id, subcredential) = HsIdKeypair::from(ExpandedKeypair::from(&hs_id))
409
            .compute_blinded_key(period)
410
            .unwrap();
411

            
412
        let expiry = SystemTime::get() + Duration::from_secs(CERT_EXPIRY_SECS);
413
        let mut rng = Config::Deterministic.into_rng();
414
        let intro_points = vec![IntroPointDesc {
415
            link_specifiers: vec![
416
                LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999)
417
                    .encode()
418
                    .unwrap(),
419
            ],
420
            ipt_ntor_key: create_curve25519_pk(&mut rng),
421
            ipt_sid_key: ed25519::Keypair::generate(&mut rng).verifying_key().into(),
422
            svc_ntor_key: create_curve25519_pk(&mut rng).into(),
423
        }];
424

            
425
        let hs_desc_sign_cert =
426
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
427
        let blinded_pk = (&blinded_id).into();
428
        let builder = HsDescBuilder::default()
429
            .blinded_id(&blinded_pk)
430
            .hs_desc_sign(&hs_desc_sign)
431
            .hs_desc_sign_cert(hs_desc_sign_cert)
432
            .create2_formats(CREATE2_FORMATS)
433
            .auth_required(None)
434
            .is_single_onion_service(true)
435
            .intro_points(&intro_points)
436
            .intro_auth_key_cert_expiry(expiry)
437
            .intro_enc_key_cert_expiry(expiry)
438
            .lifetime(LIFETIME_MINS.into())
439
            .revision_counter(REVISION_COUNT.into())
440
            .subcredential(subcredential);
441

            
442
        // Build and encode a new descriptor (cloning `builder` because it's needed later, when we
443
        // test if restricted discovery works):
444
        let encoded_desc = builder
445
            .clone()
446
            .build_sign(&mut Config::Deterministic.into_rng())
447
            .unwrap();
448

            
449
        // Now decode it...
450
        let desc = parse_hsdesc(
451
            encoded_desc.as_str(),
452
            *blinded_id.as_ref().public(),
453
            &subcredential,
454
            None, /* No restricted discovery */
455
        );
456

            
457
        let hs_desc_sign_cert =
458
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
459
        // ...and build a new descriptor using the information from the parsed descriptor,
460
        // asserting that the resulting descriptor is identical to the original.
461
        let reencoded_desc = HsDescBuilder::default()
462
            .blinded_id(&(&blinded_id).into())
463
            .hs_desc_sign(&hs_desc_sign)
464
            .hs_desc_sign_cert(hs_desc_sign_cert)
465
            // create2_formats is hard-coded rather than extracted from desc, because
466
            // create2_formats is ignored while parsing
467
            .create2_formats(CREATE2_FORMATS)
468
            .auth_required(None)
469
            .is_single_onion_service(desc.is_single_onion_service)
470
            .intro_points(&intro_points)
471
            .intro_auth_key_cert_expiry(expiry)
472
            .intro_enc_key_cert_expiry(expiry)
473
            .lifetime(desc.idx_info.lifetime)
474
            .revision_counter(desc.idx_info.revision)
475
            .subcredential(subcredential)
476
            .build_sign(&mut Config::Deterministic.into_rng())
477
            .unwrap();
478

            
479
        assert_eq!(&*encoded_desc, &*reencoded_desc);
480

            
481
        // The same test, this time with restricted discovery enabled
482
        // (with a single authorized client):
483
        let client_kp: HsClientDescEncKeypair = HsClientDescEncKeypair::generate(&mut rng);
484
        let client_pkey = client_kp.public().as_ref();
485
        let auth_clients = vec![*client_pkey];
486

            
487
        let encoded_desc = builder
488
            .auth_clients(Some(&auth_clients[..]))
489
            .build_sign(&mut Config::Deterministic.into_rng())
490
            .unwrap();
491

            
492
        // Now decode it...
493
        let desc = parse_hsdesc(
494
            encoded_desc.as_str(),
495
            *blinded_id.as_ref().public(),
496
            &subcredential,
497
            Some(&client_kp), /* With restricted discovery */
498
        );
499

            
500
        let hs_desc_sign_cert =
501
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
502
        // ...and build a new descriptor using the information from the parsed descriptor,
503
        // asserting that the resulting descriptor is identical to the original.
504
        let reencoded_desc = HsDescBuilder::default()
505
            .blinded_id(&(&blinded_id).into())
506
            .hs_desc_sign(&hs_desc_sign)
507
            .hs_desc_sign_cert(hs_desc_sign_cert)
508
            // create2_formats is hard-coded rather than extracted from desc, because
509
            // create2_formats is ignored while parsing
510
            .create2_formats(CREATE2_FORMATS)
511
            .auth_required(None)
512
            .is_single_onion_service(desc.is_single_onion_service)
513
            .intro_points(&intro_points)
514
            .intro_auth_key_cert_expiry(expiry)
515
            .intro_enc_key_cert_expiry(expiry)
516
            .auth_clients(Some(&auth_clients))
517
            .lifetime(desc.idx_info.lifetime)
518
            .revision_counter(desc.idx_info.revision)
519
            .subcredential(subcredential)
520
            .build_sign(&mut Config::Deterministic.into_rng())
521
            .unwrap();
522

            
523
        assert_eq!(&*encoded_desc, &*reencoded_desc);
524
    }
525
}