1
//! Functionality for encoding the inner document of an onion service descriptor.
2
//!
3
//! NOTE: `HsDescInner` is a private helper for building hidden service descriptors, and is
4
//! not meant to be used directly. Hidden services will use `HsDescBuilder` to build and encode
5
//! hidden service descriptors.
6

            
7
use crate::NetdocBuilder;
8
use crate::doc::hsdesc::IntroAuthType;
9
use crate::doc::hsdesc::IntroPointDesc;
10
use crate::doc::hsdesc::inner::HsInnerKwd;
11
use crate::doc::hsdesc::pow::PowParams;
12
use crate::doc::hsdesc::pow::v1::PowParamsV1;
13
use crate::encode::ItemArgument;
14
use crate::encode::NetdocEncoder;
15
use crate::types::misc::Iso8601TimeNoSp;
16

            
17
use rand::CryptoRng;
18
use rand::Rng;
19
use tor_bytes::{EncodeError, Writer};
20
use tor_cell::chancell::msg::HandshakeType;
21
use tor_cert::{CertType, CertifiedKey, Ed25519Cert};
22
use tor_error::internal;
23
use tor_error::{bad_api_usage, into_bad_api_usage};
24
use tor_llcrypto::pk::ed25519;
25
use tor_llcrypto::pk::keymanip::convert_curve25519_to_ed25519_public;
26

            
27
use base64ct::{Base64, Encoding};
28
use tor_protover::Protocols;
29

            
30
use std::sync::LazyLock;
31
use std::time::SystemTime;
32

            
33
use smallvec::SmallVec;
34

            
35
/// The representation of the inner document of an onion service descriptor.
36
///
37
/// The plaintext format of this document is described in section 2.5.2.2. of rend-spec-v3.
38
#[derive(Debug)]
39
pub(super) struct HsDescInner<'a> {
40
    /// The descriptor signing key.
41
    pub(super) hs_desc_sign: &'a ed25519::Keypair,
42
    /// A list of recognized CREATE handshakes that this onion service supports.
43
    pub(super) create2_formats: &'a [HandshakeType],
44
    /// A list of authentication types that this onion service supports.
45
    pub(super) auth_required: Option<&'a SmallVec<[IntroAuthType; 2]>>,
46
    /// If true, this a "single onion service" and is not trying to keep its own location private.
47
    pub(super) is_single_onion_service: bool,
48
    /// One or more introduction points used to contact the onion service.
49
    pub(super) intro_points: &'a [IntroPointDesc],
50
    /// The expiration time of an introduction point authentication key certificate.
51
    pub(super) intro_auth_key_cert_expiry: SystemTime,
52
    /// The expiration time of an introduction point encryption key certificate.
53
    pub(super) intro_enc_key_cert_expiry: SystemTime,
54

            
55
    /// If present, a sendme increment and a set of FlowCtrl capabilities to
56
    /// advertise with it.
57
    ///
58
    /// If this is None, we don't advertise any flowctrl capabilities.
59
    ///
60
    /// For historical reasons, the protocols capabilities here are separate
61
    /// from `supported_protos`.    
62
    pub(super) flow_control: Option<&'a (Protocols, u8)>,
63

            
64
    /// If present, a set of subprotocol capabilities that we want to advertise.
65
    pub(super) protos: Protocols,
66

            
67
    /// Proof-of-work parameters
68
    #[cfg(feature = "hs-pow-full")]
69
    pub(super) pow_params: Option<&'a PowParams>,
