electrum: add rustls as the default ssl implementation

This commit is contained in:
Alekos Filini 2020-02-06 17:04:59 +01:00
parent f5a201b98d
commit c4dc741310
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
7 changed files with 153 additions and 16 deletions

View 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"]

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View 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
}

View 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;

View 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>),
}