]> Untitled Git - bdk/commitdiff
electrum: add `rustls` as the default ssl implementation
authorAlekos Filini <alekos.filini@gmail.com>
Thu, 6 Feb 2020 16:04:59 +0000 (17:04 +0100)
committerAlekos Filini <alekos.filini@gmail.com>
Thu, 6 Feb 2020 16:22:14 +0000 (17:22 +0100)
core/electrum_client/Cargo.toml
core/electrum_client/examples/plaintext.rs [new file with mode: 0644]
core/electrum_client/examples/ssl.rs [new file with mode: 0644]
core/electrum_client/examples/tor.rs [new file with mode: 0644]
core/electrum_client/src/client.rs
core/electrum_client/src/lib.rs
core/electrum_client/src/types.rs

index ab0cc777e0e24ad8901710182f8ca2cf20a2e25e..7ae430c5f548206c868f6cebec1f486672d207a9 100644 (file)
@@ -7,16 +7,21 @@ authors = ["Alekos Filini <alekos.filini@gmail.com>"]
 
 [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 (file)
index 0000000..4b1c9bc
--- /dev/null
@@ -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 (file)
index 0000000..6b0306d
--- /dev/null
@@ -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 (file)
index 0000000..6d50b11
--- /dev/null
@@ -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);
+}
index 6a9d0ac9359cfadce4c72c1c4ad115c82da12292..0f23fa680846e65cf1dc4830fba9925273215469 100644 (file)
@@ -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<TcpStream> {
     }
 }
 
-#[cfg(feature = "ssl")]
+#[cfg(feature = "use-openssl")]
 impl Client<ClonableStream<SslStream<TcpStream>>> {
     pub fn new_ssl<A: ToSocketAddrs>(socket_addr: A, domain: Option<&str>) -> Result<Self, Error> {
         let mut builder =
@@ -107,7 +117,71 @@ impl Client<ClonableStream<SslStream<TcpStream>>> {
     }
 }
 
-#[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<rustls::ServerCertVerified, rustls::TLSError> {
+            Ok(rustls::ServerCertVerified::assertion())
+        }
+    }
+}
+
+#[cfg(all(
+    any(feature = "default", feature = "use-rustls"),
+    not(feature = "use-openssl")
+))]
+impl Client<ClonableStream<StreamOwned<ClientSession, TcpStream>>> {
+    pub fn new_ssl<A: ToSocketAddrs>(socket_addr: A, domain: Option<&str>) -> Result<Self, Error> {
+        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("<NONE>").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<ClonableStream<Socks5Stream>> {
     pub fn new_proxy<A: ToSocketAddrs, T: ToTargetAddr>(
         target_addr: T,
@@ -478,7 +552,7 @@ impl<S: Read + Write> Client<S> {
     }
 
     #[cfg(feature = "debug-calls")]
-    pub fn calls_made(&self) -> u32 {
+    pub fn calls_made(&self) -> usize {
         self.calls
     }
 
index 6c4146030aa0db0e2a18cd4aac20a8d9b729d7ad..327a52c638346d645d7c1cfd3301533dd82fb36d 100644 (file)
@@ -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;
index 61817603bd9d1f2acca0b14c84eda7360e60d891..a62f9dbc090f04ff023d81a795172b36f25931ee 100644 (file)
@@ -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<std::net::TcpStream>),
 }