From c4dc741310e76a10726b3c4cc68c0a3d5bc60f85 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Thu, 6 Feb 2020 17:04:59 +0100 Subject: [PATCH] electrum: add `rustls` as the default ssl implementation --- core/electrum_client/Cargo.toml | 15 ++-- core/electrum_client/examples/plaintext.rs | 9 +++ core/electrum_client/examples/ssl.rs | 13 ++++ core/electrum_client/examples/tor.rs | 21 ++++++ core/electrum_client/src/client.rs | 86 ++++++++++++++++++++-- core/electrum_client/src/lib.rs | 20 ++++- core/electrum_client/src/types.rs | 5 +- 7 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 core/electrum_client/examples/plaintext.rs create mode 100644 core/electrum_client/examples/ssl.rs create mode 100644 core/electrum_client/examples/tor.rs diff --git a/core/electrum_client/Cargo.toml b/core/electrum_client/Cargo.toml index ab0cc777..7ae430c5 100644 --- a/core/electrum_client/Cargo.toml +++ b/core/electrum_client/Cargo.toml @@ -7,16 +7,21 @@ authors = ["Alekos Filini "] [dependencies] log = "^0.4" +bitcoin = { version = "0.23", features = ["use-serde"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } + +# Optional dependencies socks = { version = "^0.3", optional = true } openssl = { version = "^0.10", optional = true } - -[dependencies.bitcoin] -version = "0.23" -features = ["use-serde"] +rustls = { version = "0.16.0", optional = true, features = ["dangerous_configuration"] } +webpki = { version = "0.21.0", optional = true } +webpki-roots = { version = "^0.19", optional = true } [features] +default = ["socks", "webpki", "webpki-roots", "rustls"] +minimal = [] debug-calls = [] proxy = ["socks"] -ssl = ["openssl"] +use-rustls = ["webpki", "webpki-roots", "rustls"] +use-openssl = ["openssl"] diff --git a/core/electrum_client/examples/plaintext.rs b/core/electrum_client/examples/plaintext.rs new file mode 100644 index 00000000..4b1c9bcc --- /dev/null +++ b/core/electrum_client/examples/plaintext.rs @@ -0,0 +1,9 @@ +extern crate electrum_client; + +use electrum_client::Client; + +fn main() { + let mut client = Client::new("kirsche.emzy.de:50001").unwrap(); + let res = client.server_features(); + println!("{:#?}", res); +} diff --git a/core/electrum_client/examples/ssl.rs b/core/electrum_client/examples/ssl.rs new file mode 100644 index 00000000..6b0306d1 --- /dev/null +++ b/core/electrum_client/examples/ssl.rs @@ -0,0 +1,13 @@ +extern crate electrum_client; + +use electrum_client::Client; + +fn main() { + let mut client = Client::new_ssl( + "electrum2.hodlister.co:50002", + Some("electrum2.hodlister.co"), + ) + .unwrap(); + let res = client.server_features(); + println!("{:#?}", res); +} diff --git a/core/electrum_client/examples/tor.rs b/core/electrum_client/examples/tor.rs new file mode 100644 index 00000000..6d50b116 --- /dev/null +++ b/core/electrum_client/examples/tor.rs @@ -0,0 +1,21 @@ +extern crate electrum_client; + +use electrum_client::Client; + +fn main() { + // NOTE: This assumes Tor is running localy, with an unauthenticated Socks5 listening at + // localhost:9050 + + let mut client = Client::new_proxy("ozahtqwp25chjdjd.onion:50001", "127.0.0.1:9050").unwrap(); + let res = client.server_features(); + println!("{:#?}", res); + + // works both with onion v2/v3 (if your Tor supports them) + let mut client = Client::new_proxy( + "v7gtzf7nua6hdmb2wtqaqioqmesdb4xrlly4zwr7bvayxv2bpg665pqd.onion:50001", + "127.0.0.1:9050", + ) + .unwrap(); + let res = client.server_features(); + println!("{:#?}", res); +} diff --git a/core/electrum_client/src/client.rs b/core/electrum_client/src/client.rs index 6a9d0ac9..0f23fa68 100644 --- a/core/electrum_client/src/client.rs +++ b/core/electrum_client/src/client.rs @@ -13,13 +13,23 @@ use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::{Script, Txid}; -#[cfg(feature = "ssl")] +#[cfg(feature = "use-openssl")] use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode}; +#[cfg(all( + any(feature = "default", feature = "use-rustls"), + not(feature = "use-openssl") +))] +use rustls::{ClientConfig, ClientSession, StreamOwned}; -#[cfg(feature = "socks")] +#[cfg(any(feature = "default", feature = "proxy"))] use socks::{Socks5Stream, ToTargetAddr}; -#[cfg(any(feature = "socks", feature = "proxy"))] +#[cfg(any( + feature = "default", + feature = "use-rustls", + feature = "use-openssl", + feature = "proxy" +))] use stream::ClonableStream; use batch::Batch; @@ -76,7 +86,7 @@ impl Client { } } -#[cfg(feature = "ssl")] +#[cfg(feature = "use-openssl")] impl Client>> { pub fn new_ssl(socket_addr: A, domain: Option<&str>) -> Result { let mut builder = @@ -107,7 +117,71 @@ impl Client>> { } } -#[cfg(feature = "proxy")] +#[cfg(all( + any(feature = "default", feature = "use-rustls"), + not(feature = "use-openssl") +))] +mod danger { + use rustls; + use webpki; + + pub struct NoCertificateVerification {} + + impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8], + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } + } +} + +#[cfg(all( + any(feature = "default", feature = "use-rustls"), + not(feature = "use-openssl") +))] +impl Client>> { + pub fn new_ssl(socket_addr: A, domain: Option<&str>) -> Result { + let mut config = ClientConfig::new(); + if domain.is_none() { + config + .dangerous() + .set_certificate_verifier(std::sync::Arc::new(danger::NoCertificateVerification {})) + } else { + // TODO: cert pinning + config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + } + + let tcp_stream = TcpStream::connect(socket_addr)?; + let session = ClientSession::new( + &std::sync::Arc::new(config), + webpki::DNSNameRef::try_from_ascii_str(domain.unwrap_or("not.validated")) + .map_err(|_| Error::InvalidDNSNameError(domain.unwrap_or("").to_string()))?, + ); + let stream = StreamOwned::new(session, tcp_stream); + let stream: ClonableStream<_> = stream.into(); + + let buf_reader = BufReader::new(stream.clone()); + + Ok(Self { + stream, + buf_reader, + headers: VecDeque::new(), + script_notifications: BTreeMap::new(), + + #[cfg(feature = "debug-calls")] + calls: 0, + }) + } +} + +#[cfg(any(feature = "default", feature = "proxy"))] impl Client> { pub fn new_proxy( target_addr: T, @@ -478,7 +552,7 @@ impl Client { } #[cfg(feature = "debug-calls")] - pub fn calls_made(&self) -> u32 { + pub fn calls_made(&self) -> usize { self.calls } diff --git a/core/electrum_client/src/lib.rs b/core/electrum_client/src/lib.rs index 6c414603..327a52c6 100644 --- a/core/electrum_client/src/lib.rs +++ b/core/electrum_client/src/lib.rs @@ -1,15 +1,29 @@ pub extern crate bitcoin; extern crate log; -#[cfg(feature = "ssl")] +#[cfg(feature = "use-openssl")] extern crate openssl; +#[cfg(all( + any(feature = "default", feature = "use-rustls"), + not(feature = "use-openssl") +))] +extern crate rustls; extern crate serde; extern crate serde_json; -#[cfg(feature = "proxy")] +#[cfg(any(feature = "default", feature = "proxy"))] extern crate socks; +#[cfg(any(feature = "use-rustls", feature = "default"))] +extern crate webpki; +#[cfg(any(feature = "use-rustls", feature = "default"))] +extern crate webpki_roots; pub mod batch; pub mod client; -#[cfg(any(feature = "socks", feature = "proxy"))] +#[cfg(any( + feature = "default", + feature = "use-rustls", + feature = "use-openssl", + feature = "proxy" +))] mod stream; #[cfg(test)] mod test_stream; diff --git a/core/electrum_client/src/types.rs b/core/electrum_client/src/types.rs index 61817603..a62f9dbc 100644 --- a/core/electrum_client/src/types.rs +++ b/core/electrum_client/src/types.rs @@ -167,10 +167,11 @@ pub enum Error { NotSubscribed(ScriptHash), InvalidResponse(serde_json::Value), Message(String), + InvalidDNSNameError(String), - #[cfg(feature = "ssl")] + #[cfg(feature = "use-openssl")] InvalidSslMethod(openssl::error::ErrorStack), - #[cfg(feature = "ssl")] + #[cfg(feature = "use-openssl")] SslHandshakeError(openssl::ssl::HandshakeError), }