Skip to content

Commit

Permalink
feat: add optional CryptoProvider to the client Config
Browse files Browse the repository at this point in the history
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 bitcoindevkit#135.
  • Loading branch information
oleonardolima committed Aug 6, 2024
1 parent 0b97659 commit 25d9c6b
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 12 deletions.
23 changes: 23 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,36 @@ impl ClientType {
pub fn from_config(url: &str, config: &Config) -> Result<Self, Error> {
if url.starts_with("ssl://") {
let url = url.replacen("ssl://", "", 1);

#[cfg(all(
any(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())?
}
Expand Down
23 changes: 23 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -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`].
Expand All @@ -12,6 +15,9 @@ pub struct Config {
socks5: Option<Socks5Config>,
/// timeout in seconds, default None (depends on TcpStream default)
timeout: Option<Duration>,
/// An optional [`CryptoProvider`] for users that don't want either default `aws-lc-rs` or `ring` providers
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
crypto_provider: Option<CryptoProvider>,
/// number of retry if any error, default 1
retry: u8,
/// when ssl, validate the domain, default true
Expand Down Expand Up @@ -60,6 +66,13 @@ impl ConfigBuilder {
self
}

/// Sets the custom [`CryptoProvider`].
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
pub fn crypto_provider(mut self, crypto_provider: Option<CryptoProvider>) -> 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;
Expand Down Expand Up @@ -135,6 +148,14 @@ impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}

/// Get the configuration for `crypto_provider`
///
/// Set this with [`ConfigBuilder::crypto_provider`]
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
pub fn crypto_provider(&self) -> Option<&CryptoProvider> {
self.crypto_provider.as_ref()
}
}

impl Default for Config {
Expand All @@ -144,6 +165,8 @@ impl Default for Config {
timeout: None,
retry: 1,
validate_domain: true,
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
crypto_provider: None,
}
}
}
94 changes: 82 additions & 12 deletions src/raw_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,11 @@ use bitcoin::{Script, Txid};
use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};

#[cfg(all(
any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
),
any(feature = "use-rustls", feature = "use-rustls-ring"),
not(feature = "use-openssl")
))]
use rustls::{
crypto::CryptoProvider,
pki_types::ServerName,
pki_types::{Der, TrustAnchor},
ClientConfig, ClientConnection, RootCertStore, StreamOwned,
Expand Down Expand Up @@ -368,7 +365,13 @@ impl RawClient<ElectrumSslStream> {
socket_addrs: A,
validate_domain: bool,
timeout: Option<Duration>,
crypto_provider: Option<&CryptoProvider>,
) -> Result<Self, Error> {
#[cfg(feature = "use-rustls")]
use rustls::crypto::aws_lc_rs::default_provider;
#[cfg(feature = "use-rustls-ring")]
use rustls::crypto::ring::default_provider;

debug!(
"new_ssl socket_addrs.domain():{:?} validate_domain:{} timeout:{:?}",
socket_addrs.domain(),
Expand All @@ -378,16 +381,27 @@ impl RawClient<ElectrumSslStream> {
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)
}
}
}
Expand All @@ -397,10 +411,13 @@ impl RawClient<ElectrumSslStream> {
socket_addr: A,
validate_domain: bool,
tcp_stream: TcpStream,
crypto_provider: CryptoProvider,
) -> Result<Self, Error> {
use std::convert::TryFrom;

let builder = ClientConfig::builder();
let builder = ClientConfig::builder_with_provider(crypto_provider.into())
.with_safe_default_protocol_versions()
.map_err(Error::CouldNotBuildWithSafeDefaultVersion)?;

let config = if validate_domain {
socket_addr.domain().ok_or(Error::MissingDomain)?;
Expand Down Expand Up @@ -441,6 +458,7 @@ impl RawClient<ElectrumSslStream> {
#[cfg(any(feature = "default", feature = "proxy"))]
/// Transport type used to establish a connection to a server through a socks proxy
pub type ElectrumProxyStream = Socks5Stream;

#[cfg(any(feature = "default", feature = "proxy"))]
impl RawClient<ElectrumProxyStream> {
/// Creates a new socks client and tries to connect to `target_addr` using `proxy_addr` as a
Expand All @@ -467,14 +485,66 @@ impl RawClient<ElectrumProxyStream> {
Ok(stream.into())
}

#[cfg(any(
feature = "use-openssl",
feature = "use-rustls",
feature = "use-rustls-ring"
#[cfg(all(
any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
),
not(feature = "use-openssl")
))]
/// 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<T: ToTargetAddr>(
target_addr: T,
validate_domain: bool,
proxy: &crate::Socks5Config,
timeout: Option<Duration>,
crypto_provider: Option<&CryptoProvider>,
) -> Result<RawClient<ElectrumSslStream>, Error> {
#[cfg(feature = "use-rustls")]
use rustls::crypto::aws_lc_rs::default_provider;
#[cfg(feature = "use-rustls-ring")]
use rustls::crypto::ring::default_provider;

let target = target_addr.to_target_addr()?;

let mut stream = match proxy.credentials.as_ref() {
Some(cred) => Socks5Stream::connect_with_password(
&proxy.addr,
target_addr,
&cred.username,
&cred.password,
timeout,
)?,
None => Socks5Stream::connect(&proxy.addr, target.clone(), timeout)?,
};
stream.get_mut().set_read_timeout(timeout)?;
stream.get_mut().set_write_timeout(timeout)?;

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,
)
}

#[cfg(feature = "use-openssl")]
/// 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<T: ToTargetAddr>(
target_addr: T,
validate_domain: bool,
Expand Down
5 changes: 5 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"),
}
}
}
Expand Down

0 comments on commit 25d9c6b

Please sign in to comment.