70
}
71

            
72
/// Encode the pow-params line.
73
#[cfg(feature = "hs-pow-full")]
74
2
fn encode_pow_params(
75
2
    encoder: &mut NetdocEncoder,
76
2
    pow_params: &PowParamsV1,
77
2
) -> Result<(), EncodeError> {
78
2
    let mut pow_params_enc = encoder.item(HsInnerKwd::POW_PARAMS);
79
2
    pow_params_enc.add_arg(&"v1");
80

            
81
    // It's safe to call dangerously_into_parts here, since we encode the
82
    // expiration alongside the value.
83
2
    let (seed, expiration) = pow_params.seed().clone().dangerously_into_parts();
84
2
    let expiration = expiration.end();
85

            
86
2
    seed.write_arg_onto(&mut pow_params_enc)?;
87

            
88
2
    pow_params
89
2
        .suggested_effort()
90
2
        .write_arg_onto(&mut pow_params_enc)?;
91

            
92
2
    let expiration = if let Some(expiration) = expiration {
93
2
        expiration
94
    } else {
95
        return Err(internal!("PoW seed should always have expiration").into());
96
    };
97

            
98
2
    Iso8601TimeNoSp::from(expiration).write_arg_onto(&mut pow_params_enc)?;
99

            
100
2
    Ok(())
101
2
}
102

            
103
impl<'a> NetdocBuilder for HsDescInner<'a> {
104
150
    fn build_sign<R: Rng + CryptoRng>(self, _: &mut R) -> Result<String, EncodeError> {
105
        use HsInnerKwd::*;
106

            
107
        let HsDescInner {
108
150
            hs_desc_sign,
109
150
            create2_formats,
110
150
            auth_required,
111
150
            is_single_onion_service,
112
150
            intro_points,
113
150
            intro_auth_key_cert_expiry,
114
150
            intro_enc_key_cert_expiry,
115
150
            flow_control,
116
150
            protos,
117
            #[cfg(feature = "hs-pow-full")]
118
150
            pow_params,
119
150
        } = self;
120

            
121
150
        let mut encoder = NetdocEncoder::new();
122

            
123
        {
124
150
            let mut create2_formats_enc = encoder.item(CREATE2_FORMATS);
125
162
            for fmt in create2_formats {
126
162
                let fmt: u16 = (*fmt).into();
127
162
                create2_formats_enc = create2_formats_enc.arg(&fmt);
128
162
            }
129
        }
130

            
131
        {
132
150
            if let Some(auth_required) = auth_required {
133
2
                let mut auth_required_enc = encoder.item(INTRO_AUTH_REQUIRED);
134
4
                for auth in auth_required {
135
4
                    auth_required_enc = auth_required_enc.arg(&auth.to_string());
136
4
                }
137
148
            }
138
        }
139

            
140
150
        if is_single_onion_service {
141
10
            encoder.item(SINGLE_ONION_SERVICE);
142
140
        }
143

            
144
150
        if let Some((fcp, inc)) = flow_control {
145
130
            let fcp = flowctrl_protocols(fcp);
146
130
            encoder.item(FLOW_CONTROL).arg(&fcp).arg(inc);
147
148
        }
148

            
149
150
        if !protos.is_empty() {
150
130
            encoder.item(PROTO).args_raw_string(&protos.to_string());
151
148
        }
152

            
153
        #[cfg(feature = "hs-pow-full")]
154
150
        if let Some(pow_params) = pow_params {
155
2
            match pow_params {
156
                #[cfg(feature = "hs-pow-full")]
157
2
                PowParams::V1(pow_params) => encode_pow_params(&mut encoder, pow_params)?,
158
                #[cfg(not(feature = "hs-pow-full"))]
159
                PowParams::V1(_) => {
160
                    return Err(internal!(
161
                        "Got a V1 PoW params but support for V1 is disabled."
162
                    ));
163
                }
164
            }
165
148
        }
166

            
167
        // We sort the introduction points here so as not to expose
168
        // detail about the order in which they were added, which might
169
        // be useful to an attacker somehow.  The choice of ntor
170
        // key is arbitrary; we could sort by anything, really.
171
        //
172
        // TODO SPEC: Either specify that we should sort by ntor key,
173
        // or sort by something else and specify that.
174
150
        let mut sorted_ip: Vec<_> = intro_points.iter().collect();
175
534
        sorted_ip.sort_by_key(|key| key.ipt_ntor_key.as_bytes());
176
406
        for intro_point in sorted_ip {
177
            // rend-spec-v3 0.4. "Protocol building blocks [BUILDING-BLOCKS]": the number of link
178
            // specifiers (NPSEC) must fit in a single byte.
179
406
            let nspec: u8 = intro_point
180
406
                .link_specifiers
181
406
                .len()
182
406
                .try_into()
183
406
                .map_err(into_bad_api_usage!("Too many link specifiers."))?;
184

            
185
404
            let mut link_specifiers = vec![];
186
404
            link_specifiers.write_u8(nspec);
187

            
188
1172
            for link_spec in &intro_point.link_specifiers {
189
1172
                link_specifiers.write(link_spec)?;
190
            }
191

            
192
404
            encoder
193
404
                .item(INTRODUCTION_POINT)
194
404
                .arg(&Base64::encode_string(&link_specifiers));
195
404
            encoder
196
404
                .item(ONION_KEY)
197
404
                .arg(&"ntor")
198
404
                .arg(&Base64::encode_string(&intro_point.ipt_ntor_key.to_bytes()));
199

            
200
            // For compatibility with c-tor, the introduction point authentication key is signed by
201
            // the descriptor signing key.
202
404
            let signed_auth_key = Ed25519Cert::builder()
203
404
                .cert_type(CertType::HS_IP_V_SIGNING)
204
404
                .expiration(intro_auth_key_cert_expiry)
205
404
                .signing_key(ed25519::Ed25519Identity::from(hs_desc_sign.verifying_key()))
206
404
                .cert_key(CertifiedKey::Ed25519((*intro_point.ipt_sid_key).into()))
207
404
                .encode_and_sign(hs_desc_sign)
208
404
                .map_err(into_bad_api_usage!("failed to sign the intro auth key"))?;
209

            
210
404
            encoder
211
404
                .item(AUTH_KEY)
212
404
                .object_bytes("ED25519 CERT", signed_auth_key.as_ref());
213

            
214
            // "The key is a base64 encoded curve25519 public key used to encrypt the introduction
215
            // request to service. (`KP_hss_ntor`)"
216
            //
217
            // TODO: The spec allows for multiple enc-key lines, but we currently only ever encode
218
            // a single one.
219
404
            encoder
220
404
                .item(ENC_KEY)
221
404
                .arg(&"ntor")
222
404
                .arg(&Base64::encode_string(
223
404
                    &intro_point.svc_ntor_key.as_bytes()[..],
224
404
                ));
225

            
226
            // The subject key is the ed25519 equivalent of the svc_ntor_key
227
            // curve25519 public encryption key, with its sign bit set to 0.
228
            //
229
            // (Setting the sign bit to zero has a 50% chance of making the
230
            // ed25519 public key useless for checking signatures, but that's
231
            // okay: since this cert is generated with its signing/subject keys
232
            // reversed (for compatibility reasons), we never actually generate
233
            // or check any signatures using this key.)
234
404
            let signbit = 0;
235
404
            let ed_svc_ntor_key =
236
404
                convert_curve25519_to_ed25519_public(&intro_point.svc_ntor_key, signbit)
237
404
                    .ok_or_else(|| {
238
                        bad_api_usage!("failed to convert curve25519 pk to ed25519 pk")
239
                    })?;
240

            
241
            // For compatibility with c-tor, the encryption key is signed with the descriptor
242
            // signing key.
243
404
            let signed_enc_key = Ed25519Cert::builder()
244
404
                .cert_type(CertType::HS_IP_CC_SIGNING)
245
404
                .expiration(intro_enc_key_cert_expiry)
246
404
                .signing_key(ed25519::Ed25519Identity::from(hs_desc_sign.verifying_key()))
247
404
                .cert_key(CertifiedKey::Ed25519(ed25519::Ed25519Identity::from(
248
404
                    &ed_svc_ntor_key,
249
404
                )))
250
404
                .encode_and_sign(hs_desc_sign)
251
404
                .map_err(into_bad_api_usage!(
252
                    "failed to sign the intro encryption key"
253
                ))?;
254

            
255
404
            encoder
256
404
                .item(ENC_KEY_CERT)
257
404
                .object_bytes("ED25519 CERT", signed_enc_key.as_ref());
258
        }
259

            
260
148
        encoder.finish().map_err(|e| e.into())
261
150
    }
262
}
263

            
264
/// Return a string encoding all of the `FlowCtrl` protocols in `p` that we should
265
/// encode in a `flow-control` item.
266
///
267
/// This is an inelegant function because the `flow-control` item pre-dates
268
/// the `proto` item by some time.  Don't make any more functions like this one!
269
/// Instead, put new subprotocol capabilities into the `proto` item.
270
3394
fn flowctrl_protocols(p: &Protocols) -> String {
271
    use tor_protover::ProtoKind;
272
    // We only encode FlowCtrl protocols 1-2 here.  If we decide that we would like to implement
273
    // more, we will decide later whether to advertise them in a flow-control line or in a 'proto'
274
    // line.
275
55
    static ALL_FLOWCTRL: LazyLock<Protocols> = LazyLock::new(|| {
276
55
        Protocols::from_kind_and_versions(ProtoKind::FlowCtrl, "1-2")
277
55
            .expect("Internal protocol list could not be parsed")
278
55
    });
279

            
280
3394
    ALL_FLOWCTRL
281
3394
        .intersection(p)
282
3394
        .to_string()
283
3394
        .strip_prefix("FlowCtrl=")
284
3394
        .expect("FlowCtrl protocols were not encoded correctly.")
285
3394
        .to_string()
286
3394
}
287

            
288
#[cfg(test)]
289
mod test {
290
    // @@ begin test lint list maintained by maint/add_warning @@
291
    #![allow(clippy::bool_assert_comparison)]
292
    #![allow(clippy::clone_on_copy)]
293
    #![allow(clippy::dbg_macro)]
294
    #![allow(clippy::mixed_attributes_style)]
295
    #![allow(clippy::print_stderr)]
296
    #![allow(clippy::print_stdout)]
297
    #![allow(clippy::single_char_pattern)]
298
    #![allow(clippy::unwrap_used)]
299
    #![allow(clippy::unchecked_time_subtraction)]
300
    #![allow(clippy::useless_vec)]
301
    #![allow(clippy::needless_pass_by_value)]
302
    #![allow(clippy::string_slice)] // See arti#2571
303
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
304

            
305
    use super::*;
306
    use crate::doc::hsdesc::IntroAuthType;
307
    use crate::doc::hsdesc::build::test::{create_intro_point_descriptor, expect_bug};
308
    use crate::doc::hsdesc::pow::v1::PowParamsV1;
309

            
310
    use smallvec::SmallVec;
311
    use std::net::Ipv4Addr;
312
    use std::time::UNIX_EPOCH;
313
    use tor_basic_utils::test_rng::Config;
314
    use tor_checkable::timed::TimeRangeBound;
315
    #[cfg(feature = "hs-pow-full")]
316
    use tor_hscrypto::pow::v1::{Effort, Seed};
317
    use tor_linkspec::LinkSpec;
318

            
319
    /// Build an inner document using the specified parameters.
320
    fn create_inner_desc(
321
        create2_formats: &[HandshakeType],
322
        auth_required: Option<&SmallVec<[IntroAuthType; 2]>>,
323
        is_single_onion_service: bool,
324
        intro_points: &[IntroPointDesc],
325
        flow_control: Option<(Protocols, u8)>,
326
        protos: Protocols,
327
        pow_params: Option<&PowParams>,
328
    ) -> Result<String, EncodeError> {
329
        let hs_desc_sign = ed25519::Keypair::generate(&mut Config::Deterministic.into_rng());
330

            
331
        HsDescInner {
332
            hs_desc_sign: &hs_desc_sign,
333
            create2_formats,
334
            auth_required,
335
            is_single_onion_service,
336
            intro_points,
337
            intro_auth_key_cert_expiry: UNIX_EPOCH,
338
            intro_enc_key_cert_expiry: UNIX_EPOCH,
339
            flow_control: flow_control.as_ref(),
340
            protos,
341
            #[cfg(feature = "hs-pow-full")]
342
            pow_params,
343
        }
344
        .build_sign(&mut rand::rng())
345
    }
346

            
347
    #[test]
348
    fn inner_hsdesc_no_intro_auth() {
349
        // A descriptor for a "single onion service"
350
        let hs_desc = create_inner_desc(
351
            &[HandshakeType::NTOR], /* create2_formats */
352
            None,                   /* auth_required */
353
            true,                   /* is_single_onion_service */
354
            &[],                    /* intro_points */
355
            None,
356
            Default::default(),
357
            None,
358
        )
359
        .unwrap();
360

            
361
        assert_eq!(hs_desc, "create2-formats 2\nsingle-onion-service\n");
362

            
363
        // A descriptor for a location-hidden service
364
        let hs_desc = create_inner_desc(
365
            &[HandshakeType::NTOR], /* create2_formats */
366
            None,                   /* auth_required */
367
            false,                  /* is_single_onion_service */
368
            &[],                    /* intro_points */
369
            None,
370
            Default::default(),
371
            None,
372
        )
373
        .unwrap();
374

            
375
        assert_eq!(hs_desc, "create2-formats 2\n");
376

            
377
        let link_specs1 = &[LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 1234)];
378
        let link_specs2 = &[LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 5679)];
