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