1
//! Define the ESTABLISH_INTRO message and related types.
2

            
3
use caret::caret_int;
4
use derive_deftly::Deftly;
5
use tor_bytes::{EncodeError, EncodeResult, Reader, Result, Writeable, Writer};
6
use tor_error::bad_api_usage;
7
use tor_hscrypto::ops::{HsMacKey, HS_MAC_LEN};
8
use tor_llcrypto::{
9
    pk::ed25519::{self, Ed25519Identity, ED25519_SIGNATURE_LEN},
10
    traits::ShortMac as _,
11
    util::ct::CtByteArray,
12
};
13
use tor_memquota::derive_deftly_template_HasMemoryCost;
14
use tor_units::BoundedInt32;
15

            
16
use crate::relaycell::{extlist::*, hs::AuthKeyType, msg};
17

            
18
caret_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

            
29
caret_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)]
57
pub 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

            
70
impl DosParams {
71
    /// Create a new establish intro DoS extension.
72
110
    pub fn new(rate_per_sec: Option<i32>, burst_per_sec: Option<i32>) -> crate::Result<Self> {
73
222
        let normalize = |supplied: Option<i32>| -> crate::Result<_> {
74
220
            supplied
75
220
                .map(|val| {
76
220
                    BoundedInt32::checked_new(val).map_err(|_| {
77
                        crate::err::Error::CantEncode(
78
                            "EST_INTRO_DOS_EXT parameter value out of bound.",
79
                        )
80
220
                    })
81
220
                })
82
220
                .transpose()
83
220
        };
84
        Ok(Self {
85
110
            rate_per_sec: normalize(rate_per_sec)?,
86
110
            burst_per_sec: normalize(burst_per_sec)?,
87
        })
88
110
    }
89
}
90

            
91
impl Ext for DosParams {
92
    type Id = EstIntroExtType;
93
385
    fn type_id(&self) -> EstIntroExtType {
94
385
        EstIntroExtType::DOS_PARAMS
95
385
    }
96
110
    fn take_body_from(b: &mut Reader<'_>) -> Result<Self> {
97
110
        let n_prams = b.take_u8()?;
98
110
        let mut rate_per_sec = None;
99
110
        let mut burst_per_sec = None;
100
220
        for _i in 0..n_prams {
101
220
            let param_to_store = match b.take_u8()?.into() {
102
110
                EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC => Some(&mut rate_per_sec),
103
110
                EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC => Some(&mut burst_per_sec),
104
                _ => None,
105
            };
106
220
            if let Some(param) = param_to_store {
107
220
                if let Ok(rate) = i32::try_from(b.take_u64()?) {
108
220
                    *param = BoundedInt32::checked_new(rate).ok();
109
220
                }
110
            }
111
        }
112
110
        Ok(Self {
113
110
            rate_per_sec,
114
110
            burst_per_sec,
115
110
        })
116
110
    }
117
8
    fn write_body_onto<B: Writer + ?Sized>(&self, b: &mut B) -> EncodeResult<()> {
118
8
        let mut params = vec![];
119
16
        let mut push_params = |ty, value| {
120
16
            if let Some(value) = value {
121
16
                params.push((ty, value));
122
16
            }
123
16
        };
124
8
        push_params(
125
8
            EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC,
126
8
            self.rate_per_sec,
127
8
        );
128
8
        push_params(
129
8
            EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC,
130
8
            self.burst_per_sec,
131
8
        );
132
8
        b.write_u8(u8::try_from(params.len()).map_err(|_| EncodeError::BadLengthValue)?);
133
24
        for (t, v) in params {
134
16
            b.write_u8(t.get());
135
16
            b.write_u64(v.get() as u64);
136
16
        }
137
8
        Ok(())
138
8
    }
139
}
140

            
141
decl_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)]
157
pub 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
12
#[derive(educe::Educe, Clone, Deftly)]
179
#[derive_deftly(HasMemoryCost)]
180
#[educe(Debug)]
181
pub 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

            
203
impl Writeable for EstablishIntroDetails {
204
14
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
205
14
        let auth_key_type = AuthKeyType::ED25519_SHA3_256;
206
14
        w.write_u8(auth_key_type.get());
207
14
        {
208
14
            let mut w_nested = w.write_nested_u16len();
209
14
            w_nested.write(&self.auth_key)?;
210
14
            w_nested.finish()?;
211
        }
212
14
        w.write(&self.extensions)?;
213
14
        Ok(())
214
14
    }
