1
//! Hidden service descriptor encoding.
2

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

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

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

            
109
impl<'a> ClientAuth<'a> {
110
    /// Create a new `ClientAuth` using the specified authorized clients.
111
    ///
112
    /// If `auth_clients` is empty list, there will be no authorized clients.
113
    ///
114
    /// This returns `None` if the list of `auth_clients` is `None`.
115
152
    fn new<R: RngCore + CryptoRng>(
116
152
        auth_clients: Option<&'a [curve25519::PublicKey]>,
117
152
        rng: &mut R,
118
152
    ) -> Option<ClientAuth<'a>> {
119
152
        let Some(auth_clients) = auth_clients else {
120
            // Restricted discovery is disabled
121
148
            return None;
122
        };
123

            
124
        // Generate a new `N_hs_desc_enc` descriptor_cookie key for this descriptor.
125
4
        let descriptor_cookie = rand::Rng::gen::<[u8; HS_DESC_ENC_NONCE_LEN]>(rng);
126
4

            
127
4
        let secret = curve25519::StaticSecret::random_from_rng(rng);
128
4
        let ephemeral_key = HsSvcDescEncKeypair {
129
4
            public: curve25519::PublicKey::from(&secret).into(),
130
4
            secret: secret.into(),
131
4
        };
132
4

            
133
4
        Some(ClientAuth {
134
4
            ephemeral_key,
135
4
            auth_clients,
136
4
            descriptor_cookie,
137
4
        })
138
152
    }
139
}
140

            
141
impl<'a> NetdocBuilder for HsDescBuilder<'a> {
142
152
    fn build_sign<R: RngCore + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
143
        /// The superencrypted field must be padded to the nearest multiple of 10k bytes
144
        ///
145
        /// rend-spec-v3 2.5.1.1
146
        const SUPERENCRYPTED_ALIGN: usize = 10 * (1 << 10);
147

            
148
152
        let hs_desc = self
149
152
            .build()
150
152
            .map_err(into_bad_api_usage!("the HsDesc could not be built"))?;
151

            
152
152
        let client_auth = ClientAuth::new(hs_desc.auth_clients, rng);
153

            
154
        // Construct the inner (second layer) plaintext. This is the unencrypted value of the
155
        // "encrypted" field.
156
152
        let inner_plaintext = HsDescInner {
157
152
            hs_desc_sign: hs_desc.hs_desc_sign,
158
152
            create2_formats: hs_desc.create2_formats,
159
152
            auth_required: hs_desc.auth_required.as_ref(),
160
152
            is_single_onion_service: hs_desc.is_single_onion_service,
161
152
            intro_points: hs_desc.intro_points,
162
152
            intro_auth_key_cert_expiry: hs_desc.intro_auth_key_cert_expiry,
163
152
            intro_enc_key_cert_expiry: hs_desc.intro_enc_key_cert_expiry,
164
152
            #[cfg(feature = "hs-pow-full")]
165
152
            pow_params: hs_desc.pow_params,
166
152
        }
167
152
        .build_sign(rng)?;
168

            
169
152
        let desc_enc_nonce = client_auth
170
152
            .as_ref()
171
152
            .map(|client_auth| client_auth.descriptor_cookie.into());
172
152

            
173
152
        // Encrypt the inner document. The encrypted blob is the ciphertext contained in the
174
152
        // "encrypted" field described in section 2.5.1.2. of rend-spec-v3.
175
152
        let inner_encrypted = hs_desc.encrypt_field(
176
152
            rng,
177
152
            inner_plaintext.as_bytes(),
178
152
            desc_enc_nonce.as_ref(),
179
152
            b"hsdir-encrypted-data",
180
152
        );
181

            
182
        // Construct the middle (first player) plaintext. This is the unencrypted value of the
183
        // "superencrypted" field.
184
152
        let middle_plaintext = HsDescMiddle {
185
152
            client_auth: client_auth.as_ref(),
186
152
            subcredential: hs_desc.subcredential,
187
152
            encrypted: inner_encrypted,
188
152
        }
189
152
        .build_sign(rng)?;
190

            
191
        // Section 2.5.1.1. of rend-spec-v3: before encryption, pad the plaintext to the nearest
192
        // multiple of 10k bytes
193
152
        let middle_plaintext =
194
152
            pad_with_zero_to_align(middle_plaintext.as_bytes(), SUPERENCRYPTED_ALIGN);
195
152

            
196
152
        // Encrypt the middle document. The encrypted blob is the ciphertext contained in the
197
152
        // "superencrypted" field described in section 2.5.1.1. of rend-spec-v3.
198
152
        let middle_encrypted = hs_desc.encrypt_field(
199
152
            rng,
200
152
            middle_plaintext.borrow(),
201
152
            // desc_enc_nonce is absent when handling the superencryption layer (2.5.1.1).
202
152
            None,
203
152
            b"hsdir-superencrypted-data",
204
152
        );
205
152

            
206
152
        // Finally, build the hidden service descriptor.
207
152
        HsDescOuter {
208
152
            hs_desc_sign: hs_desc.hs_desc_sign,
209
152
            hs_desc_sign_cert: hs_desc.hs_desc_sign_cert,
210
152
            lifetime: hs_desc.lifetime,
211
152
            revision_counter: hs_desc.revision_counter,
212
152
            superencrypted: middle_encrypted,
213
152
        }
214
152
        .build_sign(rng)
215
152
    }
