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