From e80e4d7c86ef7d37efc4325b9040206066016e79 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Sun, 28 Jul 2024 11:02:36 -0500 Subject: [PATCH] feat: add optional `CryptoProvider` to the client `Config` It adds a new field to the client `Config, expecting the `CryptoProvider` from the user. It uses aws-lc-rs or ring providers by default if any of these features are enabled. It's based on the suggestion comment at #135, reference: https://github.com/bitcoindevkit/rust-electrum-client/pull/135#issuecomment-2244180324 --- src/client.rs | 26 ++++++++++++++++++++++++++ src/config.rs | 37 +++++++++++++++++++++++++++++++++++++ src/raw_client.rs | 45 +++++++++++++++++++++++++++++++++++++++++---- src/types.rs | 5 +++++ 4 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/client.rs b/src/client.rs index 81cbd38..98bf7c3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -110,13 +110,39 @@ impl ClientType { pub fn from_config(url: &str, config: &Config) -> Result { if url.starts_with("ssl://") { let url = url.replacen("ssl://", "", 1); + #[cfg(all( + any( + feature = "default", + feature = "use-rustls", + feature = "use-rustls-ring" + ), + not(feature = "use-openssl") + ))] let client = match config.socks5() { Some(socks5) => RawClient::new_proxy_ssl( url.as_str(), config.validate_domain(), socks5, config.timeout(), + config.crypto_provider(), )?, + None => RawClient::new_ssl( + url.as_str(), + config.validate_domain(), + config.timeout(), + config.crypto_provider(), + )?, + }; + + #[cfg(feature = "openssl")] + let client = match config.socks5() { + Some(socks5) => RawClient::new_proxy_ssl( + url.as_str(), + config.validate_domain(), + socks5, + config.timeout(), + )?, + None => { RawClient::new_ssl(url.as_str(), config.validate_domain(), config.timeout())? } diff --git a/src/config.rs b/src/config.rs index 47da1f9..490f25f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,8 @@ use std::time::Duration; +#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))] +use rustls::crypto::CryptoProvider; + /// Configuration for an electrum client /// /// Refer to [`Client::from_config`] and [`ClientType::from_config`]. @@ -12,6 +15,16 @@ pub struct Config { socks5: Option, /// timeout in seconds, default None (depends on TcpStream default) timeout: Option, + #[cfg(all( + any( + feature = "default", + feature = "use-rustls", + feature = "use-rustls-ring" + ), + not(feature = "use-openssl") + ))] + /// An optional [`CryptoProvider`] for users that don't want either default `aws-lc-rs` or `ring` providers + crypto_provider: Option, /// number of retry if any error, default 1 retry: u8, /// when ssl, validate the domain, default true @@ -60,6 +73,13 @@ impl ConfigBuilder { self } + #[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))] + /// Sets the custom [`CryptoProvider`]. + pub fn crypto_provider(mut self, crypto_provider: Option) -> Self { + self.config.crypto_provider = crypto_provider; + self + } + /// Sets the retry attempts number pub fn retry(mut self, retry: u8) -> Self { self.config.retry = retry; @@ -135,6 +155,21 @@ impl Config { pub fn builder() -> ConfigBuilder { ConfigBuilder::new() } + + #[cfg(all( + any( + feature = "default", + feature = "use-rustls", + feature = "use-rustls-ring" + ), + not(feature = "use-openssl") + ))] + /// Get the configuration for `crypto_provider` + /// + /// Set this with [`ConfigBuilder::crypto_provider`] + pub fn crypto_provider(&self) -> Option<&CryptoProvider> { + self.crypto_provider.as_ref() + } } impl Default for Config { @@ -144,6 +179,8 @@ impl Default for Config { timeout: None, retry: 1, validate_domain: true, + #[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))] + crypto_provider: None, } } } diff --git a/src/raw_client.rs b/src/raw_client.rs index 1b83c73..35ee83d 100644 --- a/src/raw_client.rs +++ b/src/raw_client.rs @@ -31,11 +31,17 @@ use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode}; not(feature = "use-openssl") ))] use rustls::{ + crypto::CryptoProvider, pki_types::ServerName, pki_types::{Der, TrustAnchor}, ClientConfig, ClientConnection, RootCertStore, StreamOwned, }; +#[cfg(feature = "use-rustls")] +use rustls::crypto::aws_lc_rs::default_provider; +#[cfg(feature = "use-rustls-ring")] +use rustls::crypto::ring::default_provider; + #[cfg(any(feature = "default", feature = "proxy"))] use crate::socks::{Socks5Stream, TargetAddr, ToTargetAddr}; @@ -368,6 +374,7 @@ impl RawClient { socket_addrs: A, validate_domain: bool, timeout: Option, + crypto_provider: Option<&CryptoProvider>, ) -> Result { debug!( "new_ssl socket_addrs.domain():{:?} validate_domain:{} timeout:{:?}", @@ -378,16 +385,27 @@ impl RawClient { if validate_domain { socket_addrs.domain().ok_or(Error::MissingDomain)?; } + + let crypto_provider = match crypto_provider { + Some(provider) => provider.to_owned(), + + #[cfg(feature = "use-rustls")] + None => default_provider(), + + #[cfg(feature = "use-rustls-ring")] + None => default_provider(), + }; + match timeout { Some(timeout) => { let stream = connect_with_total_timeout(socket_addrs.clone(), timeout)?; stream.set_read_timeout(Some(timeout))?; stream.set_write_timeout(Some(timeout))?; - Self::new_ssl_from_stream(socket_addrs, validate_domain, stream) + Self::new_ssl_from_stream(socket_addrs, validate_domain, stream, crypto_provider) } None => { let stream = TcpStream::connect(socket_addrs.clone())?; - Self::new_ssl_from_stream(socket_addrs, validate_domain, stream) + Self::new_ssl_from_stream(socket_addrs, validate_domain, stream, crypto_provider) } } } @@ -397,10 +415,13 @@ impl RawClient { socket_addr: A, validate_domain: bool, tcp_stream: TcpStream, + crypto_provider: CryptoProvider, ) -> Result { use std::convert::TryFrom; - let builder = ClientConfig::builder(); + let builder = ClientConfig::builder_with_provider(crypto_provider.into()) + .with_safe_default_protocol_versions() + .map_err(|e| Error::CouldNotBuildWithSafeDefaultVersion(e))?; let config = if validate_domain { socket_addr.domain().ok_or(Error::MissingDomain)?; @@ -480,6 +501,7 @@ impl RawClient { validate_domain: bool, proxy: &crate::Socks5Config, timeout: Option, + crypto_provider: Option<&CryptoProvider>, ) -> Result, Error> { let target = target_addr.to_target_addr()?; @@ -496,7 +518,22 @@ impl RawClient { stream.get_mut().set_read_timeout(timeout)?; stream.get_mut().set_write_timeout(timeout)?; - RawClient::new_ssl_from_stream(target, validate_domain, stream.into_inner()) + let crypto_provider = match crypto_provider { + Some(provider) => provider.to_owned(), + + #[cfg(feature = "use-rustls")] + None => default_provider(), + + #[cfg(feature = "use-rustls-ring")] + None => default_provider(), + }; + + RawClient::new_ssl_from_stream( + target, + validate_domain, + stream.into_inner(), + crypto_provider, + ) } } diff --git a/src/types.rs b/src/types.rs index 6ecd692..382fca5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -318,6 +318,9 @@ pub enum Error { #[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))] /// Could not create a rustls client connection CouldNotCreateConnection(rustls::Error), + #[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))] + /// Could not create the `ClientConfig` with safe default protocol version + CouldNotBuildWithSafeDefaultVersion(rustls::Error), #[cfg(feature = "use-openssl")] /// Invalid OpenSSL method used @@ -365,6 +368,8 @@ impl Display for Error { Error::MissingDomain => f.write_str("Missing domain while it was explicitly asked to validate it"), Error::CouldntLockReader => f.write_str("Couldn't take a lock on the reader mutex. This means that there's already another reader thread is running"), Error::Mpsc => f.write_str("Broken IPC communication channel: the other thread probably has exited"), + #[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))] + Error::CouldNotBuildWithSafeDefaultVersion(_) => f.write_str("Couldn't build the `ClientConfig` with safe default version"), } } }