215
}
216

            
217
/// A string that we prefix onto any establish_intro body before signing it.
218
const SIG_PREFIX: &[u8] = b"Tor establish-intro cell v1";
219

            
220
impl msg::Body for EstablishIntro {
221
275
    fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
222
275
        let cursor_start = r.cursor();
223
275
        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
275
        let auth_key = match auth_key_type {
227
280
            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
275
        let extensions = r.extract()?;
236
275
        let cursor_mac = r.cursor();
237
275
        let handshake_auth = r.extract()?;
238
275
        let cursor_sig = r.cursor();
239
280
        let sig = r.read_nested_u16len(|r| r.extract())?;
240

            
241
275
        let mac_plaintext = r.range(cursor_start, cursor_mac).into();
242

            
243
275
        let public_key = ed25519::PublicKey::try_from(&auth_key)
244
275
            .map_err(|_| tor_bytes::Error::InvalidMessage("Invalid ed25519 key".into()))?;
245
275
        let mut signed_material = Vec::from(SIG_PREFIX);
246
275
        signed_material.extend(r.range(cursor_start, cursor_sig));
247
275
        let sig = Box::new(ed25519::ValidatableEd25519Signature::new(
248
275
            public_key,
249
275
            sig,
250
275
            &signed_material[..],
251
275
        ));
252
275

            
253
275
        Ok(EstablishIntro {
254
275
            body: EstablishIntroDetails {
255
275
                auth_key,
256
275
                extensions,
257
275
            },
258
275
            handshake_auth,
259
275
            mac_plaintext,
260
275
            sig,
261
275
        })
262
275
    }
263

            
264
    // Note: this is not the typical way to encode an EstablishIntro message. Actual onion services
265
    // will use `sign_and_encode`.
266
12
    fn encode_onto<W: Writer + ?Sized>(self, w: &mut W) -> EncodeResult<()> {
267
12
        w.write(&self.body)?;
268
12
        w.write_all(self.handshake_auth.as_ref());
269
12
        {
270
12
            let mut w_inner = w.write_nested_u16len();
271
12
            w_inner.write(self.sig.signature())?;
272
12
            w_inner.finish()?;
273
        }
274
12
        Ok(())
275
12
    }
276
}
277

            
278
impl EstablishIntroDetails {
279
    /// All arguments constructor
280
220
    pub fn new(auth_key: Ed25519Identity) -> Self {
281
220
        Self {
282
220
            auth_key,
283
220
            extensions: Default::default(),
284
220
        }
285
220
    }
286

            
287
    /// Set EST_INTRO_DOS_EXT with given `extension_dos`.
288
110
    pub fn set_extension_dos(&mut self, extension_dos: DosParams) {
289
110
        self.extensions.replace_by_type(extension_dos.into());
290
110
    }
291

            
292
    /// Add an extension of some other type.
293
55
    pub fn set_extension_other(&mut self, other: UnrecognizedExt<EstIntroExtType>) {
294
55
        self.extensions.replace_by_type(other.into());
295
55
    }
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
2
    pub fn sign_and_encode<'a>(
304
2
        self,
305
2
        keypair: &ed25519::Keypair,
306
2
        mac_key: impl Into<HsMacKey<'a>>,
307
2
    ) -> crate::Result<Vec<u8>> {
308
2
        if Ed25519Identity::from(keypair.verifying_key()) != self.auth_key {
309
            return Err(crate::Error::Internal(bad_api_usage!("Key mismatch")));
310
2
        }
311
2

            
312
2
        let mut output = Vec::new();
313
2

            
314
2
        output.write(&self)?;
315
2
        let mac_key: HsMacKey<'_> = mac_key.into();
316
2
        let mac = mac_key.mac(&output[..]);
317
2
        output.write(&mac)?;
318
2
        let signature = {
319
2
            let mut signed_material = Vec::from(SIG_PREFIX);
320
2
            signed_material.extend(&output[..]);
321
2
            keypair.sign(&signed_material[..])
322
2
        };
323
2
        output.write_u16(
324
2
            ED25519_SIGNATURE_LEN
325
2
                .try_into()
326
2
                .expect("ed25519 signature len is somehow > u16::MAX"),
327
2
        );
328
2
        output.write(&signature)?;
329

            
330
2
        Ok(output)
331
2
    }
332
}
333

            
334
impl 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
165
    pub fn from_parts_for_test(
347
165
        body: EstablishIntroDetails,
348
165
        mac: CtByteArray<HS_MAC_LEN>,
349
165
        signature: ed25519::Signature,
350
165
    ) -> Self {
351
        use tor_llcrypto::pk::ed25519::ValidatableEd25519Signature;
352
165
        let sig = Box::new(ValidatableEd25519Signature::new(
353
165
            body.auth_key.try_into().expect("Invalid public key"),
354
165
            signature,
355
165
            &[],
356
165
        ));
357
165
        Self {
358
165
            body,
359
165
            handshake_auth: mac,
360
165
            mac_plaintext: vec![],
361
165
            sig,
362
165
        }
363
165
    }
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
6
    pub fn check_and_unwrap<'a>(
371
6
        self,
372
6
        mac_key: impl Into<HsMacKey<'a>>,
373
6
    ) -> std::result::Result<EstablishIntroDetails, EstablishIntroSigError> {
374
        use tor_llcrypto::pk::ValidatableSignature;
375

            
376
6
        let mac_key: HsMacKey<'_> = mac_key.into();
377
6
        let mac_okay = mac_key.validate(&self.mac_plaintext, &self.handshake_auth);
378
6
        let sig_okay = self.sig.is_valid();
379
6

            
380
6
        if !(bool::from(mac_okay) & sig_okay) {
381
2
            return Err(EstablishIntroSigError::Invalid);
382
4
        }
383
4

            
384
4
        Ok(self.dangerously_unwrap())
385
6
    }
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
110
    pub fn dangerously_unwrap(self) -> EstablishIntroDetails {
391
110
        self.body
392
110
    }
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]
400
pub 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
}