Skip to content

Commit 68cef00

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, reference: #135 (comment)
1 parent 746a0e6 commit 68cef00

File tree

4 files changed

+170
-7
lines changed

4 files changed

+170
-7
lines changed

src/client.rs

+26
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,39 @@ 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+
#[cfg(all(
114+
any(
115+
feature = "default",
116+
feature = "use-rustls",
117+
feature = "use-rustls-ring"
118+
),
119+
not(feature = "use-openssl")
120+
))]
113121
let client = match config.socks5() {
114122
Some(socks5) => RawClient::new_proxy_ssl(
115123
url.as_str(),
116124
config.validate_domain(),
117125
socks5,
118126
config.timeout(),
127+
config.crypto_provider(),
119128
)?,
129+
None => RawClient::new_ssl(
130+
url.as_str(),
131+
config.validate_domain(),
132+
config.timeout(),
133+
config.crypto_provider(),
134+
)?,
135+
};
136+
137+
#[cfg(feature = "openssl")]
138+
let client = match config.socks5() {
139+
Some(socks5) => RawClient::new_proxy_ssl(
140+
url.as_str(),
141+
config.validate_domain(),
142+
socks5,
143+
config.timeout(),
144+
)?,
145+
120146
None => {
121147
RawClient::new_ssl(url.as_str(), config.validate_domain(), config.timeout())?
122148
}

src/config.rs

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

3+
#[cfg(all(
4+
any(
5+
feature = "default",
6+
feature = "use-rustls",
7+
feature = "use-rustls-ring"
8+
),
9+
not(feature = "use-openssl")
10+
))]
11+
use rustls::crypto::CryptoProvider;
12+
313
/// Configuration for an electrum client
414
///
515
/// Refer to [`Client::from_config`] and [`ClientType::from_config`].
@@ -12,6 +22,16 @@ pub struct Config {
1222
socks5: Option<Socks5Config>,
1323
/// timeout in seconds, default None (depends on TcpStream default)
1424
timeout: Option<Duration>,
25+
/// An optional [`CryptoProvider`] for users that don't want either default `aws-lc-rs` or `ring` providers
26+
#[cfg(all(
27+
any(
28+
feature = "default",
29+
feature = "use-rustls",
30+
feature = "use-rustls-ring"
31+
),
32+
not(feature = "use-openssl")
33+
))]
34+
crypto_provider: Option<CryptoProvider>,
1535
/// number of retry if any error, default 1
1636
retry: u8,
1737
/// when ssl, validate the domain, default true
@@ -60,6 +80,20 @@ impl ConfigBuilder {
6080
self
6181
}
6282

83+
/// Sets the custom [`CryptoProvider`].
84+
#[cfg(all(
85+
any(
86+
feature = "default",
87+
feature = "use-rustls",
88+
feature = "use-rustls-ring"
89+
),
90+
not(feature = "use-openssl")
91+
))]
92+
pub fn crypto_provider(mut self, crypto_provider: Option<CryptoProvider>) -> Self {
93+
self.config.crypto_provider = crypto_provider;
94+
self
95+
}
96+
6397
/// Sets the retry attempts number
6498
pub fn retry(mut self, retry: u8) -> Self {
6599
self.config.retry = retry;
@@ -135,6 +169,21 @@ impl Config {
135169
pub fn builder() -> ConfigBuilder {
136170
ConfigBuilder::new()
137171
}
172+
173+
/// Get the configuration for `crypto_provider`
174+
///
175+
/// Set this with [`ConfigBuilder::crypto_provider`]
176+
#[cfg(all(
177+
any(
178+
feature = "default",
179+
feature = "use-rustls",
180+
feature = "use-rustls-ring"
181+
),
182+
not(feature = "use-openssl")
183+
))]
184+
pub fn crypto_provider(&self) -> Option<&CryptoProvider> {
185+
self.crypto_provider.as_ref()
186+
}
138187
}
139188

140189
impl Default for Config {
@@ -144,6 +193,15 @@ impl Default for Config {
144193
timeout: None,
145194
retry: 1,
146195
validate_domain: true,
196+
#[cfg(all(
197+
any(
198+
feature = "default",
199+
feature = "use-rustls",
200+
feature = "use-rustls-ring"
201+
),
202+
not(feature = "use-openssl")
203+
))]
204+
crypto_provider: None,
147205
}
148206
}
149207
}

src/raw_client.rs

+81-7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};
3131
not(feature = "use-openssl")
3232
))]
3333
use rustls::{
34+
crypto::CryptoProvider,
3435
pki_types::ServerName,
3536
pki_types::{Der, TrustAnchor},
3637
ClientConfig, ClientConnection, RootCertStore, StreamOwned,
@@ -368,7 +369,13 @@ impl RawClient<ElectrumSslStream> {
368369
socket_addrs: A,
369370
validate_domain: bool,
370371
timeout: Option<Duration>,
372+
crypto_provider: Option<&CryptoProvider>,
371373
) -> Result<Self, Error> {
374+
#[cfg(feature = "use-rustls")]
375+
use rustls::crypto::aws_lc_rs::default_provider;
376+
#[cfg(feature = "use-rustls-ring")]
377+
use rustls::crypto::ring::default_provider;
378+
372379
debug!(
373380
"new_ssl socket_addrs.domain():{:?} validate_domain:{} timeout:{:?}",
374381
socket_addrs.domain(),
@@ -378,16 +385,28 @@ impl RawClient<ElectrumSslStream> {
378385
if validate_domain {
379386
socket_addrs.domain().ok_or(Error::MissingDomain)?;
380387
}
388+
389+
let crypto_provider = match crypto_provider {
390+
Some(provider) => provider.to_owned(),
391+
392+
// TODO: (@leonardo) It should use the proper default of each provider.
393+
#[cfg(feature = "use-rustls")]
394+
None => default_provider(),
395+
396+
#[cfg(feature = "use-rustls-ring")]
397+
None => default_provider(),
398+
};
399+
381400
match timeout {
382401
Some(timeout) => {
383402
let stream = connect_with_total_timeout(socket_addrs.clone(), timeout)?;
384403
stream.set_read_timeout(Some(timeout))?;
385404
stream.set_write_timeout(Some(timeout))?;
386-
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream)
405+
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream, crypto_provider)
387406
}
388407
None => {
389408
let stream = TcpStream::connect(socket_addrs.clone())?;
390-
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream)
409+
Self::new_ssl_from_stream(socket_addrs, validate_domain, stream, crypto_provider)
391410
}
392411
}
393412
}
@@ -397,10 +416,13 @@ impl RawClient<ElectrumSslStream> {
397416
socket_addr: A,
398417
validate_domain: bool,
399418
tcp_stream: TcpStream,
419+
crypto_provider: CryptoProvider,
400420
) -> Result<Self, Error> {
401421
use std::convert::TryFrom;
402422

403-
let builder = ClientConfig::builder();
423+
let builder = ClientConfig::builder_with_provider(crypto_provider.into())
424+
.with_safe_default_protocol_versions()
425+
.map_err(|e| Error::CouldNotBuildWithSafeDefaultVersion(e))?;
404426

405427
let config = if validate_domain {
406428
socket_addr.domain().ok_or(Error::MissingDomain)?;
@@ -467,14 +489,66 @@ impl RawClient<ElectrumProxyStream> {
467489
Ok(stream.into())
468490
}
469491

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