Skip to content

Commit 25d9c6b

Browse files
committed
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.
1 parent 0b97659 commit 25d9c6b

File tree

4 files changed

+133
-12
lines changed

4 files changed

+133
-12
lines changed

src/client.rs

+23
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,36 @@ impl ClientType {
110110
pub fn from_config(url: &str, config: &Config) -> Result<Self, Error> {
111111
if url.starts_with("ssl://") {
112112
let url = url.replacen("ssl://", "", 1);
113+
114+
#[cfg(all(
115+
any(feature = "use-rustls", feature = "use-rustls-ring"),
116+
not(feature = "use-openssl")
117+
))]
113118
let client = match config.socks5() {
114119
Some(socks5) => RawClient::new_proxy_ssl(
115120
url.as_str(),
116121
config.validate_domain(),
117122
socks5,
118123
config.timeout(),
124+
config.crypto_provider(),
125+
)?,
126+
None => RawClient::new_ssl(
127+
url.as_str(),
128+
config.validate_domain(),
129+
config.timeout(),
130+
config.crypto_provider(),
119131
)?,
132+
};
133+
134+
#[cfg(feature = "openssl")]
135+
let client = match config.socks5() {
136+
Some(socks5) => RawClient::new_proxy_ssl(
137+
url.as_str(),
138+
config.validate_domain(),
139+
socks5,
140+
config.timeout(),
141+
)?,
142+
120143
None => {
121144
RawClient::new_ssl(url.as_str(), config.validate_domain(), config.timeout())?
122145
}

src/config.rs

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use std::time::Duration;
22

3+
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
4+
use rustls::crypto::CryptoProvider;
5+
36
/// Configuration for an electrum client
47
///
58
/// Refer to [`Client::from_config`] and [`ClientType::from_config`].
@@ -12,6 +15,9 @@ pub struct Config {
1215
socks5: Option<Socks5Config>,
1316
/// timeout in seconds, default None (depends on TcpStream default)
1417
timeout: Option<Duration>,
18+
/// An optional [`CryptoProvider`] for users that don't want either default `aws-lc-rs` or `ring` providers
19+
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
20+
crypto_provider: Option<CryptoProvider>,
1521
/// number of retry if any error, default 1
1622
retry: u8,
1723
/// when ssl, validate the domain, default true
@@ -60,6 +66,13 @@ impl ConfigBuilder {
6066
self
6167
}
6268

69+
/// Sets the custom [`CryptoProvider`].
70+
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
71+
pub fn crypto_provider(mut self, crypto_provider: Option<CryptoProvider>) -> Self {
72+
self.config.crypto_provider = crypto_provider;
73+
self
74+
}
75+
6376
/// Sets the retry attempts number
6477
pub fn retry(mut self, retry: u8) -> Self {
6578
self.config.retry = retry;
@@ -135,6 +148,14 @@ impl Config {
135148
pub fn builder() -> ConfigBuilder {
136149
ConfigBuilder::new()
137150
}
151+
152+
/// Get the configuration for `crypto_provider`
153+
///
154+
/// Set this with [`ConfigBuilder::crypto_provider`]
155+
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
156+
pub fn crypto_provider(&self) -> Option<&CryptoProvider> {
157+
self.crypto_provider.as_ref()
158+
}
138159
}
139160

140161
impl Default for Config {
@@ -144,6 +165,8 @@ impl Default for Config {
144165
timeout: None,
145166
retry: 1,
146167
validate_domain: true,
168+
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
169+
crypto_provider: None,
147170
}
148171
}
149172
}

src/raw_client.rs

+82-12
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,11 @@ use bitcoin::{Script, Txid};
2323
use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};
2424

2525
#[cfg(all(
26-
any(
27-
feature = "default",
28-
feature = "use-rustls",
29-
feature = "use-rustls-ring"
30-
),
26+
any(feature = "use-rustls", feature = "use-rustls-ring"),
3127
not(feature = "use-openssl")
3228
))]
3329
use rustls::{
30+
crypto::CryptoProvider,
3431
pki_types::ServerName,
3532
pki_types::{Der, TrustAnchor},
3633
ClientConfig, ClientConnection, RootCertStore, StreamOwned,
@@ -368,7 +365,13 @@ impl RawClient<ElectrumSslStream> {
368365
socket_addrs: A,
369366
validate_domain: bool,
370367
timeout: Option<Duration>,
368+
crypto_provider: Option<&CryptoProvider>,
371369
) -> Result<Self, Error> {
370+
#[cfg(feature = "use-rustls")]
371+
use rustls::crypto::aws_lc_rs::default_provider;
372+
#[cfg(feature = "use-rustls-ring")]
373+
use rustls::crypto::ring::default_provider;
374+
372375
debug!(
373376
"new_ssl socket_addrs.domain():{:?} validate_domain:{} timeout:{:?}",
374377
socket_addrs.domain(),
@@ -378,16 +381,27 @@ impl RawClient<ElectrumSslStream> {
378381
if validate_domain {
379382
socket_addrs.domain().ok_or(Error::MissingDomain)?;
380383
}
384+
385+
let crypto_provider = match crypto_provider {
386+
Some(provider) => provider.to_owned(),
387+
388+
#[cfg(feature = "use-rustls")]
389+
None => default_provider(),
390+
391+
#[cfg(feature = "use-rustls-ring")]
392+
None => default_provider(),
393+
};
394+
381395
match timeout {
382396
Some(timeout) => {
383397
let stream = connect_with_total_timeout(socket_addrs.clone(), timeout)?;
384398
stream.set_read_timeout(Some(timeout))?;
385399
stream.set_write_timeout(Some(timeout))?;
386-
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream)
400+
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream, crypto_provider)
387401
}
388402
None => {
389403
let stream = TcpStream::connect(socket_addrs.clone())?;
390-
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream)
404+
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream, crypto_provider)
391405
}
392406
}
393407
}
@@ -397,10 +411,13 @@ impl RawClient<ElectrumSslStream> {
397411
socket_addr: A,
398412
validate_domain: bool,
399413
tcp_stream: TcpStream,
414+
crypto_provider: CryptoProvider,
400415
) -> Result<Self, Error> {
401416
use std::convert::TryFrom;
402417

403-
let builder = ClientConfig::builder();
418+
let builder = ClientConfig::builder_with_provider(crypto_provider.into())
419+
.with_safe_default_protocol_versions()
420+
.map_err(Error::CouldNotBuildWithSafeDefaultVersion)?;
404421

405422
let config = if validate_domain {
406423
socket_addr.domain().ok_or(Error::MissingDomain)?;
@@ -441,6 +458,7 @@ impl RawClient<ElectrumSslStream> {
441458
#[cfg(any(feature = "default", feature = "proxy"))]
442459
/// Transport type used to establish a connection to a server through a socks proxy
443460
pub type ElectrumProxyStream = Socks5Stream;
461+
444462
#[cfg(any(feature = "default", feature = "proxy"))]
445463
impl RawClient<ElectrumProxyStream> {
446464
/// Creates a new socks client and tries to connect to `target_addr` using `proxy_addr` as a
@@ -467,14 +485,66 @@ impl RawClient<ElectrumProxyStream> {
467485
Ok(stream.into())
468486
}
469487

470-
#[cfg(any(
471-
feature = "use-openssl",
472-
feature = "use-rustls",
473-
feature = "use-rustls-ring"
488+
#[cfg(all(
489+
any(
490+
feature = "default",
491+
feature = "use-rustls",
492+
feature = "use-rustls-ring"
493+
),
494+
not(feature = "use-openssl")
474495
))]
475496
/// Creates a new TLS client that connects to `target_addr` using `proxy_addr` as a socks proxy
476497
/// server. The DNS resolution of `target_addr`, if required, is done through the proxy. This
477498
/// allows to specify, for instance, `.onion` addresses.
499+
pub fn new_proxy_ssl<T: ToTargetAddr>(
500+
target_addr: T,
501+
validate_domain: bool,
502+
proxy: &crate::Socks5Config,
503+
timeout: Option<Duration>,
504+
crypto_provider: Option<&CryptoProvider>,
505+
) -> Result<RawClient<ElectrumSslStream>, Error> {
506+
#[cfg(feature = "use-rustls")]
507+
use rustls::crypto::aws_lc_rs::default_provider;
508+
#[cfg(feature = "use-rustls-ring")]
509+
use rustls::crypto::ring::default_provider;
510+
511+
let target = target_addr.to_target_addr()?;
512+
513+
let mut stream = match proxy.credentials.as_ref() {
514+
Some(cred) => Socks5Stream::connect_with_password(
515+
&proxy.addr,
516+
target_addr,
517+
&cred.username,
518+
&cred.password,
519+
timeout,
520+
)?,
521+
None => Socks5Stream::connect(&proxy.addr, target.clone(), timeout)?,
522+
};
523+
stream.get_mut().set_read_timeout(timeout)?;
524+
stream.get_mut().set_write_timeout(timeout)?;
525+
526+
let crypto_provider = match crypto_provider {
527+
Some(provider) => provider.to_owned(),
528+
529+
#[cfg(feature = "use-rustls")]
530+
None => default_provider(),
531+
532+
#[cfg(feature = "use-rustls-ring")]
533+
None => default_provider(),
534+
};
535+
536+
RawClient::new_ssl_from_stream(
537+
target,
538+
validate_domain,
539+
stream.into_inner(),
540+
crypto_provider,
541+
)
542+
}
543+
544+
#[cfg(feature = "use-openssl")]
545+
/// Creates a new TLS client that connects to `target_addr` using `proxy_addr` as a socks proxy
546+
/// server. The DNS resolution of `target_addr`, if required, is done through the proxy. This
547+
/// allows to specify, for instance, `.onion` addresses.
478548
pub fn new_proxy_ssl<T: ToTargetAddr>(
479549
target_addr: T,
480550
validate_domain: bool,

src/types.rs

+5
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ pub enum Error {
318318
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
319319
/// Could not create a rustls client connection
320320
CouldNotCreateConnection(rustls::Error),
321+
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
322+
/// Could not create the `ClientConfig` with safe default protocol version
323+
CouldNotBuildWithSafeDefaultVersion(rustls::Error),
321324

322325
#[cfg(feature = "use-openssl")]
323326
/// Invalid OpenSSL method used
@@ -365,6 +368,8 @@ impl Display for Error {
365368
Error::MissingDomain => f.write_str("Missing domain while it was explicitly asked to validate it"),
366369
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"),
367370
Error::Mpsc => f.write_str("Broken IPC communication channel: the other thread probably has exited"),
371+
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
372+
Error::CouldNotBuildWithSafeDefaultVersion(_) => f.write_str("Couldn't build the `ClientConfig` with safe default version"),
368373
}
369374
}
370375
}

0 commit comments

Comments
 (0)