Skip to content

Commit c90da4e

Browse files
committed
replay filter for TCP & UDP in AEAD-2022
1 parent 50d2a01 commit c90da4e

File tree

14 files changed

+155
-76
lines changed

14 files changed

+155
-76
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
/udp_echo_server.py
1313
/.idea
1414
/.devcontainer
15+
.DS_Store

crates/shadowsocks/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ stream-cipher = ["shadowsocks-crypto/v1-stream"]
3232
aead-cipher-extra = ["shadowsocks-crypto/v1-aead-extra"]
3333

3434
# Enable AEAD 2022
35-
aead-cipher-2022 = ["shadowsocks-crypto/v2", "rand/small_rng", "aes", "lru_time_cache"]
35+
aead-cipher-2022 = ["shadowsocks-crypto/v2", "rand/small_rng", "aes", "lru_time_cache", "spin"]
3636

3737
# Enable detection against replay attack
3838
security-replay-attack-detect = ["bloomfilter", "spin"]

crates/shadowsocks/src/context.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use log::warn;
77

88
use crate::{
99
config::{ReplayAttackPolicy, ServerType},
10-
crypto::v1::random_iv_or_salt,
10+
crypto::{v1::random_iv_or_salt, CipherKind},
1111
dns_resolver::DnsResolver,
1212
security::replay::ReplayProtector,
1313
};
@@ -50,15 +50,15 @@ impl Context {
5050
///
5151
/// If not, set into the current bloom filter
5252
#[inline(always)]
53-
fn check_nonce_and_set(&self, nonce: &[u8]) -> bool {
53+
fn check_nonce_and_set(&self, method: CipherKind, nonce: &[u8]) -> bool {
5454
match self.replay_policy {
5555
ReplayAttackPolicy::Ignore => false,
56-
_ => self.replay_protector.check_nonce_and_set(nonce),
56+
_ => self.replay_protector.check_nonce_and_set(method, nonce),
5757
}
5858
}
5959

6060
/// Generate nonce (IV or SALT)
61-
pub fn generate_nonce(&self, nonce: &mut [u8], unique: bool) {
61+
pub fn generate_nonce(&self, method: CipherKind, nonce: &mut [u8], unique: bool) {
6262
if nonce.is_empty() {
6363
return;
6464
}
@@ -86,7 +86,7 @@ impl Context {
8686
}
8787

8888
// Salt already exists, generate a new one.
89-
if unique && self.check_nonce_and_set(nonce) {
89+
if unique && self.check_nonce_and_set(method, nonce) {
9090
continue;
9191
}
9292

@@ -95,21 +95,21 @@ impl Context {
9595
}
9696

9797
/// Check nonce replay
98-
pub fn check_nonce_replay(&self, nonce: &[u8]) -> io::Result<()> {
98+
pub fn check_nonce_replay(&self, method: CipherKind, nonce: &[u8]) -> io::Result<()> {
9999
if nonce.is_empty() {
100100
return Ok(());
101101
}
102102

103103
match self.replay_policy {
104104
ReplayAttackPolicy::Ignore => Ok(()),
105105
ReplayAttackPolicy::Detect => {
106-
if self.replay_protector.check_nonce_and_set(nonce) {
106+
if self.replay_protector.check_nonce_and_set(method, nonce) {
107107
warn!("detected repeated nonce (iv/salt) {:?}", ByteStr::new(nonce));
108108
}
109109
Ok(())
110110
}
111111
ReplayAttackPolicy::Reject => {
112-
if self.replay_protector.check_nonce_and_set(nonce) {
112+
if self.replay_protector.check_nonce_and_set(method, nonce) {
113113
let err = io::Error::new(io::ErrorKind::Other, "detected repeated nonce (iv/salt)");
114114
Err(err)
115115
} else {
@@ -151,4 +151,9 @@ impl Context {
151151
pub fn set_replay_attack_policy(&mut self, replay_policy: ReplayAttackPolicy) {
152152
self.replay_policy = replay_policy;
153153
}
154+
155+
/// Get policy against replay attach
156+
pub fn replay_attack_policy(&self) -> ReplayAttackPolicy {
157+
self.replay_policy
158+
}
154159
}

crates/shadowsocks/src/relay/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,25 @@ pub use self::socks5::Address;
55
pub mod socks5;
66
pub mod tcprelay;
77
pub mod udprelay;
8+
9+
/// AEAD 2022 maximum padding length
10+
#[cfg(feature = "aead-cipher-2022")]
11+
const AEAD2022_MAX_PADDING_SIZE: usize = 900;
12+
13+
/// Get a properly AEAD 2022 padding size according to payload's length
14+
#[cfg(feature = "aead-cipher-2022")]
15+
fn get_aead_2022_padding_size(payload: &[u8]) -> usize {
16+
use std::cell::RefCell;
17+
18+
use rand::{rngs::SmallRng, Rng, SeedableRng};
19+
20+
thread_local! {
21+
static PADDING_RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
22+
}
23+
24+
if payload.is_empty() {
25+
PADDING_RNG.with(|rng| rng.borrow_mut().gen::<usize>() % AEAD2022_MAX_PADDING_SIZE)
26+
} else {
27+
0
28+
}
29+
}

crates/shadowsocks/src/relay/tcprelay/aead.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ impl DecryptedReader {
224224

225225
// Check repeated salt after first successful decryption #442
226226
if let Some(ref salt) = self.salt {
227-
context.check_nonce_replay(salt)?;
227+
context.check_nonce_replay(self.method, salt)?;
228228
}
229229

230230
// Remote TAG

crates/shadowsocks/src/relay/tcprelay/aead_2022.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ impl DecryptedReader {
222222

223223
// Check repeated salt after first successful decryption #442
224224
if let Some(ref salt) = self.salt {
225-
context.check_nonce_replay(salt)?;
225+
context.check_nonce_replay(self.method, salt)?;
226226
}
227227

228228
// Remote TAG

crates/shadowsocks/src/relay/tcprelay/crypto_io.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,21 +169,21 @@ impl<S> CryptoStream<S> {
169169
#[cfg(feature = "stream-cipher")]
170170
CipherCategory::Stream => {
171171
let mut local_iv = vec![0u8; prev_len];
172-
context.generate_nonce(&mut local_iv, true);
172+
context.generate_nonce(method, &mut local_iv, true);
173173
trace!("generated Stream cipher IV {:?}", ByteStr::new(&local_iv));
174174
local_iv
175175
}
176176
CipherCategory::Aead => {
177177
let mut local_salt = vec![0u8; prev_len];
178-
context.generate_nonce(&mut local_salt, true);
178+
context.generate_nonce(method, &mut local_salt, true);
179179
trace!("generated AEAD cipher salt {:?}", ByteStr::new(&local_salt));
180180
local_salt
181181
}
182182
CipherCategory::None => Vec::new(),
183183
#[cfg(feature = "aead-cipher-2022")]
184184
CipherCategory::Aead2022 => {
185185
let mut local_salt = vec![0u8; prev_len];
186-
context.generate_nonce(&mut local_salt, true);
186+
context.generate_nonce(method, &mut local_salt, true);
187187
trace!("generated AEAD cipher salt {:?}", ByteStr::new(&local_salt));
188188
local_salt
189189
}

crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ use tokio::{
1616
time,
1717
};
1818

19-
#[cfg(feature = "aead-cipher-2022")]
20-
use crate::context::Context;
2119
use crate::{
2220
config::ServerConfig,
2321
context::SharedContext,
@@ -28,6 +26,8 @@ use crate::{
2826
tcprelay::crypto_io::{CryptoRead, CryptoStream, CryptoWrite},
2927
},
3028
};
29+
#[cfg(feature = "aead-cipher-2022")]
30+
use crate::{context::Context, relay::get_aead_2022_padding_size};
3131

3232
enum ProxyClientStreamWriteState {
3333
Connect(Address),
@@ -197,8 +197,8 @@ fn poll_read_aead_2022_header<S>(
197197
where
198198
S: AsyncRead + AsyncWrite + Unpin,
199199
{
200+
use super::protocol::v2::{get_now_timestamp, Aead2022TcpStreamType, SERVER_STREAM_TIMESTAMP_MAX_DIFF};
200201
use bytes::Buf;
201-
use std::time::SystemTime;
202202

203203
// AEAD 2022 TCP Response Header
204204
//
@@ -208,9 +208,6 @@ where
208208
// | Request SALT (Variable ...)
209209
// +-------+-------+-------+-------+-------+-------+-------+-------+-------+
210210

211-
const SERVER_STREAM_TYPE: u8 = 1;
212-
const SERVER_STREAM_TIMESTAMP_MAX_DIFF: u64 = 30;
213-
214211
// Initialize buffer
215212
let method = stream.method();
216213
if header_buf.is_empty() {
@@ -230,7 +227,7 @@ where
230227
// Done reading TCP header, check all the fields
231228

232229
let stream_type = header_buf.get_u8();
233-
if stream_type != SERVER_STREAM_TYPE {
230+
if stream_type != Aead2022TcpStreamType::Server as u8 {
234231
return Err(io::Error::new(
235232
ErrorKind::Other,
236233
format!("received TCP response header with wrong type {}", stream_type),
@@ -239,10 +236,7 @@ where
239236
}
240237

241238
let timestamp = header_buf.get_u64();
242-
let now = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
243-
Ok(n) => n.as_secs(),
244-
Err(_) => panic!("SystemTime::now() is before UNIX Epoch!"),
245-
};
239+
let now = get_now_timestamp();
246240

247241
if now.abs_diff(timestamp) > SERVER_STREAM_TIMESTAMP_MAX_DIFF {
248242
return Err(io::Error::new(
@@ -317,30 +311,14 @@ fn make_first_packet_buffer(method: CipherKind, addr: &Address, buf: &[u8]) -> B
317311
//
318312
// Client -> Server TYPE=0
319313

320-
use rand::{rngs::SmallRng, Rng, SeedableRng};
321-
use std::{cell::RefCell, time::SystemTime};
314+
use super::protocol::v2::{get_now_timestamp, Aead2022TcpStreamType};
322315

323-
const CLIENT_STREAM_TYPE: u8 = 0;
324-
const MAX_PADDING_SIZE: usize = 900;
325-
326-
thread_local! {
327-
static PADDING_RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
328-
}
329-
330-
let padding_size = if buf.is_empty() {
331-
PADDING_RNG.with(|rng| rng.borrow_mut().gen::<usize>() % MAX_PADDING_SIZE)
332-
} else {
333-
// If handshake with data buffer, then padding is not required and should be 0 for letting TFO work properly.
334-
0
335-
};
316+
let padding_size = get_aead_2022_padding_size(buf);
336317

337318
buffer.reserve(1 + 8 + addr_length + 2 + padding_size);
338-
buffer.put_u8(CLIENT_STREAM_TYPE);
319+
buffer.put_u8(Aead2022TcpStreamType::Client as u8);
339320

340-
let timestamp = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
341-
Ok(n) => n.as_secs(),
342-
Err(_) => panic!("SystemTime::now() is before UNIX Epoch!"),
343-
};
321+
let timestamp = get_now_timestamp();
344322
buffer.put_u64(timestamp);
345323

346324
addr.write_to_buf(&mut buffer);

crates/shadowsocks/src/relay/tcprelay/stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl DecryptedReader {
119119
}
120120

121121
let iv = &self.buffer[..iv_len];
122-
context.check_nonce_replay(iv)?;
122+
context.check_nonce_replay(self.method, iv)?;
123123

124124
trace!("got stream iv {:?}", ByteStr::new(iv));
125125

crates/shadowsocks/src/relay/udprelay/aead.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub fn encrypt_payload_aead(
4343
let salt = &mut dst[..salt_len];
4444

4545
if salt_len > 0 {
46-
context.generate_nonce(salt, false);
46+
context.generate_nonce(method, salt, false);
4747
trace!("UDP packet generated aead salt {:?}", ByteStr::new(salt));
4848
}
4949

0 commit comments

Comments
 (0)