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, Readable, 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::{hs::ext::*, 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
98
    pub fn new(rate_per_sec: Option<i32>, burst_per_sec: Option<i32>) -> crate::Result<Self> {
73
198
        let normalize = |supplied: Option<i32>| -> crate::Result<_> {
74
196
            supplied
75
196
                .map(|val| {
76
196
                    BoundedInt32::checked_new(val).map_err(|_| {
77
                        crate::err::Error::CantEncode(
78
                            "EST_INTRO_DOS_EXT parameter value out of bound.",
79
                        )
80
196
                    })
81
196
                })
82
196
                .transpose()
83
196
        };
84
        Ok(Self {
85
98
            rate_per_sec: normalize(rate_per_sec)?,
86
98
            burst_per_sec: normalize(burst_per_sec)?,
87
        })
88
98
    }
89
}
90

            
91
impl Ext for DosParams {
92
    type Id = EstIntroExtType;
93
343
    fn type_id(&self) -> EstIntroExtType {
94
343
        EstIntroExtType::DOS_PARAMS
95
343
    }
96
98
    fn take_body_from(b: &mut Reader<'_>) -> Result<Self> {
97
98
        let n_prams = b.take_u8()?;
98
98
        let mut rate_per_sec = None;
99
98
        let mut burst_per_sec = None;
100
196
        for _i in 0..n_prams {
101
196
            let param_to_store = match b.take_u8()?.into() {
102
98
                EstIntroExtDosParamType::DOS_INTRODUCE2_RATE_PER_SEC => Some(&mut rate_per_sec),
103
98
                EstIntroExtDosParamType::DOS_INTRODUCE2_BURST_PER_SEC => Some(&mut burst_per_sec),
104
                _ => None,
105
            };
106
196
            if let Some(param) = param_to_store {
107
196
                if let Ok(rate) = i32::try_from(b.take_u64()?) {
108
196
                    *param = BoundedInt32::checked_new(rate).ok();
109
196
                }
110
            }
111
        }
112
98
        Ok(Self {
113
98
            rate_per_sec,
114
98
            burst_per_sec,
115
98
        })
116
98
    }
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
245
    fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
222
245
        let cursor_start = r.cursor();
223
245
        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
245
        let auth_key = match auth_key_type {
227
250
            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
245
        let extensions = r.extract()?;
236
245
        let cursor_mac = r.cursor();
237
245
        let handshake_auth = r.extract()?;
238
245
        let cursor_sig = r.cursor();
239
250
        let sig = r.read_nested_u16len(|r| r.extract())?;
240

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

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

            
253
245
        Ok(EstablishIntro {
254
245
            body: EstablishIntroDetails {
255
245
                auth_key,
256
245
                extensions,
257
245
            },
258
245
            handshake_auth,
259
245
            mac_plaintext,
260
245
            sig,
261
245
        })
262
245
    }
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
196
    pub fn new(auth_key: Ed25519Identity) -> Self {
281
196
        Self {
282
196
            auth_key,
283
196
            extensions: Default::default(),
284
196
        }
285
196
    }
286

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

            
292
    /// Add an extension of some other type.
293
49
    pub fn set_extension_other(&mut self, other: UnrecognizedExt<EstIntroExtType>) {
294
49
        self.extensions.replace_by_type(other.into());
295
49
    }
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
        use tor_llcrypto::pk::ed25519::Signer;
309
2
        if Ed25519Identity::from(keypair.verifying_key()) != self.auth_key {
310
            return Err(crate::Error::Internal(bad_api_usage!("Key mismatch")));
311
2
        }
312
2

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

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

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

            
335
impl EstablishIntro {
336
    /// Construct a new EstablishIntro message from its constituent parts.
337
    ///
338
    /// # Limitations
339
    ///
340
    /// This is really only useful for testing; it will construct a version of the
341
    /// object whose signatures will probably never check as valid.
342
    ///
343
    /// # Panics
344
    ///
345
    /// Panics if the body's public key is not a valid ed25519 public key
346
    #[cfg(feature = "testing")]
347
147
    pub fn from_parts_for_test(
348
147
        body: EstablishIntroDetails,
349
147
        mac: CtByteArray<HS_MAC_LEN>,
350
147
        signature: ed25519::Signature,
351
147
    ) -> Self {
352
        use tor_llcrypto::pk::ed25519::ValidatableEd25519Signature;
353
147
        let sig = Box::new(ValidatableEd25519Signature::new(
354
147
            body.auth_key.try_into().expect("Invalid public key"),
355
147
            signature,
356
147
            &[],
357
147
        ));
358
147
        Self {
359
147
            body,
360
147
            handshake_auth: mac,
361
147
            mac_plaintext: vec![],
362
147
            sig,
363
147
        }
364
147
    }
365

            
366
    /// Check whether this EstablishIntro message is well-signed (with its
367
    /// included key), and well authenticated with the provided MAC key.
368
    ///
369
    /// On success, return the [`EstablishIntroDetails`] describing how to function
370
    /// as an introduction point for this service.  On failure, return an error.
371
6
    pub fn check_and_unwrap<'a>(
372
6
        self,
373
6
        mac_key: impl Into<HsMacKey<'a>>,
374
6
    ) -> std::result::Result<EstablishIntroDetails, EstablishIntroSigError> {
375
        use tor_llcrypto::pk::ValidatableSignature;
376

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

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

            
385
4
        Ok(self.dangerously_unwrap())
386
6
    }
387

            
388
    /// Consume this EstablishIntro message and return its  body.
389
    ///
390
    /// This is a "dangerous" function because it does not check correctness for the signature or the MAC.
391
98
    pub fn dangerously_unwrap(self) -> EstablishIntroDetails {
392
98
        self.body
393
98
    }
394
}
395

            
396
/// An error that has occurred while trying to validate an EstablishIntro message.
397
///
398
/// This error is deliberately uninformative.
399
#[derive(thiserror::Error, Clone, Debug)]
400
#[non_exhaustive]
401
pub enum EstablishIntroSigError {
402
    /// The authentication information on an EstablishIntro message was incorrect.
403
    #[error("Invalid signature or MAC on ESTABLISH_INTRO message.")]
404
    Invalid,
405
}