diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5e530f9d..be079cfa 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -152,6 +152,12 @@ jobs: command: check toolchain: stable args: --no-default-features --features ws-tls-tokio + - name: Checking ws-rustls-tokio + uses: actions-rs/cargo@master + with: + command: check + toolchain: stable + args: --no-default-features --features ws-rustls-tokio - name: Checking ws-async-std uses: actions-rs/cargo@master with: diff --git a/.gitignore b/.gitignore index ded91645..402d3cb1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target Cargo.lock *.swp .idea/ +.vscode diff --git a/Cargo.toml b/Cargo.toml index fd0cfe7f..fd78fc99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,8 @@ headers = { version = "0.3", optional = true } # async-native-tls = { git = "https://github.com/async-email/async-native-tls.git", rev = "b5b5562d6cea77f913d4cbe448058c031833bf17", optional = true, default-features = false } # Temporarily use forked version released to crates.io async-native-tls = { package = "web3-async-native-tls", version = "0.4", optional = true, default-features = false } +tokio-rustls = { version = "0.23.4", optional = true } +webpki-roots = { version = "0.22.6", optional = true } async-std = { version = "1.6", optional = true } tokio = { version = "1.0", optional = true, features = ["full"] } tokio-stream = { version = "0.1", optional = true } @@ -56,6 +58,7 @@ getrandom = { version = "0.2", features = ["js"], optional = true } wasm-bindgen = { version = "0.2.68", optional = true, features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4.18", optional = true } + [dev-dependencies] # For examples env_logger = "0.10" @@ -80,6 +83,7 @@ signing = ["secp256k1", "once_cell"] ws-tokio = ["soketto", "url", "tokio", "tokio-util", "headers"] ws-async-std = ["soketto", "url", "async-std", "headers"] ws-tls-tokio = ["async-native-tls", "async-native-tls/runtime-tokio", "ws-tokio"] +ws-rustls-tokio = ["tokio-rustls", "webpki-roots", "ws-tokio"] ws-tls-async-std = ["async-native-tls", "async-native-tls/runtime-async-std", "ws-async-std"] ipc-tokio = ["tokio", "tokio-stream", "tokio-util"] arbitrary_precision = ["serde_json/arbitrary_precision", "jsonrpc-core/arbitrary_precision"] diff --git a/README.md b/README.md index ca1328ef..75e398ad 100644 --- a/README.md +++ b/README.md @@ -108,16 +108,16 @@ web3.api::().custom_method().wait().unwrap() Currently, Windows does not support IPC, which is enabled in the library by default. To compile, you need to disable the IPC feature: -``` -web3 = { version = "0.17.0", default-features = false, features = ["http"] } +```toml +web3 = { version = "_", default-features = false, features = ["http"] } ``` # Avoiding OpenSSL dependency On Linux, `native-tls` is implemented using OpenSSL. To avoid that dependency for HTTPS use the corresponding feature. -``` -web3 = { version = "0.17.0", default-features = false, features = ["http-rustls-tls"] } +```toml +web3 = { version = "_", default-features = false, features = ["http-rustls-tls", "signing", "ws-rustls-tokio", "ipc-tokio"] } ``` # Cargo Features @@ -129,6 +129,7 @@ The library supports following features: - `http-rustls-tls` - Enables TLS support via `reqwest/rustls-tls` for HTTP transport (implies `http`). - `ws-tokio` - Enables WS transport using `tokio` runtime. - `ws-tls-tokio` - Enables TLS support for WS transport (implies `ws-tokio`; default). +- `ws-rustls-tokio` - Enables rustls TLS support for WS transport (implies `ws-tokio`). - `ws-async-std` - Enables WS transport using `async-std` runtime. - `ws-tls-async-std` - Enables TLS support for WS transport (implies `ws-async-std`). - `ipc-tokio` - Enables IPC transport using `tokio` runtime (default). diff --git a/src/transports/ipc.rs b/src/transports/ipc.rs index a7abf1a2..36c4a7c5 100644 --- a/src/transports/ipc.rs +++ b/src/transports/ipc.rs @@ -394,7 +394,7 @@ mod test { }) ); - tx.write(r#"{"jsonrpc": "2.0", "id": 1, "result": {"test": 1}}"#.as_ref()) + tx.write_all(r#"{"jsonrpc": "2.0", "id": 1, "result": {"test": 1}}"#.as_ref()) .await .unwrap(); tx.flush().await.unwrap(); @@ -417,7 +417,7 @@ mod test { let response_bytes = r#"{"jsonrpc": "2.0", "id": 2, "result": {"test": "string1"}}"#; for chunk in response_bytes.as_bytes().chunks(3) { - tx.write(chunk).await.unwrap(); + tx.write_all(chunk).await.unwrap(); tx.flush().await.unwrap(); } } diff --git a/src/transports/ws.rs b/src/transports/ws.rs index 429bdfb8..9f606201 100644 --- a/src/transports/ws.rs +++ b/src/transports/ws.rs @@ -134,8 +134,17 @@ impl WsServerTask { let stream = async_native_tls::connect(host, stream).await?; MaybeTlsStream::Tls(compat::compat(stream)) } - #[cfg(not(any(feature = "ws-tls-tokio", feature = "ws-tls-async-std")))] - panic!("The library was compiled without TLS support. Enable ws-tls-tokio or ws-tls-async-std feature."); + #[cfg(all( + feature = "ws-rustls-tokio", + not(feature = "ws-tls-tokio"), + not(feature = "ws-tls-async-std") + ))] + { + let stream = tokio_rustls_connect(host, stream).await?; + MaybeTlsStream::Tls(compat::compat(stream)) + } + #[cfg(not(any(feature = "ws-tls-tokio", feature = "ws-tls-async-std", feature = "ws-rustls-tokio")))] + panic!("The library was compiled without TLS support. Enable ws-tls-tokio, ws-rustls-tokio or ws-tls-async-std feature."); } else { let stream = compat::compat(stream); MaybeTlsStream::Plain(stream) @@ -245,6 +254,33 @@ impl WsServerTask { } } +#[cfg(feature = "ws-rustls-tokio")] +async fn tokio_rustls_connect( + host: &str, + stream: tokio::net::TcpStream, +) -> error::Result> { + use std::convert::TryFrom; + use tokio_rustls::rustls::{ClientConfig, OwnedTrustAnchor, RootCertStore, ServerName}; + + let client_conf = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates({ + let mut root_cert_store = RootCertStore::empty(); + root_cert_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject, ta.spki, ta.name_constraints) + })); + root_cert_store + }) + .with_no_client_auth(); + + let dnsname = ServerName::try_from(host) + .map_err(|err| error::Error::Transport(TransportError::Message(format!("Invalid host: {err}"))))?; + + Ok(tokio_rustls::TlsConnector::from(Arc::new(client_conf)) + .connect(dnsname, stream) + .await?) +} + fn as_data_stream( receiver: soketto::connection::Receiver, ) -> impl Stream, soketto::connection::Error>> { @@ -512,13 +548,16 @@ pub mod compat { /// TLS stream type for tokio runtime. #[cfg(feature = "ws-tls-tokio")] pub type TlsStream = Compat>; + /// Rustls TLS stream type for tokio runtime. + #[cfg(all(feature = "ws-rustls-tokio", not(feature = "ws-tls-tokio")))] + pub type TlsStream = Compat>; /// Dummy TLS stream type. - #[cfg(not(feature = "ws-tls-tokio"))] + #[cfg(all(not(feature = "ws-tls-tokio"), not(feature = "ws-rustls-tokio")))] pub type TlsStream = TcpStream; /// Create new TcpStream object. pub async fn raw_tcp_stream(addrs: String) -> io::Result { - Ok(tokio::net::TcpStream::connect(addrs).await?) + tokio::net::TcpStream::connect(addrs).await } /// Wrap given argument into compatibility layer.