379
        let link_specs3 = &[LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 8901)];
380

            
381
        let mut rng = Config::Deterministic.into_rng();
382
        let intros = &[
383
            create_intro_point_descriptor(&mut rng, link_specs1),
384
            create_intro_point_descriptor(&mut rng, link_specs2),
385
            create_intro_point_descriptor(&mut rng, link_specs3),
386
        ];
387

            
388
        let hs_desc = create_inner_desc(
389
            &[
390
                HandshakeType::TAP,
391
                HandshakeType::NTOR,
392
                HandshakeType::NTOR_V3,
393
            ], /* create2_formats */
394
            None,   /* auth_required */
395
            false,  /* is_single_onion_service */
396
            intros, /* intro_points */
397
            None,
398
            Default::default(),
399
            None,
400
        )
401
        .unwrap();
402

            
403
        assert_eq!(
404
            hs_desc,
405
            r#"create2-formats 0 2 3
406
introduction-point AQAGfwAAASLF
407
onion-key ntor CJi8nDPhIFA7X9Q+oP7+jzxNo044cblmagk/d7oKWGc=
408
auth-key
409
-----BEGIN ED25519 CERT-----
410
AQkAAAAAAU4J4xGrMt9q5eHYZSmbOZTi1iKl59nd3ItYXAa/ASlRAQAgBACQKRtN
411
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61CGkJzc/ECYHzJeeAKIkRFV/6jr9
412
zAB5XnEFghZmXdDTQdqcPXAFydyeHWW4uR+Uii0wPI8VokbU0NoLTNYJGAM=
413
-----END ED25519 CERT-----
414
enc-key ntor TL7GcN+B++pB6eRN/0nBZGmWe125qh7ccQJ/Hhku+x8=
415
enc-key-cert
416
-----BEGIN ED25519 CERT-----
417
AQsAAAAAAabaCv4gv9ddyIztD1J8my9mgotmWnkHX94buLAtt15aAQAgBACQKRtN
418
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61GxlI6caS8iFp2bLmg1+Pkgij47f
419
eetKn+yDC5Q3eo/hJLDBGAQNOX7jFMdr9HjotjXIt6/Khfmg58CZC/gKhAw=
420
-----END ED25519 CERT-----
421
introduction-point AQAGfwAAAQTS
422
onion-key ntor HWIigEAdcOgqgHPDFmzhhkeqvYP/GcMT2fKb5JY6ey8=
423
auth-key
424
-----BEGIN ED25519 CERT-----
425
AQkAAAAAAZZVJwNlzVw1ZQGO7MTzC5MsySASd+fswAcjdTJJOifXAQAgBACQKRtN
426
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61IVW0XivcAKhvUvNUsU1CFznk3Mz
427
KSsp/mBoKi2iY4f4eN2SXx8U6pmnxnXFxYP6obi+tc5QWj1Jbfl1Aci3TAA=
428
-----END ED25519 CERT-----
429
enc-key ntor 9Upi9XNWyqx3ZwHeQ5r3+Dh116k+C4yHeE9BcM68HDc=
430
enc-key-cert
431
-----BEGIN ED25519 CERT-----
432
AQsAAAAAAcH+1K5m7pRnMc01mPp5AYVnJK1iZ/fKHwK0tVR/jtBvAQAgBACQKRtN
433
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61Hectpha37ioha85fpNt+/yDfebh
434
6BKUUQ0jf3SMXuNgX8SV9NSabn14WCSdKG/8RoYBCTR+yRJX0dy55mjg+go=
435
-----END ED25519 CERT-----
436
introduction-point AQAGfwAAARYv
437
onion-key ntor x/stThC6cVWJJUR7WERZj5VYVPTAOA/UDjHdtprJkiE=
438
auth-key
439
-----BEGIN ED25519 CERT-----
440
AQkAAAAAAVMhalzZJ8txKHuCX8TEhmO3LbCvDgV0zMT4eQ49SDpBAQAgBACQKRtN
441
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61GdVAiMag0dquEx4IywKDLEhxA7N
442
2RZFTS2QI+Sk3dyz46WO+epj1YBlgfOYCZlBEx+oFkRlUJdOc0Eu0sDlAw8=
443
-----END ED25519 CERT-----
444
enc-key ntor XI/a9NGh/7ClaFcKqtdI9DoP8da5ovwPDdgCHUr3xX0=
445
enc-key-cert
446
-----BEGIN ED25519 CERT-----
447
AQsAAAAAAZYGETSx12Og2xqJNMS9kGOHTEFeBkFPi7k0UaFv5HNKAQAgBACQKRtN
448
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61E8vxB5lB83+rQnWmHLzpfuMUZjG
449
o7Ct/ZB0j8YRB5lKSd07YAjA6Zo8kMnuZYX2Mb67TxWDQ/zlYJGOwLlj7A8=
450
-----END ED25519 CERT-----
451
"#
452
        );
