diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-01-11 15:45:51 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-01-12 13:52:19 +0100 |
| commit | 8837083fbc794317ea4d60faf3233c3a40879327 (patch) | |
| tree | d64701ede44860df85aa235bd01c55f274e1b2f5 | |
| parent | 2f173033f482de001420bbf348c426fa5c537604 (diff) | |
| download | mullvadvpn-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.rs | 39 | ||||
| -rw-r--r-- | mullvad-cli/Cargo.toml | 3 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/api_access.rs | 102 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/bridge.rs | 4 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/proxies.rs | 49 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v7.rs | 7 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/access_method.rs | 109 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/net.rs | 116 | ||||
| -rw-r--r-- | talpid-openvpn/src/lib.rs | 4 | ||||
| -rw-r--r-- | talpid-types/src/net/proxy.rs | 84 |
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 { |
