From ac225f0f3651bc9f1b2d94a01713653b155349cc Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Mon, 20 Jun 2022 16:30:05 +0800 Subject: [PATCH] EXPERIMENTAL: AEAD-2022 Extensible Identity Headers - https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md This is an experimental feature, which may be change in the future. Supported methods: 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm --- Cargo.lock | 30 ++- README.md | 23 ++ crates/shadowsocks-service/Cargo.toml | 1 + crates/shadowsocks-service/src/config.rs | 68 ++++- .../src/local/loadbalancing/ping_balancer.rs | 18 +- .../src/local/net/udp/association.rs | 8 +- .../shadowsocks-service/src/manager/server.rs | 43 ++- .../src/server/udprelay.rs | 14 +- crates/shadowsocks/Cargo.toml | 5 +- crates/shadowsocks/src/config.rs | 163 +++++++++++- crates/shadowsocks/src/manager/protocol.rs | 9 + crates/shadowsocks/src/relay/tcprelay/aead.rs | 9 + .../src/relay/tcprelay/aead_2022.rs | 192 +++++++++++++- .../src/relay/tcprelay/crypto_io.rs | 244 +++++++++--------- .../src/relay/tcprelay/proxy_listener.rs | 14 +- .../src/relay/tcprelay/proxy_stream/client.rs | 10 +- .../src/relay/tcprelay/proxy_stream/server.rs | 15 +- .../shadowsocks/src/relay/tcprelay/stream.rs | 9 + .../src/relay/udprelay/aead_2022.rs | 211 +++++++++++++-- .../src/relay/udprelay/crypto_io.rs | 33 ++- .../shadowsocks/src/relay/udprelay/options.rs | 18 +- .../src/relay/udprelay/proxy_socket.rs | 73 +++++- 22 files changed, 985 insertions(+), 225 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffd20cec..1eff8566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "arc-swap" @@ -1280,9 +1280,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", @@ -1608,9 +1608,9 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] @@ -1632,9 +1632,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -2023,6 +2023,7 @@ dependencies = [ "arc-swap", "async-trait", "base64", + "blake3", "bloomfilter", "byte_string", "bytes", @@ -2114,6 +2115,7 @@ version = "1.15.0" dependencies = [ "arc-swap", "async-trait", + "base64", "byte_string", "byteorder", "bytes", @@ -2270,9 +2272,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -2498,9 +2500,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "tower-layer", "tower-service", @@ -2515,9 +2517,9 @@ checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" diff --git a/README.md b/README.md index 554f5a95..866611e0 100644 --- a/README.md +++ b/README.md @@ -589,6 +589,29 @@ Example configuration: "method": "chacha20-ietf-poly1305", // Read the actual password from environment variable PASSWORD_FROM_ENV "password": "${PASSWORD_FROM_ENV}" + }, + { + // AEAD-2022 + "server": "::", + "server_port": 8390, + "method": "2022-blake3-aes-256-gcm", + "password": "3SYJ/f8nmVuzKvKglykRQDSgg10e/ADilkdRWrrY9HU=", + // For Server (OPTIONAL) + // Support multiple users with Extensible Identity Header + // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md + "users": [ + { + "name": "username", + // User's password must have the same length as server's password + "password": "4w0GKJ9U3Ox7CIXGU4A3LDQAqP6qrp/tUi/ilpOR9p4=" + } + ], + // For Client (OPTIONAL) + // If EIH enabled, then "password" should have the following format: iPSK:iPSK:iPSK:uPSK + // - iPSK is one of the middle relay servers' PSK, for the last `ssserver`, it must be server's PSK ("password") + // - uPSK is the user's PSK ("password") + // Example: + // "password": "3SYJ/f8nmVuzKvKglykRQDSgg10e/ADilkdRWrrY9HU=:4w0GKJ9U3Ox7CIXGU4A3LDQAqP6qrp/tUi/ilpOR9p4=" } ], diff --git a/crates/shadowsocks-service/Cargo.toml b/crates/shadowsocks-service/Cargo.toml index ddfa27f1..f219845b 100644 --- a/crates/shadowsocks-service/Cargo.toml +++ b/crates/shadowsocks-service/Cargo.toml @@ -127,6 +127,7 @@ smoltcp = { version = "0.8", optional = true, default-features = false, features serde = { version = "1.0", features = ["derive"] } json5 = "0.4" +base64 = "0.13" shadowsocks = { version = "1.15.0", path = "../shadowsocks", default-features = false } diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index e8bf2fc9..c5aa49b6 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -65,7 +65,16 @@ use serde::{Deserialize, Serialize}; #[cfg(any(feature = "local-tunnel", feature = "local-dns"))] use shadowsocks::relay::socks5::Address; use shadowsocks::{ - config::{ManagerAddr, Mode, ReplayAttackPolicy, ServerAddr, ServerConfig, ServerWeight}, + config::{ + ManagerAddr, + Mode, + ReplayAttackPolicy, + ServerAddr, + ServerConfig, + ServerUser, + ServerUserManager, + ServerWeight, + }, crypto::CipherKind, plugin::PluginConfig, }; @@ -265,6 +274,12 @@ struct SSLocalExtConfig { socks5_auth_config_path: Option, } +#[derive(Serialize, Deserialize, Debug)] +struct SSServerUserConfig { + name: String, + password: String, +} + #[derive(Serialize, Deserialize, Debug)] struct SSServerExtConfig { // SIP008 https://github.com/shadowsocks/shadowsocks-org/issues/89 @@ -279,6 +294,9 @@ struct SSServerExtConfig { password: Option, method: String, + #[serde(skip_serializing_if = "Option::is_none")] + users: Option>, + #[serde(skip_serializing_if = "Option::is_none")] disabled: Option, @@ -1612,6 +1630,29 @@ impl Config { let mut nsvr = ServerConfig::new(addr, password, method); + // Extensible Identity Header, Users + if let Some(users) = svr.users { + let mut user_manager = ServerUserManager::new(); + + for user in users { + let key = match base64::decode_config(&user.password, base64::STANDARD) { + Ok(k) => k, + Err(..) => { + let err = Error::new( + ErrorKind::Malformed, + "`users[].password` should be base64 encoded", + None, + ); + return Err(err); + } + }; + + user_manager.add_user(ServerUser::new(user.name, key)); + } + + nsvr.set_user_manager(user_manager); + } + match svr.mode { Some(mode) => match mode.parse::() { Ok(mode) => nsvr.set_mode(mode), @@ -2087,6 +2128,21 @@ impl Config { } } } + + // Users' key must match key length + if let Some(user_manager) = server.user_manager() { + let key_len = server.method().key_len(); + for user in user_manager.users_iter() { + if user.key().len() != key_len { + let err = Error::new( + ErrorKind::Malformed, + "`users[].password` length must be exactly the same as method's key length", + None, + ); + return Err(err); + } + } + } } Ok(()) @@ -2279,6 +2335,16 @@ impl fmt::Display for Config { Some(svr.password().to_string()) }, method: svr.method().to_string(), + users: svr.user_manager().map(|m| { + let mut vu = Vec::new(); + for u in m.users_iter() { + vu.push(SSServerUserConfig { + name: u.name().to_owned(), + password: base64::encode(u.key()), + }); + } + vu + }), disabled: None, plugin: svr.plugin().map(|p| p.plugin.to_string()), plugin_opts: svr.plugin().and_then(|p| p.plugin_opts.clone()), diff --git a/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs b/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs index 54f27992..76d1eab8 100644 --- a/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs +++ b/crates/shadowsocks-service/src/local/loadbalancing/ping_balancer.rs @@ -902,18 +902,14 @@ impl PingChecker { let addr = Address::SocketAddress(SocketAddr::new(Ipv4Addr::new(8, 8, 8, 8).into(), 53)); - let client = ProxySocket::connect_with_opts( - self.context.context(), - self.server.server_config(), - self.context.connect_opts_ref(), - ) - .await?; + let svr_cfg = self.server.server_config(); - let control = UdpSocketControlData { - client_session_id: rand::random::(), - server_session_id: 0, - packet_id: 1, - }; + let client = + ProxySocket::connect_with_opts(self.context.context(), svr_cfg, self.context.connect_opts_ref()).await?; + + let mut control = UdpSocketControlData::default(); + control.client_session_id = rand::random::(); + control.packet_id = 1; client.send_with_ctrl(&addr, &control, DNS_QUERY).await?; let mut buffer = [0u8; MAXIMUM_UDP_PAYLOAD_SIZE]; diff --git a/crates/shadowsocks-service/src/local/net/udp/association.rs b/crates/shadowsocks-service/src/local/net/udp/association.rs index 75e2689b..c7d93941 100644 --- a/crates/shadowsocks-service/src/local/net/udp/association.rs +++ b/crates/shadowsocks-service/src/local/net/udp/association.rs @@ -578,11 +578,9 @@ where } }; - let control = UdpSocketControlData { - client_session_id: self.client_session_id, - server_session_id: 0, - packet_id: self.client_packet_id, - }; + let mut control = UdpSocketControlData::default(); + control.client_session_id = self.client_session_id; + control.packet_id = self.client_packet_id; match socket.send_with_ctrl(target_addr, &control, data).await { Ok(..) => return Ok(()), diff --git a/crates/shadowsocks-service/src/manager/server.rs b/crates/shadowsocks-service/src/manager/server.rs index 556974d8..fc13f569 100644 --- a/crates/shadowsocks-service/src/manager/server.rs +++ b/crates/shadowsocks-service/src/manager/server.rs @@ -6,7 +6,7 @@ use std::{collections::HashMap, io, net::SocketAddr, sync::Arc, time::Duration}; use log::{error, info, trace}; use shadowsocks::{ - config::{Mode, ServerConfig, ServerType}, + config::{Mode, ServerConfig, ServerType, ServerUser, ServerUserManager}, context::{Context, SharedContext}, crypto::CipherKind, dns_resolver::DnsResolver, @@ -20,6 +20,7 @@ use shadowsocks::{ PingResponse, RemoveRequest, RemoveResponse, + ServerUserConfig, StatRequest, }, net::{AcceptOpts, ConnectOpts}, @@ -459,6 +460,31 @@ impl Manager { svr_cfg.set_mode(mode.unwrap_or(self.svr_cfg.mode)); + if let Some(ref users) = req.users { + let mut user_manager = ServerUserManager::new(); + + for user in users.iter() { + let key = match base64::decode_config(&user.password, base64::STANDARD) { + Ok(key) => key, + Err(..) => { + error!( + "users[].password must be encoded with base64, but found: {}", + user.password + ); + + return Err(io::Error::new( + io::ErrorKind::Other, + "users[].password must be encoded with base64", + )); + } + }; + + user_manager.add_user(ServerUser::new(&user.name, key)); + } + + svr_cfg.set_user_manager(user_manager); + } + self.add_server(svr_cfg).await; Ok(AddResponse("ok".to_owned())) @@ -484,6 +510,20 @@ impl Manager { for (_, server) in instances.iter() { let svr_cfg = &server.svr_cfg; + let mut users = None; + if let Some(user_manager) = server.svr_cfg.user_manager() { + let mut vu = Vec::with_capacity(user_manager.user_count()); + + for user in user_manager.users_iter() { + vu.push(ServerUserConfig { + name: user.name().to_owned(), + password: base64::encode(user.key()), + }); + } + + users = Some(vu); + } + let sc = protocol::ServerConfig { server_port: svr_cfg.addr().port(), password: svr_cfg.password().to_owned(), @@ -492,6 +532,7 @@ impl Manager { plugin: None, plugin_opts: None, mode: None, + users, }; servers.push(sc); } diff --git a/crates/shadowsocks-service/src/server/udprelay.rs b/crates/shadowsocks-service/src/server/udprelay.rs index 9828a449..a9c0fb29 100644 --- a/crates/shadowsocks-service/src/server/udprelay.rs +++ b/crates/shadowsocks-service/src/server/udprelay.rs @@ -417,6 +417,7 @@ impl UdpAssociation { struct ClientSessionContext { client_session_id: u64, packet_window_filter: PacketWindowFilter, + client_user_hash: Option, } impl ClientSessionContext { @@ -424,6 +425,7 @@ impl ClientSessionContext { ClientSessionContext { client_session_id, packet_window_filter: PacketWindowFilter::new(), + client_user_hash: None, } } } @@ -622,6 +624,8 @@ impl UdpAssociationContext { error!("udp client {} packet_id {} out of window", self.peer_addr, packet_id); return; } + + session_context.client_user_hash = control.user_hash.clone(); } if let Err(err) = self.dispatch_received_outbound_packet(target_addr, data).await { @@ -768,11 +772,11 @@ impl UdpAssociationContext { } }; - let control = UdpSocketControlData { - client_session_id: client_session.client_session_id, - server_session_id: self.server_session_id, - packet_id: self.server_packet_id, - }; + let mut control = UdpSocketControlData::default(); + control.client_session_id = client_session.client_session_id; + control.server_session_id = self.server_session_id; + control.packet_id = self.server_packet_id; + control.user_hash = client_session.client_user_hash.clone(); if let Err(err) = self .inbound diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index 92d00672..8feac273 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -32,9 +32,9 @@ stream-cipher = ["shadowsocks-crypto/v1-stream"] aead-cipher-extra = ["shadowsocks-crypto/v1-aead-extra"] # Enable AEAD 2022 -aead-cipher-2022 = ["shadowsocks-crypto/v2", "rand/small_rng", "aes", "lru_time_cache"] +aead-cipher-2022 = ["shadowsocks-crypto/v2", "rand/small_rng", "aes", "lru_time_cache", "blake3"] # Enable AEAD 2022 with extra ciphers -aead-cipher-2022-extra = ["shadowsocks-crypto/v2-extra"] +aead-cipher-2022-extra = ["aead-cipher-2022", "shadowsocks-crypto/v2-extra"] # Enable detection against replay attack security-replay-attack-detect = ["bloomfilter"] @@ -80,6 +80,7 @@ arc-swap = { version = "1.3", optional = true } notify = { version = "5.0.0-pre.15", optional = true } aes = { version = "0.8", optional = true } +blake3 = { version = "1.3", optional = true } [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] shadowsocks-crypto = { version = "0.4", features = ["ring"] } diff --git a/crates/shadowsocks/src/config.rs b/crates/shadowsocks/src/config.rs index d79248cd..5d417a1e 100644 --- a/crates/shadowsocks/src/config.rs +++ b/crates/shadowsocks/src/config.rs @@ -3,14 +3,17 @@ #[cfg(unix)] use std::path::PathBuf; use std::{ + collections::HashMap, error, fmt::{self, Display}, net::SocketAddr, str::FromStr, + sync::Arc, time::Duration, }; use base64::{decode_config, encode_config, URL_SAFE_NO_PAD}; +use bytes::Bytes; use cfg_if::cfg_if; use log::error; use url::{self, Url}; @@ -145,6 +148,73 @@ impl ServerWeight { } } +/// Server's user +#[derive(Clone, Debug)] +pub struct ServerUser { + name: String, + key: Bytes, +} + +impl ServerUser { + /// Create a user + pub fn new(name: N, key: K) -> ServerUser + where + N: Into, + K: Into, + { + ServerUser { + name: name.into(), + key: key.into(), + } + } + + /// Name of the user + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Encryption key of user + pub fn key(&self) -> &[u8] { + self.key.as_ref() + } +} + +/// Server multi-users manager +#[derive(Clone, Debug)] +pub struct ServerUserManager { + users: HashMap, +} + +impl ServerUserManager { + /// Create a new manager + pub fn new() -> ServerUserManager { + ServerUserManager { users: HashMap::new() } + } + + /// Add a new user + pub fn add_user(&mut self, user: ServerUser) { + // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md + let hash = blake3::hash(user.key()); + let user_hash = Bytes::from(hash.as_bytes()[0..16].to_owned()); + self.users.insert(user_hash, user); + } + + /// Get user by hash key + pub fn get_user_by_hash(&self, user_hash: &[u8]) -> Option<&ServerUser> { + self.users.get(user_hash) + } + + /// Number of users + pub fn user_count(&self) -> usize { + self.users.len() + } + + /// Iterate users + pub fn users_iter(&self) -> impl Iterator { + self.users.iter().map(|(_, v)| v) + } +} + /// Configuration for a server #[derive(Clone, Debug)] pub struct ServerConfig { @@ -159,6 +229,16 @@ pub struct ServerConfig { /// Handshake timeout (connect) timeout: Option, + /// Extensible Identity Headers (AEAD-2022) + /// + /// For client, assemble EIH headers + identity_keys: Arc>, + + /// Extensible Identity Headers (AEAD-2022) + /// + /// For server, support multi-users with EIH + user_manager: Option>, + /// Plugin config plugin: Option, /// Plugin address @@ -209,6 +289,51 @@ fn make_derived_key(_method: CipherKind, password: &str, enc_key: &mut [u8]) { openssl_bytes_to_key(password.as_bytes(), enc_key); } +fn password_to_keys

