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, reference: bitcoindevkit#135 (comment)
  • Loading branch information
oleonardolima committed Jul 28, 2024
1 parent b415b5c commit 42a4352
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
10 changes: 7 additions & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,14 @@ impl ClientType {
config.validate_domain(),
socks5,
config.timeout(),
config.crypto_provider(),
)?,
None => RawClient::new_ssl(
url.as_str(),
config.validate_domain(),
config.timeout(),
config.crypto_provider(),
)?,
None => {
RawClient::new_ssl(url.as_str(), config.validate_domain(), config.timeout())?
}
};

Ok(ClientType::SSL(client))
Expand Down
21 changes: 21 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::time::Duration;

use rustls::crypto::CryptoProvider;

/// Configuration for an electrum client
///
/// Refer to [`Client::from_config`] and [`ClientType::from_config`].
Expand All @@ -12,6 +14,9 @@ pub struct Config {
socks5: Option<Socks5Config>,
/// timeout in seconds, default None (depends on TcpStream default)
timeout: Option<Duration>,
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
/// An optional [`CryptoProvider`] for users that don't want either default `aws-lc-rs` or `ring` providers
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 +65,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<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 +147,14 @@ impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}

#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
/// 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 {
Expand All @@ -144,6 +164,7 @@ impl Default for Config {
timeout: None,
retry: 1,
validate_domain: true,
crypto_provider: None,
}
}
}
44 changes: 40 additions & 4 deletions src/raw_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ use bitcoin::{Script, Txid};
#[cfg(feature = "use-openssl")]
use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};

#[cfg(feature = "use-rustls")]
use rustls::crypto::aws_lc_rs::default_provider;
#[cfg(feature = "use-rustls-ring")]
use rustls::crypto::ring::default_provider;
#[cfg(all(
any(
feature = "default",
Expand All @@ -31,6 +35,7 @@ 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,
Expand Down Expand Up @@ -368,6 +373,7 @@ impl RawClient<ElectrumSslStream> {
socket_addrs: A,
validate_domain: bool,
timeout: Option<Duration>,
crypto_provider: Option<&CryptoProvider>,
) -> Result<Self, Error> {
debug!(
"new_ssl socket_addrs.domain():{:?} validate_domain:{} timeout:{:?}",
Expand All @@ -378,16 +384,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 +414,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(|e| Error::CouldNotBuildWithSafeDefaultVersion(e))?;

let config = if validate_domain {
socket_addr.domain().ok_or(Error::MissingDomain)?;
Expand Down Expand Up @@ -480,6 +500,7 @@ impl RawClient<ElectrumProxyStream> {
validate_domain: bool,
proxy: &crate::Socks5Config,
timeout: Option<Duration>,
crypto_provider: Option<&CryptoProvider>,
) -> Result<RawClient<ElectrumSslStream>, Error> {
let target = target_addr.to_target_addr()?;

Expand All @@ -496,7 +517,22 @@ impl RawClient<ElectrumProxyStream> {
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,
)
}
}

Expand Down
4 changes: 4 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,7 @@ 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"),
Error::CouldNotBuildWithSafeDefaultVersion(_) => f.write_str("Couldn't build the `ClientConfig` with safe default version"),
}
}
}
Expand Down

0 comments on commit 42a4352

Please sign in to comment.