summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2024-01-11 15:45:51 +0100
committerMarkus Pettersson <markus.pettersson@mullvad.net>2024-01-12 13:52:19 +0100
commit8837083fbc794317ea4d60faf3233c3a40879327 (patch)
treed64701ede44860df85aa235bd01c55f274e1b2f5
parent2f173033f482de001420bbf348c426fa5c537604 (diff)
downloadmullvadvpn-8837083fbc794317ea4d60faf3233c3a40879327.tar.xz
mullvadvpn-8837083fbc794317ea4d60faf3233c3a40879327.zip
Validate SOCKS5 credentials
Validate SOCKS credentials by checking that both `username` and `password` both have a length between 1 and 255 bytes. Link to RFC detailing SOCKS5 username/password authentication: https://datatracker.ietf.org/doc/html/rfc1929
-rw-r--r--mullvad-api/src/https_client_with_sni.rs39
-rw-r--r--mullvad-cli/Cargo.toml3
-rw-r--r--mullvad-cli/src/cmds/api_access.rs102
-rw-r--r--mullvad-cli/src/cmds/bridge.rs4
-rw-r--r--mullvad-cli/src/cmds/proxies.rs49
-rw-r--r--mullvad-daemon/src/migrations/v7.rs7
-rw-r--r--mullvad-management-interface/src/types/conversions/access_method.rs109
-rw-r--r--mullvad-management-interface/src/types/conversions/net.rs116
-rw-r--r--talpid-openvpn/src/lib.rs4
-rw-r--r--talpid-types/src/net/proxy.rs84
10 files changed, 279 insertions, 238 deletions
diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs
index 3a9bb8d75f..28f40a7d87 100644
--- a/mullvad-api/src/https_client_with_sni.rs
+++ b/mullvad-api/src/https_client_with_sni.rs
@@ -33,8 +33,7 @@ use std::{
task::{Context, Poll},
time::Duration,
};
-use talpid_types::ErrorExt;
-
+use talpid_types::{net::proxy, ErrorExt};
use tokio::{
io::{AsyncRead, AsyncWrite},
net::{TcpSocket, TcpStream},
@@ -126,13 +125,16 @@ impl InnerConnectionMode {
let first_hop = socks.peer;
let make_proxy_stream = |tcp_stream| async {
match socks.authentication {
- SocksAuth::None => {
+ None => {
tokio_socks::tcp::Socks5Stream::connect_with_socket(tcp_stream, addr)
.await
}
- SocksAuth::Password { username, password } => {
+ Some(credentials) => {
tokio_socks::tcp::Socks5Stream::connect_with_password_and_socket(
- tcp_stream, addr, &username, &password,
+ tcp_stream,
+ addr,
+ credentials.username(),
+ credentials.password(),
)
.await
}
@@ -217,13 +219,7 @@ impl From<ParsedShadowsocksConfig> for ServerConfig {
#[derive(Clone)]
struct SocksConfig {
peer: SocketAddr,
- authentication: SocksAuth,
-}
-
-#[derive(Clone)]
-pub enum SocksAuth {
- None,
- Password { username: String, password: String },
+ authentication: Option<proxy::SocksAuth>,
}
#[derive(err_derive::Error, Debug)]
@@ -237,7 +233,6 @@ impl TryFrom<ApiConnectionMode> for InnerConnectionMode {
fn try_from(config: ApiConnectionMode) -> Result<Self, Self::Error> {
use std::net::Ipv4Addr;
- use talpid_types::net::proxy;
Ok(match config {
ApiConnectionMode::Direct => InnerConnectionMode::Direct,
ApiConnectionMode::Proxied(proxy_settings) => match proxy_settings {
@@ -254,20 +249,12 @@ impl TryFrom<ApiConnectionMode> for InnerConnectionMode {
}
ProxyConfig::Socks5Local(config) => InnerConnectionMode::Socks5(SocksConfig {
peer: SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), config.local_port),
- authentication: SocksAuth::None,
+ authentication: None,
+ }),
+ ProxyConfig::Socks5Remote(config) => InnerConnectionMode::Socks5(SocksConfig {
+ peer: config.endpoint,
+ authentication: config.auth,
}),
- ProxyConfig::Socks5Remote(config) => {
- let authentication = match config.auth {
- Some(proxy::SocksAuth { username, password }) => {
- SocksAuth::Password { username, password }
- }
- None => SocksAuth::None,
- };
- InnerConnectionMode::Socks5(SocksConfig {
- peer: config.endpoint,
- authentication,
- })
- }
},
})
}
diff --git a/mullvad-cli/Cargo.toml b/mullvad-cli/Cargo.toml
index d0453c85f4..8ab61979b5 100644
--- a/mullvad-cli/Cargo.toml
+++ b/mullvad-cli/Cargo.toml
@@ -17,9 +17,10 @@ path = "src/main.rs"
anyhow = "1.0"
chrono = { workspace = true }
clap = { workspace = true }
+err-derive = { workspace = true }
futures = "0.3"
-natord = "1.0.9"
itertools = "0.10"
+natord = "1.0.9"
mullvad-types = { path = "../mullvad-types", features = ["clap"] }
mullvad-version = { path = "../mullvad-version" }
diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs
index 8883ea516b..d24d5a0674 100644
--- a/mullvad-cli/src/cmds/api_access.rs
+++ b/mullvad-cli/src/cmds/api_access.rs
@@ -86,7 +86,7 @@ impl ApiAccess {
let mut rpc = MullvadProxyClient::new().await?;
let name = cmd.name().to_string();
let enabled = cmd.enabled();
- let access_method = AccessMethod::from(cmd);
+ let access_method = AccessMethod::try_from(cmd)?;
rpc.add_access_method(name, enabled, access_method).await?;
Ok(())
}
@@ -139,10 +139,16 @@ impl ApiAccess {
let port = cmd.params.port.unwrap_or(remote.endpoint.port());
AccessMethod::from(match remote.auth {
None => Socks5Remote::new((ip, port)),
- Some(SocksAuth { username, password }) => {
- let username = cmd.params.username.unwrap_or(username);
- let password = cmd.params.password.unwrap_or(password);
- let auth = SocksAuth { username, password };
+ Some(credentials) => {
+ let username = cmd
+ .params
+ .username
+ .unwrap_or(credentials.username().to_string());
+ let password = cmd
+ .params
+ .password
+ .unwrap_or(credentials.password().to_string());
+ let auth = SocksAuth::new(username, password)?;
Socks5Remote::new_with_authentication((ip, port), auth)
}
})
@@ -361,73 +367,45 @@ pub struct EditParams {
/// Since these are not supposed to be used outside of the CLI,
/// we define them in a hidden-away module.
mod conversions {
- use crate::cmds::proxies::SocksAuthentication;
-
use super::{AddCustomCommands, AddSocks5Commands};
+ use crate::cmds::proxies::{Error, SocksAuthentication};
use mullvad_types::access_method as daemon_types;
use talpid_types::net::proxy as talpid_types;
- impl From<AddCustomCommands> for daemon_types::AccessMethod {
- fn from(value: AddCustomCommands) -> Self {
+ impl TryFrom<AddCustomCommands> for daemon_types::AccessMethod {
+ type Error = Error;
+ fn try_from(value: AddCustomCommands) -> Result<Self, Self::Error> {
match value {
AddCustomCommands::Socks5(socks) => match socks {
- AddSocks5Commands::Local {
- name: _,
- disabled: _,
- add,
- } => {
- let (local_port, remote_ip, remote_port, transport_protocol) = (
+ AddSocks5Commands::Local { add, .. } => Ok(daemon_types::AccessMethod::from(
+ talpid_types::Socks5Local::new_with_transport_protocol(
+ (add.remote_ip, add.remote_port),
add.local_port,
- add.remote_ip,
- add.remote_port,
add.transport_protocol,
- );
- println!("Adding SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}/{transport_protocol}");
- talpid_types::Socks5Local::new_with_transport_protocol(
- (remote_ip, remote_port),
- local_port,
- transport_protocol,
- )
- .into()
+ ),
+ )),
+ AddSocks5Commands::Remote { add, .. } => {
+ Ok(daemon_types::AccessMethod::from(match add.authentication {
+ Some(SocksAuthentication { username, password }) => {
+ let auth = talpid_types::SocksAuth::new(username, password)?;
+ talpid_types::Socks5Remote::new_with_authentication(
+ (add.remote_ip, add.remote_port),
+ auth,
+ )
+ }
+ None => {
+ talpid_types::Socks5Remote::new((add.remote_ip, add.remote_port))
+ }
+ }))
}
- AddSocks5Commands::Remote {
- add,
- name: _,
- disabled: _,
- } => daemon_types::AccessMethod::from(match add.authentication {
- Some(SocksAuthentication { username, password }) => {
- println!(
- "Adding SOCKS5-proxy: {username}:{password}@{}:{}",
- add.remote_ip, add.remote_port
- );
- let auth = talpid_types::SocksAuth { username, password };
- talpid_types::Socks5Remote::new_with_authentication(
- (add.remote_ip, add.remote_port),
- auth,
- )
- }
- None => {
- println!("Adding SOCKS5-proxy: {}:{}", add.remote_ip, add.remote_port);
- talpid_types::Socks5Remote::new((add.remote_ip, add.remote_port))
- }
- }),
},
- AddCustomCommands::Shadowsocks {
- add,
- name: _,
- disabled: _,
- } => {
- let (password, cipher, remote_ip, remote_port) =
- (add.password, add.cipher, add.remote_ip, add.remote_port);
- println!(
- "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}"
- );
- daemon_types::AccessMethod::from(talpid_types::Shadowsocks::new(
- (remote_ip, remote_port),
- cipher,
- password,
- ))
- }
+ AddCustomCommands::Shadowsocks { add, .. } => Ok(daemon_types::AccessMethod::from(
+ talpid_types::Shadowsocks::new(
+ (add.remote_ip, add.remote_port),
+ add.cipher,
+ add.password,
+ ),
+ )),
}
}
}
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs
index 329921868b..861d73feb2 100644
--- a/mullvad-cli/src/cmds/bridge.rs
+++ b/mullvad-cli/src/cmds/bridge.rs
@@ -303,7 +303,7 @@ impl Bridge {
match custom_bridge {
CustomProxy::Shadowsocks(ss) => *ss = edit.merge_shadowsocks(ss),
CustomProxy::Socks5Local(local) => *local = edit.merge_socks_local(local),
- CustomProxy::Socks5Remote(remote) => *remote = edit.merge_socks_remote(remote),
+ CustomProxy::Socks5Remote(remote) => *remote = edit.merge_socks_remote(remote)?,
};
rpc.set_bridge_settings(settings.bridge_settings)
@@ -343,7 +343,7 @@ impl Bridge {
CustomProxy::Socks5Local(Socks5Local::from(add))
}
AddCustomCommands::Socks5(AddSocks5Commands::Remote { add }) => {
- CustomProxy::Socks5Remote(Socks5Remote::from(add))
+ CustomProxy::Socks5Remote(Socks5Remote::try_from(add)?)
}
AddCustomCommands::Shadowsocks { add } => {
CustomProxy::Shadowsocks(Shadowsocks::from(add))
diff --git a/mullvad-cli/src/cmds/proxies.rs b/mullvad-cli/src/cmds/proxies.rs
index 6e32836a25..5c8a79ff6c 100644
--- a/mullvad-cli/src/cmds/proxies.rs
+++ b/mullvad-cli/src/cmds/proxies.rs
@@ -1,10 +1,15 @@
+use clap::Args;
use std::net::{IpAddr, SocketAddr};
use talpid_types::net::{
proxy::{Shadowsocks, Socks5Local, Socks5Remote, SocksAuth, SHADOWSOCKS_CIPHERS},
Endpoint, TransportProtocol,
};
-use clap::Args;
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ #[error(display = "{}", _0)]
+ InvalidAuth(#[error(source)] talpid_types::net::proxy::Error),
+}
#[derive(Args, Debug, Clone)]
pub struct Socks5LocalAdd {
@@ -48,15 +53,16 @@ pub struct Socks5RemoteAdd {
pub authentication: Option<SocksAuthentication>,
}
-impl From<Socks5RemoteAdd> for Socks5Remote {
- fn from(add: Socks5RemoteAdd) -> Self {
- Self {
+impl TryFrom<Socks5RemoteAdd> for Socks5Remote {
+ type Error = Error;
+ fn try_from(add: Socks5RemoteAdd) -> Result<Self, Self::Error> {
+ Ok(Self {
endpoint: SocketAddr::new(add.remote_ip, add.remote_port),
- auth: add.authentication.map(|auth| SocksAuth {
- username: auth.username,
- password: auth.password,
- }),
- }
+ auth: add
+ .authentication
+ .map(|auth| SocksAuth::new(auth.username, auth.password))
+ .transpose()?,
+ })
}
}
@@ -134,13 +140,13 @@ impl ProxyEditParams {
)
}
- pub fn merge_socks_remote(self, remote: &Socks5Remote) -> Socks5Remote {
+ pub fn merge_socks_remote(self, remote: &Socks5Remote) -> Result<Socks5Remote, Error> {
let ip = self.ip.unwrap_or(remote.endpoint.ip());
let port = self.port.unwrap_or(remote.endpoint.port());
- match &remote.auth {
+ let config = match &remote.auth {
None => match (self.username, self.password) {
(Some(username), Some(password)) => {
- let auth = SocksAuth { username, password };
+ let auth = SocksAuth::new(username, password)?;
Socks5Remote::new_with_authentication((ip, port), auth)
}
(None, None) => Socks5Remote::new((ip, port)),
@@ -149,13 +155,14 @@ impl ProxyEditParams {
Socks5Remote::new((ip, port))
}
},
- Some(SocksAuth { username, password }) => {
- let username = self.username.unwrap_or(username.to_owned());
- let password = self.password.unwrap_or(password.to_owned());
- let auth = SocksAuth { username, password };
+ Some(credentials) => {
+ let username = self.username.unwrap_or(credentials.username().to_string());
+ let password = self.password.unwrap_or(credentials.password().to_string());
+ let auth = SocksAuth::new(username, password)?;
Socks5Remote::new_with_authentication((ip, port), auth)
}
- }
+ };
+ Ok(config)
}
pub fn merge_shadowsocks(self, shadowsocks: &Shadowsocks) -> Shadowsocks {
@@ -169,7 +176,7 @@ impl ProxyEditParams {
pub mod pp {
use crate::print_option;
- use talpid_types::net::proxy::{CustomProxy, SocksAuth};
+ use talpid_types::net::proxy::CustomProxy;
pub struct CustomProxyFormatter<'a> {
pub custom_proxy: &'a CustomProxy,
@@ -188,9 +195,9 @@ pub mod pp {
print_option!("Protocol", "Socks5");
print_option!("Peer", remote.endpoint);
match &remote.auth {
- Some(SocksAuth { username, password }) => {
- print_option!("Username", username);
- print_option!("Password", password);
+ Some(credentials) => {
+ print_option!("Username", credentials.username());
+ print_option!("Password", credentials.password());
}
None => (),
}
diff --git a/mullvad-daemon/src/migrations/v7.rs b/mullvad-daemon/src/migrations/v7.rs
index 4baf49b571..efd3193624 100644
--- a/mullvad-daemon/src/migrations/v7.rs
+++ b/mullvad-daemon/src/migrations/v7.rs
@@ -182,10 +182,9 @@ fn migrate_bridge_settings(settings: &mut serde_json::Value) -> Result<()> {
.parse()
.map_err(|_| Error::InvalidSettingsContent)?,
auth: custom_bridge_remote.get("auth").and_then(|auth| {
- Some(SocksAuth {
- username: auth.get("username")?.to_string(),
- password: auth.get("password")?.to_string(),
- })
+ let username = auth.get("username")?.to_string();
+ let password = auth.get("password")?.to_string();
+ SocksAuth::new(username, password).ok()
}),
})),
}
diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs
index a5aa84d374..0b2406af47 100644
--- a/mullvad-management-interface/src/types/conversions/access_method.rs
+++ b/mullvad-management-interface/src/types/conversions/access_method.rs
@@ -42,15 +42,11 @@ mod settings {
/// [`crate::types::proto::ApiAccessMethod`] type to the internal
/// [`mullvad_types::access_method::AccessMethodSetting`] data type.
mod data {
- use std::net::Ipv4Addr;
-
use crate::types::{proto, FromProtobufTypeError};
use mullvad_types::access_method::{
AccessMethod, AccessMethodSetting, BuiltInAccessMethod, Id,
};
- use talpid_types::net::proxy::{
- CustomProxy, Shadowsocks, Socks5Local, Socks5Remote, SocksAuth,
- };
+ use talpid_types::net::proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remote};
impl TryFrom<proto::AccessMethodSetting> for AccessMethodSetting {
type Error = FromProtobufTypeError;
@@ -146,19 +142,7 @@ mod data {
type Error = FromProtobufTypeError;
fn try_from(value: proto::Socks5Local) -> Result<Self, Self::Error> {
- use crate::types::conversions::net::try_transport_protocol_from_i32;
- let remote_ip = value.remote_ip.parse::<Ipv4Addr>().map_err(|_| {
- FromProtobufTypeError::InvalidArgument(
- "Could not parse Socks5 (local) message from protobuf",
- )
- })?;
- Ok(AccessMethod::from(
- Socks5Local::new_with_transport_protocol(
- (remote_ip, value.remote_port as u16),
- value.local_port as u16,
- try_transport_protocol_from_i32(value.remote_transport_protocol)?,
- ),
- ))
+ Socks5Local::try_from(value).map(AccessMethod::from)
}
}
@@ -166,18 +150,7 @@ mod data {
type Error = FromProtobufTypeError;
fn try_from(value: proto::Socks5Remote) -> Result<Self, Self::Error> {
- let proto::Socks5Remote { ip, port, auth } = value;
- let ip = ip.parse::<Ipv4Addr>().map_err(|_| {
- FromProtobufTypeError::InvalidArgument(
- "Could not parse Socks5 (remote) message from protobuf",
- )
- })?;
- let port = port as u16;
-
- Ok(AccessMethod::from(match auth.map(SocksAuth::from) {
- Some(auth) => Socks5Remote::new_with_authentication((ip, port), auth),
- None => Socks5Remote::new((ip, port)),
- }))
+ Socks5Remote::try_from(value).map(AccessMethod::from)
}
}
@@ -185,69 +158,57 @@ mod data {
type Error = FromProtobufTypeError;
fn try_from(value: proto::Shadowsocks) -> Result<Self, Self::Error> {
- let ip = value.ip.parse::<Ipv4Addr>().map_err(|_| {
- FromProtobufTypeError::InvalidArgument(
- "Could not parse Socks5 (remote) message from protobuf",
- )
- })?;
-
- Ok(AccessMethod::from(Shadowsocks::new(
- (ip, value.port as u16),
- value.cipher,
- value.password,
- )))
+ Shadowsocks::try_from(value).map(AccessMethod::from)
}
}
impl From<BuiltInAccessMethod> for proto::AccessMethod {
fn from(value: BuiltInAccessMethod) -> Self {
- let access_method = match value {
+ proto::AccessMethod {
+ access_method: Some(proto::access_method::AccessMethod::from(value)),
+ }
+ }
+ }
+
+ impl From<BuiltInAccessMethod> for proto::access_method::AccessMethod {
+ fn from(value: BuiltInAccessMethod) -> Self {
+ match value {
mullvad_types::access_method::BuiltInAccessMethod::Direct => {
proto::access_method::AccessMethod::Direct(proto::access_method::Direct {})
}
mullvad_types::access_method::BuiltInAccessMethod::Bridge => {
proto::access_method::AccessMethod::Bridges(proto::access_method::Bridges {})
}
- };
- proto::AccessMethod {
- access_method: Some(access_method),
}
}
}
impl From<CustomProxy> for proto::AccessMethod {
fn from(value: CustomProxy) -> Self {
- let access_method = match value {
- CustomProxy::Shadowsocks(ss) => {
- proto::access_method::AccessMethod::Shadowsocks(proto::Shadowsocks {
- ip: ss.endpoint.ip().to_string(),
- port: u32::from(ss.endpoint.port()),
- password: ss.password,
- cipher: ss.cipher,
- })
+ proto::AccessMethod {
+ access_method: Some(proto::access_method::AccessMethod::from(value)),
+ }
+ }
+ }
+
+ impl From<CustomProxy> for proto::access_method::AccessMethod {
+ fn from(value: CustomProxy) -> Self {
+ match value {
+ CustomProxy::Shadowsocks(config) => {
+ proto::access_method::AccessMethod::Shadowsocks(proto::Shadowsocks::from(
+ config,
+ ))
}
- CustomProxy::Socks5Local(Socks5Local {
- remote_endpoint,
- local_port,
- }) => proto::access_method::AccessMethod::Socks5local(proto::Socks5Local {
- remote_ip: remote_endpoint.address.ip().to_string(),
- remote_port: remote_endpoint.address.port() as u32,
- remote_transport_protocol: i32::from(proto::TransportProtocol::from(
- remote_endpoint.protocol,
- )),
- local_port: u32::from(local_port),
- }),
- CustomProxy::Socks5Remote(Socks5Remote { endpoint, auth }) => {
- proto::access_method::AccessMethod::Socks5remote(proto::Socks5Remote {
- ip: endpoint.ip().to_string(),
- port: u32::from(endpoint.port()),
- auth: auth.map(proto::SocksAuth::from),
- })
+ CustomProxy::Socks5Local(config) => {
+ proto::access_method::AccessMethod::Socks5local(proto::Socks5Local::from(
+ config,
+ ))
+ }
+ CustomProxy::Socks5Remote(config) => {
+ proto::access_method::AccessMethod::Socks5remote(proto::Socks5Remote::from(
+ config,
+ ))
}
- };
-
- proto::AccessMethod {
- access_method: Some(access_method),
}
}
}
diff --git a/mullvad-management-interface/src/types/conversions/net.rs b/mullvad-management-interface/src/types/conversions/net.rs
index 9a873e82ab..80648cbf8d 100644
--- a/mullvad-management-interface/src/types/conversions/net.rs
+++ b/mullvad-management-interface/src/types/conversions/net.rs
@@ -236,18 +236,21 @@ mod proxy {
type Error = FromProtobufTypeError;
fn try_from(value: proto::Socks5Remote) -> Result<Self, Self::Error> {
- let proto::Socks5Remote { ip, port, auth } = value;
- let ip = ip.parse::<Ipv4Addr>().map_err(|_| {
+ let ip = value.ip.parse::<Ipv4Addr>().map_err(|_| {
FromProtobufTypeError::InvalidArgument(
"Could not parse Socks5 (remote) message from protobuf",
)
})?;
- let port = port as u16;
-
- Ok(match auth.map(SocksAuth::from) {
- Some(auth) => Socks5Remote::new_with_authentication((ip, port), auth),
+ let port = value.port as u16;
+ let socks = match value.auth {
+ Some(credentials) => {
+ let auth = SocksAuth::try_from(credentials)?;
+ Socks5Remote::new_with_authentication((ip, port), auth)
+ }
None => Socks5Remote::new((ip, port)),
- })
+ };
+
+ Ok(socks)
}
}
@@ -271,37 +274,58 @@ mod proxy {
impl From<CustomProxy> for proto::CustomProxy {
fn from(value: CustomProxy) -> Self {
- let proxy_method = match value {
- CustomProxy::Shadowsocks(ss) => {
- proto::custom_proxy::ProxyMethod::Shadowsocks(proto::Shadowsocks {
- ip: ss.endpoint.ip().to_string(),
- port: ss.endpoint.port() as u32,
- password: ss.password,
- cipher: ss.cipher,
- })
- }
- CustomProxy::Socks5Local(Socks5Local {
- remote_endpoint,
- local_port,
- }) => proto::custom_proxy::ProxyMethod::Socks5local(proto::Socks5Local {
- remote_ip: remote_endpoint.address.ip().to_string(),
- remote_port: remote_endpoint.address.port() as u32,
- remote_transport_protocol: i32::from(proto::TransportProtocol::from(
- remote_endpoint.protocol,
- )),
- local_port: local_port as u32,
+ proto::CustomProxy {
+ proxy_method: Some(match value {
+ CustomProxy::Shadowsocks(config) => {
+ proto::custom_proxy::ProxyMethod::Shadowsocks(proto::Shadowsocks::from(
+ config,
+ ))
+ }
+ CustomProxy::Socks5Local(config) => {
+ proto::custom_proxy::ProxyMethod::Socks5local(proto::Socks5Local::from(
+ config,
+ ))
+ }
+ CustomProxy::Socks5Remote(config) => {
+ proto::custom_proxy::ProxyMethod::Socks5remote(proto::Socks5Remote::from(
+ config,
+ ))
+ }
}),
- CustomProxy::Socks5Remote(Socks5Remote { endpoint, auth }) => {
- proto::custom_proxy::ProxyMethod::Socks5remote(proto::Socks5Remote {
- ip: endpoint.ip().to_string(),
- port: endpoint.port() as u32,
- auth: auth.map(proto::SocksAuth::from),
- })
- }
- };
+ }
+ }
+ }
- proto::CustomProxy {
- proxy_method: Some(proxy_method),
+ impl From<Shadowsocks> for proto::Shadowsocks {
+ fn from(value: Shadowsocks) -> Self {
+ proto::Shadowsocks {
+ ip: value.endpoint.ip().to_string(),
+ port: value.endpoint.port() as u32,
+ password: value.password,
+ cipher: value.cipher,
+ }
+ }
+ }
+
+ impl From<Socks5Local> for proto::Socks5Local {
+ fn from(value: Socks5Local) -> Self {
+ proto::Socks5Local {
+ remote_ip: value.remote_endpoint.address.ip().to_string(),
+ remote_port: value.remote_endpoint.address.port() as u32,
+ remote_transport_protocol: i32::from(proto::TransportProtocol::from(
+ value.remote_endpoint.protocol,
+ )),
+ local_port: value.local_port as u32,
+ }
+ }
+ }
+
+ impl From<Socks5Remote> for proto::Socks5Remote {
+ fn from(value: Socks5Remote) -> Self {
+ proto::Socks5Remote {
+ ip: value.endpoint.ip().to_string(),
+ port: value.endpoint.port() as u32,
+ auth: value.auth.map(proto::SocksAuth::from),
}
}
}
@@ -309,18 +333,22 @@ mod proxy {
impl From<SocksAuth> for proto::SocksAuth {
fn from(value: SocksAuth) -> Self {
proto::SocksAuth {
- username: value.username,
- password: value.password,
+ username: value.username().to_string(),
+ password: value.password().to_string(),
}
}
}
- impl From<proto::SocksAuth> for SocksAuth {
- fn from(value: proto::SocksAuth) -> Self {
- Self {
- username: value.username,
- password: value.password,
- }
+ impl TryFrom<proto::SocksAuth> for SocksAuth {
+ type Error = FromProtobufTypeError;
+
+ fn try_from(value: proto::SocksAuth) -> Result<Self, Self::Error> {
+ SocksAuth::new(value.username, value.password).map_err(|_| {
+ FromProtobufTypeError::InvalidArgument(
+ "Failed to parse Socks5 with authentication. \
+ Make sure the credentials are valid.",
+ )
+ })
}
}
}
diff --git a/talpid-openvpn/src/lib.rs b/talpid-openvpn/src/lib.rs
index 04f94a1a84..f47072a9f6 100644
--- a/talpid-openvpn/src/lib.rs
+++ b/talpid-openvpn/src/lib.rs
@@ -528,8 +528,8 @@ impl<C: OpenVpnBuilder + Send + 'static> OpenVpnMonitor<C> {
if let Some(CustomProxy::Socks5Remote(ref remote_proxy)) = proxy_settings {
if let Some(ref proxy_auth) = remote_proxy.auth {
return Ok(Some(Self::create_credentials_file(
- &proxy_auth.username,
- &proxy_auth.password,
+ proxy_auth.username(),
+ proxy_auth.password(),
)?));
}
}
diff --git a/talpid-types/src/net/proxy.rs b/talpid-types/src/net/proxy.rs
index 5c16f19a7f..d88afdf6c2 100644
--- a/talpid-types/src/net/proxy.rs
+++ b/talpid-types/src/net/proxy.rs
@@ -4,6 +4,13 @@ use std::{fmt, net::SocketAddr};
use super::TransportProtocol;
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ /// Validation of SOCKS5 username or password failed.
+ #[error(display = "Invalid SOCKS5 authentication credentials: {}", _0)]
+ InvalidSocksAuthValues(&'static str),
+}
+
/// Types of bridges that can be used to proxy a connection to a tunnel
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -82,10 +89,83 @@ pub struct Socks5Remote {
pub auth: Option<SocksAuth>,
}
+/// A valid SOCKS5 username/password authentication according to
+/// RFC 1929: <https://datatracker.ietf.org/doc/html/rfc1929>.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct SocksAuth {
- pub username: String,
- pub password: String,
+ username: String,
+ password: String,
+}
+
+impl SocksAuth {
+ /// Validate a SOCKS5 username/password based authentication.
+ ///
+ /// # Examples
+ ///
+ /// A valid username and password both have to be between 1 and 255 bytes.
+ ///
+ /// ```
+ /// use talpid_types::net::proxy::SocksAuth;
+ ///
+ /// let valid_auth = SocksAuth::new("FooBar".to_string(), "hunter2".to_string());
+ /// assert!(valid_auth.is_ok());
+ /// ```
+ ///
+ /// An empty username or password is not valid.
+ ///
+ /// ```
+ /// use talpid_types::net::proxy::SocksAuth;
+ ///
+ /// // An empty username is not a valid username.
+ /// let invalid_username = SocksAuth::new("".to_string(), "hunter2".to_string());
+ /// assert!(invalid_username.is_err());
+ ///
+ /// // Likeweise, an empty password is not a valid password.
+ /// let invalid_password = SocksAuth::new("FooBar".to_string(), "".to_string());
+ /// assert!(invalid_password.is_err());
+ /// ```
+ ///
+ /// The upper limit for a valid username and password is 255 bytes
+ ///
+ /// ```
+ /// use talpid_types::net::proxy::SocksAuth;
+ ///
+ /// let max_valid_username = SocksAuth::new("x".repeat(255), "hunter2".to_string());
+ /// assert!(max_valid_username.is_ok());
+ ///
+ /// let too_long_username = SocksAuth::new("x".repeat(256), "hunter2".to_string());
+ /// assert!(too_long_username.is_err());
+ ///
+ /// let max_valid_password = SocksAuth::new("FooBar".to_string(), "x".repeat(255));
+ /// assert!(max_valid_username.is_ok());
+ ///
+ /// let too_long_password = SocksAuth::new("FooBar".to_string(), "x".repeat(256));
+ /// assert!(too_long_username.is_err());
+ /// ```
+ pub fn new(username: String, password: String) -> Result<Self, Error> {
+ if !(1..=255).contains(&password.as_bytes().len()) {
+ return Err(Error::InvalidSocksAuthValues(
+ "Password length should between 1 and 255 bytes",
+ ));
+ }
+ if !(1..=255).contains(&username.as_bytes().len()) {
+ return Err(Error::InvalidSocksAuthValues(
+ "Username length should between 1 and 255 bytes",
+ ));
+ }
+
+ Ok(SocksAuth { username, password })
+ }
+
+ /// Read the username.
+ pub fn username(&self) -> &str {
+ &self.username
+ }
+
+ /// Read the password.
+ pub fn password(&self) -> &str {
+ &self.password
+ }
}
impl Shadowsocks {