216
}
217

            
218
/// Create the descriptor signing key certificate.
219
///
220
/// Returns the encoded representation of the certificate
221
/// obtained by signing the descriptor signing key `hs_desc_sign`
222
/// with the blinded id key `blind_id`.
223
///
224
/// This certificate is meant to be passed to [`HsDescBuilder::hs_desc_sign_cert`].
225
2960
pub fn create_desc_sign_key_cert(
226
2960
    hs_desc_sign: &ed25519::PublicKey,
227
2960
    blind_id: &HsBlindIdKeypair,
228
2960
    expiry: SystemTime,
229
2960
) -> Result<EncodedEd25519Cert, CertEncodeError> {
230
2960
    // "The certificate cross-certifies the short-term descriptor signing key with the blinded
231
2960
    // public key.  The certificate type must be [08], and the blinded public key must be
232
2960
    // present as the signing-key extension."
233
2960
    Ed25519Cert::constructor()
234
2960
        .cert_type(CertType::HS_BLINDED_ID_V_SIGNING)
235
2960
        .expiration(expiry)
236
2960
        .signing_key(ed25519::Ed25519Identity::from(blind_id.as_ref().public()))
237
2960
        .cert_key(CertifiedKey::Ed25519(hs_desc_sign.into()))
238
2960
        .encode_and_sign(blind_id)
239
2960
}
240

            
241
impl<'a> HsDesc<'a> {
242
    /// Encrypt the specified plaintext using the algorithm described in section
243
    /// `[HS-DESC-ENCRYPTION-KEYS]` of rend-spec-v3.txt.
244
304
    fn encrypt_field<R: RngCore + CryptoRng>(
245
304
        &self,
246
304
        rng: &mut R,
247
304
        plaintext: &[u8],
248
304
        desc_enc_nonce: Option<&HsDescEncNonce>,
249
304
        string_const: &[u8],
250
304
    ) -> Vec<u8> {
251
304
        let encrypt = HsDescEncryption {
252
304
            blinded_id: &ed25519::Ed25519Identity::from(self.blinded_id.as_ref()).into(),
253
304
            desc_enc_nonce,
254
304
            subcredential: &self.subcredential,
255
304
            revision: self.revision_counter,
256
304
            string_const,
257
304
        };
258
304

            
259
304
        encrypt.encrypt(rng, plaintext)
260
304
    }
261
}
262

            
263
/// Pad `v` with zeroes to the next multiple of `alignment`.
264
2960
fn pad_with_zero_to_align(v: &[u8], alignment: usize) -> Cow<[u8]> {
265
2960
    let padding = (alignment - (v.len() % alignment)) % alignment;
266
2960

            
267
2960
    if padding > 0 {
268
2960
        let padded = v
269
2960
            .iter()
270
2960
            .copied()
271
2960
            .chain(std::iter::repeat(0).take(padding))
272
2960
            .collect::<Vec<_>>();
273
2960

            
274
2960
        Cow::Owned(padded)
275
    } else {
276
        // No need to pad.
277
        Cow::Borrowed(v)
278
    }
279
2960
}
280

            
281
#[cfg(test)]
282
mod test {
283
    // @@ begin test lint list maintained by maint/add_warning @@
284
    #![allow(clippy::bool_assert_comparison)]
285
    #![allow(clippy::clone_on_copy)]
286
    #![allow(clippy::dbg_macro)]
287
    #![allow(clippy::mixed_attributes_style)]
288
    #![allow(clippy::print_stderr)]
289
    #![allow(clippy::print_stdout)]
290
    #![allow(clippy::single_char_pattern)]
291
    #![allow(clippy::unwrap_used)]
292
    #![allow(clippy::unchecked_duration_subtraction)]
293
    #![allow(clippy::useless_vec)]
294
    #![allow(clippy::needless_pass_by_value)]
295
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
296

            
297
    use std::net::Ipv4Addr;
298
    use std::time::Duration;
299

            
300
    use super::*;
301
    use crate::doc::hsdesc::{EncryptedHsDesc, HsDesc as ParsedHsDesc};
302
    use tor_basic_utils::test_rng::Config;
303
    use tor_checkable::{SelfSigned, Timebound};
304
    use tor_hscrypto::pk::{HsClientDescEncKeypair, HsIdKeypair};
305
    use tor_hscrypto::time::TimePeriod;
306
    use tor_linkspec::LinkSpec;
307
    use tor_llcrypto::pk::{curve25519, ed25519::ExpandedKeypair};
308

            
309
    // TODO: move the test helpers to a separate module and make them more broadly available if
310
    // necessary.
311

            
312
    /// Expect `err` to be a `Bug`, and return its string representation.
313
    ///
314
    /// # Panics
315
    ///
316
    /// Panics if `err` is not a `Bug`.
317
    pub(super) fn expect_bug(err: EncodeError) -> String {
318
        match err {
319
            EncodeError::Bug(b) => b.to_string(),
320
            EncodeError::BadLengthValue => panic!("expected Bug, got BadLengthValue"),
321
            _ => panic!("expected Bug, got unknown error"),
322
        }
323
    }
324

            
325
    pub(super) fn create_intro_point_descriptor<R: RngCore + CryptoRng>(
326
        rng: &mut R,
327
        link_specifiers: &[LinkSpec],
328
    ) -> IntroPointDesc {
329
        let link_specifiers = link_specifiers
330
            .iter()
331
            .map(|link_spec| link_spec.encode())
332
            .collect::<Result<Vec<_>, _>>()
333
            .unwrap();
334

            
335
        IntroPointDesc {
336
            link_specifiers,
337
            ipt_ntor_key: create_curve25519_pk(rng),
338
            ipt_sid_key: ed25519::Keypair::generate(rng).verifying_key().into(),
339
            svc_ntor_key: create_curve25519_pk(rng).into(),
340
        }
341
    }
342

            
343
    /// Create a new curve25519 public key.
344
    pub(super) fn create_curve25519_pk<R: RngCore + CryptoRng>(
345
        rng: &mut R,
346
    ) -> curve25519::PublicKey {
347
        let ephemeral_key = curve25519::EphemeralSecret::random_from_rng(rng);
348
        (&ephemeral_key).into()
349
    }
350

            
351
    /// Parse the specified hidden service descriptor.
352
    fn parse_hsdesc(
353
        unparsed_desc: &str,
354
        blinded_pk: ed25519::PublicKey,
355
        subcredential: &Subcredential,
356
        hsc_desc_enc: Option<&HsClientDescEncKeypair>,
357
    ) -> ParsedHsDesc {
358
        const TIMESTAMP: &str = "2023-01-23T15:00:00Z";
359

            
360
        let id = ed25519::Ed25519Identity::from(blinded_pk);
361
        let enc_desc: EncryptedHsDesc = ParsedHsDesc::parse(unparsed_desc, &id.into())
362
            .unwrap()
363
            .check_signature()
364
            .unwrap()
365
            .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
366
            .unwrap();
367

            
368
        enc_desc
369
            .decrypt(subcredential, hsc_desc_enc)
370
            .unwrap()
371
            .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
372
            .unwrap()
373
            .check_signature()
374
            .unwrap()
375
    }
376

            
377
    #[test]
378
    fn encode_decode() {
379
        const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::TAP, HandshakeType::NTOR];
380
        const LIFETIME_MINS: u16 = 100;
381
        const REVISION_COUNT: u64 = 2;
382
        const CERT_EXPIRY_SECS: u64 = 60 * 60;
383

            
384
        let mut rng = Config::Deterministic.into_rng();
385
        // The identity keypair of the hidden service.
386
        let hs_id = ed25519::Keypair::generate(&mut rng);
387
        let hs_desc_sign = ed25519::Keypair::generate(&mut rng);
388
        let period = TimePeriod::new(
389
            humantime::parse_duration("24 hours").unwrap(),
390
            humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
391
            humantime::parse_duration("12 hours").unwrap(),
392
        )
393
        .unwrap();
394
        let (_, blinded_id, subcredential) = HsIdKeypair::from(ExpandedKeypair::from(&hs_id))
395
            .compute_blinded_key(period)
396
            .unwrap();
397

            
398
        let expiry = SystemTime::now() + Duration::from_secs(CERT_EXPIRY_SECS);
399
        let mut rng = Config::Deterministic.into_rng();
400
        let intro_points = vec![IntroPointDesc {
401
            link_specifiers: vec![LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999)
402
                .encode()
403
                .unwrap()],
404
            ipt_ntor_key: create_curve25519_pk(&mut rng),
405
            ipt_sid_key: ed25519::Keypair::generate(&mut rng).verifying_key().into(),
406
            svc_ntor_key: create_curve25519_pk(&mut rng).into(),
407
        }];
