1use caret::caret_int;
4use derive_deftly::Deftly;
5use tor_bytes::{EncodeError, EncodeResult, Reader, Result, Writeable, Writer};
6use tor_error::bad_api_usage;
7use tor_hscrypto::ops::{HsMacKey, HS_MAC_LEN};
8use tor_llcrypto::{
9 pk::ed25519::{self, Ed25519Identity, ED25519_SIGNATURE_LEN},
10 traits::ShortMac as _,
11 util::ct::CtByteArray,
12};
13use tor_memquota::derive_deftly_template_HasMemoryCost;
14use tor_units::BoundedInt32;
15
16use crate::relaycell::{extlist::*, hs::AuthKeyType, msg};
17
18caret_int! {
19 #[derive(Ord, PartialOrd)]
23 pub struct EstIntroExtType(u8) {
24 DOS_PARAMS = 1,
26 }
27}
28
29caret_int! {
30 pub struct EstIntroExtDosParamType(u8) {
35 DOS_INTRODUCE2_RATE_PER_SEC = 1,
38 DOS_INTRODUCE2_BURST_PER_SEC = 2,
41 }
42}
43
44#[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
56#[derive_deftly(HasMemoryCost)]
57pub struct DosParams {
58 rate_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
63 burst_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
68}
69
70impl DosParams {
71 pub fn new(rate_per_sec: Option<i32>, burst_per_sec: Option<i32>) -> crate::Result<Self> {
73 let normalize = |supplied: Option<i32>| -> crate::Result<_> {
74 supplied
75 .map(|val| {
76 BoundedInt32::checked_new(val).map_err(|_| {
77 crate::err::Error::CantEncode(
78 "EST_INTRO_DOS_EXT parameter value out of bound.",
79 )
80 })
81 })
82 .transpose()
83 };
84 Ok(Self {
85 rate_per_sec: normalize(rate_per_sec)?,
86 burst_per_sec: normalize(burst_per_sec)?,
87 })
88 }
89}
90
91impl Ext for DosParams {
92 type Id = EstIntroExtType;
93 fn type_id(&self) -> EstIntroExtType {
94 EstIntroExtType::DOS_PARAMS
95 }
96 fn take_body_from(b: &mut Reader<'_>) -> Result<Self> {
97 let n_prams = b.take_u8()?;
98 let mut rate_per_sec = None;
99 let mut burst_per_sec = None;
100 for _i in 0..n_prams {
101 let param_to_store = match b.take_u8()?.into() {
102 EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC => Some(&mut rate_per_sec),
103 EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC => Some(&mut burst_per_sec),
104 _ => None,
105 };
106 if let Some(param) = param_to_store {
107 if let Ok(rate) = i32::try_from(b.take_u64()?) {
108 *param = BoundedInt32::checked_new(rate).ok();
109 }
110 }
111 }
112 Ok(Self {
113 rate_per_sec,
114 burst_per_sec,
115 })
116 }
117 fn write_body_onto<B: Writer + ?Sized>(&self, b: &mut B) -> EncodeResult<()> {
118 let mut params = vec![];
119 let mut push_params = |ty, value| {
120 if let Some(value) = value {
121 params.push((ty, value));
122 }
123 };
124 push_params(
125 EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC,
126 self.rate_per_sec,
127 );
128 push_params(
129 EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC,
130 self.burst_per_sec,
131 );
132 b.write_u8(u8::try_from(params.len()).map_err(|_| EncodeError::BadLengthValue)?);
133 for (t, v) in params {
134 b.write_u8(t.get());
135 b.write_u64(v.get() as u64);
136 }
137 Ok(())
138 }
139}
140
141decl_extension_group! {
142 #[derive(Debug,Clone,Deftly)]
144 #[derive_deftly(HasMemoryCost)]
145 enum EstablishIntroExt [ EstIntroExtType ] {
146 DosParams,
147 }
148}
149
150#[derive(Debug, Clone, Deftly)]
156#[derive_deftly(HasMemoryCost)]
157pub struct EstablishIntroDetails {
158 auth_key: Ed25519Identity,
160 extensions: ExtList<EstablishIntroExt>,
162}
163
164#[derive(educe::Educe, Clone, Deftly)]
179#[derive_deftly(HasMemoryCost)]
180#[educe(Debug)]
181pub struct EstablishIntro {
182 body: EstablishIntroDetails,
184 handshake_auth: CtByteArray<HS_MAC_LEN>,
190 #[educe(Debug(ignore))]
192 mac_plaintext: Vec<u8>,
193 #[educe(Debug(ignore))]
200 sig: Box<ed25519::ValidatableEd25519Signature>,
201}
202
203impl Writeable for EstablishIntroDetails {
204 fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
205 let auth_key_type = AuthKeyType::ED25519_SHA3_256;
206 w.write_u8(auth_key_type.get());
207 {
208 let mut w_nested = w.write_nested_u16len();
209 w_nested.write(&self.auth_key)?;
210 w_nested.finish()?;
211 }
212 w.write(&self.extensions)?;
213 Ok(())
214 }
215}
216
217const SIG_PREFIX: &[u8] = b"Tor establish-intro cell v1";
219
220impl msg::Body for EstablishIntro {
221 fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
222 let cursor_start = r.cursor();
223 let auth_key_type: AuthKeyType = r.take_u8()?.into();
224 let auth_key = match auth_key_type {
227 AuthKeyType::ED25519_SHA3_256 => r.read_nested_u16len(|r| r.extract())?,
228 _ => {
229 return Err(tor_bytes::Error::InvalidMessage(
230 format!("unrecognized authkey type {:?}", auth_key_type).into(),
231 ))
232 }
233 };
234
235 let extensions = r.extract()?;
236 let cursor_mac = r.cursor();
237 let handshake_auth = r.extract()?;
238 let cursor_sig = r.cursor();
239 let sig = r.read_nested_u16len(|r| r.extract())?;
240
241 let mac_plaintext = r.range(cursor_start, cursor_mac).into();
242
243 let public_key = ed25519::PublicKey::try_from(&auth_key)
244 .map_err(|_| tor_bytes::Error::InvalidMessage("Invalid ed25519 key".into()))?;
245 let mut signed_material = Vec::from(SIG_PREFIX);
246 signed_material.extend(r.range(cursor_start, cursor_sig));
247 let sig = Box::new(ed25519::ValidatableEd25519Signature::new(
248 public_key,
249 sig,
250 &signed_material[..],
251 ));
252
253 Ok(EstablishIntro {
254 body: EstablishIntroDetails {
255 auth_key,
256 extensions,
257 },
258 handshake_auth,
259 mac_plaintext,
260 sig,
261 })
262 }
263
264 fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
267 w.write(&self.body)?;
268 w.write_all(self.handshake_auth.as_ref());
269 {
270 let mut w_inner = w.write_nested_u16len();
271 w_inner.write(self.sig.signature())?;
272 w_inner.finish()?;
273 }
274 Ok(())
275 }
276}
277
278impl EstablishIntroDetails {
279 pub fn new(auth_key: Ed25519Identity) -> Self {
281 Self {
282 auth_key,
283 extensions: Default::default(),
284 }
285 }
286
287 pub fn set_extension_dos(&mut self, extension_dos: DosParams) {
289 self.extensions.replace_by_type(extension_dos.into());
290 }
291
292 pub fn set_extension_other(&mut self, other: UnrecognizedExt<EstIntroExtType>) {
294 self.extensions.replace_by_type(other.into());
295 }
296
297 pub fn sign_and_encode<'a>(
304 self,
305 keypair: &ed25519::Keypair,
306 mac_key: impl Into<HsMacKey<'a>>,
307 ) -> crate::Result<Vec<u8>> {
308 if Ed25519Identity::from(keypair.verifying_key()) != self.auth_key {
309 return Err(crate::Error::Internal(bad_api_usage!("Key mismatch")));
310 }
311
312 let mut output = Vec::new();
313
314 output.write(&self)?;
315 let mac_key: HsMacKey<'_> = mac_key.into();
316 let mac = mac_key.mac(&output[..]);
317 output.write(&mac)?;
318 let signature = {
319 let mut signed_material = Vec::from(SIG_PREFIX);
320 signed_material.extend(&output[..]);
321 keypair.sign(&signed_material[..])
322 };
323 output.write_u16(
324 ED25519_SIGNATURE_LEN
325 .try_into()
326 .expect("ed25519 signature len is somehow > u16::MAX"),
327 );
328 output.write(&signature)?;
329
330 Ok(output)
331 }
332}
333
334impl EstablishIntro {
335 #[cfg(feature = "testing")]
346 pub fn from_parts_for_test(
347 body: EstablishIntroDetails,
348 mac: CtByteArray<HS_MAC_LEN>,
349 signature: ed25519::Signature,
350 ) -> Self {
351 use tor_llcrypto::pk::ed25519::ValidatableEd25519Signature;
352 let sig = Box::new(ValidatableEd25519Signature::new(
353 body.auth_key.try_into().expect("Invalid public key"),
354 signature,
355 &[],
356 ));
357 Self {
358 body,
359 handshake_auth: mac,
360 mac_plaintext: vec![],
361 sig,
362 }
363 }
364
365 pub fn check_and_unwrap<'a>(
371 self,
372 mac_key: impl Into<HsMacKey<'a>>,
373 ) -> std::result::Result<EstablishIntroDetails, EstablishIntroSigError> {
374 use tor_llcrypto::pk::ValidatableSignature;
375
376 let mac_key: HsMacKey<'_> = mac_key.into();
377 let mac_okay = mac_key.validate(&self.mac_plaintext, &self.handshake_auth);
378 let sig_okay = self.sig.is_valid();
379
380 if !(bool::from(mac_okay) & sig_okay) {
381 return Err(EstablishIntroSigError::Invalid);
382 }
383
384 Ok(self.dangerously_unwrap())
385 }
386
387 pub fn dangerously_unwrap(self) -> EstablishIntroDetails {
391 self.body
392 }
393}
394
395#[derive(thiserror::Error, Clone, Debug)]
399#[non_exhaustive]
400pub enum EstablishIntroSigError {
401 #[error("Invalid signature or MAC on ESTABLISH_INTRO message.")]
403 Invalid,
404}