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

            
32
/// An intermediary type for encoding hidden service descriptors.
33
///
34
/// This object is constructed via [`HsDescBuilder`], and then turned into a
35
/// signed document using [`HsDescBuilder::build_sign()`].
36
///
37
/// TODO: Add an example for using this API.
38
#[derive(Builder)]
39
#[builder(public, derive(Debug, Clone), pattern = "owned", build_fn(vis = ""))]
40
struct HsDesc<'a> {
41
    /// The blinded hidden service public key used for the first half of the "SECRET_DATA" field.
42
    ///
43
    /// (See rend-spec v3 2.5.1.1 and 2.5.2.1.)
44
    blinded_id: &'a HsBlindIdKey,
45
    /// The short-term descriptor signing key (KP_hs_desc_sign, KS_hs_desc_sign).
46
    hs_desc_sign: &'a ed25519::Keypair,
47
    /// The descriptor signing key certificate.
48
    ///
49
    /// This certificate can be created using [`create_desc_sign_key_cert`].
50
    hs_desc_sign_cert: EncodedEd25519Cert,
51
    /// A list of recognized CREATE handshakes that this onion service supports.
52
    create2_formats: &'a [HandshakeType],
53
    /// A list of authentication types that this onion service supports.
54
    auth_required: Option<SmallVec<[IntroAuthType; 2]>>,
55
    /// If true, this a "single onion service" and is not trying to keep its own location private.
56
    is_single_onion_service: bool,
57
    /// One or more introduction points used to contact the onion service.
58
    intro_points: &'a [IntroPointDesc],
59
    /// The expiration time of an introduction point authentication key certificate.
60
    intro_auth_key_cert_expiry: SystemTime,
61
    /// The expiration time of an introduction point encryption key certificate.
62
    intro_enc_key_cert_expiry: SystemTime,
63
    /// The list of clients authorized to access the hidden service.
64
    ///
65
    /// If `None`, client authentication is disabled.
66
    /// If `Some(&[])`, client authorization is enabled,
67
    /// but there will be no authorized clients.
68
    ///
69
    /// If client authorization is disabled, the resulting middle document will contain a single
70
    /// `auth-client` line populated with random values.
71
    ///
72
    /// Client authorization is disabled by default.
73
    #[builder(default)]
74
    auth_clients: Option<&'a [curve25519::PublicKey]>,
75
    /// The lifetime of this descriptor, in minutes.
76
    ///
77
    /// This doesn't actually list the starting time or the end time for the
78
    /// descriptor: presumably, because we didn't want to leak the onion
79
    /// service's view of the wallclock.
80
    lifetime: IntegerMinutes<u16>,
81
    /// A revision counter to tell whether this descriptor is more or less recent
82
    /// than another one for the same blinded ID.
83
    revision_counter: RevisionCounter,
84
    /// The "subcredential" of the onion service.
85
    subcredential: Subcredential,
86
}
87

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

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

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

            
122
        let secret = curve25519::StaticSecret::random_from_rng(rng);
123
        let ephemeral_key = HsSvcDescEncKeypair {
124
            public: curve25519::PublicKey::from(&secret).into(),
125
            secret: secret.into(),
126
        };
127

            
128
        Some(ClientAuth {
129
            ephemeral_key,
130
            auth_clients,
131
            descriptor_cookie,
132
        })
133
    }
134
}
135

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

            
143
        let hs_desc = self
144
            .build()
145
            .map_err(into_bad_api_usage!("the HsDesc could not be built"))?;
146

            
147
        let client_auth = ClientAuth::new(hs_desc.auth_clients, rng);
148

            
149
        // Construct the inner (second layer) plaintext. This is the unencrypted value of the
150
        // "encrypted" field.
151
        let inner_plaintext = HsDescInner {
152
            hs_desc_sign: hs_desc.hs_desc_sign,
153
            create2_formats: hs_desc.create2_formats,
154
            auth_required: hs_desc.auth_required.as_ref(),
155
            is_single_onion_service: hs_desc.is_single_onion_service,
156
            intro_points: hs_desc.intro_points,
157
            intro_auth_key_cert_expiry: hs_desc.intro_auth_key_cert_expiry,
158
            intro_enc_key_cert_expiry: hs_desc.intro_enc_key_cert_expiry,
159
        }
160
        .build_sign(rng)?;
161

            
162
        let desc_enc_nonce = client_auth
163
            .as_ref()
164
            .map(|client_auth| client_auth.descriptor_cookie.into());
165

            
166
        // Encrypt the inner document. The encrypted blob is the ciphertext contained in the
167
        // "encrypted" field described in section 2.5.1.2. of rend-spec-v3.
168
        let inner_encrypted = hs_desc.encrypt_field(
169
            rng,
170
            inner_plaintext.as_bytes(),
171
            desc_enc_nonce.as_ref(),
172
            b"hsdir-encrypted-data",
173
        );
174

            
175
        // Construct the middle (first player) plaintext. This is the unencrypted value of the
176
        // "superencrypted" field.