453
    }
454

            
455
    #[test]
456
    fn inner_hsdesc_too_many_link_specifiers() {
457
        let link_spec = LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999);
458
        let link_specifiers =
459
            std::iter::repeat_n(link_spec, u8::MAX as usize + 1).collect::<Vec<_>>();
460

            
461
        let intros = &[create_intro_point_descriptor(
462
            &mut Config::Deterministic.into_rng(),
463
            &link_specifiers,
464
        )];
465

            
466
        // A descriptor for a location-hidden service with an introduction point with too many link
467
        // specifiers
468
        let err = create_inner_desc(
469
            &[HandshakeType::NTOR], /* create2_formats */
470
            None,                   /* auth_required */
471
            false,                  /* is_single_onion_service */
472
            intros,                 /* intro_points */
473
            None,
474
            Default::default(),
475
            None,
476
        )
477
        .unwrap_err();
478

            
479
        assert!(expect_bug(err).contains("Too many link specifiers."));
480
    }
481

            
482
    #[test]
483
    fn inner_hsdesc_intro_auth() {
484
        let mut rng = Config::Deterministic.into_rng();
485
        let link_specs = &[LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 8080)];
486
        let intros = &[create_intro_point_descriptor(&mut rng, link_specs)];
487
        let auth = SmallVec::from([IntroAuthType::Ed25519, IntroAuthType::Ed25519]);
