From b08c59815d79b240238414c35ef60ece97e6d290 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Fri, 27 Nov 2020 15:13:35 +0100 Subject: [PATCH] Add support for TLS over socks5 proxy --- src/client.rs | 20 +++++++++--------- src/raw_client.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++--- src/types.rs | 2 -- 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/client.rs b/src/client.rs index cc1b182..afa3ae9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -87,17 +87,20 @@ macro_rules! impl_inner_call { } impl ClientType { - /// Constructor that supports multiple backends and allows configuration through + /// Constructor that supports multiple backends and allows configuration through /// the [Config] pub fn from_config(url: &str, config: &Config) -> Result { if url.starts_with("ssl://") { - if config.socks5().is_some() { - return Err(Error::SSLOverSocks5); - } - let url = url.replacen("ssl://", "", 1); - let client = - RawClient::new_ssl(url.as_str(), config.validate_domain(), config.timeout())?; + let client = match config.socks5() { + Some(socks5) => { + RawClient::new_proxy_ssl(url.as_str(), config.validate_domain(), socks5)? + } + None => { + RawClient::new_ssl(url.as_str(), config.validate_domain(), config.timeout())? + } + }; + Ok(ClientType::SSL(client)) } else { let url = url.replacen("tcp://", "", 1); @@ -127,9 +130,6 @@ impl Client { /// Generic constructor that supports multiple backends and allows configuration through /// the [Config] - /// - /// **NOTE**: SSL-over-socks5 is currently not supported and will generate a runtime error. - /// pub fn from_config(url: &str, config: Config) -> Result { let client_type = RwLock::new(ClientType::from_config(url, &config)?); diff --git a/src/raw_client.rs b/src/raw_client.rs index 21d92da..254cea0 100644 --- a/src/raw_client.rs +++ b/src/raw_client.rs @@ -27,7 +27,7 @@ use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode}; use rustls::{ClientConfig, ClientSession, StreamOwned}; #[cfg(any(feature = "default", feature = "proxy"))] -use socks::{Socks5Stream, ToTargetAddr}; +use socks::{Socks5Stream, TargetAddr, ToTargetAddr}; use stream::ClonableStream; @@ -75,6 +75,28 @@ impl ToSocketAddrsDomain for (&str, u16) { } } +impl ToSocketAddrsDomain for TargetAddr { + fn domain(&self) -> Option<&str> { + match self { + TargetAddr::Ip(_) => None, + TargetAddr::Domain(domain, _) => Some(domain.as_str()), + } + } +} + +macro_rules! impl_to_socket_addrs_domain { + ( $ty:ty ) => { + impl ToSocketAddrsDomain for $ty {} + }; +} + +impl_to_socket_addrs_domain!(std::net::SocketAddr); +impl_to_socket_addrs_domain!(std::net::SocketAddrV4); +impl_to_socket_addrs_domain!(std::net::SocketAddrV6); +impl_to_socket_addrs_domain!((std::net::IpAddr, u16)); +impl_to_socket_addrs_domain!((std::net::Ipv4Addr, u16)); +impl_to_socket_addrs_domain!((std::net::Ipv6Addr, u16)); + /// Instance of an Electrum client /// /// A `Client` maintains a constant connection with an Electrum server and exposes methods to @@ -327,8 +349,8 @@ impl RawClient { pub type ElectrumProxyStream = Socks5Stream; #[cfg(any(feature = "default", feature = "proxy"))] impl RawClient { - /// Creates a new socks client and tries to connect to `target_addr` using `proxy_addr` as an - /// unauthenticated socks proxy server. The DNS resolution of `target_addr`, if required, is done + /// Creates a new socks client and tries to connect to `target_addr` using `proxy_addr` as a + /// socks proxy server. The DNS resolution of `target_addr`, if required, is done /// through the proxy. This allows to specify, for instance, `.onion` addresses. pub fn new_proxy( target_addr: T, @@ -346,6 +368,30 @@ impl RawClient { Ok(stream.into()) } + + #[cfg(any(feature = "use-openssl", feature = "use-rustls"))] + /// Creates a new TLS client that connects to `target_addr` using `proxy_addr` as a socks proxy + /// server. The DNS resolution of `target_addr`, if required, is done through the proxy. This + /// allows to specify, for instance, `.onion` addresses. + pub fn new_proxy_ssl( + target_addr: T, + validate_domain: bool, + proxy: &crate::Socks5Config, + ) -> Result, Error> { + let target = target_addr.to_target_addr()?; + + let stream = match proxy.credentials.as_ref() { + Some(cred) => Socks5Stream::connect_with_password( + &proxy.addr, + target_addr, + &cred.username, + &cred.password, + )?, + None => Socks5Stream::connect(&proxy.addr, target.clone())?, + }; + + RawClient::new_ssl_from_stream(target, validate_domain, stream.into_inner()) + } } #[derive(Debug)] diff --git a/src/types.rs b/src/types.rs index 0295e24..a4b45df 100644 --- a/src/types.rs +++ b/src/types.rs @@ -287,8 +287,6 @@ pub enum Error { InvalidDNSNameError(String), /// Missing domain while it was explicitly asked to validate it MissingDomain, - /// SSL over a socks5 proxy is currently not supported - SSLOverSocks5, /// Made one or multiple attempts, always in Error AllAttemptsErrored(Vec), /// There was an io error reading the socket, to be shared between threads