mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-11-06 16:14:11 +00:00
relax requirement of base64 padding in configuration
- allow non-canonical base64 - unify base64 engine in shadowsocks crate - ref shadowsocks/shadowsocks-android#2995 - ref https://github.com/shadowsocks/shadowsocks-android/commit/243cafd41d0712da1f5693067f1f9b0fc6408645
This commit is contained in:
Generated
+3
-4
@@ -2201,7 +2201,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shadowsocks"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arc-swap",
|
||||
@@ -2262,7 +2262,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shadowsocks-rust"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"build-time",
|
||||
@@ -2298,11 +2298,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shadowsocks-service"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"byte_string",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "shadowsocks-rust"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
authors = ["Shadowsocks Contributors"]
|
||||
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
|
||||
repository = "https://github.com/shadowsocks/shadowsocks-rust"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "shadowsocks-service"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
authors = ["Shadowsocks Contributors"]
|
||||
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
|
||||
repository = "https://github.com/shadowsocks/shadowsocks-rust"
|
||||
@@ -120,9 +120,8 @@ smoltcp = { version = "0.9", optional = true, default-features = false, features
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
json5 = "0.4"
|
||||
base64 = "0.21"
|
||||
|
||||
shadowsocks = { version = "1.15.0", path = "../shadowsocks", default-features = false }
|
||||
shadowsocks = { version = "1.15.3", path = "../shadowsocks", default-features = false }
|
||||
|
||||
# Just for the ioctl call macro
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
//!
|
||||
//! These defined server will be used with a load balancing algorithm.
|
||||
|
||||
use base64::Engine as _;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
convert::{From, Infallible},
|
||||
@@ -1725,8 +1724,8 @@ impl Config {
|
||||
let mut user_manager = ServerUserManager::new();
|
||||
|
||||
for user in users {
|
||||
let key = match base64::engine::general_purpose::STANDARD.decode(&user.password) {
|
||||
Ok(k) => k,
|
||||
let user = match ServerUser::with_encoded_key(user.name, &user.password) {
|
||||
Ok(u) => u,
|
||||
Err(..) => {
|
||||
let err = Error::new(
|
||||
ErrorKind::Malformed,
|
||||
@@ -1737,7 +1736,7 @@ impl Config {
|
||||
}
|
||||
};
|
||||
|
||||
user_manager.add_user(ServerUser::new(user.name, key));
|
||||
user_manager.add_user(user);
|
||||
}
|
||||
|
||||
nsvr.set_user_manager(user_manager);
|
||||
@@ -2498,7 +2497,7 @@ impl fmt::Display for Config {
|
||||
for u in m.users_iter() {
|
||||
vu.push(SSServerUserConfig {
|
||||
name: u.name().to_owned(),
|
||||
password: base64::engine::general_purpose::STANDARD.encode(u.key()),
|
||||
password: u.encoded_key(),
|
||||
});
|
||||
}
|
||||
vu
|
||||
|
||||
@@ -18,7 +18,7 @@ use shadowsocks::{net::TcpSocketOpts, relay::socks5::Address};
|
||||
use smoltcp::{
|
||||
iface::{Config as InterfaceConfig, Interface, SocketHandle, SocketSet},
|
||||
phy::{DeviceCapabilities, Medium},
|
||||
socket::{tcp::Socket as TcpSocket, tcp::SocketBuffer as TcpSocketBuffer, tcp::State as TcpState},
|
||||
socket::tcp::{Socket as TcpSocket, SocketBuffer as TcpSocketBuffer, State as TcpState},
|
||||
storage::RingBuffer,
|
||||
time::{Duration as SmolDuration, Instant as SmolInstant},
|
||||
wire::{IpAddress, IpCidr, Ipv4Address, Ipv6Address, TcpPacket},
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
//! Shadowsocks Manager server
|
||||
|
||||
use base64::Engine as _;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashMap, io, net::SocketAddr, sync::Arc, time::Duration};
|
||||
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use log::{error, info, trace};
|
||||
use shadowsocks::{
|
||||
config::{Mode, ServerConfig, ServerType, ServerUser, ServerUserManager},
|
||||
@@ -14,22 +11,12 @@ use shadowsocks::{
|
||||
crypto::CipherKind,
|
||||
dns_resolver::DnsResolver,
|
||||
manager::protocol::{
|
||||
self,
|
||||
AddRequest,
|
||||
AddResponse,
|
||||
ErrorResponse,
|
||||
ListResponse,
|
||||
ManagerRequest,
|
||||
PingResponse,
|
||||
RemoveRequest,
|
||||
RemoveResponse,
|
||||
ServerUserConfig,
|
||||
StatRequest,
|
||||
self, AddRequest, AddResponse, ErrorResponse, ListResponse, ManagerRequest, PingResponse, RemoveRequest,
|
||||
RemoveResponse, ServerUserConfig, StatRequest,
|
||||
},
|
||||
net::{AcceptOpts, ConnectOpts},
|
||||
plugin::PluginConfig,
|
||||
ManagerListener,
|
||||
ServerAddr,
|
||||
ManagerListener, ServerAddr,
|
||||
};
|
||||
use tokio::{sync::Mutex, task::JoinHandle};
|
||||
|
||||
@@ -472,8 +459,8 @@ impl Manager {
|
||||
let mut user_manager = ServerUserManager::new();
|
||||
|
||||
for user in users.iter() {
|
||||
let key = match STANDARD.decode(&user.password) {
|
||||
Ok(key) => key,
|
||||
let user = match ServerUser::with_encoded_key(&user.name, &user.password) {
|
||||
Ok(u) => u,
|
||||
Err(..) => {
|
||||
error!(
|
||||
"users[].password must be encoded with base64, but found: {}",
|
||||
@@ -487,7 +474,7 @@ impl Manager {
|
||||
}
|
||||
};
|
||||
|
||||
user_manager.add_user(ServerUser::new(&user.name, key));
|
||||
user_manager.add_user(user);
|
||||
}
|
||||
|
||||
svr_cfg.set_user_manager(user_manager);
|
||||
@@ -525,7 +512,7 @@ impl Manager {
|
||||
for user in user_manager.users_iter() {
|
||||
vu.push(ServerUserConfig {
|
||||
name: user.name().to_owned(),
|
||||
password: STANDARD.encode(user.key()),
|
||||
password: user.encoded_key(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "shadowsocks"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
authors = ["Shadowsocks Contributors"]
|
||||
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
|
||||
repository = "https://github.com/shadowsocks/shadowsocks-rust"
|
||||
|
||||
@@ -12,14 +12,12 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use base64::{
|
||||
engine::general_purpose::{STANDARD, URL_SAFE, URL_SAFE_NO_PAD},
|
||||
Engine as _,
|
||||
};
|
||||
use base64::Engine as _;
|
||||
use byte_string::ByteStr;
|
||||
use bytes::Bytes;
|
||||
use cfg_if::cfg_if;
|
||||
use log::error;
|
||||
use thiserror::Error;
|
||||
use url::{self, Url};
|
||||
|
||||
use crate::{
|
||||
@@ -28,6 +26,27 @@ use crate::{
|
||||
relay::socks5::Address,
|
||||
};
|
||||
|
||||
const USER_KEY_BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new(
|
||||
&base64::alphabet::STANDARD,
|
||||
base64::engine::GeneralPurposeConfig::new()
|
||||
.with_encode_padding(true)
|
||||
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
|
||||
);
|
||||
|
||||
const AEAD2022_PASSWORD_BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new(
|
||||
&base64::alphabet::STANDARD,
|
||||
base64::engine::GeneralPurposeConfig::new()
|
||||
.with_encode_padding(true)
|
||||
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
|
||||
);
|
||||
|
||||
const URL_PASSWORD_BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new(
|
||||
&base64::alphabet::URL_SAFE,
|
||||
base64::engine::GeneralPurposeConfig::new()
|
||||
.with_encode_padding(false)
|
||||
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
|
||||
);
|
||||
|
||||
/// Shadowsocks server type
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ServerType {
|
||||
@@ -164,7 +183,7 @@ impl Debug for ServerUser {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ServerUser")
|
||||
.field("name", &self.name)
|
||||
.field("key", &STANDARD.encode(&self.key))
|
||||
.field("key", &USER_KEY_BASE64_ENGINE.encode(&self.key))
|
||||
.field("identity_hash", &ByteStr::new(&self.identity_hash))
|
||||
.finish()
|
||||
}
|
||||
@@ -190,6 +209,15 @@ impl ServerUser {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a user from encoded key
|
||||
pub fn with_encoded_key<N>(name: N, key: &str) -> Result<ServerUser, ServerUserError>
|
||||
where
|
||||
N: Into<String>,
|
||||
{
|
||||
let key = USER_KEY_BASE64_ENGINE.decode(key)?;
|
||||
Ok(ServerUser::new(name, key))
|
||||
}
|
||||
|
||||
/// Name of the user
|
||||
pub fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
@@ -200,6 +228,11 @@ impl ServerUser {
|
||||
self.key.as_ref()
|
||||
}
|
||||
|
||||
/// Get Base64 encoded key of user
|
||||
pub fn encoded_key(&self) -> String {
|
||||
USER_KEY_BASE64_ENGINE.encode(&self.key)
|
||||
}
|
||||
|
||||
/// User's identity hash
|
||||
///
|
||||
/// https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md
|
||||
@@ -215,6 +248,14 @@ impl ServerUser {
|
||||
}
|
||||
}
|
||||
|
||||
/// ServerUser related errors
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum ServerUserError {
|
||||
/// Invalid User key encoding
|
||||
#[error("{0}")]
|
||||
InvalidKeyEncoding(#[from] base64::DecodeError),
|
||||
}
|
||||
|
||||
/// Server multi-users manager
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServerUserManager {
|
||||
@@ -305,7 +346,7 @@ pub struct ServerConfig {
|
||||
fn make_derived_key(method: CipherKind, password: &str, enc_key: &mut [u8]) {
|
||||
if method.is_aead_2022() {
|
||||
// AEAD 2022 password is a base64 form of enc_key
|
||||
match STANDARD.decode(password) {
|
||||
match AEAD2022_PASSWORD_BASE64_ENGINE.decode(password) {
|
||||
Ok(v) => {
|
||||
if v.len() != enc_key.len() {
|
||||
panic!(
|
||||
@@ -366,7 +407,7 @@ where
|
||||
make_derived_key(method, upsk, &mut enc_key);
|
||||
|
||||
for ipsk in split_iter {
|
||||
match STANDARD.decode(ipsk) {
|
||||
match USER_KEY_BASE64_ENGINE.decode(ipsk) {
|
||||
Ok(v) => {
|
||||
identity_keys.push(Bytes::from(v));
|
||||
}
|
||||
@@ -567,7 +608,7 @@ impl ServerConfig {
|
||||
/// ```
|
||||
pub fn to_qrcode_url(&self) -> String {
|
||||
let param = format!("{}:{}@{}", self.method(), self.password(), self.addr());
|
||||
format!("ss://{}", URL_SAFE_NO_PAD.encode(param))
|
||||
format!("ss://{}", URL_PASSWORD_BASE64_ENGINE.encode(param))
|
||||
}
|
||||
|
||||
/// Get [SIP002](https://github.com/shadowsocks/shadowsocks-org/issues/27) URL
|
||||
@@ -576,13 +617,13 @@ impl ServerConfig {
|
||||
if #[cfg(feature = "aead-cipher-2022")] {
|
||||
let user_info = if !self.method().is_aead_2022() {
|
||||
let user_info = format!("{}:{}", self.method(), self.password());
|
||||
URL_SAFE_NO_PAD.encode(&user_info)
|
||||
URL_PASSWORD_BASE64_ENGINE.encode(&user_info)
|
||||
} else {
|
||||
format!("{}:{}", self.method(), percent_encoding::utf8_percent_encode(self.password(), percent_encoding::NON_ALPHANUMERIC))
|
||||
};
|
||||
} else {
|
||||
let mut user_info = format!("{}:{}", self.method(), self.password());
|
||||
user_info = URL_SAFE_NO_PAD.encode(&user_info)
|
||||
user_info = URL_PASSWORD_BASE64_ENGINE.encode(&user_info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,7 +673,7 @@ impl ServerConfig {
|
||||
None => return Err(UrlParseError::MissingHost),
|
||||
};
|
||||
|
||||
let mut decoded_body = match URL_SAFE_NO_PAD.decode(encoded) {
|
||||
let mut decoded_body = match URL_PASSWORD_BASE64_ENGINE.decode(encoded) {
|
||||
Ok(b) => match String::from_utf8(b) {
|
||||
Ok(b) => b,
|
||||
Err(..) => return Err(UrlParseError::InvalidServerAddr),
|
||||
@@ -685,15 +726,9 @@ impl ServerConfig {
|
||||
// reborrow to fit AsRef<[u8]>
|
||||
let decoded_user_info: &str = &decoded_user_info;
|
||||
|
||||
let base64_config = if decoded_user_info.ends_with('=') {
|
||||
// Some implementation, like outline,
|
||||
// or those with Python (base64 in Python will still have '=' padding for URL safe encode)
|
||||
URL_SAFE
|
||||
} else {
|
||||
URL_SAFE_NO_PAD
|
||||
};
|
||||
|
||||
let account = match base64_config.decode(decoded_user_info) {
|
||||
// Some implementation, like outline,
|
||||
// or those with Python (base64 in Python will still have '=' padding for URL safe encode)
|
||||
let account = match URL_PASSWORD_BASE64_ENGINE.decode(decoded_user_info) {
|
||||
Ok(account) => match String::from_utf8(account) {
|
||||
Ok(ac) => ac,
|
||||
Err(..) => return Err(UrlParseError::InvalidAuthInfo),
|
||||
|
||||
Reference in New Issue
Block a user