177
        let middle_plaintext = HsDescMiddle {
178
            client_auth: client_auth.as_ref(),
179
            subcredential: hs_desc.subcredential,
180
            encrypted: inner_encrypted,
181
        }
182
        .build_sign(rng)?;
183

            
184
        // Section 2.5.1.1. of rend-spec-v3: before encryption, pad the plaintext to the nearest
185
        // multiple of 10k bytes
186
        let middle_plaintext =
187
            pad_with_zero_to_align(middle_plaintext.as_bytes(), SUPERENCRYPTED_ALIGN);
188

            
189
        // Encrypt the middle document. The encrypted blob is the ciphertext contained in the
190
        // "superencrypted" field described in section 2.5.1.1. of rend-spec-v3.
191
        let middle_encrypted = hs_desc.encrypt_field(
192
            rng,
193
            middle_plaintext.borrow(),
194
            // desc_enc_nonce is absent when handling the superencryption layer (2.5.1.1).
195
            None,
196
            b"hsdir-superencrypted-data",
197
        );
198

            
199
        // Finally, build the hidden service descriptor.
200
        HsDescOuter {
201
            hs_desc_sign: hs_desc.hs_desc_sign,
202
            hs_desc_sign_cert: hs_desc.hs_desc_sign_cert,
203
            lifetime: hs_desc.lifetime,
204
            revision_counter: hs_desc.revision_counter,
205
            superencrypted: middle_encrypted,
206
        }
207
        .build_sign(rng)
208
    }
209
}
210

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

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

            
252
        encrypt.encrypt(rng, plaintext)
253
    }
254
}
255

            
256
/// Pad `v` with zeroes to the next multiple of `alignment`.
257
fn pad_with_zero_to_align(v: &[u8], alignment: usize) -> Cow<[u8]> {
258
    let padding = (alignment - (v.len() % alignment)) % alignment;
259

            
260
    if padding > 0 {
261
        let padded = v
262
            .iter()
263
            .copied()
264
            .chain(std::iter::repeat(0).take(padding))
265
            .collect::<Vec<_>>();
266

            
267
        Cow::Owned(padded)
268
    } else {
269
        // No need to pad.
270
        Cow::Borrowed(v)
271
    }
272
}
273

            
274
#[cfg(test)]
275
mod test {
276
    // @@ begin test lint list maintained by maint/add_warning @@
277
    #![allow(clippy::bool_assert_comparison)]
278
    #![allow(clippy::clone_on_copy)]
279
    #![allow(clippy::dbg_macro)]
280
    #![allow(clippy::print_stderr)]
281
    #![allow(clippy::print_stdout)]
282
    #![allow(clippy::single_char_pattern)]
283
    #![allow(clippy::unwrap_used)]
284
    #![allow(clippy::unchecked_duration_subtraction)]
285
    #![allow(clippy::useless_vec)]
286
    #![allow(clippy::needless_pass_by_value)]
287
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
288

            
289
    use std::net::Ipv4Addr;
290
    use std::time::Duration;
291

            
292
    use super::*;
293
    use crate::doc::hsdesc::{EncryptedHsDesc, HsDesc as ParsedHsDesc};
294
    use tor_basic_utils::test_rng::Config;
295
    use tor_checkable::{SelfSigned, Timebound};
296
    use tor_hscrypto::pk::{HsClientDescEncKeypair, HsIdKeypair};
297
    use tor_hscrypto::time::TimePeriod;
298
    use tor_linkspec::LinkSpec;
299
    use tor_llcrypto::pk::{curve25519, ed25519::ExpandedKeypair};
300

            
301
    // TODO: move the test helpers to a separate module and make them more broadly available if
302
    // necessary.
303

            
304
    /// Expect `err` to be a `Bug`, and return its string representation.
305
    ///
306
    /// # Panics
307
    ///
308
    /// Panics if `err` is not a `Bug`.
309
    pub(super) fn expect_bug(err: EncodeError) -> String {
310
        match err {
311
            EncodeError::Bug(b) => b.to_string(),
312
            EncodeError::BadLengthValue => panic!("expected Bug, got BadLengthValue"),
313
            _ => panic!("expected Bug, got unknown error"),
314
        }
315
    }
316

            
317
    pub(super) fn create_intro_point_descriptor<R: RngCore + CryptoRng>(
318
        rng: &mut R,
319
        link_specifiers: &[LinkSpec],
320
    ) -> IntroPointDesc {
321
        let link_specifiers = link_specifiers
322
            .iter()
323
            .map(|link_spec| link_spec.encode())
324
            .collect::<Result<Vec<_>, _>>()
325
            .unwrap();
326

            
327
        IntroPointDesc {
328
            link_specifiers,
329
            ipt_ntor_key: create_curve25519_pk(rng),
330
            ipt_sid_key: ed25519::Keypair::generate(rng).verifying_key().into(),
331
            svc_ntor_key: create_curve25519_pk(rng).into(),
332
        }
333
    }
334

            
335
    /// Create a new curve25519 public key.
336
    pub(super) fn create_curve25519_pk<R: RngCore + CryptoRng>(
337
        rng: &mut R,
338
    ) -> curve25519::PublicKey {
339
        let ephemeral_key = curve25519::EphemeralSecret::random_from_rng(rng);
340
        (&ephemeral_key).into()
341
    }
342

            
343
    /// Parse the specified hidden service descriptor.
344
    fn parse_hsdesc(
345
        unparsed_desc: &str,
346
        blinded_pk: ed25519::PublicKey,
347
        subcredential: &Subcredential,
348
        hsc_desc_enc: Option<&HsClientDescEncKeypair>,
349
    ) -> ParsedHsDesc {
350
        const TIMESTAMP: &str = "2023-01-23T15:00:00Z";
351

            
352
        let id = ed25519::Ed25519Identity::from(blinded_pk);
353
        let enc_desc: EncryptedHsDesc = ParsedHsDesc::parse(unparsed_desc, &id.into())
354
            .unwrap()
355
            .check_signature()
356
            .unwrap()
357
            .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
358
            .unwrap();
359

            
360
        enc_desc
361
            .decrypt(subcredential, hsc_desc_enc)
362
            .unwrap()
363
            .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
364
            .unwrap()
365
            .check_signature()
366
            .unwrap()
367
    }
368

            
369
    #[test]
370
    fn encode_decode() {
371
        const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::TAP, HandshakeType::NTOR];
372
        const LIFETIME_MINS: u16 = 100;
373
        const REVISION_COUNT: u64 = 2;
374
        const CERT_EXPIRY_SECS: u64 = 60 * 60;
375

            
376
        let mut rng = Config::Deterministic.into_rng();
377
        // The identity keypair of the hidden service.
378
        let hs_id = ed25519::Keypair::generate(&mut rng);
379
        let hs_desc_sign = ed25519::Keypair::generate(&mut rng);
380
        let period = TimePeriod::new(
381
            humantime::parse_duration("24 hours").unwrap(),
382
            humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
383
            humantime::parse_duration("12 hours").unwrap(),
384
        )
385
        .unwrap();
386
        let (_, blinded_id, subcredential) = HsIdKeypair::from(ExpandedKeypair::from(&hs_id))
387
            .compute_blinded_key(period)
388
            .unwrap();
389

            
390
        let expiry = SystemTime::now() + Duration::from_secs(CERT_EXPIRY_SECS);
391
        let mut rng = Config::Deterministic.into_rng();
392
        let intro_points = vec![IntroPointDesc {
393
            link_specifiers: vec![LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999)
394
                .encode()
395
                .unwrap()],
396
            ipt_ntor_key: create_curve25519_pk(&mut rng),
397
            ipt_sid_key: ed25519::Keypair::generate(&mut rng).verifying_key().into(),
398
            svc_ntor_key: create_curve25519_pk(&mut rng).into(),
399
        }];
