tor_cell/relaycell/hs/
est_intro.rs

1//! Define the ESTABLISH_INTRO message and related types.
2
3use 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    /// The introduction protocol extension type.
20    ///
21    /// Documented in <https://spec.torproject.org/rend-spec/introduction-protocol.html#EST_INTRO>
22    #[derive(Ord, PartialOrd)]
23    pub struct EstIntroExtType(u8) {
24        /// The extension used to send DoS parameters
25        DOS_PARAMS = 1,
26    }
27}
28
29caret_int! {
30    /// The recognized parameter types in an establish intro
31    /// DoS extension.
32    ///
33    /// See <https://spec.torproject.org/rend-spec/introduction-protocol.html#EST_INTRO_DOS_EXT>
34    pub struct EstIntroExtDosParamType(u8) {
35        /// The rate per second of INTRODUCE2 cell relayed
36        /// to the service.
37        DOS_INTRODUCE2_RATE_PER_SEC = 1,
38        /// The burst per second of INTRODUCE2 cell relayed
39        /// to the service
40        DOS_INTRODUCE2_BURST_PER_SEC = 2,
41    }
42}
43
44/// Extension to tell the introduction point to rate-limit.
45///
46/// When we sent this extension, it tells the introduction point to rate-limit
47/// the INTRODUCE2 messages it sends us to the rates shown here.
48///
49/// When this extension is not sent, the introduction point imposes a rate-limit
50/// depending on parameters in the latest consensus.
51///
52/// This extension requires protover `HSIntro=5`.
53///
54/// See <https://spec.torproject.org/rend-spec/introduction-protocol.html#EST_INTRO_DOS_EXT>.
55#[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
56#[derive_deftly(HasMemoryCost)]
57pub struct DosParams {
58    /// An optional parameter indicates the rate per second of
59    /// INTRODUCE2 cell relayed to the service.
60    ///
61    /// Min: 0, Max: 2147483647
62    rate_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
63    /// An optional parameter indicates the burst per second of
64    /// INTRODUCE2 cell relayed to the service
65    ///
66    /// Min: 0, Max: 2147483647
67    burst_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
68}
69
70impl DosParams {
71    /// Create a new establish intro DoS extension.
72    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    /// An extension to an EstablishIntro cell.
143    #[derive(Debug,Clone,Deftly)]
144    #[derive_deftly(HasMemoryCost)]
145    enum EstablishIntroExt [ EstIntroExtType ] {
146        DosParams,
147    }
148}
149
150/// The body of an EstablishIntro message, after the signature and MAC are
151/// verified.
152///
153/// This tells the introduction point which key it should act as an introduction
154/// for, and how.
155#[derive(Debug, Clone, Deftly)]
156#[derive_deftly(HasMemoryCost)]
157pub struct EstablishIntroDetails {
158    /// The public introduction point auth key.
159    auth_key: Ed25519Identity,
160    /// A list of extensions on this cell.
161    extensions: ExtList<EstablishIntroExt>,
162}
163
164/// A hidden services establishes a new introduction point, by sending an
165/// EstablishIntro message.
166///
167/// This may represent either an outbound body that we're sending, or a decoded
168/// body that we're receiving.
169///
170/// # Usage
171///
172/// This type is a good choice for handling an incoming EstablishIntro message
173/// on a Relay, but not for generating an outgoing EstablishIntro message.
174///
175/// Onion services should not construct this message object; instead, they
176/// should construct an [`EstablishIntroDetails`], and then call its
177/// `sign_and_encode` method.
178#[derive(educe::Educe, Clone, Deftly)]
179#[derive_deftly(HasMemoryCost)]
180#[educe(Debug)]
181pub struct EstablishIntro {
182    /// The underlying body of this, wrapped in authentication.
183    body: EstablishIntroDetails,
184    /// The MAC of all earlier fields in the cell, using a key derived from the
185    /// handshake between the onion service and the introduction point.
186    ///
187    /// This MAC binds the EstablishIntro message to a single circuit, and keeps
188    /// it from being replayed.
189    handshake_auth: CtByteArray<HS_MAC_LEN>,
190    /// A textual record of all the fields in the message that are covered by the MAC.
191    #[educe(Debug(ignore))]
192    mac_plaintext: Vec<u8>,
193    /// A signature using `auth_key` of all contents of the message.
194    ///
195    /// This signature proves possession of `auth_key` and thereby ensures that
196    /// the request really comes from that key's holder.
197    ///
198    /// (This field is boxed to manage variant size.)
199    #[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
217/// A string that we prefix onto any establish_intro body before signing it.
218const 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        // Only Ed25519 is recognized... and it *needs* to be recognized or else we
225        // can't verify the signature.
226        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    // Note: this is not the typical way to encode an EstablishIntro message. Actual onion services
265    // will use `sign_and_encode`.
266    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    /// All arguments constructor
280    pub fn new(auth_key: Ed25519Identity) -> Self {
281        Self {
282            auth_key,
283            extensions: Default::default(),
284        }
285    }
286
287    /// Set EST_INTRO_DOS_EXT with given `extension_dos`.
288    pub fn set_extension_dos(&mut self, extension_dos: DosParams) {
289        self.extensions.replace_by_type(extension_dos.into());
290    }
291
292    /// Add an extension of some other type.
293    pub fn set_extension_other(&mut self, other: UnrecognizedExt<EstIntroExtType>) {
294        self.extensions.replace_by_type(other.into());
295    }
296
297    /// Sign and authenticate this body using a provided Ed25519 keypair and MAC
298    /// key.
299    ///
300    /// The MAC key is derived from the circuit handshake between the onion
301    /// service and the introduction point.  The Ed25519 keypair must match the
302    /// one given as the auth_key for this body.
303    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    /// Construct a new EstablishIntro message from its constituent parts.
336    ///
337    /// # Limitations
338    ///
339    /// This is really only useful for testing; it will construct a version of the
340    /// object whose signatures will probably never check as valid.
341    ///
342    /// # Panics
343    ///
344    /// Panics if the body's public key is not a valid ed25519 public key
345    #[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    /// Check whether this EstablishIntro message is well-signed (with its
366    /// included key), and well authenticated with the provided MAC key.
367    ///
368    /// On success, return the [`EstablishIntroDetails`] describing how to function
369    /// as an introduction point for this service.  On failure, return an error.
370    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    /// Consume this EstablishIntro message and return its  body.
388    ///
389    /// This is a "dangerous" function because it does not check correctness for the signature or the MAC.
390    pub fn dangerously_unwrap(self) -> EstablishIntroDetails {
391        self.body
392    }
393}
394
395/// An error that has occurred while trying to validate an EstablishIntro message.
396///
397/// This error is deliberately uninformative.
398#[derive(thiserror::Error, Clone, Debug)]
399#[non_exhaustive]
400pub enum EstablishIntroSigError {
401    /// The authentication information on an EstablishIntro message was incorrect.
402    #[error("Invalid signature or MAC on ESTABLISH_INTRO message.")]
403    Invalid,
404}