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:
zonyitoo
2023-02-26 12:15:45 +08:00
parent 18c08de882
commit b382920c81
8 changed files with 74 additions and 55 deletions
Generated
+3 -4
View File
@@ -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
View File
@@ -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"
+2 -3
View File
@@ -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]
+4 -5
View File
@@ -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 -1
View File
@@ -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"
+55 -20
View File
@@ -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),