1
//! An implementation of Tor's current relay cell cryptography.
2
//!
3
//! These are not very good algorithms; they were the best we could come up with
4
//! in ~2002.  They are somewhat inefficient, and vulnerable to tagging attacks.
5
//! They should get replaced within the next several years.  For information on
6
//! some older proposed alternatives so far, see proposals 261, 295, and 298.
7
//!
8
//! I am calling this design `tor1`; it does not have a generally recognized
9
//! name.
10

            
11
use crate::{Error, Result, client::circuit::CircuitBinding, crypto::binding::CIRC_BINDING_LEN};
12

            
13
use cipher::{KeyIvInit, StreamCipher};
14
use digest::Digest;
15
use tor_cell::{chancell::ChanCmd, relaycell::msg::SendmeTag};
16
use tor_error::internal;
17
use typenum::Unsigned;
18

            
19
use super::{
20
    ClientLayer, CryptInit, InboundClientLayer, InboundRelayLayer, OutboundClientLayer,
21
    OutboundRelayLayer, RelayCellBody, RelayLayer,
22
};
23

            
24
/// Length of SENDME tag generated by this encryption method.
25
const SENDME_TAG_LEN: usize = 20;
26

            
27
/// A CryptState represents one layer of shared cryptographic state between
28
/// a relay and a client for a single hop, in a single direction.
29
///
30
/// For example, if a client makes a 3-hop circuit, then it will have 6
31
/// `CryptState`s, one for each relay, for each direction of communication.
32
///
33
/// Note that although `CryptState` is used to implement [`OutboundClientLayer`],
34
/// [`InboundClientLayer`], [`OutboundRelayLayer`], and [`InboundRelayLayer`],
35
/// each instance will only be used for one of these roles.
36
///
37
/// It is parameterized on a stream cipher and a digest type: most circuits
38
/// will use AES-128-CTR and SHA1, but v3 onion services use AES-256-CTR and
39
/// SHA-3.
40
struct CryptState<SC: StreamCipher, D: Digest + Clone> {
41
    /// Stream cipher for en/decrypting cell bodies.
42
    ///
43
    /// This cipher is the one keyed with Kf or Kb in the spec.
44
    cipher: SC,
45
    /// Digest for authenticating cells to/from this hop.
46
    ///
47
    /// This digest is the one keyed with Df or Db in the spec.
48
    digest: D,
49
    /// Most recent digest value generated by this crypto.
50
    last_sendme_tag: SendmeTag,
51
}
52

            
53
/// A pair of CryptStates shared between a client and a relay, one for the
54
/// outbound (away from the client) direction, and one for the inbound
55
/// (towards the client) direction.
56
#[cfg_attr(feature = "bench", visibility::make(pub))]
57
pub(crate) struct CryptStatePair<SC: StreamCipher, D: Digest + Clone> {
58
    /// State for en/decrypting cells sent away from the client.
59
    fwd: CryptState<SC, D>,
60
    /// State for en/decrypting cells sent towards the client.
61
    back: CryptState<SC, D>,
62
    /// A circuit binding key.
63
    binding: CircuitBinding,
64
}
65

            
66
impl<SC: StreamCipher + KeyIvInit, D: Digest + Clone> CryptInit for CryptStatePair<SC, D> {
67
358
    fn seed_len() -> usize {
68
358
        SC::KeySize::to_usize() * 2 + D::OutputSize::to_usize() * 2 + CIRC_BINDING_LEN
69
358
    }
70
182
    fn initialize(mut seed: &[u8]) -> Result<Self> {
71
        // This corresponds to the use of the KDF algorithm as described in
72
        // tor-spec 5.2.2
73
182
        if seed.len() != Self::seed_len() {
74
            return Err(Error::from(internal!(
75
                "seed length {} was invalid",
76
                seed.len()
77
            )));
78
182
        }
79

            
80
        // Advances `seed` by `n` bytes, returning the advanced bytes
81
910
        let mut take_seed = |n: usize| -> &[u8] {
82
910
            let res = &seed[..n];
83
910
            seed = &seed[n..];
84
910
            res
85
910
        };
86

            
87
182
        let dlen = D::OutputSize::to_usize();
88
182
        let keylen = SC::KeySize::to_usize();
89

            
90
182
        let df = take_seed(dlen);
91
182
        let db = take_seed(dlen);
92
182
        let kf = take_seed(keylen);
93
182
        let kb = take_seed(keylen);
94
182
        let binding_key = take_seed(CIRC_BINDING_LEN);
95

            
96
182
        let fwd = CryptState {
97
182
            cipher: SC::new(
98
182
                kf.try_into().expect("Incorrect size, despite validation!"),
99
182
                &Default::default(),
100
182
            ),
101
182
            digest: D::new().chain_update(df),
102
182
            last_sendme_tag: [0_u8; SENDME_TAG_LEN].into(),
103
182
        };
104
182
        let back = CryptState {
105
182
            cipher: SC::new(
106
182
                kb.try_into().expect("Incorrect size, despite validation!"),
107
182
                &Default::default(),
108
182
            ),
109
182
            digest: D::new().chain_update(db),
110
182
            last_sendme_tag: [0_u8; SENDME_TAG_LEN].into(),
111
182
        };
112
182
        let binding = CircuitBinding::try_from(binding_key)?;
113

            
114
182
        Ok(CryptStatePair { fwd, back, binding })
115
182
    }
116
}
117

            
118
impl<SC, D> ClientLayer<ClientOutbound<SC, D>, ClientInbound<SC, D>> for CryptStatePair<SC, D>
119
where
120
    SC: StreamCipher,
