1mod inner;
4mod middle;
5mod outer;
6
7use crate::doc::hsdesc::{IntroAuthType, IntroPointDesc};
8use crate::NetdocBuilder;
9use rand::{CryptoRng, RngCore};
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_units::IntegerMinutes;
19
20use derive_builder::Builder;
21use smallvec::SmallVec;
22
23use std::borrow::{Borrow, Cow};
24use std::time::SystemTime;
25
26use self::inner::HsDescInner;
27use self::middle::HsDescMiddle;
28use self::outer::HsDescOuter;
29
30use super::desc_enc::{HsDescEncNonce, HsDescEncryption, HS_DESC_ENC_NONCE_LEN};
31use super::pow::PowParams;
32
33#[derive(Builder)]
40#[builder(public, derive(Debug, Clone), pattern = "owned", build_fn(vis = ""))]
41struct HsDesc<'a> {
42 blinded_id: &'a HsBlindIdKey,
46 hs_desc_sign: &'a ed25519::Keypair,
48 hs_desc_sign_cert: EncodedEd25519Cert,
52 create2_formats: &'a [HandshakeType],
54 auth_required: Option<SmallVec<[IntroAuthType; 2]>>,
56 is_single_onion_service: bool,
58 intro_points: &'a [IntroPointDesc],
60 intro_auth_key_cert_expiry: SystemTime,
62 intro_enc_key_cert_expiry: SystemTime,
64 #[builder(default)]
66 #[cfg(feature = "hs-pow-full")]
67 pow_params: Option<&'a PowParams>,
68
69 #[builder(default)]
80 auth_clients: Option<&'a [curve25519::PublicKey]>,
81 lifetime: IntegerMinutes<u16>,
87 revision_counter: RevisionCounter,
90 subcredential: Subcredential,
92
93 #[builder(field(type = "Option<usize>", build = "()"), setter(strip_option))]
97 #[allow(dead_code)]
98 max_generated_len: (),
99}
100
101#[derive(Debug)]
103pub(super) struct ClientAuth<'a> {
104 ephemeral_key: HsSvcDescEncKeypair,
109 auth_clients: &'a [curve25519::PublicKey],
111 descriptor_cookie: [u8; HS_DESC_ENC_NONCE_LEN],
115}
116
117impl<'a> ClientAuth<'a> {
118 fn new<R: RngCore + CryptoRng>(
124 auth_clients: Option<&'a [curve25519::PublicKey]>,
125 rng: &mut R,
126 ) -> Option<ClientAuth<'a>> {
127 let Some(auth_clients) = auth_clients else {
128 return None;
130 };
131
132 let descriptor_cookie = rand::Rng::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(rng);
134
135 let secret = curve25519::StaticSecret::random_from_rng(rng);
136 let ephemeral_key = HsSvcDescEncKeypair {
137 public: curve25519::PublicKey::from(&secret).into(),
138 secret: secret.into(),
139 };
140
141 Some(ClientAuth {
142 ephemeral_key,
143 auth_clients,
144 descriptor_cookie,
145 })
146 }
147}
148
149impl<'a> NetdocBuilder for HsDescBuilder<'a> {
150 fn build_sign<R: RngCore + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
151 const SUPERENCRYPTED_ALIGN: usize = 10 * (1 << 10);
155
156 let max_generated_len = self.max_generated_len.unwrap_or(usize::MAX);
157
158 let hs_desc = self
159 .build()
160 .map_err(into_bad_api_usage!("the HsDesc could not be built"))?;
161
162 let client_auth = ClientAuth::new(hs_desc.auth_clients, rng);
163
164 let inner_plaintext = HsDescInner {
167 hs_desc_sign: hs_desc.hs_desc_sign,
168 create2_formats: hs_desc.create2_formats,
169 auth_required: hs_desc.auth_required.as_ref(),
170 is_single_onion_service: hs_desc.is_single_onion_service,
171 intro_points: hs_desc.intro_points,
172 intro_auth_key_cert_expiry: hs_desc.intro_auth_key_cert_expiry,
173 intro_enc_key_cert_expiry: hs_desc.intro_enc_key_cert_expiry,
174 #[cfg(feature = "hs-pow-full")]
175 pow_params: hs_desc.pow_params,
176 }
177 .build_sign(rng)?;
178
179 let desc_enc_nonce = client_auth
180 .as_ref()
181 .map(|client_auth| client_auth.descriptor_cookie.into());
182
183 let inner_encrypted = hs_desc.encrypt_field(
186 rng,
187 inner_plaintext.as_bytes(),
188 desc_enc_nonce.as_ref(),
189 b"hsdir-encrypted-data",
190 );
191
192 let middle_plaintext = HsDescMiddle {
195 client_auth: client_auth.as_ref(),
196 subcredential: hs_desc.subcredential,
197 encrypted: inner_encrypted,
198 }
199 .build_sign(rng)?;
200
201 let middle_plaintext =
204 pad_with_zero_to_align(middle_plaintext.as_bytes(), SUPERENCRYPTED_ALIGN);
205
206 let middle_encrypted = hs_desc.encrypt_field(
209 rng,
210 middle_plaintext.borrow(),
211 None,
213 b"hsdir-superencrypted-data",
214 );
215
216 let hsdesc = HsDescOuter {
218 hs_desc_sign: hs_desc.hs_desc_sign,
219 hs_desc_sign_cert: hs_desc.hs_desc_sign_cert,
220 lifetime: hs_desc.lifetime,
221 revision_counter: hs_desc.revision_counter,
222 superencrypted: middle_encrypted,
223 }
224 .build_sign(rng)?;
225
226 if hsdesc.len() > max_generated_len {
227 return Err(EncodeError::BadLengthValue);
228 }
229 Ok(hsdesc)
230 }
231}
232
233pub fn create_desc_sign_key_cert(
241 hs_desc_sign: &ed25519::PublicKey,
242 blind_id: &HsBlindIdKeypair,
243 expiry: SystemTime,
244) -> Result<EncodedEd25519Cert, CertEncodeError> {
245 Ed25519Cert::constructor()
249 .cert_type(CertType::HS_BLINDED_ID_V_SIGNING)
250 .expiration(expiry)
251 .signing_key(ed25519::Ed25519Identity::from(blind_id.as_ref().public()))
252 .cert_key(CertifiedKey::Ed25519(hs_desc_sign.into()))
253 .encode_and_sign(blind_id)
254}
255
256impl<'a> HsDesc<'a> {
257 fn encrypt_field<R: RngCore + CryptoRng>(
260 &self,
261 rng: &mut R,
262 plaintext: &[u8],
263 desc_enc_nonce: Option<&HsDescEncNonce>,
264 string_const: &[u8],
265 ) -> Vec<u8> {
266 let encrypt = HsDescEncryption {
267 blinded_id: &ed25519::Ed25519Identity::from(self.blinded_id.as_ref()).into(),
268 desc_enc_nonce,
269 subcredential: &self.subcredential,
270 revision: self.revision_counter,
271 string_const,
272 };
273
274 encrypt.encrypt(rng, plaintext)
275 }
276}
277
278fn pad_with_zero_to_align(v: &[u8], alignment: usize) -> Cow<[u8]> {
280 let padding = (alignment - (v.len() % alignment)) % alignment;
281
282 if padding > 0 {
283 let padded = v
284 .iter()
285 .copied()
286 .chain(std::iter::repeat_n(0, padding))
287 .collect::<Vec<_>>();
288
289 Cow::Owned(padded)
290 } else {
291 Cow::Borrowed(v)
293 }
294}
295
296#[cfg(test)]
297mod test {
298 #![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_duration_subtraction)]
308 #![allow(clippy::useless_vec)]
309 #![allow(clippy::needless_pass_by_value)]
310 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
324 pub(super) fn expect_bug(err: EncodeError) -> String {
333 match err {
334 EncodeError::Bug(b) => b.to_string(),
335 EncodeError::BadLengthValue => panic!("expected Bug, got BadLengthValue"),
336 _ => panic!("expected Bug, got unknown error"),
337 }
338 }
339
340 pub(super) fn create_intro_point_descriptor<R: RngCore + CryptoRng>(
341 rng: &mut R,
342 link_specifiers: &[LinkSpec],
343 ) -> IntroPointDesc {
344 let link_specifiers = link_specifiers
345 .iter()
346 .map(|link_spec| link_spec.encode())
347 .collect::<Result<Vec<_>, _>>()
348 .unwrap();
349
350 IntroPointDesc {
351 link_specifiers,
352 ipt_ntor_key: create_curve25519_pk(rng),
353 ipt_sid_key: ed25519::Keypair::generate(rng).verifying_key().into(),
354 svc_ntor_key: create_curve25519_pk(rng).into(),
355 }
356 }
357
358 pub(super) fn create_curve25519_pk<R: RngCore + CryptoRng>(
360 rng: &mut R,
361 ) -> curve25519::PublicKey {
362 let ephemeral_key = curve25519::EphemeralSecret::random_from_rng(rng);
363 (&ephemeral_key).into()
364 }
365
366 fn parse_hsdesc(
368 unparsed_desc: &str,
369 blinded_pk: ed25519::PublicKey,
370 subcredential: &Subcredential,
371 hsc_desc_enc: Option<&HsClientDescEncKeypair>,
372 ) -> ParsedHsDesc {
373 const TIMESTAMP: &str = "2023-01-23T15:00:00Z";
374
375 let id = ed25519::Ed25519Identity::from(blinded_pk);
376 let enc_desc: EncryptedHsDesc = ParsedHsDesc::parse(unparsed_desc, &id.into())
377 .unwrap()
378 .check_signature()
379 .unwrap()
380 .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
381 .unwrap();
382
383 enc_desc
384 .decrypt(subcredential, hsc_desc_enc)
385 .unwrap()
386 .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
387 .unwrap()
388 .check_signature()
389 .unwrap()
390 }
391
392 #[test]
393 fn encode_decode() {
394 const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::TAP, HandshakeType::NTOR];
395 const LIFETIME_MINS: u16 = 100;
396 const REVISION_COUNT: u64 = 2;
397 const CERT_EXPIRY_SECS: u64 = 60 * 60;
398
399 let mut rng = Config::Deterministic.into_rng();
400 let hs_id = ed25519::Keypair::generate(&mut rng);
402 let hs_desc_sign = ed25519::Keypair::generate(&mut rng);
403 let period = TimePeriod::new(
404 humantime::parse_duration("24 hours").unwrap(),
405 humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
406 humantime::parse_duration("12 hours").unwrap(),
407 )
408 .unwrap();
409 let (_, blinded_id, subcredential) = HsIdKeypair::from(ExpandedKeypair::from(&hs_id))
410 .compute_blinded_key(period)
411 .unwrap();
412
413 let expiry = SystemTime::now() + Duration::from_secs(CERT_EXPIRY_SECS);
414 let mut rng = Config::Deterministic.into_rng();
415 let intro_points = vec![IntroPointDesc {
416 link_specifiers: vec![LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999)
417 .encode()
418 .unwrap()],
419 ipt_ntor_key: create_curve25519_pk(&mut rng),
420 ipt_sid_key: ed25519::Keypair::generate(&mut rng).verifying_key().into(),
421 svc_ntor_key: create_curve25519_pk(&mut rng).into(),
422 }];
423
424 let hs_desc_sign_cert =
425 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
426 let blinded_pk = (&blinded_id).into();
427 let builder = HsDescBuilder::default()
428 .blinded_id(&blinded_pk)
429 .hs_desc_sign(&hs_desc_sign)
430 .hs_desc_sign_cert(hs_desc_sign_cert)
431 .create2_formats(CREATE2_FORMATS)
432 .auth_required(None)
433 .is_single_onion_service(true)
434 .intro_points(&intro_points)
435 .intro_auth_key_cert_expiry(expiry)
436 .intro_enc_key_cert_expiry(expiry)
437 .lifetime(LIFETIME_MINS.into())
438 .revision_counter(REVISION_COUNT.into())
439 .subcredential(subcredential);
440
441 let encoded_desc = builder
444 .clone()
445 .build_sign(&mut Config::Deterministic.into_rng())
446 .unwrap();
447
448 let desc = parse_hsdesc(
450 encoded_desc.as_str(),
451 *blinded_id.as_ref().public(),
452 &subcredential,
453 None, );
455
456 let hs_desc_sign_cert =
457 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
458 let reencoded_desc = HsDescBuilder::default()
461 .blinded_id(&(&blinded_id).into())
462 .hs_desc_sign(&hs_desc_sign)
463 .hs_desc_sign_cert(hs_desc_sign_cert)
464 .create2_formats(CREATE2_FORMATS)
467 .auth_required(None)
468 .is_single_onion_service(desc.is_single_onion_service)
469 .intro_points(&intro_points)
470 .intro_auth_key_cert_expiry(expiry)
471 .intro_enc_key_cert_expiry(expiry)
472 .lifetime(desc.idx_info.lifetime)
473 .revision_counter(desc.idx_info.revision)
474 .subcredential(subcredential)
475 .build_sign(&mut Config::Deterministic.into_rng())
476 .unwrap();
477
478 assert_eq!(&*encoded_desc, &*reencoded_desc);
479
480 let client_kp: HsClientDescEncKeypair = HsClientDescEncKeypair::generate(&mut rng);
483 let client_pkey = client_kp.public().as_ref();
484 let auth_clients = vec![*client_pkey];
485
486 let encoded_desc = builder
487 .auth_clients(Some(&auth_clients[..]))
488 .build_sign(&mut Config::Deterministic.into_rng())
489 .unwrap();
490
491 let desc = parse_hsdesc(
493 encoded_desc.as_str(),
494 *blinded_id.as_ref().public(),
495 &subcredential,
496 Some(&client_kp), );
498
499 let hs_desc_sign_cert =
500 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
501 let reencoded_desc = HsDescBuilder::default()
504 .blinded_id(&(&blinded_id).into())
505 .hs_desc_sign(&hs_desc_sign)
506 .hs_desc_sign_cert(hs_desc_sign_cert)
507 .create2_formats(CREATE2_FORMATS)
510 .auth_required(None)
511 .is_single_onion_service(desc.is_single_onion_service)
512 .intro_points(&intro_points)
513 .intro_auth_key_cert_expiry(expiry)
514 .intro_enc_key_cert_expiry(expiry)
515 .auth_clients(Some(&auth_clients))
516 .lifetime(desc.idx_info.lifetime)
517 .revision_counter(desc.idx_info.revision)
518 .subcredential(subcredential)
519 .build_sign(&mut Config::Deterministic.into_rng())
520 .unwrap();
521
522 assert_eq!(&*encoded_desc, &*reencoded_desc);
523 }
524}