408

            
409
        let hs_desc_sign_cert =
410
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
411
        let blinded_pk = (&blinded_id).into();
412
        let builder = HsDescBuilder::default()
413
            .blinded_id(&blinded_pk)
414
            .hs_desc_sign(&hs_desc_sign)
415
            .hs_desc_sign_cert(hs_desc_sign_cert)
416
            .create2_formats(CREATE2_FORMATS)
417
            .auth_required(None)
418
            .is_single_onion_service(true)
419
            .intro_points(&intro_points)
420
            .intro_auth_key_cert_expiry(expiry)
421
            .intro_enc_key_cert_expiry(expiry)
422
            .lifetime(LIFETIME_MINS.into())
423
            .revision_counter(REVISION_COUNT.into())
424
            .subcredential(subcredential);
425

            
426
        // Build and encode a new descriptor (cloning `builder` because it's needed later, when we
427
        // test if restricted discovery works):
428
        let encoded_desc = builder
429
            .clone()
430
            .build_sign(&mut Config::Deterministic.into_rng())
431
            .unwrap();
432

            
433
        // Now decode it...
434
        let desc = parse_hsdesc(
435
            encoded_desc.as_str(),
436
            *blinded_id.as_ref().public(),
437
            &subcredential,
438
            None, /* No restricted discovery */
439
        );