121
    D: Digest + Clone,
122
{
123
130
    fn split_client_layer(self) -> (ClientOutbound<SC, D>, ClientInbound<SC, D>, CircuitBinding) {
124
130
        (self.fwd.into(), self.back.into(), self.binding)
125
130
    }
126
}
127

            
128
/// An inbound relay layer, encrypting relay cells for a client.
129
#[cfg_attr(feature = "bench", visibility::make(pub))]
130
#[derive(derive_more::From)]
131
pub(crate) struct RelayInbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
132
impl<SC: StreamCipher, D: Digest + Clone> InboundRelayLayer for RelayInbound<SC, D> {
133
746
    fn originate(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> SendmeTag {
134
746
        cell.set_digest::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag);
135
746
        self.encrypt_inbound(cmd, cell);
136
746
        self.0.last_sendme_tag
137
746
    }
138
2094
    fn encrypt_inbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) {
139
        // This is describe in tor-spec 5.5.3.1, "Relaying Backward at Onion Routers"
140
2094
        self.0.cipher.apply_keystream(cell.as_mut());
141
2094
    }
142
}
143

            
144
/// An outbound relay layer, decrypting relay cells from a client.
145
#[cfg_attr(feature = "bench", visibility::make(pub))]
146
#[derive(derive_more::From)]
147
pub(crate) struct RelayOutbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
148
impl<SC: StreamCipher, D: Digest + Clone> OutboundRelayLayer for RelayOutbound<SC, D> {
149
2054
    fn decrypt_outbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) -> Option<SendmeTag> {
150
        // This is describe in tor-spec 5.5.2.2, "Relaying Forward at Onion Routers"
151
2054
        self.0.cipher.apply_keystream(cell.as_mut());
152
2054
        if cell.is_recognized::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag) {
153
746
            Some(self.0.last_sendme_tag)
154
        } else {
155
1308
            None
156
        }
157
2054
    }
158
}
159
impl<SC: StreamCipher, D: Digest + Clone> RelayLayer<RelayOutbound<SC, D>, RelayInbound<SC, D>>
160
    for CryptStatePair<SC, D>
161
{
162
52
    fn split_relay_layer(self) -> (RelayOutbound<SC, D>, RelayInbound<SC, D>, CircuitBinding) {
163
52
        let CryptStatePair { fwd, back, binding } = self;
164
52
        (fwd.into(), back.into(), binding)
165
52
    }
166
}
167

            
168
/// An outbound client layer, encrypting relay cells for a relay.
169
#[cfg_attr(feature = "bench", visibility::make(pub))]
170
#[derive(derive_more::From)]
171
pub(crate) struct ClientOutbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
172

            
173
impl<SC: StreamCipher, D: Digest + Clone> OutboundClientLayer for ClientOutbound<SC, D> {
174
848
    fn originate_for(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> SendmeTag {
175
848
        cell.set_digest::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag);
176
848
        self.encrypt_outbound(cmd, cell);
177
848
        self.0.last_sendme_tag
178
848
    }
179
2360
    fn encrypt_outbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) {
180
        // This is a single iteration of the loop described in tor-spec
181
        // 5.5.2.1, "routing away from the origin."
182
2360
        self.0.cipher.apply_keystream(&mut cell.0[..]);
183
2360
    }
184
}
185

            
186
/// An outbound client layer, decryption relay cells from a relay.
187
#[cfg_attr(feature = "bench", visibility::make(pub))]
188
#[derive(derive_more::From)]
189
pub(crate) struct ClientInbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
190
impl<SC: StreamCipher, D: Digest + Clone> InboundClientLayer for ClientInbound<SC, D> {
191
2100
    fn decrypt_inbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) -> Option<SendmeTag> {
192
        // This is a single iteration of the loop described in tor-spec
193
        // 5.5.3, "routing to the origin."
194
2100
        self.0.cipher.apply_keystream(&mut cell.0[..]);
195
2100
        if cell.is_recognized::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag) {
196
746
            Some(self.0.last_sendme_tag)
197
        } else {
198
1354
            None
199
        }
