1mod inner;
4mod middle;
5mod outer;
6
7use crate::NetdocBuilder;
8use crate::doc::hsdesc::{IntroAuthType, IntroPointDesc};
9use rand::{CryptoRng, Rng};
10use tor_bytes::EncodeError;
11use tor_cell::chancell::msg::HandshakeType;
12use tor_cert::{CertEncodeError, CertType, CertifiedKey, Ed25519Cert, EncodedEd25519Cert};
13use tor_error::into_bad_api_usage;
14use tor_hscrypto::pk::{HsBlindIdKey, HsBlindIdKeypair, HsSvcDescEncKeypair};
15use tor_hscrypto::{RevisionCounter, Subcredential};
16use tor_llcrypto::pk::curve25519;
17use tor_llcrypto::pk::ed25519;
18use tor_protover::Protocols;
19use tor_units::IntegerMinutes;
20
21use derive_builder::Builder;
22use smallvec::SmallVec;
23
24use std::borrow::{Borrow, Cow};
25use std::time::SystemTime;
26
27use self::inner::HsDescInner;
28use self::middle::HsDescMiddle;
29use self::outer::HsDescOuter;
30
31use super::desc_enc::{HS_DESC_ENC_NONCE_LEN, HsDescEncNonce, HsDescEncryption};
32use super::pow::PowParams;
33
34#[derive(Builder)]
41#[builder(public, derive(Debug, Clone), pattern = "owned", build_fn(vis = ""))]
42struct HsDesc<'a> {
43 blinded_id: &'a HsBlindIdKey,
47 hs_desc_sign: &'a ed25519::Keypair,
49 hs_desc_sign_cert: EncodedEd25519Cert,
53 create2_formats: &'a [HandshakeType],
55 auth_required: Option<SmallVec<[IntroAuthType; 2]>>,
57 is_single_onion_service: bool,
59 intro_points: &'a [IntroPointDesc],
61 intro_auth_key_cert_expiry: SystemTime,
63 intro_enc_key_cert_expiry: SystemTime,
65 #[builder(default)]
67 #[cfg(feature = "hs-pow-full")]
68 pow_params: Option<&'a PowParams>,
69
70 #[builder(default)]
78 flow_control: Option<(Protocols, u8)>,
79
80 #[builder(default)]
88 supported_protocols: Protocols,
89
90 #[builder(default)]
101 auth_clients: Option<&'a [curve25519::PublicKey]>,
102 lifetime: IntegerMinutes<u16>,
108 revision_counter: RevisionCounter,
111 subcredential: Subcredential,
113
114 #[builder(field(type = "Option<usize>", build = "()"), setter(strip_option))]
118 #[allow(dead_code)]
119 max_generated_len: (),
120}
121
122#[derive(Debug)]
124pub(super) struct ClientAuth<'a> {
125 ephemeral_key: HsSvcDescEncKeypair,
130 auth_clients: &'a [curve25519::PublicKey],
132 descriptor_cookie: [u8; HS_DESC_ENC_NONCE_LEN],
136}
137
138impl<'a> ClientAuth<'a> {
139 fn new<R: Rng + CryptoRng>(
145 auth_clients: Option<&'a [curve25519::PublicKey]>,
146 rng: &mut R,
147 ) -> Option<ClientAuth<'a>> {
148 let Some(auth_clients) = auth_clients else {
149 return None;
151 };
152
153 let descriptor_cookie = rand::RngExt::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(rng);
155
156 let secret = curve25519::StaticSecret::random_from_rng(rng);
157 let ephemeral_key = HsSvcDescEncKeypair {
158 public: curve25519::PublicKey::from(&secret).into(),
159 secret: secret.into(),
160 };
161
162 Some(ClientAuth {
163 ephemeral_key,
164 auth_clients,
165 descriptor_cookie,
166 })
167 }
168}
169
170impl<'a> NetdocBuilder for HsDescBuilder<'a> {
171 fn build_sign<R: Rng + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
172 const SUPERENCRYPTED_ALIGN: usize = 10 * (1 << 10);
176
177 let max_generated_len = self.max_generated_len.unwrap_or(usize::MAX);
178
179 let hs_desc = self
180 .build()
181 .map_err(into_bad_api_usage!("the HsDesc could not be built"))?;
182
183 let client_auth = ClientAuth::new(hs_desc.auth_clients, rng);
184
185 let inner_plaintext = HsDescInner {
188 hs_desc_sign: hs_desc.hs_desc_sign,
189 create2_formats: hs_desc.create2_formats,
190 auth_required: hs_desc.auth_required.as_ref(),
191 is_single_onion_service: hs_desc.is_single_onion_service,
192 intro_points: hs_desc.intro_points,
193 intro_auth_key_cert_expiry: hs_desc.intro_auth_key_cert_expiry,
194 intro_enc_key_cert_expiry: hs_desc.intro_enc_key_cert_expiry,
195 flow_control: hs_desc.flow_control.as_ref(),
196 protos: hs_desc.supported_protocols.clone(),
197 #[cfg(feature = "hs-pow-full")]
198 pow_params: hs_desc.pow_params,
199 }
200 .build_sign(rng)?;
201
202 let desc_enc_nonce = client_auth
203 .as_ref()
204 .map(|client_auth| client_auth.descriptor_cookie.into());
205
206 let inner_encrypted = hs_desc.encrypt_field(
209 rng,
210 inner_plaintext.as_bytes(),
211 desc_enc_nonce.as_ref(),
212 b"hsdir-encrypted-data",
213 );
214
215 let middle_plaintext = HsDescMiddle {
218 client_auth: client_auth.as_ref(),
219 subcredential: hs_desc.subcredential,
220 encrypted: inner_encrypted,
221 }
222 .build_sign(rng)?;
223
224 let middle_plaintext =
227 pad_with_zero_to_align(middle_plaintext.as_bytes(), SUPERENCRYPTED_ALIGN);
228
229 let middle_encrypted = hs_desc.encrypt_field(
232 rng,
233 middle_plaintext.borrow(),
234 None,
236 b"hsdir-superencrypted-data",
237 );
238
239 let hsdesc = HsDescOuter {
241 hs_desc_sign: hs_desc.hs_desc_sign,
242 hs_desc_sign_cert: hs_desc.hs_desc_sign_cert,
243 lifetime: hs_desc.lifetime,
244 revision_counter: hs_desc.revision_counter,
245 superencrypted: middle_encrypted,
246 }
247 .build_sign(rng)?;
248
249 if hsdesc.len() > max_generated_len {
250 return Err(EncodeError::BadLengthValue);
251 }
252 Ok(hsdesc)
253 }
254}
255
256pub fn create_desc_sign_key_cert(
264 hs_desc_sign: &ed25519::PublicKey,
265 blind_id: &HsBlindIdKeypair,
266 expiry: SystemTime,
267) -> Result<EncodedEd25519Cert, CertEncodeError> {
268 Ed25519Cert::builder()
272 .cert_type(CertType::HS_BLINDED_ID_V_SIGNING)
273 .expiration(expiry)
274 .signing_key(ed25519::Ed25519Identity::from(blind_id.as_ref().public()))
275 .cert_key(CertifiedKey::Ed25519(hs_desc_sign.into()))
276 .encode_and_sign(blind_id)
277}
278
279impl<'a> HsDesc<'a> {
280 fn encrypt_field<R: Rng + CryptoRng>(
283 &self,
284 rng: &mut R,
285 plaintext: &[u8],
286 desc_enc_nonce: Option<&HsDescEncNonce>,
287 string_const: &[u8],
288 ) -> Vec<u8> {
289 let encrypt = HsDescEncryption {
290 blinded_id: &ed25519::Ed25519Identity::from(self.blinded_id.as_ref()).into(),
291 desc_enc_nonce,
292 subcredential: &self.subcredential,
293 revision: self.revision_counter,
294 string_const,
295 };
296
297 encrypt.encrypt(rng, plaintext)
298 }
299}
300
301fn pad_with_zero_to_align(v: &[u8], alignment: usize) -> Cow<[u8]> {
303 let padding = (alignment - (v.len() % alignment)) % alignment;
304
305 if padding > 0 {
306 let padded = v
307 .iter()
308 .copied()
309 .chain(std::iter::repeat_n(0, padding))
310 .collect::<Vec<_>>();
311
312 Cow::Owned(padded)
313 } else {
314 Cow::Borrowed(v)
316 }
317}
318
319#[cfg(test)]
320mod test {
321 #![allow(clippy::bool_assert_comparison)]
323 #![allow(clippy::clone_on_copy)]
324 #![allow(clippy::dbg_macro)]
325 #![allow(clippy::mixed_attributes_style)]
326 #![allow(clippy::print_stderr)]
327 #![allow(clippy::print_stdout)]
328 #![allow(clippy::single_char_pattern)]
329 #![allow(clippy::unwrap_used)]
330 #![allow(clippy::unchecked_time_subtraction)]
331 #![allow(clippy::useless_vec)]
332 #![allow(clippy::needless_pass_by_value)]
333 #![allow(clippy::string_slice)] use std::net::Ipv4Addr;
337 use std::time::Duration;
338
339 use super::*;
340 use crate::doc::hsdesc::{EncryptedHsDesc, HsDesc as ParsedHsDesc};
341 use tor_basic_utils::test_rng::Config;
342 use tor_checkable::{SelfSigned, TimeBound};
343 use tor_hscrypto::pk::{HsClientDescEncKeypair, HsIdKeypair};
344 use tor_hscrypto::time::TimePeriod;
345 use tor_linkspec::LinkSpec;
346 use tor_llcrypto::pk::{curve25519, ed25519::ExpandedKeypair};
347 use web_time_compat::SystemTimeExt;
348
349 pub(super) fn expect_bug(err: EncodeError) -> String {
358 match err {
359 EncodeError::Bug(b) => b.to_string(),
360 EncodeError::BadLengthValue => panic!("expected Bug, got BadLengthValue"),
361 _ => panic!("expected Bug, got unknown error"),
362 }
363 }
364
365 pub(super) fn create_intro_point_descriptor<R: Rng + CryptoRng>(
366 rng: &mut R,
367 link_specifiers: &[LinkSpec],
368 ) -> IntroPointDesc {
369 let link_specifiers = link_specifiers
370 .iter()
371 .map(|link_spec| link_spec.encode())
372 .collect::<Result<Vec<_>, _>>()
373 .unwrap();
374
375 IntroPointDesc {
376 link_specifiers,
377 ipt_ntor_key: create_curve25519_pk(rng),
378 ipt_sid_key: ed25519::Keypair::generate(rng).verifying_key().into(),
379 svc_ntor_key: create_curve25519_pk(rng).into(),
380 }
381 }
382
383 pub(super) fn create_curve25519_pk<R: Rng + CryptoRng>(rng: &mut R) -> curve25519::PublicKey {
385 let ephemeral_key = curve25519::EphemeralSecret::random_from_rng(rng);
386 (&ephemeral_key).into()
387 }
388
389 fn parse_hsdesc(
391 unparsed_desc: &str,
392 blinded_pk: ed25519::PublicKey,
393 subcredential: &Subcredential,
394 hsc_desc_enc: Option<&HsClientDescEncKeypair>,
395 ) -> ParsedHsDesc {
396 const TIMESTAMP: &str = "2023-01-23T15:00:00Z";
397
398 let id = ed25519::Ed25519Identity::from(blinded_pk);
399 let enc_desc: EncryptedHsDesc = ParsedHsDesc::parse(unparsed_desc, &id.into())
400 .unwrap()
401 .check_signature()
402 .unwrap()
403 .if_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
404 .unwrap();
405
406 enc_desc
407 .decrypt(subcredential, hsc_desc_enc)
408 .unwrap()
409 .if_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
410 .unwrap()
411 .check_signature()
412 .unwrap()
413 }
414
415 #[test]
416 fn encode_decode() {
417 const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::TAP, HandshakeType::NTOR];
418 const LIFETIME_MINS: u16 = 100;
419 const REVISION_COUNT: u64 = 2;
420 const CERT_EXPIRY_SECS: u64 = 60 * 60;
421
422 let mut rng = Config::Deterministic.into_rng();
423 let hs_id = ed25519::Keypair::generate(&mut rng);
425 let hs_desc_sign = ed25519::Keypair::generate(&mut rng);
426 let period = TimePeriod::new(
427 humantime::parse_duration("24 hours").unwrap(),
428 humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
429 humantime::parse_duration("12 hours").unwrap(),
430 )
431 .unwrap();
432 let (_, blinded_id, subcredential) = HsIdKeypair::from(ExpandedKeypair::from(&hs_id))
433 .compute_blinded_key(period)
434 .unwrap();
435
436 let expiry = SystemTime::get() + Duration::from_secs(CERT_EXPIRY_SECS);
437 let mut rng = Config::Deterministic.into_rng();
438 let intro_points = vec![IntroPointDesc {
439 link_specifiers: vec![
440 LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999)
441 .encode()
442 .unwrap(),
443 ],
444 ipt_ntor_key: create_curve25519_pk(&mut rng),
445 ipt_sid_key: ed25519::Keypair::generate(&mut rng).verifying_key().into(),
446 svc_ntor_key: create_curve25519_pk(&mut rng).into(),
447 }];
448
449 let hs_desc_sign_cert =
450 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
451 let blinded_pk = (&blinded_id).into();
452 let builder = HsDescBuilder::default()
453 .blinded_id(&blinded_pk)
454 .hs_desc_sign(&hs_desc_sign)
455 .hs_desc_sign_cert(hs_desc_sign_cert)
456 .create2_formats(CREATE2_FORMATS)
457 .auth_required(None)
458 .is_single_onion_service(true)
459 .intro_points(&intro_points)
460 .intro_auth_key_cert_expiry(expiry)
461 .intro_enc_key_cert_expiry(expiry)
462 .lifetime(LIFETIME_MINS.into())
463 .revision_counter(REVISION_COUNT.into())
464 .subcredential(subcredential);
465
466 let encoded_desc = builder
469 .clone()
470 .build_sign(&mut Config::Deterministic.into_rng())
471 .unwrap();
472
473 let desc = parse_hsdesc(
475 encoded_desc.as_str(),
476 *blinded_id.as_ref().public(),
477 &subcredential,
478 None, );
480
481 let hs_desc_sign_cert =
482 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
483 let reencoded_desc = HsDescBuilder::default()
486 .blinded_id(&(&blinded_id).into())
487 .hs_desc_sign(&hs_desc_sign)
488 .hs_desc_sign_cert(hs_desc_sign_cert)
489 .create2_formats(CREATE2_FORMATS)
492 .auth_required(None)
493 .is_single_onion_service(desc.is_single_onion_service)
494 .intro_points(&intro_points)
495 .intro_auth_key_cert_expiry(expiry)
496 .intro_enc_key_cert_expiry(expiry)
497 .lifetime(desc.idx_info.lifetime)
498 .revision_counter(desc.idx_info.revision)
499 .subcredential(subcredential)
500 .build_sign(&mut Config::Deterministic.into_rng())
501 .unwrap();
502
503 assert_eq!(&*encoded_desc, &*reencoded_desc);
504
505 let client_kp: HsClientDescEncKeypair = HsClientDescEncKeypair::generate(&mut rng);
508 let client_pkey = client_kp.public().as_ref();
509 let auth_clients = vec![*client_pkey];
510
511 let encoded_desc = builder
512 .auth_clients(Some(&auth_clients[..]))
513 .build_sign(&mut Config::Deterministic.into_rng())
514 .unwrap();
515
516 let desc = parse_hsdesc(
518 encoded_desc.as_str(),
519 *blinded_id.as_ref().public(),
520 &subcredential,
521 Some(&client_kp), );
523
524 let hs_desc_sign_cert =
525 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
526 let reencoded_desc = HsDescBuilder::default()
529 .blinded_id(&(&blinded_id).into())
530 .hs_desc_sign(&hs_desc_sign)
531 .hs_desc_sign_cert(hs_desc_sign_cert)
532 .create2_formats(CREATE2_FORMATS)
535 .auth_required(None)
536 .is_single_onion_service(desc.is_single_onion_service)
537 .intro_points(&intro_points)
538 .intro_auth_key_cert_expiry(expiry)
539 .intro_enc_key_cert_expiry(expiry)
540 .auth_clients(Some(&auth_clients))
541 .lifetime(desc.idx_info.lifetime)
542 .revision_counter(desc.idx_info.revision)
543 .subcredential(subcredential)
544 .build_sign(&mut Config::Deterministic.into_rng())
545 .unwrap();
546
547 assert_eq!(&*encoded_desc, &*reencoded_desc);
548 }
549}