400

            
401
        let hs_desc_sign_cert =
402
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
403
        let blinded_pk = (&blinded_id).into();
404
        let builder = HsDescBuilder::default()
405
            .blinded_id(&blinded_pk)
406
            .hs_desc_sign(&hs_desc_sign)
407
            .hs_desc_sign_cert(hs_desc_sign_cert)
408
            .create2_formats(CREATE2_FORMATS)
409
            .auth_required(None)
410
            .is_single_onion_service(true)
411
            .intro_points(&intro_points)
412
            .intro_auth_key_cert_expiry(expiry)
413
            .intro_enc_key_cert_expiry(expiry)
414
            .lifetime(LIFETIME_MINS.into())
415
            .revision_counter(REVISION_COUNT.into())
416
            .subcredential(subcredential);
417

            
418
        // Build and encode a new descriptor (cloning `builder` because it's needed later, when we
419
        // test if client auth works):
420
        let encoded_desc = builder
421
            .clone()
422
            .build_sign(&mut Config::Deterministic.into_rng())
423
            .unwrap();
424

            
425
        // Now decode it...
426
        let desc = parse_hsdesc(
427
            encoded_desc.as_str(),
428
            *blinded_id.as_ref().public(),
429
            &subcredential,
430
            None, /* No client auth */
431
        );
432

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

            
455
        assert_eq!(&*encoded_desc, &*reencoded_desc);
456

            
457
        // The same test, this time with client auth enabled (with a single authorized client):
458
        let client_kp: HsClientDescEncKeypair = HsClientDescEncKeypair::generate(&mut rng);
459
        let client_pkey = client_kp.public().as_ref();
460
        let auth_clients = vec![*client_pkey];
461

            
462
        let encoded_desc = builder
463
            .auth_clients(Some(&auth_clients[..]))
464
            .build_sign(&mut Config::Deterministic.into_rng())
465
            .unwrap();
466

            
467
        // Now decode it...
468
        let desc = parse_hsdesc(
469
            encoded_desc.as_str(),
470
            *blinded_id.as_ref().public(),
471
            &subcredential,
472
            Some(&client_kp), /* With client auth */
473
        );
474

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

            
498
        assert_eq!(&*encoded_desc, &*reencoded_desc);
499
    }
500
}