Skip to main content

tor_proto/crypto/
cell.rs

1//! Relay cell cryptography
2//!
3//! The Tor protocol centers around "RELAY cells", which are transmitted through
4//! the network along circuits.  The client that creates a circuit shares two
5//! different sets of keys and state with each of the relays on the circuit: one
6//! for "outbound" traffic, and one for "inbound" traffic.
7//!
8//! So for example, if a client creates a 3-hop circuit with relays R1, R2, and
9//! R3, the client has:
10//!   * An "inbound" cryptographic state shared with R1.
11//!   * An "inbound" cryptographic state shared with R2.
12//!   * An "inbound" cryptographic state shared with R3.
13//!   * An "outbound" cryptographic state shared with R1.
14//!   * An "outbound" cryptographic state shared with R2.
15//!   * An "outbound" cryptographic state shared with R3.
16//!
17//! In this module at least, we'll call each of these state objects a "layer" of
18//! the circuit's encryption.
19//!
20//! The Tor specification does not describe these layer objects very explicitly.
21//! In the current relay cryptography protocol, each layer contains:
22//!    * A keyed AES-CTR state. (AES-128 or AES-256)  This cipher uses a key
23//!      called `Kf` or `Kb` in the spec, where `Kf` is a "forward" key used in
24//!      the outbound direction, and `Kb` is a "backward" key used in the
25//!      inbound direction.
26//!    * A running digest. (SHA1 or SHA3)  This digest is initialized with a
27//!      value called `Df` or `Db` in the spec.
28//!
29//! This `crypto::cell` module itself provides traits and implementations that
30//! should work for all current future versions of the relay cell crypto design.
31//! The current Tor protocols are instantiated in a `tor1` submodule.
32
33#[cfg(feature = "bench")]
34pub(crate) mod bench_utils;
35#[cfg(feature = "counter-galois-onion")]
36pub(crate) mod cgo;
37pub(crate) mod tor1;
38
39use crate::{Error, Result};
40use derive_deftly::Deftly;
41use tor_cell::{
42    chancell::{BoxedCellBody, ChanCmd},
43    relaycell::msg::SendmeTag,
44};
45use tor_memquota::derive_deftly_template_HasMemoryCost;
46
47use super::binding::CircuitBinding;
48
49/// Type for the body of a relay cell.
50#[cfg_attr(feature = "bench", visibility::make(pub))]
51#[derive(Clone, derive_more::From, derive_more::Into)]
52pub(crate) struct RelayCellBody(BoxedCellBody);
53
54impl AsRef<[u8]> for RelayCellBody {
55    fn as_ref(&self) -> &[u8] {
56        &self.0[..]
57    }
58}
59impl AsMut<[u8]> for RelayCellBody {
60    fn as_mut(&mut self) -> &mut [u8] {
61        &mut self.0[..]
62    }
63}
64
65/// Represents the ability for one hop of a circuit's cryptographic state to be
66/// initialized from a given seed.
67#[cfg_attr(feature = "bench", visibility::make(pub))]
68pub(crate) trait CryptInit: Sized {
69    /// Return the number of bytes that this state will require.
70    fn seed_len() -> usize;
71    /// Construct this state from a seed of the appropriate length.
72    fn initialize(seed: &[u8]) -> Result<Self>;
73    /// Initialize this object from a key generator.
74    fn construct<K: super::handshake::KeyGenerator>(keygen: K) -> Result<Self> {
75        let seed = keygen.expand(Self::seed_len())?;
76        Self::initialize(&seed[..])
77    }
78}
79
80/// A paired object containing the inbound and outbound cryptographic layers
81/// used by a client to communicate with a single hop on one of its circuits.
82///
83/// TODO: Maybe we should fold this into CryptInit.
84#[cfg_attr(feature = "bench", visibility::make(pub))]
85pub(crate) trait ClientLayer<F, B>
86where
87    F: OutboundClientLayer,
88    B: InboundClientLayer,
89{
90    /// Consume this ClientLayer and return a paired forward and reverse
91    /// crypto layer, and a [`CircuitBinding`] object
92    fn split_client_layer(self) -> (F, B, CircuitBinding);
93}
94
95/// A paired object containing the inbound and outbound cryptographic layers
96/// used by a relay to implement a client's circuits.
97///
98#[allow(dead_code)] // To be used by relays.
99#[cfg_attr(feature = "bench", visibility::make(pub))]
100pub(crate) trait RelayLayer<F, B>
101where
102    F: OutboundRelayLayer,
103    B: InboundRelayLayer,
104{
105    /// Consume this ClientLayer and return a paired forward and reverse
106    /// crypto layers, and a [`CircuitBinding`] object
107    fn split_relay_layer(self) -> (F, B, CircuitBinding);
108}
109
110/// Represents a relay's view of the inbound crypto state on a given circuit.
111#[allow(dead_code)] // Relays are not yet implemented.
112#[cfg_attr(feature = "bench", visibility::make(pub))]
113pub(crate) trait InboundRelayLayer {
114    /// Prepare a RelayCellBody to be sent towards the client,
115    /// and encrypt it.
116    ///
117    /// Return the authentication tag.
118    fn originate(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> SendmeTag;
119    /// Encrypt a RelayCellBody that is moving towards the client.
120    fn encrypt_inbound(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody);
121}
122
123/// Represent a relay's view of the outbound crypto state on a given circuit.
124#[allow(dead_code)]
125#[cfg_attr(feature = "bench", visibility::make(pub))]
126pub(crate) trait OutboundRelayLayer {
127    /// Decrypt a RelayCellBody that is coming from the client.
128    ///
129    /// Return an authentication tag if it is addressed to us.
130    fn decrypt_outbound(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> Option<SendmeTag>;
131}
132
133/// A client's view of the cryptographic state shared with a single relay on a
134/// circuit, as used for outbound cells.
135#[cfg_attr(feature = "bench", visibility::make(pub))]
136pub(crate) trait OutboundClientLayer {
137    /// Prepare a RelayCellBody to be sent to the relay at this layer, and
138    /// encrypt it.
139    ///
140    /// Return the authentication tag.
141    fn originate_for(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> SendmeTag;
142    /// Encrypt a RelayCellBody to be decrypted by this layer.
143    fn encrypt_outbound(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody);
144}
145
146/// A client's view of the crypto state shared with a single relay on a circuit,
147/// as used for inbound cells.
148#[cfg_attr(feature = "bench", visibility::make(pub))]
149pub(crate) trait InboundClientLayer {
150    /// Decrypt a CellBody that passed through this layer.
151    ///
152    /// Return an authentication tag if this layer is the originator.
153    fn decrypt_inbound(&mut self, cmd: ChanCmd, cell: &mut RelayCellBody) -> Option<SendmeTag>;
154}
155
156/// Type to store hop indices on a circuit.
157///
158/// Hop indices are zero-based: "0" denotes the first hop on the circuit.
159#[derive(Copy, Clone, Eq, PartialEq, Debug, Deftly, Ord, PartialOrd)]
160#[derive_deftly(HasMemoryCost)]
161pub struct HopNum(u8);
162
163impl HopNum {
164    /// Return an object that implements [`Display`](std::fmt::Display) for printing `HopNum`s.
165    ///
166    /// This will display the `HopNum` as a 1-indexed value (the string representation of the first
167    /// hop is `"#1"`).
168    ///
169    /// To display the zero-based underlying representation of the `HopNum`, use
170    /// [`Debug`](std::fmt::Debug).
171    pub fn display(&self) -> HopNumDisplay {
172        HopNumDisplay(*self)
173    }
174
175    /// Return true if this is  the first hop of a circuit.
176    pub(crate) fn is_first_hop(&self) -> bool {
177        self.0 == 0
178    }
179}
180
181/// A helper for displaying [`HopNum`]s.
182///
183/// The [`Display`](std::fmt::Display) of this type displays the `HopNum` as a 1-based index
184/// prefixed with the number sign (`#`). For example, the string representation of the first hop is
185/// `"#1"`.
186#[derive(Copy, Clone, Eq, PartialEq, Debug)]
187pub struct HopNumDisplay(HopNum);
188
189impl std::fmt::Display for HopNumDisplay {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
191        let hop_num: u8 = self.0.into();
192
193        write!(f, "#{}", hop_num + 1)
194    }
195}
196
197impl From<HopNum> for u8 {
198    fn from(hop: HopNum) -> u8 {
199        hop.0
200    }
201}
202
203impl From<u8> for HopNum {
204    fn from(v: u8) -> HopNum {
205        HopNum(v)
206    }
207}
208
209impl From<HopNum> for usize {
210    fn from(hop: HopNum) -> usize {
211        hop.0 as usize
212    }
213}
214
215/// A client's view of the cryptographic state for an entire
216/// constructed circuit, as used for sending cells.
217#[cfg_attr(feature = "bench", visibility::make(pub), derive(Default))]
218pub(crate) struct OutboundClientCrypt {
219    /// Vector of layers, one for each hop on the circuit, ordered from the
220    /// closest hop to the farthest.
221    layers: Vec<Box<dyn OutboundClientLayer + Send>>,
222}
223
224/// A client's view of the cryptographic state for an entire
225/// constructed circuit, as used for receiving cells.
226#[cfg_attr(feature = "bench", visibility::make(pub), derive(Default))]
227pub(crate) struct InboundClientCrypt {
228    /// Vector of layers, one for each hop on the circuit, ordered from the
229    /// closest hop to the farthest.
230    layers: Vec<Box<dyn InboundClientLayer + Send>>,
231}
232
233impl OutboundClientCrypt {
234    /// Return a new (empty) OutboundClientCrypt.
235    #[cfg_attr(feature = "bench", visibility::make(pub))]
236    pub(crate) fn new() -> Self {
237        OutboundClientCrypt { layers: Vec::new() }
238    }
239    /// Prepare a cell body to sent away from the client.
240    ///
241    /// The cell is prepared for the `hop`th hop, and then encrypted with
242    /// the appropriate keys.
243    ///
244    /// On success, returns a reference to tag that should be expected
245    /// for an authenticated SENDME sent in response to this cell.
246    #[cfg_attr(feature = "bench", visibility::make(pub))]
247    pub(crate) fn encrypt(
248        &mut self,
249        cmd: ChanCmd,
250        cell: &mut RelayCellBody,
251        hop: HopNum,
252    ) -> Result<SendmeTag> {
253        let hop: usize = hop.into();
254        if hop >= self.layers.len() {
255            return Err(Error::NoSuchHop);
256        }
257
258        let mut layers = self.layers.iter_mut().take(hop + 1).rev();
259        let first_layer = layers.next().ok_or(Error::NoSuchHop)?;
260        let tag = first_layer.originate_for(cmd, cell);
261        for layer in layers {
262            layer.encrypt_outbound(cmd, cell);
263        }
264        Ok(tag)
265    }
266
267    /// Add a new layer to this OutboundClientCrypt
268    pub(crate) fn add_layer(&mut self, layer: Box<dyn OutboundClientLayer + Send>) {
269        assert!(self.layers.len() < u8::MAX as usize);
270        self.layers.push(layer);
271    }
272
273    /// Return the number of layers configured on this OutboundClientCrypt.
274    pub(crate) fn n_layers(&self) -> usize {
275        self.layers.len()
276    }
277}
278
279impl InboundClientCrypt {
280    /// Return a new (empty) InboundClientCrypt.
281    #[cfg_attr(feature = "bench", visibility::make(pub))]
282    pub(crate) fn new() -> Self {
283        InboundClientCrypt { layers: Vec::new() }
284    }
285    /// Decrypt an incoming cell that is coming to the client.
286    ///
287    /// On success, return which hop was the originator of the cell.
288    // TODO(nickm): Use a real type for the tag, not just `&[u8]`.
289    #[cfg_attr(feature = "bench", visibility::make(pub))]
290    pub(crate) fn decrypt(
291        &mut self,
292        cmd: ChanCmd,
293        cell: &mut RelayCellBody,
294    ) -> Result<(HopNum, SendmeTag)> {
295        for (hopnum, layer) in self.layers.iter_mut().enumerate() {
296            if let Some(tag) = layer.decrypt_inbound(cmd, cell) {
297                let hopnum = HopNum(u8::try_from(hopnum).expect("Somehow > 255 hops"));
298                return Ok((hopnum, tag));
299            }
300        }
301        Err(Error::BadCellAuth)
302    }
303    /// Add a new layer to this InboundClientCrypt
304    pub(crate) fn add_layer(&mut self, layer: Box<dyn InboundClientLayer + Send>) {
305        assert!(self.layers.len() < u8::MAX as usize);
306        self.layers.push(layer);
307    }
308
309    /// Return the number of layers configured on this InboundClientCrypt.
310    ///
311    /// TODO: use HopNum
312    #[allow(dead_code)]
313    pub(crate) fn n_layers(&self) -> usize {
314        self.layers.len()
315    }
316}
317
318/// Standard Tor relay crypto, as instantiated for RELAY cells.
319pub(crate) type Tor1RelayCrypto =
320    tor1::CryptStatePair<tor_llcrypto::cipher::aes::Aes128Ctr, tor_llcrypto::d::Sha1>;
321
322/// Standard Tor relay crypto, as instantiated for the HSv3 protocol.
323///
324/// (The use of SHA3 is ridiculously overkill.)
325#[cfg(feature = "hs-common")]
326pub(crate) type Tor1Hsv3RelayCrypto =
327    tor1::CryptStatePair<tor_llcrypto::cipher::aes::Aes256Ctr, tor_llcrypto::d::Sha3_256>;
328
329/// Counter galois onion relay crypto.
330//
331// We use `aes` directly here instead of tor_llcrypto::aes, which may or may not be OpenSSL:
332// the OpenSSL implementations have bad performance when it comes to re-keying
333// or changing IVs.
334#[cfg(feature = "counter-galois-onion")]
335pub(crate) type CgoRelayCrypto = cgo::CryptStatePair<aes::Aes128, aes::Aes128Enc>;
336
337#[cfg(test)]
338mod test {
339    // @@ begin test lint list maintained by maint/add_warning @@
340    #![allow(clippy::bool_assert_comparison)]
341    #![allow(clippy::clone_on_copy)]
342    #![allow(clippy::dbg_macro)]
343    #![allow(clippy::mixed_attributes_style)]
344    #![allow(clippy::print_stderr)]
345    #![allow(clippy::print_stdout)]
346    #![allow(clippy::single_char_pattern)]
347    #![allow(clippy::unwrap_used)]
348    #![allow(clippy::unchecked_time_subtraction)]
349    #![allow(clippy::useless_vec)]
350    #![allow(clippy::needless_pass_by_value)]
351    #![allow(clippy::string_slice)] // See arti#2571
352    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
353
354    use super::*;
355    use rand::{Rng, seq::IndexedRandom as _};
356    use tor_basic_utils::{RngExt as _, test_rng::testing_rng};
357    use tor_bytes::SecretBuf;
358    use tor_cell::relaycell::RelayCellFormat;
359
360    pub(crate) fn add_layers(
361        cc_out: &mut OutboundClientCrypt,
362        cc_in: &mut InboundClientCrypt,
363        pair: Tor1RelayCrypto,
364    ) {
365        let (outbound, inbound, _) = pair.split_client_layer();
366        cc_out.add_layer(Box::new(outbound));
367        cc_in.add_layer(Box::new(inbound));
368    }
369
370    #[test]
371    fn roundtrip() {
372        // Take canned keys and make sure we can do crypto correctly.
373        use crate::crypto::handshake::ShakeKeyGenerator as KGen;
374        fn s(seed: &[u8]) -> SecretBuf {
375            seed.to_vec().into()
376        }
377
378        let seed1 = s(b"hidden we are free");
379        let seed2 = s(b"free to speak, to free ourselves");
380        let seed3 = s(b"free to hide no more");
381
382        let mut cc_out = OutboundClientCrypt::new();
383        let mut cc_in = InboundClientCrypt::new();
384        let pair = Tor1RelayCrypto::construct(KGen::new(seed1.clone())).unwrap();
385        add_layers(&mut cc_out, &mut cc_in, pair);
386        let pair = Tor1RelayCrypto::construct(KGen::new(seed2.clone())).unwrap();
387        add_layers(&mut cc_out, &mut cc_in, pair);
388        let pair = Tor1RelayCrypto::construct(KGen::new(seed3.clone())).unwrap();
389        add_layers(&mut cc_out, &mut cc_in, pair);
390
391        assert_eq!(cc_in.n_layers(), 3);
392        assert_eq!(cc_out.n_layers(), 3);
393
394        let (mut r1f, mut r1b, _) = Tor1RelayCrypto::construct(KGen::new(seed1))
395            .unwrap()
396            .split_relay_layer();
397        let (mut r2f, mut r2b, _) = Tor1RelayCrypto::construct(KGen::new(seed2))
398            .unwrap()
399            .split_relay_layer();
400        let (mut r3f, mut r3b, _) = Tor1RelayCrypto::construct(KGen::new(seed3))
401            .unwrap()
402            .split_relay_layer();
403        let cmd = ChanCmd::RELAY;
404
405        let mut rng = testing_rng();
406        for _ in 1..300 {
407            // outbound cell
408            let mut cell = Box::new([0_u8; 509]);
409            let mut cell_orig = [0_u8; 509];
410            rng.fill_bytes(&mut cell_orig);
411            cell.copy_from_slice(&cell_orig);
412            let mut cell = cell.into();
413            let _tag = cc_out.encrypt(cmd, &mut cell, 2.into()).unwrap();
414            assert_ne!(&cell.as_ref()[9..], &cell_orig.as_ref()[9..]);
415            assert!(r1f.decrypt_outbound(cmd, &mut cell).is_none());
416            assert!(r2f.decrypt_outbound(cmd, &mut cell).is_none());
417            assert!(r3f.decrypt_outbound(cmd, &mut cell).is_some());
418
419            assert_eq!(&cell.as_ref()[9..], &cell_orig.as_ref()[9..]);
420
421            // inbound cell
422            let mut cell = Box::new([0_u8; 509]);
423            let mut cell_orig = [0_u8; 509];
424            rng.fill_bytes(&mut cell_orig);
425            cell.copy_from_slice(&cell_orig);
426            let mut cell = cell.into();
427
428            r3b.originate(cmd, &mut cell);
429            r2b.encrypt_inbound(cmd, &mut cell);
430            r1b.encrypt_inbound(cmd, &mut cell);
431            let (layer, _tag) = cc_in.decrypt(cmd, &mut cell).unwrap();
432            assert_eq!(layer, 2.into());
433            assert_eq!(&cell.as_ref()[9..], &cell_orig.as_ref()[9..]);
434
435            // TODO: Test tag somehow.
436        }
437
438        // Try a failure: sending a cell to a nonexistent hop.
439        {
440            let mut cell = Box::new([0_u8; 509]).into();
441            let err = cc_out.encrypt(cmd, &mut cell, 10.into());
442            assert!(matches!(err, Err(Error::NoSuchHop)));
443        }
444
445        // Try a failure: A junk cell with no correct auth from any layer.
446        {
447            let mut cell = Box::new([0_u8; 509]).into();
448            let err = cc_in.decrypt(cmd, &mut cell);
449            assert!(matches!(err, Err(Error::BadCellAuth)));
450        }
451    }
452
453    #[test]
454    fn hop_num_display() {
455        for i in 0..10 {
456            let hop_num = HopNum::from(i);
457            let expect = format!("#{}", i + 1);
458
459            assert_eq!(expect, hop_num.display().to_string());
460        }
461    }
462
463    /// Helper: Clear every field in the tor1 `cell` that is reserved for cryptography by relay cell
464    /// format `version.
465    ///
466    /// We do this so that we can be sure that the _other_ fields have all been transmitted correctly.
467    fn clean_cell_fields(cell: &mut RelayCellBody, format: RelayCellFormat) {
468        use super::tor1;
469        match format {
470            RelayCellFormat::V0 => {
471                cell.0[tor1::RECOGNIZED_RANGE].fill(0);
472                cell.0[tor1::DIGEST_RANGE].fill(0);
473            }
474            RelayCellFormat::V1 => {
475                cell.0[0..16].fill(0);
476            }
477            _ => {
478                panic!("Unrecognized format!");
479            }
480        }
481    }
482
483    /// Helper: Test a single-hop message, forward from the client.
484    fn test_fwd_one_hop<CS, RS, CF, CB, RF, RB>(format: RelayCellFormat)
485    where
486        CS: CryptInit + ClientLayer<CF, CB>,
487        RS: CryptInit + RelayLayer<RF, RB>,
488        CF: OutboundClientLayer,
489        CB: InboundClientLayer,
490        RF: OutboundRelayLayer,
491        RB: InboundRelayLayer,
492    {
493        let mut rng = testing_rng();
494        assert_eq!(CS::seed_len(), RS::seed_len());
495        let mut seed = vec![0; CS::seed_len()];
496        rng.fill_bytes(&mut seed[..]);
497        let (mut client, _, _) = CS::initialize(&seed).unwrap().split_client_layer();
498        let (mut relay, _, _) = RS::initialize(&seed).unwrap().split_relay_layer();
499
500        for _ in 0..5 {
501            let mut cell = RelayCellBody(Box::new([0_u8; 509]));
502            rng.fill_bytes(&mut cell.0[..]);
503            clean_cell_fields(&mut cell, format);
504            let msg_orig = cell.clone();
505
506            let ctag = client.originate_for(ChanCmd::RELAY, &mut cell);
507            assert_ne!(cell.0[16..], msg_orig.0[16..]);
508            let rtag = relay.decrypt_outbound(ChanCmd::RELAY, &mut cell);
509            clean_cell_fields(&mut cell, format);
510            assert_eq!(cell.0[..], msg_orig.0[..]);
511            assert_eq!(rtag, Some(ctag));
512        }
513    }
514
515    /// Helper: Test a single-hop message, backwards towards the client.
516    fn test_rev_one_hop<CS, RS, CF, CB, RF, RB>(format: RelayCellFormat)
517    where
518        CS: CryptInit + ClientLayer<CF, CB>,
519        RS: CryptInit + RelayLayer<RF, RB>,
520        CF: OutboundClientLayer,
521        CB: InboundClientLayer,
522        RF: OutboundRelayLayer,
523        RB: InboundRelayLayer,
524    {
525        let mut rng = testing_rng();
526        assert_eq!(CS::seed_len(), RS::seed_len());
527        let mut seed = vec![0; CS::seed_len()];
528        rng.fill_bytes(&mut seed[..]);
529        let (_, mut client, _) = CS::initialize(&seed).unwrap().split_client_layer();
530        let (_, mut relay, _) = RS::initialize(&seed).unwrap().split_relay_layer();
531
532        for _ in 0..5 {
533            let mut cell = RelayCellBody(Box::new([0_u8; 509]));
534            rng.fill_bytes(&mut cell.0[..]);
535            clean_cell_fields(&mut cell, format);
536            let msg_orig = cell.clone();
537
538            let rtag = relay.originate(ChanCmd::RELAY, &mut cell);
539            assert_ne!(cell.0[16..], msg_orig.0[16..]);
540            let ctag = client.decrypt_inbound(ChanCmd::RELAY, &mut cell);
541            clean_cell_fields(&mut cell, format);
542            assert_eq!(cell.0[..], msg_orig.0[..]);
543            assert_eq!(ctag, Some(rtag));
544        }
545    }
546
547    fn test_fwd_three_hops_leaky<CS, RS, CF, CB, RF, RB>(format: RelayCellFormat)
548    where
549        CS: CryptInit + ClientLayer<CF, CB>,
550        RS: CryptInit + RelayLayer<RF, RB>,
551        CF: OutboundClientLayer + Send + 'static,
552        CB: InboundClientLayer,
553        RF: OutboundRelayLayer,
554        RB: InboundRelayLayer,
555    {
556        let mut rng = testing_rng();
557        assert_eq!(CS::seed_len(), RS::seed_len());
558        let mut client = OutboundClientCrypt::new();
559        let mut relays = Vec::new();
560        for _ in 0..3 {
561            let mut seed = vec![0; CS::seed_len()];
562            rng.fill_bytes(&mut seed[..]);
563            let (client_layer, _, _) = CS::initialize(&seed).unwrap().split_client_layer();
564            let (relay_layer, _, _) = RS::initialize(&seed).unwrap().split_relay_layer();
565            client.add_layer(Box::new(client_layer));
566            relays.push(relay_layer);
567        }
568
569        'cell_loop: for _ in 0..32 {
570            let mut cell = RelayCellBody(Box::new([0_u8; 509]));
571            rng.fill_bytes(&mut cell.0[..]);
572            clean_cell_fields(&mut cell, format);
573            let msg_orig = cell.clone();
574            let cmd = *[ChanCmd::RELAY, ChanCmd::RELAY_EARLY]
575                .choose(&mut rng)
576                .unwrap();
577            let hop: u8 = rng.gen_range_checked(0_u8..=2).unwrap();
578
579            let ctag = client.encrypt(cmd, &mut cell, hop.into()).unwrap();
580
581            for r_idx in 0..=hop {
582                let rtag = relays[r_idx as usize].decrypt_outbound(cmd, &mut cell);
583                if let Some(rtag) = rtag {
584                    clean_cell_fields(&mut cell, format);
585                    assert_eq!(cell.0[..], msg_orig.0[..]);
586                    assert_eq!(rtag, ctag);
587                    continue 'cell_loop;
588                }
589            }
590            panic!("None of the relays thought that this cell was recognized!");
591        }
592    }
593
594    fn test_rev_three_hops_leaky<CS, RS, CF, CB, RF, RB>(format: RelayCellFormat)
595    where
596        CS: CryptInit + ClientLayer<CF, CB>,
597        RS: CryptInit + RelayLayer<RF, RB>,
598        CF: OutboundClientLayer,
599        CB: InboundClientLayer + Send + 'static,
600        RF: OutboundRelayLayer,
601        RB: InboundRelayLayer,
602    {
603        let mut rng = testing_rng();
604        assert_eq!(CS::seed_len(), RS::seed_len());
605        let mut client = InboundClientCrypt::new();
606        let mut relays = Vec::new();
607        for _ in 0..3 {
608            let mut seed = vec![0; CS::seed_len()];
609            rng.fill_bytes(&mut seed[..]);
610            let (_, client_layer, _) = CS::initialize(&seed).unwrap().split_client_layer();
611            let (_, relay_layer, _) = RS::initialize(&seed).unwrap().split_relay_layer();
612            client.add_layer(Box::new(client_layer));
613            relays.push(relay_layer);
614        }
615
616        for _ in 0..32 {
617            let mut cell = RelayCellBody(Box::new([0_u8; 509]));
618            rng.fill_bytes(&mut cell.0[..]);
619            clean_cell_fields(&mut cell, format);
620            let msg_orig = cell.clone();
621            let cmd = *[ChanCmd::RELAY, ChanCmd::RELAY_EARLY]
622                .choose(&mut rng)
623                .unwrap();
624            let hop: u8 = rng.gen_range_checked(0_u8..=2).unwrap();
625
626            let rtag = relays[hop as usize].originate(cmd, &mut cell);
627            for r_idx in (0..hop.into()).rev() {
628                relays[r_idx as usize].encrypt_inbound(cmd, &mut cell);
629            }
630
631            let (observed_hop, ctag) = client.decrypt(cmd, &mut cell).unwrap();
632            assert_eq!(observed_hop, hop.into());
633            clean_cell_fields(&mut cell, format);
634            assert_eq!(cell.0[..], msg_orig.0[..]);
635            assert_eq!(ctag, rtag);
636        }
637    }
638
639    macro_rules! integration_tests { { $modname:ident($fmt:expr, $ctype:ty, $rtype:ty) } => {
640        mod $modname {
641            use super::*;
642            #[test]
643            fn test_fwd_one_hop() {
644                super::test_fwd_one_hop::<$ctype, $rtype, _, _, _, _>($fmt);
645            }
646            #[test]
647            fn test_rev_one_hop() {
648                super::test_rev_one_hop::<$ctype, $rtype, _, _, _, _>($fmt);
649            }
650            #[test]
651            fn test_fwd_three_hops_leaky() {
652                super::test_fwd_three_hops_leaky::<$ctype, $rtype, _, _, _, _>($fmt);
653            }
654            #[test]
655            fn test_rev_three_hops_leaky() {
656                super::test_rev_three_hops_leaky::<$ctype, $rtype, _, _, _, _>($fmt);
657            }
658        }
659    }}
660
661    integration_tests! { tor1(RelayCellFormat::V0, Tor1RelayCrypto, Tor1RelayCrypto) }
662    #[cfg(feature = "hs-common")]
663    integration_tests! { tor1_hs(RelayCellFormat::V0, Tor1Hsv3RelayCrypto, Tor1Hsv3RelayCrypto) }
664
665    #[cfg(feature = "counter-galois-onion")]
666    integration_tests! {
667        cgo_aes128(RelayCellFormat::V1,
668            cgo::CryptStatePair<aes::Aes128Dec, aes::Aes128Enc>,// client
669            cgo::CryptStatePair<aes::Aes128Enc, aes::Aes128Enc> // relay
670        )
671    }
672    #[cfg(feature = "counter-galois-onion")]
673    integration_tests! {
674        cgo_aes256(RelayCellFormat::V1,
675            cgo::CryptStatePair<aes::Aes256Dec, aes::Aes256Enc>,// client
676            cgo::CryptStatePair<aes::Aes256Enc, aes::Aes256Enc> // relay
677        )
678    }
679}