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 #[builder(default)]
79 auth_clients: Option<&'a [curve25519::PublicKey]>,
80 lifetime: IntegerMinutes<u16>,
86 revision_counter: RevisionCounter,
89 subcredential: Subcredential,
91}
92
93#[derive(Debug)]
95pub(super) struct ClientAuth<'a> {
96 ephemeral_key: HsSvcDescEncKeypair,
101 auth_clients: &'a [curve25519::PublicKey],
103 descriptor_cookie: [u8; HS_DESC_ENC_NONCE_LEN],
107}
108
109impl<'a> ClientAuth<'a> {
110 fn new<R: RngCore + CryptoRng>(
116 auth_clients: Option<&'a [curve25519::PublicKey]>,
117 rng: &mut R,
118 ) -> Option<ClientAuth<'a>> {
119 let Some(auth_clients) = auth_clients else {
120 return None;
122 };
123
124 let descriptor_cookie = rand::Rng::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(rng);
126
127 let secret = curve25519::StaticSecret::random_from_rng(rng);
128 let ephemeral_key = HsSvcDescEncKeypair {
129 public: curve25519::PublicKey::from(&secret).into(),
130 secret: secret.into(),
131 };
132
133 Some(ClientAuth {
134 ephemeral_key,
135 auth_clients,
136 descriptor_cookie,
137 })
138 }
139}
140
141impl<'a> NetdocBuilder for HsDescBuilder<'a> {
142 fn build_sign<R: RngCore + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
143 const SUPERENCRYPTED_ALIGN: usize = 10 * (1 << 10);
147
148 let hs_desc = self
149 .build()
150 .map_err(into_bad_api_usage!("the HsDesc could not be built"))?;
151
152 let client_auth = ClientAuth::new(hs_desc.auth_clients, rng);
153
154 let inner_plaintext = HsDescInner {
157 hs_desc_sign: hs_desc.hs_desc_sign,
158 create2_formats: hs_desc.create2_formats,
159 auth_required: hs_desc.auth_required.as_ref(),
160 is_single_onion_service: hs_desc.is_single_onion_service,
161 intro_points: hs_desc.intro_points,
162 intro_auth_key_cert_expiry: hs_desc.intro_auth_key_cert_expiry,
163 intro_enc_key_cert_expiry: hs_desc.intro_enc_key_cert_expiry,
164 #[cfg(feature = "hs-pow-full")]
165 pow_params: hs_desc.pow_params,
166 }
167 .build_sign(rng)?;
168
169 let desc_enc_nonce = client_auth
170 .as_ref()
171 .map(|client_auth| client_auth.descriptor_cookie.into());
172
173 let inner_encrypted = hs_desc.encrypt_field(
176 rng,
177 inner_plaintext.as_bytes(),
178 desc_enc_nonce.as_ref(),
179 b"hsdir-encrypted-data",
180 );
181
182 let middle_plaintext = HsDescMiddle {
185 client_auth: client_auth.as_ref(),
186 subcredential: hs_desc.subcredential,
187 encrypted: inner_encrypted,
188 }
189 .build_sign(rng)?;
190
191 let middle_plaintext =
194 pad_with_zero_to_align(middle_plaintext.as_bytes(), SUPERENCRYPTED_ALIGN);
195
196 let middle_encrypted = hs_desc.encrypt_field(
199 rng,
200 middle_plaintext.borrow(),
201 None,
203 b"hsdir-superencrypted-data",
204 );
205
206 HsDescOuter {
208 hs_desc_sign: hs_desc.hs_desc_sign,
209 hs_desc_sign_cert: hs_desc.hs_desc_sign_cert,
210 lifetime: hs_desc.lifetime,
211 revision_counter: hs_desc.revision_counter,
212 superencrypted: middle_encrypted,
213 }
214 .build_sign(rng)
215 }
216}
217
218pub fn create_desc_sign_key_cert(
226 hs_desc_sign: &ed25519::PublicKey,
227 blind_id: &HsBlindIdKeypair,
228 expiry: SystemTime,
229) -> Result<EncodedEd25519Cert, CertEncodeError> {
230 Ed25519Cert::constructor()
234 .cert_type(CertType::HS_BLINDED_ID_V_SIGNING)
235 .expiration(expiry)
236 .signing_key(ed25519::Ed25519Identity::from(blind_id.as_ref().public()))
237 .cert_key(CertifiedKey::Ed25519(hs_desc_sign.into()))
238 .encode_and_sign(blind_id)
239}
240
241impl<'a> HsDesc<'a> {
242 fn encrypt_field<R: RngCore + CryptoRng>(
245 &self,
246 rng: &mut R,
247 plaintext: &[u8],
248 desc_enc_nonce: Option<&HsDescEncNonce>,
249 string_const: &[u8],
250 ) -> Vec<u8> {
251 let encrypt = HsDescEncryption {
252 blinded_id: &ed25519::Ed25519Identity::from(self.blinded_id.as_ref()).into(),
253 desc_enc_nonce,
254 subcredential: &self.subcredential,
255 revision: self.revision_counter,
256 string_const,
257 };
258
259 encrypt.encrypt(rng, plaintext)
260 }
261}
262
263fn pad_with_zero_to_align(v: &[u8], alignment: usize) -> Cow<[u8]> {
265 let padding = (alignment - (v.len() % alignment)) % alignment;
266
267 if padding > 0 {
268 let padded = v
269 .iter()
270 .copied()
271 .chain(std::iter::repeat(0).take(padding))
272 .collect::<Vec<_>>();
273
274 Cow::Owned(padded)
275 } else {
276 Cow::Borrowed(v)
278 }
279}
280
281#[cfg(test)]
282mod test {
283 #![allow(clippy::bool_assert_comparison)]
285 #![allow(clippy::clone_on_copy)]
286 #![allow(clippy::dbg_macro)]
287 #![allow(clippy::mixed_attributes_style)]
288 #![allow(clippy::print_stderr)]
289 #![allow(clippy::print_stdout)]
290 #![allow(clippy::single_char_pattern)]
291 #![allow(clippy::unwrap_used)]
292 #![allow(clippy::unchecked_duration_subtraction)]
293 #![allow(clippy::useless_vec)]
294 #![allow(clippy::needless_pass_by_value)]
295 use std::net::Ipv4Addr;
298 use std::time::Duration;
299
300 use super::*;
301 use crate::doc::hsdesc::{EncryptedHsDesc, HsDesc as ParsedHsDesc};
302 use tor_basic_utils::test_rng::Config;
303 use tor_checkable::{SelfSigned, Timebound};
304 use tor_hscrypto::pk::{HsClientDescEncKeypair, HsIdKeypair};
305 use tor_hscrypto::time::TimePeriod;
306 use tor_linkspec::LinkSpec;
307 use tor_llcrypto::pk::{curve25519, ed25519::ExpandedKeypair};
308
309 pub(super) fn expect_bug(err: EncodeError) -> String {
318 match err {
319 EncodeError::Bug(b) => b.to_string(),
320 EncodeError::BadLengthValue => panic!("expected Bug, got BadLengthValue"),
321 _ => panic!("expected Bug, got unknown error"),
322 }
323 }
324
325 pub(super) fn create_intro_point_descriptor<R: RngCore + CryptoRng>(
326 rng: &mut R,
327 link_specifiers: &[LinkSpec],
328 ) -> IntroPointDesc {
329 let link_specifiers = link_specifiers
330 .iter()
331 .map(|link_spec| link_spec.encode())
332 .collect::<Result<Vec<_>, _>>()
333 .unwrap();
334
335 IntroPointDesc {
336 link_specifiers,
337 ipt_ntor_key: create_curve25519_pk(rng),
338 ipt_sid_key: ed25519::Keypair::generate(rng).verifying_key().into(),
339 svc_ntor_key: create_curve25519_pk(rng).into(),
340 }
341 }
342
343 pub(super) fn create_curve25519_pk<R: RngCore + CryptoRng>(
345 rng: &mut R,
346 ) -> curve25519::PublicKey {
347 let ephemeral_key = curve25519::EphemeralSecret::random_from_rng(rng);
348 (&ephemeral_key).into()
349 }
350
351 fn parse_hsdesc(
353 unparsed_desc: &str,
354 blinded_pk: ed25519::PublicKey,
355 subcredential: &Subcredential,
356 hsc_desc_enc: Option<&HsClientDescEncKeypair>,
357 ) -> ParsedHsDesc {
358 const TIMESTAMP: &str = "2023-01-23T15:00:00Z";
359
360 let id = ed25519::Ed25519Identity::from(blinded_pk);
361 let enc_desc: EncryptedHsDesc = ParsedHsDesc::parse(unparsed_desc, &id.into())
362 .unwrap()
363 .check_signature()
364 .unwrap()
365 .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
366 .unwrap();
367
368 enc_desc
369 .decrypt(subcredential, hsc_desc_enc)
370 .unwrap()
371 .check_valid_at(&humantime::parse_rfc3339(TIMESTAMP).unwrap())
372 .unwrap()
373 .check_signature()
374 .unwrap()
375 }
376
377 #[test]
378 fn encode_decode() {
379 const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::TAP, HandshakeType::NTOR];
380 const LIFETIME_MINS: u16 = 100;
381 const REVISION_COUNT: u64 = 2;
382 const CERT_EXPIRY_SECS: u64 = 60 * 60;
383
384 let mut rng = Config::Deterministic.into_rng();
385 let hs_id = ed25519::Keypair::generate(&mut rng);
387 let hs_desc_sign = ed25519::Keypair::generate(&mut rng);
388 let period = TimePeriod::new(
389 humantime::parse_duration("24 hours").unwrap(),
390 humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
391 humantime::parse_duration("12 hours").unwrap(),
392 )
393 .unwrap();
394 let (_, blinded_id, subcredential) = HsIdKeypair::from(ExpandedKeypair::from(&hs_id))
395 .compute_blinded_key(period)
396 .unwrap();
397
398 let expiry = SystemTime::now() + Duration::from_secs(CERT_EXPIRY_SECS);
399 let mut rng = Config::Deterministic.into_rng();
400 let intro_points = vec![IntroPointDesc {
401 link_specifiers: vec![LinkSpec::OrPort(Ipv4Addr::LOCALHOST.into(), 9999)
402 .encode()
403 .unwrap()],
404 ipt_ntor_key: create_curve25519_pk(&mut rng),
405 ipt_sid_key: ed25519::Keypair::generate(&mut rng).verifying_key().into(),
406 svc_ntor_key: create_curve25519_pk(&mut rng).into(),
407 }];
408
409 let hs_desc_sign_cert =
410 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
411 let blinded_pk = (&blinded_id).into();
412 let builder = HsDescBuilder::default()
413 .blinded_id(&blinded_pk)
414 .hs_desc_sign(&hs_desc_sign)
415 .hs_desc_sign_cert(hs_desc_sign_cert)
416 .create2_formats(CREATE2_FORMATS)
417 .auth_required(None)
418 .is_single_onion_service(true)
419 .intro_points(&intro_points)
420 .intro_auth_key_cert_expiry(expiry)
421 .intro_enc_key_cert_expiry(expiry)
422 .lifetime(LIFETIME_MINS.into())
423 .revision_counter(REVISION_COUNT.into())
424 .subcredential(subcredential);
425
426 let encoded_desc = builder
429 .clone()
430 .build_sign(&mut Config::Deterministic.into_rng())
431 .unwrap();
432
433 let desc = parse_hsdesc(
435 encoded_desc.as_str(),
436 *blinded_id.as_ref().public(),
437 &subcredential,
438 None, );
440
441 let hs_desc_sign_cert =
442 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
443 let reencoded_desc = HsDescBuilder::default()
446 .blinded_id(&(&blinded_id).into())
447 .hs_desc_sign(&hs_desc_sign)
448 .hs_desc_sign_cert(hs_desc_sign_cert)
449 .create2_formats(CREATE2_FORMATS)
452 .auth_required(None)
453 .is_single_onion_service(desc.is_single_onion_service)
454 .intro_points(&intro_points)
455 .intro_auth_key_cert_expiry(expiry)
456 .intro_enc_key_cert_expiry(expiry)
457 .lifetime(desc.idx_info.lifetime)
458 .revision_counter(desc.idx_info.revision)
459 .subcredential(subcredential)
460 .build_sign(&mut Config::Deterministic.into_rng())
461 .unwrap();
462
463 assert_eq!(&*encoded_desc, &*reencoded_desc);
464
465 let client_kp: HsClientDescEncKeypair = HsClientDescEncKeypair::generate(&mut rng);
468 let client_pkey = client_kp.public().as_ref();
469 let auth_clients = vec![*client_pkey];
470
471 let encoded_desc = builder
472 .auth_clients(Some(&auth_clients[..]))
473 .build_sign(&mut Config::Deterministic.into_rng())
474 .unwrap();
475
476 let desc = parse_hsdesc(
478 encoded_desc.as_str(),
479 *blinded_id.as_ref().public(),
480 &subcredential,
481 Some(&client_kp), );
483
484 let hs_desc_sign_cert =
485 create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, expiry).unwrap();
486 let reencoded_desc = HsDescBuilder::default()
489 .blinded_id(&(&blinded_id).into())
490 .hs_desc_sign(&hs_desc_sign)
491 .hs_desc_sign_cert(hs_desc_sign_cert)
492 .create2_formats(CREATE2_FORMATS)
495 .auth_required(None)
496 .is_single_onion_service(desc.is_single_onion_service)
497 .intro_points(&intro_points)
498 .intro_auth_key_cert_expiry(expiry)
499 .intro_enc_key_cert_expiry(expiry)
500 .auth_clients(Some(&auth_clients))
501 .lifetime(desc.idx_info.lifetime)
502 .revision_counter(desc.idx_info.revision)
503 .subcredential(subcredential)
504 .build_sign(&mut Config::Deterministic.into_rng())
505 .unwrap();
506
507 assert_eq!(&*encoded_desc, &*reencoded_desc);
508 }
509}