(method: CipherKind, password: P) -> (String, Box<[u8]>, Vec) +where + P: Into, +{ + let password = password.into(); + + #[cfg(feature = "aead-cipher-2022")] + if matches!( + method, + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM + ) { + // Extensible Identity Headers + // iPSK1:iPSK2:iPSK3:...:uPSK + + let mut identity_keys = Vec::new(); + + let mut split_iter = password.rsplit(':'); + + let upsk = split_iter.next().expect("uPSK"); + + let mut enc_key = vec![0u8; method.key_len()].into_boxed_slice(); + make_derived_key(method, upsk, &mut enc_key); + + for ipsk in split_iter { + match base64::decode_config(ipsk, base64::STANDARD) { + Ok(v) => { + identity_keys.push(Bytes::from(v)); + } + Err(err) => { + panic!("iPSK {} is not base64 encoded, error: {}", ipsk, err); + } + } + } + + identity_keys.reverse(); + + return (upsk.to_owned(), enc_key, identity_keys); + } + + let mut enc_key = vec![0u8; method.key_len()].into_boxed_slice(); + make_derived_key(method, &password, &mut enc_key); + + return (password, enc_key, Vec::new()); +} + impl ServerConfig { /// Create a new `ServerConfig` pub fn new(addr: A, password: P, method: CipherKind) -> ServerConfig @@ -216,16 +341,15 @@ impl ServerConfig { A: Into, P: Into, { - let password = password.into(); - - let mut enc_key = vec![0u8; method.key_len()].into_boxed_slice(); - make_derived_key(method, &password, &mut enc_key); + let (password, enc_key, identity_keys) = password_to_keys(method, password); ServerConfig { addr: addr.into(), password, method, enc_key, + identity_keys: Arc::new(identity_keys), + user_manager: None, timeout: None, plugin: None, plugin_addr: None, @@ -242,12 +366,12 @@ impl ServerConfig { P: Into, { self.method = method; - self.password = password.into(); - let mut enc_key = vec![0u8; method.key_len()].into_boxed_slice(); - make_derived_key(method, &self.password, &mut enc_key); + let (password, enc_key, identity_keys) = password_to_keys(method, password); + self.password = password; self.enc_key = enc_key; + self.identity_keys = Arc::new(identity_keys); } /// Set plugin @@ -278,6 +402,31 @@ impl ServerConfig { self.password.as_str() } + /// Get identity keys (Client) + pub fn identity_keys(&self) -> &[Bytes] { + &self.identity_keys + } + + /// Clone identity keys (Client) + pub fn clone_identity_keys(&self) -> Arc> { + self.identity_keys.clone() + } + + /// Set user manager, enable Server's multi-user support with EIH + pub fn set_user_manager(&mut self, user_manager: ServerUserManager) { + self.user_manager = Some(Arc::new(user_manager)); + } + + /// Get user manager (Server) + pub fn user_manager(&self) -> Option<&ServerUserManager> { + self.user_manager.as_deref() + } + + /// Clone user manager (Server) + pub fn clone_user_manager(&self) -> Option> { + self.user_manager.clone() + } + /// Get method pub fn method(&self) -> CipherKind { self.method diff --git a/crates/shadowsocks/src/manager/protocol.rs b/crates/shadowsocks/src/manager/protocol.rs index ddd6465d..ef24d12f 100644 --- a/crates/shadowsocks/src/manager/protocol.rs +++ b/crates/shadowsocks/src/manager/protocol.rs @@ -16,6 +16,13 @@ pub trait ManagerProtocol: Sized { fn to_bytes(&self) -> Result, Error>; } +/// Server's user configuration +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ServerUserConfig { + pub name: String, + pub password: String, +} + /// Server's configuration #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ServerConfig { @@ -31,6 +38,8 @@ pub struct ServerConfig { pub plugin_opts: Option, #[serde(skip_serializing_if = "Option::is_none")] pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub users: Option>, } /// `add` request diff --git a/crates/shadowsocks/src/relay/tcprelay/aead.rs b/crates/shadowsocks/src/relay/tcprelay/aead.rs index 423af8dd..c72e69d5 100644 --- a/crates/shadowsocks/src/relay/tcprelay/aead.rs +++ b/crates/shadowsocks/src/relay/tcprelay/aead.rs @@ -68,6 +68,7 @@ pub struct DecryptedReader { buffer: BytesMut, method: CipherKind, salt: Option, + has_handshaked: bool, } impl DecryptedReader { @@ -81,6 +82,7 @@ impl DecryptedReader { buffer: BytesMut::with_capacity(method.salt_len()), method, salt: None, + has_handshaked: false, } } else { DecryptedReader { @@ -89,6 +91,7 @@ impl DecryptedReader { buffer: BytesMut::with_capacity(2 + method.tag_len()), method, salt: None, + has_handshaked: false, } } } @@ -117,6 +120,7 @@ impl DecryptedReader { self.buffer.clear(); self.state = DecryptReadState::ReadLength; self.buffer.reserve(2 + self.method.tag_len()); + self.has_handshaked = true; } DecryptReadState::ReadLength => match ready!(self.poll_read_length(cx, stream))? { None => { @@ -289,6 +293,11 @@ impl DecryptedReader { Ok(plen) } + + /// Check if handshake finished + pub fn handshaked(&self) -> bool { + self.has_handshaked + } } enum EncryptWriteState { diff --git a/crates/shadowsocks/src/relay/tcprelay/aead_2022.rs b/crates/shadowsocks/src/relay/tcprelay/aead_2022.rs index 8e414535..ecc766d7 100644 --- a/crates/shadowsocks/src/relay/tcprelay/aead_2022.rs +++ b/crates/shadowsocks/src/relay/tcprelay/aead_2022.rs @@ -48,19 +48,27 @@ use std::{ marker::Unpin, pin::Pin, slice, + sync::Arc, task::{self, Poll}, time::SystemTime, u16, }; +use aes::{ + cipher::{BlockDecrypt, BlockEncrypt, KeyInit}, + Aes128, + Aes256, + Block, +}; use byte_string::ByteStr; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::ready; -use log::trace; +use log::{error, trace}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use super::{crypto_io::StreamType, proxy_stream::protocol::v2::SERVER_STREAM_TIMESTAMP_MAX_DIFF}; use crate::{ + config::ServerUserManager, context::Context, crypto::{v2::tcp::TcpCipher, CipherKind}, }; @@ -73,9 +81,19 @@ fn get_now_timestamp() -> u64 { } } +#[inline] +fn method_support_eih(method: CipherKind) -> bool { + matches!( + method, + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM + ) +} + /// AEAD packet payload must be smaller than 0xFFFF (u16::MAX) pub const MAX_PACKET_SIZE: usize = 0xFFFF; +const AEAD2022_EIH_SUBKEY_CONTEXT: &str = "shadowsocks 2022 identity subkey"; + enum DecryptReadState { ReadHeader { key: Bytes }, ReadLength, @@ -93,10 +111,22 @@ pub struct DecryptedReader { salt: Option, request_salt: Option, data_chunk_count: u64, + user_manager: Option>, + user_key: Option, + has_handshaked: bool, } impl DecryptedReader { pub fn new(stream_ty: StreamType, method: CipherKind, key: &[u8]) -> DecryptedReader { + DecryptedReader::with_user_manager(stream_ty, method, key, None) + } + + pub fn with_user_manager( + stream_ty: StreamType, + method: CipherKind, + key: &[u8], + user_manager: Option>, + ) -> DecryptedReader { if method.salt_len() > 0 { DecryptedReader { stream_ty, @@ -109,6 +139,9 @@ impl DecryptedReader { salt: None, request_salt: None, data_chunk_count: 0, + user_manager, + user_key: None, + has_handshaked: false, } } else { DecryptedReader { @@ -122,6 +155,9 @@ impl DecryptedReader { salt: None, request_salt: None, data_chunk_count: 0, + user_manager, + user_key: None, + has_handshaked: false, } } } @@ -166,6 +202,7 @@ impl DecryptedReader { self.buffer.clear(); self.state = DecryptReadState::ReadData { length }; self.buffer.reserve(length + self.method.tag_len()); + self.has_handshaked = true; } } } @@ -222,7 +259,10 @@ impl DecryptedReader { StreamType::Client => salt_len, StreamType::Server => 0, }; - let header_len = salt_len + 1 + 8 + request_salt_len + 2 + self.method.tag_len(); + let require_eih = + self.stream_ty == StreamType::Server && method_support_eih(self.method) && self.user_manager.is_some(); + let eih_len = if require_eih { 16 } else { 0 }; + let header_len = salt_len + eih_len + 1 + 8 + request_salt_len + 2 + self.method.tag_len(); if self.buffer.len() < header_len { self.buffer.resize(header_len, 0); } @@ -240,11 +280,65 @@ impl DecryptedReader { .into(); } - let (salt, header_chunk) = header_buf.split_at_mut(salt_len); + let (salt, mut header_chunk) = header_buf.split_at_mut(salt_len); trace!("got AEAD salt {:?}", ByteStr::new(salt)); - let mut cipher = TcpCipher::new(self.method, key, salt); + // Extensible Identity Header + // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md + let mut cipher = if require_eih { + if let Some(ref user_manager) = self.user_manager { + // Assume we have at least 1 EIH + if header_chunk.len() < 16 { + error!("expecting EIH, but header chunk len: {}", header_chunk.len()); + return Err(io::Error::new(ErrorKind::Other, "header too short, expecting EIH")).into(); + } + + let (eih, remain_header_chunk) = header_chunk.split_at_mut(16); + header_chunk = remain_header_chunk; + + let key_material = [key, salt].concat(); + let identity_sub_key = blake3::derive_key(AEAD2022_EIH_SUBKEY_CONTEXT, &key_material); + let mut user_hash = Block::from([0u8; 16]); + match self.method { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { + let cipher = Aes128::new_from_slice(&identity_sub_key[0..16]).expect("AES-128"); + cipher.decrypt_block_b2b(Block::from_slice(eih), &mut user_hash); + } + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + let cipher = Aes256::new_from_slice(&identity_sub_key[0..32]).expect("AES-256"); + cipher.decrypt_block_b2b(Block::from_slice(eih), &mut user_hash); + } + _ => unreachable!("{} doesn't support EIH", self.method), + } + + let user_hash = user_hash.as_slice(); + trace!( + "server EIH {:?}, hash: {:?}", + ByteStr::new(eih), + ByteStr::new(user_hash) + ); + + match user_manager.get_user_by_hash(user_hash) { + None => { + return Err(io::Error::new( + ErrorKind::Other, + format!("user with identity {:?} not found", ByteStr::new(user_hash)), + )) + .into(); + } + Some(user) => { + trace!("user {} choosen by EIH", user.name()); + self.user_key = Some(Bytes::copy_from_slice(user.key())); + TcpCipher::new(self.method, user.key(), salt) + } + } + } else { + unreachable!("user_manager must not be None") + } + } else { + TcpCipher::new(self.method, key, salt) + }; // Decrypt the header chunk if !cipher.decrypt_packet(header_chunk) { @@ -402,6 +496,16 @@ impl DecryptedReader { _ => (self.data_chunk_count, 0), } } + + /// Get authenticated user key + pub fn user_key(&self) -> Option<&[u8]> { + self.user_key.as_deref() + } + + /// Check if handshake finished + pub fn handshaked(&self) -> bool { + self.has_handshaked + } } enum EncryptWriteState { @@ -414,6 +518,7 @@ enum EncryptWriteState { pub struct EncryptedWriter { stream_ty: StreamType, cipher: TcpCipher, + method: CipherKind, buffer: BytesMut, state: EncryptWriteState, salt: Bytes, @@ -423,13 +528,85 @@ pub struct EncryptedWriter { impl EncryptedWriter { /// Creates a new EncryptedWriter pub fn new(stream_ty: StreamType, method: CipherKind, key: &[u8], nonce: &[u8]) -> EncryptedWriter { + static EMPTY_IDENTITY: [Bytes; 0] = []; + EncryptedWriter::with_identity(stream_ty, method, key, nonce, &EMPTY_IDENTITY) + } + + /// Creates a new EncryptedWriter with identities + pub fn with_identity( + stream_ty: StreamType, + method: CipherKind, + key: &[u8], + nonce: &[u8], + identity_keys: &[Bytes], + ) -> EncryptedWriter { // nonce should be sent with the first packet - let mut buffer = BytesMut::with_capacity(nonce.len()); + let mut buffer = BytesMut::with_capacity(nonce.len() + identity_keys.len() * 16); buffer.put(nonce); + // Extensible Identity Headers + // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md + #[inline] + fn make_eih(method: CipherKind, sub_key: &[u8], ipsk: &[u8], buffer: &mut BytesMut) { + let ipsk_hash = blake3::hash(ipsk); + let ipsk_plain_text = &ipsk_hash.as_bytes()[0..16]; + + match method { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { + let enc_key = &sub_key[0..16]; + let cipher = Aes128::new_from_slice(enc_key).expect("AES-128"); + + let ipsk_plain_text = Block::from_slice(ipsk_plain_text); + let mut block = Block::from([0u8; 16]); + cipher.encrypt_block_b2b(ipsk_plain_text, &mut block); + + trace!( + "client EIH {:?}, hash: {:?}", + ByteStr::new(block.as_slice()), + ByteStr::new(ipsk_plain_text) + ); + buffer.put(block.as_slice()); + } + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + let enc_key = &sub_key[0..32]; + let cipher = Aes256::new_from_slice(enc_key).expect("AES-256"); + + let ipsk_plain_text = Block::from_slice(ipsk_plain_text); + let mut block = Block::from([0u8; 16]); + cipher.encrypt_block_b2b(ipsk_plain_text, &mut block); + + trace!( + "client EIH {:?}, hash: {:?}", + ByteStr::new(block.as_slice()), + ByteStr::new(ipsk_plain_text) + ); + buffer.put(block.as_slice()); + } + _ => unreachable!("{} doesn't support EIH", method), + } + } + + if stream_ty == StreamType::Client && method_support_eih(method) { + let mut sub_key: Option<[u8; blake3::OUT_LEN]> = None; + + for ipsk in identity_keys { + if let Some(ref sub_key) = sub_key { + make_eih(method, sub_key, ipsk, &mut buffer); + } + + let key_material = [ipsk, nonce].concat(); + sub_key = Some(blake3::derive_key(AEAD2022_EIH_SUBKEY_CONTEXT, &key_material)); + } + + if let Some(ref sub_key) = sub_key { + make_eih(method, sub_key, key, &mut buffer); + } + } + EncryptedWriter { stream_ty, cipher: TcpCipher::new(method, key, nonce), + method, buffer, state: EncryptWriteState::AssembleHeader, salt: Bytes::copy_from_slice(nonce), @@ -448,6 +625,11 @@ impl EncryptedWriter { self.request_salt = Some(request_salt); } + /// Reset cipher with key + pub fn reset_cipher_with_key(&mut self, key: &[u8]) { + self.cipher = TcpCipher::new(self.method, key, &self.salt); + } + /// Attempt to write encrypted data into the writer pub fn poll_write_encrypted( &mut self, diff --git a/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs b/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs index 2664fdd3..28a36f24 100644 --- a/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs +++ b/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs @@ -4,15 +4,18 @@ use std::{ io, marker::Unpin, pin::Pin, + sync::Arc, task::{self, Poll}, }; use byte_string::ByteStr; use bytes::Bytes; +use futures::ready; use log::trace; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf, ReadHalf, WriteHalf}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::{ + config::ServerUserManager, context::Context, crypto::{CipherCategory, CipherKind}, }; @@ -46,8 +49,19 @@ pub enum DecryptedReader { impl DecryptedReader { /// Create a new reader for reading encrypted data pub fn new(stream_ty: StreamType, method: CipherKind, key: &[u8]) -> DecryptedReader { + DecryptedReader::with_user_manager(stream_ty, method, key, None) + } + + /// Create a new reader for reading encrypted data + pub fn with_user_manager( + stream_ty: StreamType, + method: CipherKind, + key: &[u8], + user_manager: Option>, + ) -> DecryptedReader { if cfg!(not(feature = "aead-cipher-2022")) { let _ = stream_ty; + let _ = user_manager; } match method.category() { @@ -56,7 +70,12 @@ impl DecryptedReader { CipherCategory::Aead => DecryptedReader::Aead(AeadDecryptedReader::new(method, key)), CipherCategory::None => DecryptedReader::None, #[cfg(feature = "aead-cipher-2022")] - CipherCategory::Aead2022 => DecryptedReader::Aead2022(Aead2022DecryptedReader::new(stream_ty, method, key)), + CipherCategory::Aead2022 => DecryptedReader::Aead2022(Aead2022DecryptedReader::with_user_manager( + stream_ty, + method, + key, + user_manager, + )), } } @@ -105,6 +124,29 @@ impl DecryptedReader { DecryptedReader::Aead2022(ref reader) => reader.request_salt(), } } + + /// Get authenticated user key (AEAD2022) + pub fn user_key(&self) -> Option<&[u8]> { + match *self { + #[cfg(feature = "stream-cipher")] + DecryptedReader::Stream(..) => None, + DecryptedReader::Aead(..) => None, + DecryptedReader::None => None, + #[cfg(feature = "aead-cipher-2022")] + DecryptedReader::Aead2022(ref reader) => reader.user_key(), + } + } + + pub fn handshaked(&self) -> bool { + match *self { + #[cfg(feature = "stream-cipher")] + DecryptedReader::Stream(ref reader) => reader.handshaked(), + DecryptedReader::Aead(ref reader) => reader.handshaked(), + DecryptedReader::None => true, + #[cfg(feature = "aead-cipher-2022")] + DecryptedReader::Aead2022(ref reader) => reader.handshaked(), + } + } } /// Writer for writing encrypted data stream into shadowsocks' tunnel @@ -136,6 +178,35 @@ impl EncryptedWriter { } } + /// Create a new writer for writing encrypted data + pub fn with_identity( + stream_ty: StreamType, + method: CipherKind, + key: &[u8], + nonce: &[u8], + identity_keys: &[Bytes], + ) -> EncryptedWriter { + if cfg!(not(feature = "aead-cipher-2022")) { + let _ = stream_ty; + let _ = identity_keys; + } + + match method.category() { + #[cfg(feature = "stream-cipher")] + CipherCategory::Stream => EncryptedWriter::Stream(StreamEncryptedWriter::new(method, key, nonce)), + CipherCategory::Aead => EncryptedWriter::Aead(AeadEncryptedWriter::new(method, key, nonce)), + CipherCategory::None => EncryptedWriter::None, + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => EncryptedWriter::Aead2022(Aead2022EncryptedWriter::with_identity( + stream_ty, + method, + key, + nonce, + identity_keys, + )), + } + } + /// Attempt to write encrypted data to `stream` #[inline] pub fn poll_write_encrypted( @@ -180,6 +251,18 @@ impl EncryptedWriter { } } } + + /// Reset cipher with authenticated user key + pub fn reset_cipher_with_key(&mut self, key: &[u8]) { + match *self { + #[cfg(feature = "aead-cipher-2022")] + EncryptedWriter::Aead2022(ref mut writer) => writer.reset_cipher_with_key(key), + _ => { + let _ = key; + panic!("only AEAD-2022 cipher could authenticate with multiple users"); + } + } + } } /// A bidirectional stream for read/write encrypted data in shadowsocks' tunnel @@ -188,6 +271,7 @@ pub struct CryptoStream { dec: DecryptedReader, enc: EncryptedWriter, method: CipherKind, + has_handshaked: bool, } impl CryptoStream { @@ -198,6 +282,20 @@ impl CryptoStream { stream_ty: StreamType, method: CipherKind, key: &[u8], + ) -> CryptoStream { + static EMPTY_IDENTITY: [Bytes; 0] = []; + CryptoStream::from_stream_with_identity(context, stream, stream_ty, method, key, &EMPTY_IDENTITY, None) + } + + /// Create a new CryptoStream with the underlying stream connection + pub fn from_stream_with_identity( + context: &Context, + stream: S, + stream_ty: StreamType, + method: CipherKind, + key: &[u8], + identity_keys: &[Bytes], + user_manager: Option>, ) -> CryptoStream { let category = method.category(); @@ -241,9 +339,10 @@ impl CryptoStream { CryptoStream { stream, - dec: DecryptedReader::new(stream_ty, method, key), - enc: EncryptedWriter::new(stream_ty, method, key, &iv), + dec: DecryptedReader::with_user_manager(stream_ty, method, key, user_manager), + enc: EncryptedWriter::with_identity(stream_ty, method, key, &iv, identity_keys), method, + has_handshaked: false, } } @@ -253,6 +352,7 @@ impl CryptoStream { dec: DecryptedReader::None, enc: EncryptedWriter::None, method, + has_handshaked: false, } } @@ -355,10 +455,23 @@ where ) -> Poll> { let CryptoStream { ref mut dec, + ref mut enc, ref mut stream, + ref mut has_handshaked, .. } = *self; - dec.poll_read_decrypted(cx, context, stream, buf) + ready!(dec.poll_read_decrypted(cx, context, stream, buf))?; + + if !*has_handshaked && dec.handshaked() { + *has_handshaked = true; + + // Reset writer cipher with authenticated user key + if let Some(user_key) = dec.user_key() { + enc.reset_cipher_with_key(user_key); + } + } + + Ok(()).into() } } @@ -398,124 +511,3 @@ where Pin::new(&mut self.stream).poll_shutdown(cx) } } - -impl CryptoStream -where - S: AsyncRead + AsyncWrite + Unpin, -{ - pub fn into_split(self) -> (CryptoStreamReadHalf, CryptoStreamWriteHalf) { - let (reader, writer) = tokio::io::split(self.stream); - - ( - CryptoStreamReadHalf { - reader, - dec: self.dec, - method: self.method, - }, - CryptoStreamWriteHalf { - writer, - enc: self.enc, - method: self.method, - }, - ) - } -} - -pub struct CryptoStreamReadHalf { - reader: ReadHalf, - dec: DecryptedReader, - method: CipherKind, -} - -impl CryptoStreamReadHalf { - /// Get encryption method - pub fn method(&self) -> CipherKind { - self.method - } -} - -impl CryptoStreamReadHalf -where - S: AsyncRead + Unpin, -{ - /// Get received IV (Stream) or Salt (AEAD, AEAD2022) - pub fn nonce(&self) -> Option<&[u8]> { - self.dec.nonce() - } -} - -impl CryptoRead for CryptoStreamReadHalf -where - S: AsyncRead + Unpin, -{ - /// Attempt to read decrypted data from `stream` - #[inline] - fn poll_read_decrypted( - mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - context: &Context, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - let CryptoStreamReadHalf { - ref mut dec, - ref mut reader, - .. - } = *self; - dec.poll_read_decrypted(cx, context, reader, buf) - } -} - -pub struct CryptoStreamWriteHalf { - writer: WriteHalf, - enc: EncryptedWriter, - method: CipherKind, -} - -impl CryptoStreamWriteHalf { - /// Get encryption method - pub fn method(&self) -> CipherKind { - self.method - } - - /// Get sent IV (Stream) or Salt (AEAD, AEAD2022) - pub fn sent_nonce(&self) -> &[u8] { - self.enc.nonce() - } -} - -impl CryptoWrite for CryptoStreamWriteHalf -where - S: AsyncWrite + Unpin, -{ - /// Attempt to write encrypted data to `stream` - #[inline] - fn poll_write_encrypted( - mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - buf: &[u8], - ) -> Poll> { - let CryptoStreamWriteHalf { - ref mut enc, - ref mut writer, - .. - } = *self; - enc.poll_write_encrypted(cx, writer, buf) - } -} - -impl CryptoStreamWriteHalf -where - S: AsyncWrite + Unpin, -{ - /// Polls `flush` on the underlying stream - #[inline] - pub fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll> { - Pin::new(&mut self.writer).poll_flush(cx) - } - - /// Polls `shutdown` on the underlying stream - #[inline] - pub fn poll_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll> { - Pin::new(&mut self.writer).poll_shutdown(cx) - } -} diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs index 4bbd2bd9..b0b29ed8 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs @@ -1,6 +1,6 @@ //! A TCP listener for accepting shadowsocks' client connection -use std::{io, net::SocketAddr}; +use std::{io, net::SocketAddr, sync::Arc}; use once_cell::sync::Lazy; use tokio::{ @@ -9,7 +9,7 @@ use tokio::{ }; use crate::{ - config::{ServerAddr, ServerConfig}, + config::{ServerAddr, ServerConfig, ServerUserManager}, context::SharedContext, crypto::CipherKind, net::{AcceptOpts, TcpListener}, @@ -22,6 +22,7 @@ pub struct ProxyListener { method: CipherKind, key: Box<[u8]>, context: SharedContext, + user_manager: Option>, } static DEFAULT_ACCEPT_OPTS: Lazy = Lazy::new(Default::default); @@ -57,6 +58,7 @@ impl ProxyListener { method: svr_cfg.method(), key: svr_cfg.key().to_vec().into_boxed_slice(), context, + user_manager: svr_cfg.clone_user_manager(), } } @@ -76,7 +78,13 @@ impl ProxyListener { let stream = map_fn(stream); // Create a ProxyServerStream and read the target address from it - let stream = ProxyServerStream::from_stream(self.context.clone(), stream, self.method, &self.key); + let stream = ProxyServerStream::from_stream( + self.context.clone(), + stream, + self.method, + &self.key, + self.user_manager.clone(), + ); Ok((stream, peer_addr)) } diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs index fb55ca7c..a94df603 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs @@ -150,7 +150,15 @@ where A: Into

, { let addr = addr.into(); - let stream = CryptoStream::from_stream(&context, stream, StreamType::Client, svr_cfg.method(), svr_cfg.key()); + let stream = CryptoStream::from_stream_with_identity( + &context, + stream, + StreamType::Client, + svr_cfg.method(), + svr_cfg.key(), + svr_cfg.identity_keys(), + None, + ); #[cfg(not(feature = "aead-cipher-2022"))] let reader_state = ProxyClientStreamReadState::Established; diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs index 433bb273..b29f8a98 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs @@ -3,14 +3,17 @@ use std::{ io, pin::Pin, + sync::Arc, task::{self, Poll}, }; +use bytes::Bytes; use futures::ready; use pin_project::pin_project; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::{ + config::ServerUserManager, context::SharedContext, crypto::CipherKind, relay::{ @@ -44,6 +47,7 @@ impl ProxyServerStream { stream: S, method: CipherKind, key: &[u8], + user_manager: Option>, ) -> ProxyServerStream { #[cfg(feature = "aead-cipher-2022")] let writer_state = if method.is_aead_2022() { @@ -55,8 +59,17 @@ impl ProxyServerStream { #[cfg(not(feature = "aead-cipher-2022"))] let writer_state = ProxyServerStreamWriteState::Established; + static EMPTY_IDENTITY: [Bytes; 0] = []; ProxyServerStream { - stream: CryptoStream::from_stream(&context, stream, StreamType::Server, method, key), + stream: CryptoStream::from_stream_with_identity( + &context, + stream, + StreamType::Server, + method, + key, + &EMPTY_IDENTITY, + user_manager, + ), context, writer_state, has_handshaked: false, diff --git a/crates/shadowsocks/src/relay/tcprelay/stream.rs b/crates/shadowsocks/src/relay/tcprelay/stream.rs index bad6d240..1413f4fc 100644 --- a/crates/shadowsocks/src/relay/tcprelay/stream.rs +++ b/crates/shadowsocks/src/relay/tcprelay/stream.rs @@ -30,6 +30,7 @@ pub struct DecryptedReader { buffer: BytesMut, method: CipherKind, iv: Option, + has_handshaked: bool, } impl DecryptedReader { @@ -43,6 +44,7 @@ impl DecryptedReader { buffer: BytesMut::with_capacity(method.iv_len()), method, iv: None, + has_handshaked: false, } } else { DecryptedReader { @@ -51,6 +53,7 @@ impl DecryptedReader { buffer: BytesMut::new(), method, iv: Some(Bytes::new()), + has_handshaked: false, } } } @@ -79,6 +82,7 @@ impl DecryptedReader { self.buffer.clear(); self.buffer.truncate(0); self.state = DecryptReadState::Read; + self.has_handshaked = true; } DecryptReadState::Read => { let before_n = buf.filled().len(); @@ -162,6 +166,11 @@ impl DecryptedReader { Ok(size).into() } + + /// Check if handshake finished + pub fn handshaked(&self) -> bool { + self.has_handshaked + } } enum EncryptWriteState { diff --git a/crates/shadowsocks/src/relay/udprelay/aead_2022.rs b/crates/shadowsocks/src/relay/udprelay/aead_2022.rs index 52192b9b..ab3d7de0 100644 --- a/crates/shadowsocks/src/relay/udprelay/aead_2022.rs +++ b/crates/shadowsocks/src/relay/udprelay/aead_2022.rs @@ -49,7 +49,7 @@ use std::{ cmp::Ordering, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, - io::{self, Cursor, ErrorKind, Seek, SeekFrom}, + io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom}, rc::Rc, slice, time::{Duration, SystemTime}, @@ -62,13 +62,14 @@ use aes::{ Block, }; use byte_string::ByteStr; -use bytes::{Buf, BufMut, BytesMut}; -use log::trace; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use log::{error, trace}; use lru_time_cache::LruCache; #[cfg(feature = "aead-cipher-2022-extra")] use crate::crypto::v2::udp::ChaCha8Poly1305Cipher; use crate::{ + config::ServerUserManager, context::Context, crypto::{ v2::udp::{ChaCha20Poly1305Cipher, UdpCipher}, @@ -140,6 +141,14 @@ pub fn get_now_timestamp() -> u64 { } } +#[inline] +fn method_support_eih(method: CipherKind) -> bool { + matches!( + method, + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM + ) +} + fn get_cipher(method: CipherKind, key: &[u8], session_id: u64) -> Rc { CIPHER_CACHE.with(|cache| { let mut cache = cache.borrow_mut(); @@ -158,7 +167,15 @@ fn get_cipher(method: CipherKind, key: &[u8], session_id: u64) -> Rc }) } -fn encrypt_message(_context: &Context, method: CipherKind, key: &[u8], packet: &mut BytesMut, session_id: u64) { +fn encrypt_message( + _context: &Context, + method: CipherKind, + ipsk: &[u8], + key: &[u8], + packet: &mut BytesMut, + session_id: u64, + eih_len: usize, +) { unsafe { packet.advance_mut(method.tag_len()); } @@ -189,21 +206,26 @@ fn encrypt_message(_context: &Context, method: CipherKind, key: &[u8], packet: & let cipher = get_cipher(method, key, session_id); // Encrypt the rest of the packet with AEAD cipher (AES-*-GCM) - let (packet_header, message) = packet.split_at_mut(16); + let (packet_header, mut message) = packet.split_at_mut(16); let nonce = &packet_header[4..16]; + + if eih_len > 0 { + message = &mut message[eih_len..]; + } + cipher.encrypt_packet(nonce, message); // [SessionID + PacketID] is encrypted with AES-ECB with PSK // No padding is required because these 2 fields are 128-bits, which is exactly the same as AES's block size match method { CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { - let cipher = Aes128::new_from_slice(key).expect("AES-128 init"); - let block = Block::from_mut_slice(&mut packet[0..16]); + let cipher = Aes128::new_from_slice(ipsk).expect("AES-128 init"); + let block = Block::from_mut_slice(packet_header); cipher.encrypt_block(block); } CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { - let cipher = Aes256::new_from_slice(key).expect("AES-256 init"); - let block = Block::from_mut_slice(&mut packet[0..16]); + let cipher = Aes256::new_from_slice(ipsk).expect("AES-256 init"); + let block = Block::from_mut_slice(packet_header); cipher.encrypt_block(block); } _ => unreachable!("{} is not an AES-*-GCM cipher", method), @@ -213,7 +235,13 @@ fn encrypt_message(_context: &Context, method: CipherKind, key: &[u8], packet: & } } -fn decrypt_message(_context: &Context, method: CipherKind, key: &[u8], packet: &mut [u8]) -> bool { +fn decrypt_message( + _context: &Context, + method: CipherKind, + key: &[u8], + packet: &mut [u8], + user_manager: Option<&ServerUserManager>, +) -> bool { match method { CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => { // ChaCha20-Poly1305 uses PSK as key, prepended nonce in packet @@ -265,7 +293,7 @@ fn decrypt_message(_context: &Context, method: CipherKind, key: &[u8], packet: & // [SessionID + PacketID] is encrypted with AES-ECB with PSK // No padding is required because these 2 fields are 128-bits, which is exactly the same as AES's block size - let (packet_header, message) = packet.split_at_mut(16); + let (packet_header, mut message) = packet.split_at_mut(16); match method { CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { @@ -289,10 +317,56 @@ fn decrypt_message(_context: &Context, method: CipherKind, key: &[u8], packet: & u64::from_be(session_id_slice[0]) }; + let cipher = if method_support_eih(method) { + if let Some(user_manager) = user_manager { + // Extensible Identity Header + // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md + + let (eih, remain_message) = message.split_at_mut(16); + message = remain_message; + + let session_id_packet_id = &packet_header[0..16]; + + trace!( + "server EIH {:?}, session_id_packet_id: {:?}", + ByteStr::new(eih), + ByteStr::new(session_id_packet_id) + ); + + match method { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { + let cipher = Aes128::new_from_slice(key).expect("AES-128 init"); + cipher.decrypt_block(Block::from_mut_slice(eih)); + } + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + let cipher = Aes256::new_from_slice(key).expect("AES-256 init"); + cipher.decrypt_block(Block::from_mut_slice(eih)) + } + _ => unreachable!("{} doesn't support EIH", method), + } + + for i in 0..16 { + eih[i] ^= session_id_packet_id[i]; + } + + match user_manager.get_user_by_hash(&eih) { + None => { + error!("user with identity {:?} not found", ByteStr::new(&eih)); + return false; + } + Some(user) => { + trace!("user {} choosen by EIH", user.name()); + get_cipher(method, user.key(), session_id) + } + } + } else { + get_cipher(method, key, session_id) + } + } else { + get_cipher(method, key, session_id) + }; + let nonce = &packet_header[4..16]; - - let cipher = get_cipher(method, key, session_id); - if !cipher.decrypt_packet(nonce, message) { return false; } @@ -321,14 +395,27 @@ pub fn encrypt_client_payload_aead_2022( key: &[u8], addr: &Address, control: &UdpSocketControlData, + identity_keys: &[Bytes], payload: &[u8], dst: &mut BytesMut, ) { let padding_size = get_aead_2022_padding_size(payload); let nonce_size = get_nonce_len(method); + let require_eih = method_support_eih(method) && !identity_keys.is_empty(); + let eih_size = if require_eih { identity_keys.len() * 16 } else { 0 }; dst.reserve( - nonce_size + 8 + 8 + 1 + 8 + 2 + padding_size + addr.serialized_len() + payload.len() + method.tag_len(), + nonce_size + + 8 + + 8 + + eih_size + + 1 + + 8 + + 2 + + padding_size + + addr.serialized_len() + + payload.len() + + method.tag_len(), ); // Generate IV @@ -345,6 +432,67 @@ pub fn encrypt_client_payload_aead_2022( // Add header fields dst.put_u64(control.client_session_id); dst.put_u64(control.packet_id); + + // Extensible Identity Header + // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md + if require_eih { + #[inline] + fn make_eih( + method: CipherKind, + ipsk: &[u8], + ipskn: &[u8], + session_id_packet_id: &[u8], + identity_header: &mut [u8; 16], + ) { + let ipskn_hash = blake3::hash(ipskn); + let plain_text = &ipskn_hash.as_bytes()[0..16]; + + identity_header.copy_from_slice(plain_text); + + trace!( + "identity_header: {:?}, session_id_packet_id: {:?}", + ByteStr::new(identity_header), + ByteStr::new(session_id_packet_id) + ); + + for i in 0..16 { + identity_header[i] ^= session_id_packet_id[i]; + } + + match method { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { + let cipher = Aes128::new_from_slice(ipsk).expect("AES-128 init"); + cipher.encrypt_block(Block::from_mut_slice(identity_header)); + } + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + let cipher = Aes256::new_from_slice(ipsk).expect("AES-256 init"); + cipher.encrypt_block(Block::from_mut_slice(identity_header)); + } + _ => unreachable!("{} doesn't support EIH", method), + } + + trace!( + "client EIH {:?}, hash: {:?}", + ByteStr::new(identity_header), + ByteStr::new(plain_text) + ); + } + + for (ipsk, ipskn) in identity_keys + .iter() + .map(AsRef::as_ref) + .zip(identity_keys.iter().map(AsRef::as_ref).skip(1).chain(Some(key))) + { + trace!("DST: {:?}", ByteStr::new(&dst)); + let session_id_packet_id = &dst[nonce_size..nonce_size + 16]; + + let mut identity_header = [0u8; 16]; + make_eih(method, ipsk, ipskn, session_id_packet_id, &mut identity_header); + + dst.put(identity_header.as_slice()); + } + } + dst.put_u8(CLIENT_SOCKET_TYPE); dst.put_u64(get_now_timestamp()); dst.put_u16(padding_size as u16); @@ -356,7 +504,12 @@ pub fn encrypt_client_payload_aead_2022( addr.write_to_buf(dst); dst.put_slice(payload); - encrypt_message(context, method, key, dst, control.client_session_id); + let ipsk = if identity_keys.is_empty() { + key + } else { + &identity_keys[0] + }; + encrypt_message(context, method, ipsk, key, dst, control.client_session_id, eih_size); } /// Decrypt `Client -> Server` UDP AEAD protocol packet @@ -365,15 +518,19 @@ pub async fn decrypt_client_payload_aead_2022( method: CipherKind, key: &[u8], payload: &mut [u8], + user_manager: Option<&ServerUserManager>, ) -> io::Result<(usize, Address, UdpSocketControlData)> { let nonce_len = get_nonce_len(method); let tag_len = method.tag_len(); - if payload.len() < nonce_len + tag_len + 8 + 8 + 1 + 8 + 2 { + let require_eih = method_support_eih(method) && user_manager.is_some(); + let eih_len = if require_eih { 16 } else { 0 }; + + if payload.len() < nonce_len + tag_len + 8 + 8 + eih_len + 1 + 8 + 2 { let err = io::Error::new(ErrorKind::InvalidData, "udp packet too short"); return Err(err); } - if !decrypt_message(context, method, key, payload) { + if !decrypt_message(context, method, key, payload, user_manager) { return Err(io::Error::new(io::ErrorKind::Other, "invalid tag-in")); } @@ -382,6 +539,18 @@ pub async fn decrypt_client_payload_aead_2022( let client_session_id = cursor.get_u64(); let packet_id = cursor.get_u64(); + + let mut user_hash = None; + if require_eih { + let mut eih = BytesMut::with_capacity(16); + unsafe { + eih.set_len(16); + } + cursor.read_exact(&mut eih)?; + + user_hash = Some(eih.freeze()); + } + let socket_type = cursor.get_u8(); if socket_type != CLIENT_SOCKET_TYPE { return Err(io::Error::new( @@ -408,6 +577,7 @@ pub async fn decrypt_client_payload_aead_2022( client_session_id, server_session_id: 0, packet_id, + user_hash, }; let addr = Address::read_from(&mut cursor).await?; @@ -463,7 +633,7 @@ pub fn encrypt_server_payload_aead_2022( addr.write_to_buf(dst); dst.put_slice(payload); - encrypt_message(context, method, key, dst, control.server_session_id); + encrypt_message(context, method, key, key, dst, control.server_session_id, 0); } /// Decrypt `Server -> Client` UDP AEAD protocol packet @@ -480,7 +650,7 @@ pub async fn decrypt_server_payload_aead_2022( return Err(err); } - if !decrypt_message(context, method, key, payload) { + if !decrypt_message(context, method, key, payload, None) { return Err(io::Error::new(io::ErrorKind::Other, "invalid tag-in")); } @@ -517,6 +687,7 @@ pub async fn decrypt_server_payload_aead_2022( client_session_id, server_session_id, packet_id, + user_hash: None, }; let addr = Address::read_from(&mut cursor).await?; diff --git a/crates/shadowsocks/src/relay/udprelay/crypto_io.rs b/crates/shadowsocks/src/relay/udprelay/crypto_io.rs index ae5e7e59..a1cdf749 100644 --- a/crates/shadowsocks/src/relay/udprelay/crypto_io.rs +++ b/crates/shadowsocks/src/relay/udprelay/crypto_io.rs @@ -21,9 +21,10 @@ //! ``` use std::io::{self, Cursor, ErrorKind}; -use bytes::{BufMut, BytesMut}; +use bytes::{BufMut, Bytes, BytesMut}; use crate::{ + config::ServerUserManager, context::Context, crypto::{CipherCategory, CipherKind}, relay::socks5::Address, @@ -50,12 +51,14 @@ pub fn encrypt_client_payload( key: &[u8], addr: &Address, control: &UdpSocketControlData, + identity_keys: &[Bytes], payload: &[u8], dst: &mut BytesMut, ) { match method.category() { CipherCategory::None => { let _ = control; + let _ = identity_keys; dst.reserve(addr.serialized_len() + payload.len()); addr.write_to_buf(dst); dst.put_slice(payload); @@ -63,14 +66,18 @@ pub fn encrypt_client_payload( #[cfg(feature = "stream-cipher")] CipherCategory::Stream => { let _ = control; + let _ = identity_keys; encrypt_payload_stream(context, method, key, addr, payload, dst) } CipherCategory::Aead => { let _ = control; + let _ = identity_keys; encrypt_payload_aead(context, method, key, addr, payload, dst) } #[cfg(feature = "aead-cipher-2022")] - CipherCategory::Aead2022 => encrypt_client_payload_aead_2022(context, method, key, addr, control, payload, dst), + CipherCategory::Aead2022 => { + encrypt_client_payload_aead_2022(context, method, key, addr, control, identity_keys, payload, dst) + } } } @@ -111,9 +118,11 @@ pub async fn decrypt_client_payload( method: CipherKind, key: &[u8], payload: &mut [u8], + user_manager: Option<&ServerUserManager>, ) -> io::Result<(usize, Address, Option)> { match method.category() { CipherCategory::None => { + let _ = user_manager; let mut cur = Cursor::new(payload); match Address::read_from(&mut cur).await { Ok(address) => { @@ -129,14 +138,20 @@ pub async fn decrypt_client_payload( } } #[cfg(feature = "stream-cipher")] - CipherCategory::Stream => decrypt_payload_stream(context, method, key, payload) - .await - .map(|(n, a)| (n, a, None)), - CipherCategory::Aead => decrypt_payload_aead(context, method, key, payload) - .await - .map(|(n, a)| (n, a, None)), + CipherCategory::Stream => { + let _ = user_manager; + decrypt_payload_stream(context, method, key, payload) + .await + .map(|(n, a)| (n, a, None)) + } + CipherCategory::Aead => { + let _ = user_manager; + decrypt_payload_aead(context, method, key, payload) + .await + .map(|(n, a)| (n, a, None)) + } #[cfg(feature = "aead-cipher-2022")] - CipherCategory::Aead2022 => decrypt_client_payload_aead_2022(context, method, key, payload) + CipherCategory::Aead2022 => decrypt_client_payload_aead_2022(context, method, key, payload, user_manager) .await .map(|(n, a, c)| (n, a, Some(c))), } diff --git a/crates/shadowsocks/src/relay/udprelay/options.rs b/crates/shadowsocks/src/relay/udprelay/options.rs index eb4d5767..b7d09cdd 100644 --- a/crates/shadowsocks/src/relay/udprelay/options.rs +++ b/crates/shadowsocks/src/relay/udprelay/options.rs @@ -1,6 +1,9 @@ //! UDP Socket options and extra data -#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +use bytes::Bytes; + +#[derive(Debug, Clone, Eq, PartialEq)] +#[non_exhaustive] pub struct UdpSocketControlData { /// Session ID in client. /// @@ -12,4 +15,17 @@ pub struct UdpSocketControlData { pub server_session_id: u64, /// Packet counter pub packet_id: u64, + /// Extensible Identity Header user's hash + pub user_hash: Option, +} + +impl Default for UdpSocketControlData { + fn default() -> UdpSocketControlData { + UdpSocketControlData { + client_session_id: 0, + server_session_id: 0, + packet_id: 0, + user_hash: None, + } + } } diff --git a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs index 114b3077..45bf0474 100644 --- a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs +++ b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs @@ -1,14 +1,15 @@ //! UDP socket for communicating with shadowsocks' proxy server -use std::{io, net::SocketAddr, time::Duration}; +use std::{io, net::SocketAddr, sync::Arc, time::Duration}; -use bytes::BytesMut; +use byte_string::ByteStr; +use bytes::{Bytes, BytesMut}; use log::{trace, warn}; use once_cell::sync::Lazy; use tokio::{net::ToSocketAddrs, time}; use crate::{ - config::{ServerAddr, ServerConfig}, + config::{ServerAddr, ServerConfig, ServerUserManager}, context::SharedContext, crypto::CipherKind, net::{AcceptOpts, ConnectOpts, UdpSocket as ShadowUdpSocket}, @@ -43,6 +44,8 @@ pub struct ProxySocket { send_timeout: Option, recv_timeout: Option, context: SharedContext, + identity_keys: Arc>, + user_manager: Option>, } impl ProxySocket { @@ -94,6 +97,14 @@ impl ProxySocket { send_timeout: None, recv_timeout: None, context, + identity_keys: match socket_type { + UdpSocketType::Client => svr_cfg.clone_identity_keys(), + UdpSocketType::Server => Arc::new(Vec::new()), + }, + user_manager: match socket_type { + UdpSocketType::Client => None, + UdpSocketType::Server => svr_cfg.clone_user_manager(), + }, } } @@ -130,17 +141,46 @@ impl ProxySocket { &self, addr: &Address, control: &UdpSocketControlData, + identity_keys: &[Bytes], payload: &[u8], send_buf: &mut BytesMut, - ) { + ) -> io::Result<()> { match self.socket_type { - UdpSocketType::Client => { - encrypt_client_payload(&self.context, self.method, &self.key, addr, control, payload, send_buf) - } + UdpSocketType::Client => encrypt_client_payload( + &self.context, + self.method, + &self.key, + addr, + control, + identity_keys, + payload, + send_buf, + ), UdpSocketType::Server => { - encrypt_server_payload(&self.context, self.method, &self.key, addr, control, payload, send_buf) + let mut key = self.key.as_ref(); + + if let Some(ref user_hash) = control.user_hash { + if let Some(ref user_manager) = self.user_manager { + match user_manager.get_user_by_hash(user_hash) { + None => { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("user with hash {:?} not found", ByteStr::new(user_hash)), + )); + } + Some(user) => { + trace!("udp encrypt with user {} identity", user.name()); + key = user.key(); + } + } + } + } + + encrypt_server_payload(&self.context, self.method, key, addr, control, payload, send_buf) } } + + Ok(()) } /// Send a UDP packet to addr through proxy @@ -157,7 +197,7 @@ impl ProxySocket { payload: &[u8], ) -> io::Result { let mut send_buf = BytesMut::new(); - self.encrypt_send_buffer(addr, control, payload, &mut send_buf); + self.encrypt_send_buffer(addr, control, &self.identity_keys, payload, &mut send_buf)?; trace!( "UDP server client send to {}, control: {:?}, payload length {} bytes, packet length {} bytes", @@ -202,7 +242,7 @@ impl ProxySocket { payload: &[u8], ) -> io::Result { let mut send_buf = BytesMut::new(); - self.encrypt_send_buffer(addr, control, payload, &mut send_buf); + self.encrypt_send_buffer(addr, control, &self.identity_keys, payload, &mut send_buf)?; trace!( "UDP server client send to, addr {}, control: {:?}, payload length {} bytes, packet length {} bytes", @@ -235,10 +275,13 @@ impl ProxySocket { async fn decrypt_recv_buffer( &self, recv_buf: &mut [u8], + user_manager: Option<&ServerUserManager>, ) -> io::Result<(usize, Address, Option)> { match self.socket_type { UdpSocketType::Client => decrypt_server_payload(&self.context, self.method, &self.key, recv_buf).await, - UdpSocketType::Server => decrypt_client_payload(&self.context, self.method, &self.key, recv_buf).await, + UdpSocketType::Server => { + decrypt_client_payload(&self.context, self.method, &self.key, recv_buf, user_manager).await + } } } @@ -270,7 +313,9 @@ impl ProxySocket { }, }; - let (n, addr, control) = self.decrypt_recv_buffer(&mut recv_buf[..recv_n]).await?; + let (n, addr, control) = self + .decrypt_recv_buffer(&mut recv_buf[..recv_n], self.user_manager.as_deref()) + .await?; trace!( "UDP server client receive from {}, control: {:?}, packet length {} bytes, payload length {} bytes", @@ -313,7 +358,9 @@ impl ProxySocket { }, }; - let (n, addr, control) = self.decrypt_recv_buffer(&mut recv_buf[..recv_n]).await?; + let (n, addr, control) = self + .decrypt_recv_buffer(&mut recv_buf[..recv_n], self.user_manager.as_deref()) + .await?; trace!( "UDP server client receive from {}, addr {}, control: {:?}, packet length {} bytes, payload length {} bytes",