tor_proto/crypto/cell/
tor1.rs

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
11use crate::{circuit::CircuitBinding, crypto::binding::CIRC_BINDING_LEN, Error, Result};
12
13use cipher::{KeyIvInit, StreamCipher};
14use digest::Digest;
15use tor_cell::{chancell::ChanCmd, relaycell::msg::SendmeTag};
16use tor_error::internal;
17use typenum::Unsigned;
18
19use super::{
20    ClientLayer, CryptInit, InboundClientLayer, InboundRelayLayer, OutboundClientLayer,
21    OutboundRelayLayer, RelayCellBody, RelayLayer,
22};
23
24/// Length of SENDME tag generated by this encryption method.
25const 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.
40struct 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.
56pub(crate) struct CryptStatePair<SC: StreamCipher, D: Digest + Clone> {
57    /// State for en/decrypting cells sent away from the client.
58    fwd: CryptState<SC, D>,
59    /// State for en/decrypting cells sent towards the client.
60    back: CryptState<SC, D>,
61    /// A circuit binding key.
62    binding: CircuitBinding,
63}
64
65impl<SC: StreamCipher + KeyIvInit, D: Digest + Clone> CryptInit for CryptStatePair<SC, D> {
66    fn seed_len() -> usize {
67        SC::KeySize::to_usize() * 2 + D::OutputSize::to_usize() * 2 + CIRC_BINDING_LEN
68    }
69    fn initialize(mut seed: &[u8]) -> Result<Self> {
70        // This corresponds to the use of the KDF algorithm as described in
71        // tor-spec 5.2.2
72        if seed.len() != Self::seed_len() {
73            return Err(Error::from(internal!(
74                "seed length {} was invalid",
75                seed.len()
76            )));
77        }
78
79        // Advances `seed` by `n` bytes, returning the advanced bytes
80        let mut take_seed = |n: usize| -> &[u8] {
81            let res = &seed[..n];
82            seed = &seed[n..];
83            res
84        };
85
86        let dlen = D::OutputSize::to_usize();
87        let keylen = SC::KeySize::to_usize();
88
89        let df = take_seed(dlen);
90        let db = take_seed(dlen);
91        let kf = take_seed(keylen);
92        let kb = take_seed(keylen);
93        let binding_key = take_seed(CIRC_BINDING_LEN);
94
95        let fwd = CryptState {
96            cipher: SC::new(kf.into(), &Default::default()),
97            digest: D::new().chain_update(df),
98            last_sendme_tag: [0_u8; SENDME_TAG_LEN].into(),
99        };
100        let back = CryptState {
101            cipher: SC::new(kb.into(), &Default::default()),
102            digest: D::new().chain_update(db),
103            last_sendme_tag: [0_u8; SENDME_TAG_LEN].into(),
104        };
105        let binding = CircuitBinding::try_from(binding_key)?;
106
107        Ok(CryptStatePair { fwd, back, binding })
108    }
109}
110
111impl<SC, D> ClientLayer<ClientOutbound<SC, D>, ClientInbound<SC, D>> for CryptStatePair<SC, D>
112where
113    SC: StreamCipher,
114    D: Digest + Clone,
115{
116    fn split_client_layer(self) -> (ClientOutbound<SC, D>, ClientInbound<SC, D>, CircuitBinding) {
117        (self.fwd.into(), self.back.into(), self.binding)
118    }
119}
120
121/// An inbound relay layer, encrypting relay cells for a client.
122#[derive(derive_more::From)]
123pub(crate) struct RelayInbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
124impl<SC: StreamCipher, D: Digest + Clone> InboundRelayLayer for RelayInbound<SC, D> {
125    fn originate(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> SendmeTag {
126        cell.set_digest::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag);
127        self.encrypt_inbound(cmd, cell);
128        self.0.last_sendme_tag
129    }
130    fn encrypt_inbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) {
131        // This is describe in tor-spec 5.5.3.1, "Relaying Backward at Onion Routers"
132        self.0.cipher.apply_keystream(cell.as_mut());
133    }
134}
135
136/// An outbound relay layer, decrypting relay cells from a client.
137#[derive(derive_more::From)]
138pub(crate) struct RelayOutbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
139impl<SC: StreamCipher, D: Digest + Clone> OutboundRelayLayer for RelayOutbound<SC, D> {
140    fn decrypt_outbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) -> Option<SendmeTag> {
141        // This is describe in tor-spec 5.5.2.2, "Relaying Forward at Onion Routers"
142        self.0.cipher.apply_keystream(cell.as_mut());
143        if cell.is_recognized::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag) {
144            Some(self.0.last_sendme_tag)
145        } else {
146            None
147        }
148    }
149}
150impl<SC: StreamCipher, D: Digest + Clone> RelayLayer<RelayOutbound<SC, D>, RelayInbound<SC, D>>
151    for CryptStatePair<SC, D>
152{
153    fn split_relay_layer(self) -> (RelayOutbound<SC, D>, RelayInbound<SC, D>, CircuitBinding) {
154        let CryptStatePair { fwd, back, binding } = self;
155        (fwd.into(), back.into(), binding)
156    }
157}
158
159/// An outbound client layer, encrypting relay cells for a relay.
160#[derive(derive_more::From)]
161pub(crate) struct ClientOutbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
162
163impl<SC: StreamCipher, D: Digest + Clone> OutboundClientLayer for ClientOutbound<SC, D> {
164    fn originate_for(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> SendmeTag {
165        cell.set_digest::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag);
166        self.encrypt_outbound(cmd, cell);
167        self.0.last_sendme_tag
168    }
169    fn encrypt_outbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) {
170        // This is a single iteration of the loop described in tor-spec
171        // 5.5.2.1, "routing away from the origin."
172        self.0.cipher.apply_keystream(&mut cell.0[..]);
173    }
174}
175
176/// An outbound client layer, decryption relay cells from a relay.
177#[derive(derive_more::From)]
178pub(crate) struct ClientInbound<SC: StreamCipher, D: Digest + Clone>(CryptState<SC, D>);
179impl<SC: StreamCipher, D: Digest + Clone> InboundClientLayer for ClientInbound<SC, D> {
180    fn decrypt_inbound(&mut self, _cmd: ChanCmd, cell: &mut RelayCellBody) -> Option<SendmeTag> {
181        // This is a single iteration of the loop described in tor-spec
182        // 5.5.3, "routing to the origin."
183        self.0.cipher.apply_keystream(&mut cell.0[..]);
184        if cell.is_recognized::<_>(&mut self.0.digest, &mut self.0.last_sendme_tag) {
185            Some(self.0.last_sendme_tag)
186        } else {
187            None
188        }
189    }
190}
191
192/// Location in the relay cell for our "recognized" field.
193pub(super) const RECOGNIZED_RANGE: std::ops::Range<usize> = 1..3;
194/// Location in the relay cell for our "Digest" field.
195pub(super) const DIGEST_RANGE: std::ops::Range<usize> = 5..9;
196/// An all-zero digest value.
197pub(super) const EMPTY_DIGEST: &[u8] = &[0, 0, 0, 0];
198
199/// Functions on RelayCellBody that implement the digest/recognized
200/// algorithm.
201///
202/// The current relay crypto protocol uses two wholly inadequate fields to
203/// see whether a cell is intended for its current recipient: a two-byte
204/// "recognized" field that needs to be all-zero; and a four-byte "digest"
205/// field containing a running digest of all cells (for this recipient) to
206/// this one, seeded with an initial value (either Df or Db in the spec).
207///
208/// These operations are described in tor-spec section 6.1 "Relay cells"
209//
210// TODO: It may be that we should un-parameterize the functions
211// that use RCF: given our timeline for deployment of CGO encryption,
212// it is likely that we will never actually want to  support `tor1` encryption
213// with any other format than RelayCellFormat::V0.
214impl RelayCellBody {
215    /// Returns the byte slice of the `recognized` field.
216    fn recognized(&self) -> &[u8] {
217        &self.0[RECOGNIZED_RANGE]
218    }
219    /// Returns the mut byte slice of the `recognized` field.
220    fn recognized_mut(&mut self) -> &mut [u8] {
221        &mut self.0[RECOGNIZED_RANGE]
222    }
223    /// Returns the byte slice of the `digest` field.
224    fn digest(&self) -> &[u8] {
225        &self.0[DIGEST_RANGE]
226    }
227    /// Returns the mut byte slice of the `digest` field.
228    fn digest_mut(&mut self) -> &mut [u8] {
229        &mut self.0[DIGEST_RANGE]
230    }
231    /// Prepare a cell body by setting its digest and recognized field.
232    fn set_digest<D: Digest + Clone>(&mut self, d: &mut D, sendme_tag: &mut SendmeTag) {
233        self.recognized_mut().fill(0); // Set 'Recognized' to zero
234        self.digest_mut().fill(0); // Set Digest to zero
235
236        d.update(&self.0[..]);
237        // TODO(nickm) can we avoid this clone?  Probably not.
238        let computed_digest = d.clone().finalize();
239        // TODO PERF: Make sure this compiles nicely.
240        *sendme_tag = SendmeTag::try_from(&computed_digest[..SENDME_TAG_LEN])
241            .expect("Somehow produced a SENDME tag of invalid length!");
242        let used_digest_prefix = &computed_digest[0..DIGEST_RANGE.len()];
243        self.digest_mut().copy_from_slice(used_digest_prefix);
244    }
245    /// Check whether this just-decrypted cell is now an authenticated plaintext.
246    ///
247    /// This method returns true if the `recognized` field is all zeros, and if the
248    /// `digest` field is a digest of the correct material.
249    /// If it returns true, it also sets `rcvg` to the appropriate authenticated
250    /// SENDME tag to use if acknowledging this message.
251    ///
252    /// If this method returns false, then either further decryption is required,
253    /// or the cell is corrupt.
254    ///
255    // TODO #1336: Further optimize and/or benchmark this.
256    fn is_recognized<D: Digest + Clone>(&self, d: &mut D, rcvd: &mut SendmeTag) -> bool {
257        use crate::util::ct;
258
259        // Validate 'Recognized' field
260        if !ct::is_zero(self.recognized()) {
261            return false;
262        }
263
264        // Now also validate the 'Digest' field:
265
266        let mut dtmp = d.clone();
267        // Add bytes up to the 'Digest' field
268        dtmp.update(&self.0[..DIGEST_RANGE.start]);
269        // Add zeroes where the 'Digest' field is
270        dtmp.update(EMPTY_DIGEST);
271        // Add the rest of the bytes
272        dtmp.update(&self.0[DIGEST_RANGE.end..]);
273        // Clone the digest before finalize destroys it because we will use
274        // it in the future
275        let dtmp_clone = dtmp.clone();
276        let result = dtmp.finalize();
277
278        if ct::bytes_eq(self.digest(), &result[0..DIGEST_RANGE.len()]) {
279            // Copy useful things out of this cell (we keep running digest)
280            *d = dtmp_clone;
281            *rcvd = SendmeTag::try_from(&result[..SENDME_TAG_LEN])
282                .expect("Somehow generated a sendme tag of invalid length!");
283            return true;
284        }
285
286        false
287    }
288}
289
290/// Benchmark utilities for the `tor1` module.
291#[cfg(feature = "bench")]
292pub mod bench_utils {
293    use super::*;
294
295    use crate::crypto::handshake::ShakeKeyGenerator as KGen;
296    use crate::{
297        bench_utils::{InboundClientLayerWrapper, OutboundClientLayerWrapper, RelayCryptState},
298        crypto::cell::bench_utils::RelayBody,
299        Result,
300    };
301    use tor_bytes::SecretBuf;
302
303    /// The throughput for a relay cell in bytes with the Tor1 scheme.
304    pub const TOR1_THROUGHPUT: u64 = 498;
305
306    /// Public wrapper around a tor1 client's cryptographic state.
307    pub struct Tor1ClientCryptState<SC: StreamCipher, D: Digest + Clone> {
308        /// Layer for traffic moving away from the client.
309        fwd: ClientOutbound<SC, D>,
310        /// Layer for traffic moving toward the client.
311        back: ClientInbound<SC, D>,
312    }
313
314    impl<SC: StreamCipher + KeyIvInit, D: Digest + Clone> Tor1ClientCryptState<SC, D> {
315        /// Return a new `Tor1ClientCryptState` based on a seed.
316        pub fn construct(seed: SecretBuf) -> Result<Self> {
317            let (outbound, inbound, _) =
318                CryptStatePair::construct(KGen::new(seed))?.split_client_layer();
319            Ok(Self {
320                back: inbound,
321                fwd: outbound,
322            })
323        }
324    }
325
326    impl<SC, D> From<Tor1ClientCryptState<SC, D>> for OutboundClientLayerWrapper
327    where
328        SC: StreamCipher + Send + 'static,
329        D: Digest + Clone + Send + 'static,
330    {
331        fn from(state: Tor1ClientCryptState<SC, D>) -> Self {
332            Self(Box::new(state.fwd))
333        }
334    }
335
336    impl<SC, D> From<Tor1ClientCryptState<SC, D>> for InboundClientLayerWrapper
337    where
338        SC: StreamCipher + Send + 'static,
339        D: Digest + Clone + Send + 'static,
340    {
341        fn from(value: Tor1ClientCryptState<SC, D>) -> Self {
342            Self(Box::new(value.back))
343        }
344    }
345
346    /// Public wrapper around a tor1 relay's cryptographic state.
347    pub struct Tor1RelayCryptState<SC: StreamCipher, D: Digest + Clone> {
348        /// Layer for traffic moving away from the client.
349        fwd: RelayOutbound<SC, D>,
350        /// Layer for traffic moving toward the client.
351        back: RelayInbound<SC, D>,
352    }
353
354    impl<SC: StreamCipher + KeyIvInit, D: Digest + Clone> Tor1RelayCryptState<SC, D> {
355        /// Return a new `Tor1RelayCryptState` based on a seed.
356        pub fn construct(seed: SecretBuf) -> Result<Self> {
357            let (outbound, inbound, _) =
358                CryptStatePair::construct(KGen::new(seed))?.split_relay_layer();
359            Ok(Self {
360                back: inbound,
361                fwd: outbound,
362            })
363        }
364    }
365
366    impl<SC: StreamCipher + KeyIvInit, D: Digest + Clone> RelayCryptState
367        for Tor1RelayCryptState<SC, D>
368    {
369        fn originate(&mut self, cell: &mut RelayBody) {
370            let cell = &mut cell.0;
371            self.back.originate(ChanCmd::RELAY, cell);
372        }
373
374        fn encrypt(&mut self, cell: &mut RelayBody) {
375            let cell = &mut cell.0;
376            self.back.encrypt_inbound(ChanCmd::RELAY, cell);
377        }
378
379        fn decrypt(&mut self, cell: &mut RelayBody) {
380            let cell = &mut cell.0;
381            self.fwd.decrypt_outbound(ChanCmd::RELAY, cell);
382        }
383    }
384
385    /// Public wrapper around the `set_digest` method of the `RelayCellBody` struct.
386    pub fn set_digest<D: Digest + Clone>(
387        relay_body: &mut RelayBody,
388        d: &mut D,
389        used_digest: &mut SendmeTag,
390    ) {
391        relay_body.0.set_digest::<D>(d, used_digest);
392    }
393
394    /// Public wrapper around the `is_recognized` method of the `RelayCellBody` struct.
395    pub fn is_recognized<D: Digest + Clone>(
396        relay_body: &RelayBody,
397        d: &mut D,
398        rcvd: &mut SendmeTag,
399    ) -> bool {
400        relay_body.0.is_recognized::<D>(d, rcvd)
401    }
402}
403
404#[cfg(test)]
405mod test {
406    // @@ begin test lint list maintained by maint/add_warning @@
407    #![allow(clippy::bool_assert_comparison)]
408    #![allow(clippy::clone_on_copy)]
409    #![allow(clippy::dbg_macro)]
410    #![allow(clippy::mixed_attributes_style)]
411    #![allow(clippy::print_stderr)]
412    #![allow(clippy::print_stdout)]
413    #![allow(clippy::single_char_pattern)]
414    #![allow(clippy::unwrap_used)]
415    #![allow(clippy::unchecked_duration_subtraction)]
416    #![allow(clippy::useless_vec)]
417    #![allow(clippy::needless_pass_by_value)]
418    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
419
420    use crate::crypto::cell::{
421        test::add_layers, InboundClientCrypt, OutboundClientCrypt, Tor1RelayCrypto,
422    };
423
424    use super::*;
425
426    // From tor's test_relaycrypt.c
427
428    #[test]
429    fn testvec() {
430        use digest::XofReader;
431        use digest::{ExtendableOutput, Update};
432
433        // (The ....s at the end here are the KH ca)
434        const K1: &[u8; 92] =
435            b"    'My public key is in this signed x509 object', said Tom assertively.      (N-PREG-VIRYL)";
436        const K2: &[u8; 92] =
437            b"'Let's chart the pedal phlanges in the tomb', said Tom cryptographically.  (PELCG-GBR-TENCU)";
438        const K3: &[u8; 92] =
439            b"     'Segmentation fault bugs don't _just happen_', said Tom seethingly.        (P-GUVAT-YL)";
440
441        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.";
442        let cmd = ChanCmd::RELAY;
443
444        // These test vectors were generated from Tor.
445        let data: &[(usize, &str)] = &include!("../../../testdata/cell_crypt.rs");
446
447        let mut cc_out = OutboundClientCrypt::new();
448        let mut cc_in = InboundClientCrypt::new();
449        let pair = Tor1RelayCrypto::initialize(&K1[..]).unwrap();
450        add_layers(&mut cc_out, &mut cc_in, pair);
451        let pair = Tor1RelayCrypto::initialize(&K2[..]).unwrap();
452        add_layers(&mut cc_out, &mut cc_in, pair);
453        let pair = Tor1RelayCrypto::initialize(&K3[..]).unwrap();
454        add_layers(&mut cc_out, &mut cc_in, pair);
455
456        let mut xof = tor_llcrypto::d::Shake256::default();
457        xof.update(&SEED[..]);
458        let mut stream = xof.finalize_xof();
459
460        let mut j = 0;
461        for cellno in 0..51 {
462            let mut body = Box::new([0_u8; 509]);
463            body[0] = 2; // command: data.
464            body[4] = 1; // streamid: 1.
465            body[9] = 1; // length: 498
466            body[10] = 242;
467            stream.read(&mut body[11..]);
468
469            let mut cell = body.into();
470            let _ = cc_out.encrypt(cmd, &mut cell, 2.into());
471
472            if cellno == data[j].0 {
473                let expected = hex::decode(data[j].1).unwrap();
474                assert_eq!(cell.as_ref(), &expected[..]);
475                j += 1;
476            }
477        }
478    }
479}