1mod inner;
4mod middle;
5mod outer;
6
7use crate::NetdocBuilder;
8use crate::doc::hsdesc::{IntroAuthType, IntroPointDesc};
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::{HS_DESC_ENC_NONCE_LEN, HsDescEncNonce, HsDescEncryption};
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![
417 LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999)
418 .encode()
419 .unwrap(),
420 ],
421 ipt_ntor_key: create_curve25519_pk(&mut rng),
422 ipt_sid_key: ed25519::Keypair::generate(&mut rng).verifying_key().into(),
423 svc_ntor_key: create_curve25519_pk(&mut rng).into(),
424 }];
425
426 let hs_desc_sign_cert =
427 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
428 let blinded_pk = (&blinded_id).into();
429 let builder = HsDescBuilder::default()
430 .blinded_id(&blinded_pk)
431 .hs_desc_sign(&hs_desc_sign)
432 .hs_desc_sign_cert(hs_desc_sign_cert)
433 .create2_formats(CREATE2_FORMATS)
434 .auth_required(None)
435 .is_single_onion_service(true)
436 .intro_points(&intro_points)
437 .intro_auth_key_cert_expiry(expiry)
438 .intro_enc_key_cert_expiry(expiry)
439 .lifetime(LIFETIME_MINS.into())
440 .revision_counter(REVISION_COUNT.into())
441 .subcredential(subcredential);
442
443 let encoded_desc = builder
446 .clone()
447 .build_sign(&mut Config::Deterministic.into_rng())
448 .unwrap();
449
450 let desc = parse_hsdesc(
452 encoded_desc.as_str(),
453 *blinded_id.as_ref().public(),
454 &subcredential,
455 None, );
457
458 let hs_desc_sign_cert =
459 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
460 let reencoded_desc = HsDescBuilder::default()
463 .blinded_id(&(&blinded_id).into())
464 .hs_desc_sign(&hs_desc_sign)
465 .hs_desc_sign_cert(hs_desc_sign_cert)
466 .create2_formats(CREATE2_FORMATS)
469 .auth_required(None)
470 .is_single_onion_service(desc.is_single_onion_service)
471 .intro_points(&intro_points)
472 .intro_auth_key_cert_expiry(expiry)
473 .intro_enc_key_cert_expiry(expiry)
474 .lifetime(desc.idx_info.lifetime)
475 .revision_counter(desc.idx_info.revision)
476 .subcredential(subcredential)
477 .build_sign(&mut Config::Deterministic.into_rng())
478 .unwrap();
479
480 assert_eq!(&*encoded_desc, &*reencoded_desc);
481
482 let client_kp: HsClientDescEncKeypair = HsClientDescEncKeypair::generate(&mut rng);
485 let client_pkey = client_kp.public().as_ref();
486 let auth_clients = vec![*client_pkey];
487
488 let encoded_desc = builder
489 .auth_clients(Some(&auth_clients[..]))
490 .build_sign(&mut Config::Deterministic.into_rng())
491 .unwrap();
492
493 let desc = parse_hsdesc(
495 encoded_desc.as_str(),
496 *blinded_id.as_ref().public(),
497 &subcredential,
498 Some(&client_kp), );
500
501 let hs_desc_sign_cert =
502 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
503 let reencoded_desc = HsDescBuilder::default()
506 .blinded_id(&(&blinded_id).into())
507 .hs_desc_sign(&hs_desc_sign)
508 .hs_desc_sign_cert(hs_desc_sign_cert)
509 .create2_formats(CREATE2_FORMATS)
512 .auth_required(None)
513 .is_single_onion_service(desc.is_single_onion_service)
514 .intro_points(&intro_points)
515 .intro_auth_key_cert_expiry(expiry)
516 .intro_enc_key_cert_expiry(expiry)
517 .auth_clients(Some(&auth_clients))
518 .lifetime(desc.idx_info.lifetime)
519 .revision_counter(desc.idx_info.revision)
520 .subcredential(subcredential)
521 .build_sign(&mut Config::Deterministic.into_rng())
522 .unwrap();
523
524 assert_eq!(&*encoded_desc, &*reencoded_desc);
525 }
526}