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

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

            
14
use crate::relaycell::{hs::ext::*, hs::AuthKeyType, msg};
15

            
16
caret_int! {
17
    /// The introduction protocol extension type.
18
    ///
19
    /// Documented in <https://spec.torproject.org/rend-spec/introduction-protocol.html#EST_INTRO>
20
4
    #[derive(Ord, PartialOrd)]
21
    pub struct EstIntroExtType(u8) {
22
        /// The extension used to send DoS parameters
23
        DOS_PARAMS = 1,
24
    }
25
}
26

            
27
caret_int! {
28
    /// The recognized parameter types in an establish intro
29
    /// DoS extension.
30
    ///
31
    /// See <https://spec.torproject.org/rend-spec/introduction-protocol.html#EST_INTRO_DOS_EXT>
32
    pub struct EstIntroExtDosParamType(u8) {
33
        /// The rate per second of INTRODUCE2 cell relayed
34
        /// to the service.
35
        DOS_INTRODUCE2_RATE_PER_SEC = 1,
36
        /// The burst per second of INTRODUCE2 cell relayed
37
        /// to the service
38
        DOS_INTRODUCE2_BURST_PER_SEC = 2,
39
    }
40
}
41

            
42
/// Extension to tell the introduction point to rate-limit.
43
///
44
/// When we sent this extension, it tells the introduction point to rate-limit
45
/// the INTRODUCE2 messages it sends us to the rates shown here.
46
///
47
/// When this extension is not sent, the introduction point imposes a rate-limit
48
/// depending on parameters in the latest consensus.
49
///
50
/// This extension requires protover `HSIntro=5`.
51
///
52
/// See <https://spec.torproject.org/rend-spec/introduction-protocol.html#EST_INTRO_DOS_EXT>.
53
8
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
54
pub struct DosParams {
55
    /// An optional parameter indicates the rate per second of
56
    /// INTRODUCE2 cell relayed to the service.
57
    ///
58
    /// Min: 0, Max: 2147483647
59
    rate_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
60
    /// An optional parameter indicates the burst per second of
61
    /// INTRODUCE2 cell relayed to the service
62
    ///
63
    /// Min: 0, Max: 2147483647
64
    burst_per_sec: Option<BoundedInt32<0, { i32::MAX }>>,
65
}
66

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

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

            
138
141
decl_extension_group! {
139
141
    /// An extension to an EstablishIntro cell.
140
150
    #[derive(Debug,Clone)]
141
141
    enum EstablishIntroExt [ EstIntroExtType ] {
142
141
        DosParams,
143
141
    }
144
141
}
145

            
146
/// The body of an EstablishIntro message, after the signature and MAC are
147
/// verified.
148
///
149
/// This tells the introduction point which key it should act as an introduction
150
/// for, and how.
151
16
#[derive(Debug, Clone)]
152
pub struct EstablishIntroDetails {
153
    /// The public introduction point auth key.
154
    auth_key: Ed25519Identity,
155
    /// A list of extensions on this cell.
156
    extensions: ExtList<EstablishIntroExt>,
157
}
158

            
159
/// A hidden services establishes a new introduction point, by sending an
160
/// EstablishIntro message.
161
///
162
/// This may represent either an outbound body that we're sending, or a decoded
163
/// body that we're receiving.
164
///
165
/// # Usage
166
///
167
/// This type is a good choice for handling an incoming EstablishIntro message
168
/// on a Relay, but not for generating an outgoing EstablishIntro message.
169
///
170
/// Onion services should not construct this message object; instead, they
171
/// should construct an [`EstablishIntroDetails`], and then call its
172
/// `sign_and_encode` method.
173
12
#[derive(educe::Educe, Clone)]
174
#[educe(Debug)]
175
pub struct EstablishIntro {
176
    /// The underlying body of this, wrapped in authentication.
177
    body: EstablishIntroDetails,
178
    /// The MAC of all earlier fields in the cell, using a key derived from the
179
    /// handshake between the onion service and the introduction point.
180
    ///
181
    /// This MAC binds the EstablishIntro message to a single circuit, and keeps
182
    /// it from being replayed.
183
    handshake_auth: CtByteArray<HS_MAC_LEN>,
184
    /// A textual record of all the fields in the message that are covered by the MAC.
185
    #[educe(Debug(ignore))]
186
    mac_plaintext: Vec<u8>,
187
    /// A signature using `auth_key` of all contents of the message.
188
    ///
189
    /// This signature proves possession of `auth_key` and thereby ensures that
190
    /// the request really comes from that key's holder.
191
    ///
192
    /// (This field is boxed to manage variant size.)
193
    #[educe(Debug(ignore))]
194
    sig: Box<ed25519::ValidatableEd25519Signature>,
195
}
196

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

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

            
214
impl msg::Body for EstablishIntro {
215
235
    fn decode_from_reader(r: &mut Reader<'_>) -> Result<Self> {
216
235
        let cursor_start = r.cursor();
217
235
        let auth_key_type: AuthKeyType = r.take_u8()?.into();
218
        // Only Ed25519 is recognized... and it *needs* to be recognized or else we
219
        // can't verify the signature.
220
235
        let auth_key = match auth_key_type {
221
240
            AuthKeyType::ED25519_SHA3_256 => r.read_nested_u16len(|r| r.extract())?,
222
            _ => {
223
                return Err(tor_bytes::Error::InvalidMessage(
224
                    format!("unrecognized authkey type {:?}", auth_key_type).into(),
225
                ))
226
            }
227
        };
228

            
229
235
        let extensions = r.extract()?;
230
235
        let cursor_mac = r.cursor();
231
235
        let handshake_auth = r.extract()?;
232
235
        let cursor_sig = r.cursor();
233
240
        let sig = r.read_nested_u16len(|r| r.extract())?;
234

            
235
235
        let mac_plaintext = r.range(cursor_start, cursor_mac).into();
236

            
237
235
        let public_key = ed25519::PublicKey::try_from(&auth_key)
238
235
            .map_err(|_| tor_bytes::Error::InvalidMessage("Invalid ed25519 key".into()))?;
239
235
        let mut signed_material = Vec::from(SIG_PREFIX);
240
235
        signed_material.extend(r.range(cursor_start, cursor_sig));
241
235
        let sig = Box::new(ed25519::ValidatableEd25519Signature::new(
242
235
            public_key,
243
235
            sig,
244
235
            &signed_material[..],
245
235
        ));
246
235

            
247
235
        Ok(EstablishIntro {
248
235
            body: EstablishIntroDetails {
249
235
                auth_key,
250
235
                extensions,
251
235
            },
252
235
            handshake_auth,
253
235
            mac_plaintext,
254
235
            sig,
255
235
        })
256
235
    }
257

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

            
272
impl EstablishIntroDetails {
273
    /// All arguments constructor
274
188
    pub fn new(auth_key: Ed25519Identity) -> Self {
275
188
        Self {
276
188
            auth_key,
277
188
            extensions: Default::default(),
278
188
        }
279
188
    }
280

            
281
    /// Set EST_INTRO_DOS_EXT with given `extension_dos`.
282
94
    pub fn set_extension_dos(&mut self, extension_dos: DosParams) {
283
94
        self.extensions.replace_by_type(extension_dos.into());
284
94
    }
285

            
286
    /// Add an extension of some other type.
287
47
    pub fn set_extension_other(&mut self, other: UnrecognizedExt<EstIntroExtType>) {
288
47
        self.extensions.replace_by_type(other.into());
289
47
    }
290

            
291
    /// Sign and authenticate this body using a provided Ed25519 keypair and MAC
292
    /// key.
293
    ///
294
    /// The MAC key is derived from the circuit handshake between the onion
295
    /// service and the introduction point.  The Ed25519 keypair must match the
296
    /// one given as the auth_key for this body.
297
2
    pub fn sign_and_encode<'a>(
298
2
        self,
299
2
        keypair: &ed25519::Keypair,
300
2
        mac_key: impl Into<HsMacKey<'a>>,
301
2
    ) -> crate::Result<Vec<u8>> {
302
2
        use tor_llcrypto::pk::ed25519::Signer;
303
2
        if Ed25519Identity::from(keypair.verifying_key()) != self.auth_key {
304
            return Err(crate::Error::Internal(bad_api_usage!("Key mismatch")));
305
2
        }
306
2

            
307
2
        let mut output = Vec::new();
308
2

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

            
325
2
        Ok(output)
326
2
    }
327
}
328

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

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

            
371
6
        let mac_key: HsMacKey<'_> = mac_key.into();
372
6
        let mac_okay = mac_key.validate(&self.mac_plaintext, &self.handshake_auth);
373
6
        let sig_okay = self.sig.is_valid();
374
6

            
375
6
        if !(bool::from(mac_okay) & sig_okay) {
376
2
            return Err(EstablishIntroSigError::Invalid);
377
4
        }
378
4

            
379
4
        Ok(self.dangerously_unwrap())
380
6
    }
381

            
382
    /// Consume this EstablishIntro message and return its  body.
383
    ///
384
    /// This is a "dangerous" function because it does not check correctness for the signature or the MAC.
385
94
    pub fn dangerously_unwrap(self) -> EstablishIntroDetails {
386
94
        self.body
387
94
    }
388
}
389

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