440

            
441
        let hs_desc_sign_cert =
442
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
443
        // ...and build a new descriptor using the information from the parsed descriptor,
444
        // asserting that the resulting descriptor is identical to the original.
445
        let reencoded_desc = HsDescBuilder::default()
446
            .blinded_id(&(&blinded_id).into())
447
            .hs_desc_sign(&hs_desc_sign)
448
            .hs_desc_sign_cert(hs_desc_sign_cert)
449
            // create2_formats is hard-coded rather than extracted from desc, because
450
            // create2_formats is ignored while parsing
451
            .create2_formats(CREATE2_FORMATS)
452
            .auth_required(None)
453
            .is_single_onion_service(desc.is_single_onion_service)
454
            .intro_points(&intro_points)
455
            .intro_auth_key_cert_expiry(expiry)
456
            .intro_enc_key_cert_expiry(expiry)
457
            .lifetime(desc.idx_info.lifetime)
458
            .revision_counter(desc.idx_info.revision)
459
            .subcredential(subcredential)
460
            .build_sign(&mut Config::Deterministic.into_rng())
461
            .unwrap();
462

            
463
        assert_eq!(&*encoded_desc, &*reencoded_desc);
464

            
465
        // The same test, this time with restricted discovery enabled
466
        // (with a single authorized client):
467
        let client_kp: HsClientDescEncKeypair = HsClientDescEncKeypair::generate(&mut rng);
468
        let client_pkey = client_kp.public().as_ref();
469
        let auth_clients = vec![*client_pkey];
470

            
471
        let encoded_desc = builder
472
            .auth_clients(Some(&auth_clients[..]))
473
            .build_sign(&mut Config::Deterministic.into_rng())
474
            .unwrap();
475

            
476
        // Now decode it...
477
        let desc = parse_hsdesc(
478
            encoded_desc.as_str(),
479
            *blinded_id.as_ref().public(),
480
            &subcredential,
481
            Some(&client_kp), /* With restricted discovery */
482
        );
483

            
484
        let hs_desc_sign_cert =
485
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
486
        // ...and build a new descriptor using the information from the parsed descriptor,
487
        // asserting that the resulting descriptor is identical to the original.
488
        let reencoded_desc = HsDescBuilder::default()
489
            .blinded_id(&(&blinded_id).into())
490
            .hs_desc_sign(&hs_desc_sign)
491
            .hs_desc_sign_cert(hs_desc_sign_cert)
492
            // create2_formats is hard-coded rather than extracted from desc, because
493
            // create2_formats is ignored while parsing
494
            .create2_formats(CREATE2_FORMATS)
495
            .auth_required(None)
496
            .is_single_onion_service(desc.is_single_onion_service)
497
            .intro_points(&intro_points)
498
            .intro_auth_key_cert_expiry(expiry)
499
            .intro_enc_key_cert_expiry(expiry)
500
            .auth_clients(Some(&auth_clients))
501
            .lifetime(desc.idx_info.lifetime)
502
            .revision_counter(desc.idx_info.revision)
503
            .subcredential(subcredential)
504
            .build_sign(&mut Config::Deterministic.into_rng())
505
            .unwrap();
506

            
507
        assert_eq!(&*encoded_desc, &*reencoded_desc);
508
    }
509
}