488

            
489
        // A descriptor for a location-hidden service with 1 introduction points which requires
490
        // auth.
491
        let hs_desc = create_inner_desc(
492
            &[HandshakeType::NTOR], /* create2_formats */
493
            Some(&auth),            /* auth_required */
494
            false,                  /* is_single_onion_service */
495
            intros,                 /* intro_points */
496
            None,
497
            Default::default(),
498
            None,
499
        )
500
        .unwrap();
501

            
502
        assert_eq!(
503
            hs_desc,
504
            r#"create2-formats 2
505
intro-auth-required ed25519 ed25519
506
introduction-point AQAGfwAAAR+Q
507
onion-key ntor HWIigEAdcOgqgHPDFmzhhkeqvYP/GcMT2fKb5JY6ey8=
508
auth-key
509
-----BEGIN ED25519 CERT-----
510
AQkAAAAAAZZVJwNlzVw1ZQGO7MTzC5MsySASd+fswAcjdTJJOifXAQAgBACQKRtN
511
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61IVW0XivcAKhvUvNUsU1CFznk3Mz
512
KSsp/mBoKi2iY4f4eN2SXx8U6pmnxnXFxYP6obi+tc5QWj1Jbfl1Aci3TAA=
513
-----END ED25519 CERT-----
514
enc-key ntor 9Upi9XNWyqx3ZwHeQ5r3+Dh116k+C4yHeE9BcM68HDc=
515
enc-key-cert
516
-----BEGIN ED25519 CERT-----
517
AQsAAAAAAcH+1K5m7pRnMc01mPp5AYVnJK1iZ/fKHwK0tVR/jtBvAQAgBACQKRtN
518
eNThmyleMYdmFucrbgPcZNDO6S81MZD1r7q61Hectpha37ioha85fpNt+/yDfebh
519
6BKUUQ0jf3SMXuNgX8SV9NSabn14WCSdKG/8RoYBCTR+yRJX0dy55mjg+go=
520
-----END ED25519 CERT-----
521
"#
522
        );