200
2100
    }
201
}
202

            
203
/// Location in the relay cell for our "recognized" field.
204
pub(super) const RECOGNIZED_RANGE: std::ops::Range<usize> = 1..3;
205
/// Location in the relay cell for our "Digest" field.
206
pub(super) const DIGEST_RANGE: std::ops::Range<usize> = 5..9;
207
/// An all-zero digest value.
208
pub(super) const EMPTY_DIGEST: &[u8] = &[0, 0, 0, 0];
209

            
210
/// Functions on RelayCellBody that implement the digest/recognized
211
/// algorithm.
212
///
213
/// The current relay crypto protocol uses two wholly inadequate fields to
214
/// see whether a cell is intended for its current recipient: a two-byte
215
/// "recognized" field that needs to be all-zero; and a four-byte "digest"
216
/// field containing a running digest of all cells (for this recipient) to
217
/// this one, seeded with an initial value (either Df or Db in the spec).
218
///
219
/// These operations are described in tor-spec section 6.1 "Relay cells"
220
//
221
// TODO: It may be that we should un-parameterize the functions
222
// that use RCF: given our timeline for deployment of CGO encryption,
223
// it is likely that we will never actually want to  support `tor1` encryption
224
// with any other format than RelayCellFormat::V0.
225
impl RelayCellBody {
226
    /// Returns the byte slice of the `recognized` field.
227
4154
    fn recognized(&self) -> &[u8] {
228
4154
        &self.0[RECOGNIZED_RANGE]
229
4154
    }
230
    /// Returns the mut byte slice of the `recognized` field.
231
1594
    fn recognized_mut(&mut self) -> &mut [u8] {
232
1594
        &mut self.0[RECOGNIZED_RANGE]
233
1594
    }
234
    /// Returns the byte slice of the `digest` field.
235
1492
    fn digest(&self) -> &[u8] {
236
1492
        &self.0[DIGEST_RANGE]
237
1492
    }
238
    /// Returns the mut byte slice of the `digest` field.
239
3188
    fn digest_mut(&mut self) -> &mut [u8] {
240
3188
        &mut self.0[DIGEST_RANGE]
241
3188
    }
242
    /// Prepare a cell body by setting its digest and recognized field.
243
1594
    #[cfg_attr(feature = "bench", visibility::make(pub))]
244
1594
    fn set_digest<D: Digest + Clone>(&mut self, d: &mut D, sendme_tag: &mut SendmeTag) {
245
1594
        self.recognized_mut().fill(0); // Set 'Recognized' to zero
246
1594
        self.digest_mut().fill(0); // Set Digest to zero
247

            
248
1594
        d.update(&self.0[..]);
249
        // TODO(nickm) can we avoid this clone?  Probably not.
250
1594
        let computed_digest = d.clone().finalize();
251
        // TODO PERF: Make sure this compiles nicely.
252
1594
        *sendme_tag = SendmeTag::try_from(&computed_digest[..SENDME_TAG_LEN])
253
1594
            .expect("Somehow produced a SENDME tag of invalid length!");
254
1594
        let used_digest_prefix = &computed_digest[0..DIGEST_RANGE.len()];
255
1594
        self.digest_mut().copy_from_slice(used_digest_prefix);
256
1594
    }
257
    /// Check whether this just-decrypted cell is now an authenticated plaintext.
258
    ///
259
    /// This method returns true if the `recognized` field is all zeros, and if the
260
    /// `digest` field is a digest of the correct material.
261
    /// If it returns true, it also sets `rcvg` to the appropriate authenticated
262
    /// SENDME tag to use if acknowledging this message.
263
    ///
264
    /// If this method returns false, then either further decryption is required,
265
    /// or the cell is corrupt.
266
    ///
267
    // TODO #1336: Further optimize and/or benchmark this.
268
4154
    #[cfg_attr(feature = "bench", visibility::make(pub))]
269
4154
    fn is_recognized<D: Digest + Clone>(&self, d: &mut D, rcvd: &mut SendmeTag) -> bool {
270
        use crate::util::ct;
271

            
272
        // Validate 'Recognized' field
273
4154
        if !ct::is_zero(self.recognized()) {
274
2662
            return false;
275
1492
        }
276

            
277
        // Now also validate the 'Digest' field:
278

            
279
1492
        let mut dtmp = d.clone();
280
        // Add bytes up to the 'Digest' field
281
1492
        dtmp.update(&self.0[..DIGEST_RANGE.start]);
282
        // Add zeroes where the 'Digest' field is
283
1492
        dtmp.update(EMPTY_DIGEST);
284
        // Add the rest of the bytes
285
1492
        dtmp.update(&self.0[DIGEST_RANGE.end..]);
286
        // Clone the digest before finalize destroys it because we will use
287
        // it in the future
288
1492
        let dtmp_clone = dtmp.clone();
289
1492
        let result = dtmp.finalize();
290

            
291
1492
        if ct::bytes_eq(self.digest(), &result[0..DIGEST_RANGE.len()]) {
292
            // Copy useful things out of this cell (we keep running digest)
293
1492
            *d = dtmp_clone;
294
1492
            *rcvd = SendmeTag::try_from(&result[..SENDME_TAG_LEN])
295
1492
                .expect("Somehow generated a sendme tag of invalid length!");
296
1492
            return true;
297
        }
298

            
299
        false
300
4154
    }
301
}
302

            
303
/// Benchmark utilities for the `tor1` module.
304
#[cfg(feature = "bench")]
305
pub mod bench_utils {
306
    pub use super::ClientInbound;
307
    pub use super::ClientOutbound;
308
    pub use super::CryptStatePair;
309
    pub use super::RelayInbound;
310
    pub use super::RelayOutbound;
311

            
312
    /// The throughput for a relay cell in bytes with the Tor1 scheme.
313
    pub const TOR1_THROUGHPUT: u64 = 498;
314
}
315

            
316
#[cfg(test)]
317
mod test {
318
    // @@ begin test lint list maintained by maint/add_warning @@
319
    #![allow(clippy::bool_assert_comparison)]
320
    #![allow(clippy::clone_on_copy)]
321
    #![allow(clippy::dbg_macro)]
322
    #![allow(clippy::mixed_attributes_style)]
323
    #![allow(clippy::print_stderr)]
324
    #![allow(clippy::print_stdout)]
325
    #![allow(clippy::single_char_pattern)]
326
    #![allow(clippy::unwrap_used)]
327
    #![allow(clippy::unchecked_time_subtraction)]
328
    #![allow(clippy::useless_vec)]
329
    #![allow(clippy::needless_pass_by_value)]
330
    #![allow(clippy::string_slice)] // See arti#2571
331
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
332

            
333
    use crate::crypto::cell::{
334
        InboundClientCrypt, OutboundClientCrypt, Tor1RelayCrypto, test::add_layers,
335
    };
336

            
337
    use super::*;
338

            
339
    // From tor's test_relaycrypt.c
340

            
341
    #[test]
342
    fn testvec() {
343
        use digest::XofReader;
344
        use digest::{ExtendableOutput, Update};
345

            
346
        // (The ....s at the end here are the KH ca)
347
        const K1: &[u8; 92] =
348
            b"    'My public key is in this signed x509 object', said Tom assertively.      (N-PREG-VIRYL)";
349
        const K2: &[u8; 92] =
350
            b"'Let's chart the pedal phlanges in the tomb', said Tom cryptographically.  (PELCG-GBR-TENCU)";
351
        const K3: &[u8; 92] =
352
            b"     'Segmentation fault bugs don't _just happen_', said Tom seethingly.        (P-GUVAT-YL)";
353

            
354
        const SEED: &[u8;108] = b"'You mean to tell me that there's a version of Sha-3 with no limit on the output length?', said Tom shakily.";
355
        let cmd = ChanCmd::RELAY;
356

            
357
        // These test vectors were generated from Tor.
358
        let data: &[(usize, &str)] = &include!("../../../testdata/cell_crypt.rs");
359

            
360
        let mut cc_out = OutboundClientCrypt::new();
361
        let mut cc_in = InboundClientCrypt::new();
362
        let pair = Tor1RelayCrypto::initialize(&K1[..]).unwrap();
363
        add_layers(&mut cc_out, &mut cc_in, pair);
364
        let pair = Tor1RelayCrypto::initialize(&K2[..]).unwrap();
365
        add_layers(&mut cc_out, &mut cc_in, pair);
366
        let pair = Tor1RelayCrypto::initialize(&K3[..]).unwrap();
367
        add_layers(&mut cc_out, &mut cc_in, pair);
368

            
369
        let mut xof = tor_llcrypto::d::Shake256::default();
370
        xof.update(&SEED[..]);
371
        let mut stream = xof.finalize_xof();
372

            
373
        let mut j = 0;
374
        for cellno in 0..51 {
375
            let mut body = Box::new([0_u8; 509]);
376
            body[0] = 2; // command: data.
377
            body[4] = 1; // streamid: 1.
378
            body[9] = 1; // length: 498
379
            body[10] = 242;
380
            stream.read(&mut body[11..]);
381

            
382
            let mut cell = body.into();
383
            let _ = cc_out.encrypt(cmd, &mut cell, 2.into());
384

            
385
            if cellno == data[j].0 {
386
                let expected = hex::decode(data[j].1).unwrap();
387
                assert_eq!(cell.as_ref(), &expected[..]);
388
                j += 1;
389
            }
390
        }
391
    }
392
}