523
    }
524

            
525
    #[test]
526
    #[cfg(feature = "hs-pow-full")]
527
    fn inner_hsdesc_pow_params() {
528
        use humantime::parse_rfc3339;
529

            
530
        let mut rng = Config::Deterministic.into_rng();
531
        let link_specs = &[LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 8080)];
532
        let intros = &[create_intro_point_descriptor(&mut rng, link_specs)];
533

            
534
        let pow_expiration = parse_rfc3339("1994-04-29T00:00:00Z").unwrap();
535
        let pow_params = PowParams::V1(PowParamsV1::new(
536
            TimeRangeBound::new(Seed::from([0; 32]), ..pow_expiration),
537
            Effort::new(64),
538
        ));
539

            
540
        let hs_desc = create_inner_desc(
541
            &[HandshakeType::NTOR], /* create2_formats */
542
            None,                   /* auth_required */
543
            false,                  /* is_single_onion_service */
544
            intros,                 /* intro_points */
545
            None,
546
            Default::default(),
547
            Some(&pow_params),
548
        )
549
        .unwrap();
550

            
551
        assert!(hs_desc.contains(
552
            "\npow-params v1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 64 1994-04-29T00:00:00\n"
553
        ));
554
    }
555

            
556
    #[test]
557
    fn inner_hsdesc_protos_flowctl() {
558
        let mut rng = Config::Deterministic.into_rng();
559
        let link_specs = &[LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 8080)];
560
        let intros = &[create_intro_point_descriptor(&mut rng, link_specs)];
561

            
562
        let protos = "Relay=1-20 Wombat=12 Link=4".parse().unwrap();
563
        let fcp = "FlowCtrl=1-6".parse().unwrap();
564
        let flow_control = Some((fcp, 33));
565

            
566
        let hs_desc = create_inner_desc(
567
            &[HandshakeType::NTOR],
568
            None,
569
            false,
570
            intros,
571
            flow_control,
572
            protos,
573
            None,
574
        )
575
        .unwrap();
576

            
577
        // Only FlowCtrl 1-2 can make it through.
578
        assert!(hs_desc.contains("\nflow-control 1-2 33\n"));
579
        // All declared protocols make it into the `proto` line.
580
        assert!(hs_desc.contains("\nproto Link=4 Relay=1-20 Wombat=12\n"));
581
    }
582
}