From 4ac53a5a39e74d5eb12bee22d0fd4783acaae670 Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:17:17 +0800 Subject: [PATCH] feat: optional rustls support (#330) * initial implementation of rustls support * Refactor create_self_signed_cert.sh script * resolve lint errors * Fix handling of Option in tls.rs * Update cargo-hack check command and feature dependencies * fix missing point * Add conditional check to skip test if client or server is not enabled * clean up things * fix for windows CI * try fixing Windows CI * Update src/main.rs * Update src/transport/websocket.rs * add missing messages * split the tls mod Co-authored-by: Ning Sun --- .github/workflows/rust.yml | 8 +- Cargo.lock | 164 ++++++++++++++++++++++++ Cargo.toml | 71 ++++++++-- examples/tls/create_self_signed_cert.sh | 5 +- examples/tls/identity.pfx | Bin 3587 -> 3453 bytes examples/tls/rootCA.crt | 32 ++--- src/client.rs | 19 +-- src/helper.rs | 8 ++ src/lib.rs | 9 +- src/server.rs | 16 +-- src/transport/mod.rs | 23 +++- src/transport/{tls.rs => native_tls.rs} | 13 +- src/transport/rustls.rs | 156 ++++++++++++++++++++++ src/transport/websocket.rs | 14 +- tests/integration_test.rs | 23 ++-- 15 files changed, 482 insertions(+), 79 deletions(-) rename src/transport/{tls.rs => native_tls.rs} (91%) create mode 100644 src/transport/rustls.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 45c693a..916c0ed 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -32,7 +32,9 @@ jobs: - name: Setup cargo-hack run: cargo install cargo-hack - name: Check all features - run: cargo hack check --feature-powerset --no-dev-deps + run: > + cargo hack check --feature-powerset --no-dev-deps + --mutually-exclusive-features default,native-tls,websocket-native-tls,rustls,websocket-rustls build: name: Build for ${{ matrix.target }} @@ -62,8 +64,10 @@ jobs: - uses: Swatinem/rust-cache@v1 - name: Build run: cargo build - - name: Run tests + - name: Run tests with native-tls run: cargo test --verbose + - name: Run tests with rustls + run: cargo test --verbose --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reload - uses: actions/upload-artifact@v2 with: name: rathole-${{ matrix.target }} diff --git a/Cargo.lock b/Cargo.lock index ce79bad..77a6517 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,6 +243,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -258,6 +267,15 @@ dependencies = [ "serde", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.83" @@ -497,6 +515,15 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.7" @@ -850,6 +877,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.9" @@ -982,6 +1018,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -1312,6 +1349,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p12" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" +dependencies = [ + "cbc", + "cipher", + "des", + "getrandom", + "hmac", + "lazy_static", + "rc2", + "sha1", + "yasna", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1540,13 +1594,17 @@ dependencies = [ "lazy_static", "notify", "openssl", + "p12", "rand", + "rustls-native-certs", + "rustls-pemfile", "serde", "sha2", "snowstorm", "socket2 0.4.9", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-tungstenite", "tokio-util", "toml", @@ -1556,6 +1614,15 @@ dependencies = [ "vergen", ] +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1609,6 +1676,20 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1637,6 +1718,60 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +dependencies = [ + "base64 0.21.4", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -1838,6 +1973,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.10.0" @@ -2030,6 +2171,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -2261,6 +2413,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -2501,6 +2659,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index bcd2295..44fcf97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,18 +11,48 @@ build = "build.rs" include = ["src/**/*", "LICENSE", "README.md", "build.rs"] [features] -default = ["server", "client", "tls", "noise", "websocket", "hot-reload"] +default = [ + "server", + "client", + "native-tls", + "noise", + "websocket-native-tls", + "hot-reload", +] # Run as a server server = [] # Run as a client client = [] + # TLS support -tls = ["tokio-native-tls"] +native-tls = ["tokio-native-tls"] +rustls = [ + "tokio-rustls", + "rustls-pemfile", + "rustls-native-certs", + "p12", +] + # Noise support noise = ["snowstorm", "base64"] + # Websocket support -websocket = ["tokio-tungstenite", "tokio-util", "futures-core", "futures-sink", "tls"] +websocket-native-tls = [ + "tokio-tungstenite", + "tokio-util", + "futures-core", + "futures-sink", + "native-tls", +] +websocket-rustls = [ + "tokio-tungstenite", + "tokio-util", + "futures-core", + "futures-sink", + "rustls", +] + # Configuration hot-reload support hot-reload = ["notify"] @@ -67,27 +97,42 @@ hex = "0.4" rand = "0.8" backoff = { version = "0.4", features = ["tokio"] } tracing = "0.1" -tracing-subscriber = { version="0.3", features=["env-filter"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } socket2 = { version = "0.4", features = ["all"] } fdlimit = "0.2" -tokio-native-tls = { version = "0.3", optional = true } async-trait = "0.1" -snowstorm = { version = "0.4", optional = true, features = ["stream"], default-features = false } +snowstorm = { version = "0.4", optional = true, features = [ + "stream", +], default-features = false } base64 = { version = "0.13", optional = true } notify = { version = "5.0.0-pre.13", optional = true } -console-subscriber = { version = "0.1", optional = true, features = ["parking_lot"] } +console-subscriber = { version = "0.1", optional = true, features = [ + "parking_lot", +] } atty = "0.2" -async-http-proxy = { version = "1.2", features = ["runtime-tokio", "basic-auth"] } +async-http-proxy = { version = "1.2", features = [ + "runtime-tokio", + "basic-auth", +] } async-socks5 = "0.5" url = { version = "2.2", features = ["serde"] } -tokio-tungstenite = { version="0.20.1", optional = true} -tokio-util = { version="0.7.9", optional = true, features = ["io"] } -futures-core = { version="0.3.28", optional = true } -futures-sink = { version="0.3.28", optional = true } +tokio-tungstenite = { version = "0.20.1", optional = true } +tokio-util = { version = "0.7.9", optional = true, features = ["io"] } +futures-core = { version = "0.3.28", optional = true } +futures-sink = { version = "0.3.28", optional = true } +tokio-native-tls = { version = "0.3", optional = true } +tokio-rustls = { version = "0.25", optional = true } +rustls-native-certs = { version = "0.7", optional = true } +rustls-pemfile = { version = "2.0", optional = true } +p12 = { version = "0.6.3", optional = true } [target.'cfg(target_env = "musl")'.dependencies] openssl = { version = "0.10", features = ["vendored"] } [build-dependencies] -vergen = { version = "7.4.2", default-features = false, features = ["build", "git", "cargo"] } +vergen = { version = "7.4.2", default-features = false, features = [ + "build", + "git", + "cargo", +] } anyhow = "1.0" diff --git a/examples/tls/create_self_signed_cert.sh b/examples/tls/create_self_signed_cert.sh index e110a1f..6ab9db3 100644 --- a/examples/tls/create_self_signed_cert.sh +++ b/examples/tls/create_self_signed_cert.sh @@ -56,7 +56,8 @@ openssl x509 -req \ -sha256 -extfile cert.conf # create pkcs12 -openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile rootCA.crt -passout pass:1234 +openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile rootCA.crt \ + -passout pass:1234 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES # clean up -rm server.csr csr.conf cert.conf \ No newline at end of file +rm server.csr csr.conf cert.conf diff --git a/examples/tls/identity.pfx b/examples/tls/identity.pfx index dfc5160debcac20c939f71971a08c9a87a789c08..2041157ad25488f41cfdbbb0f3f62bc8407716f9 100644 GIT binary patch literal 3453 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 literal 3587 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 { - #[cfg(feature = "tls")] + #[cfg(any(feature = "native-tls", feature = "rustls"))] { let mut client = Client::::from(config).await?; client.run(shutdown_rx, update_rx).await } - #[cfg(not(feature = "tls"))] - crate::helper::feature_not_compile("tls") + #[cfg(not(any(feature = "native-tls", feature = "rustls")))] + crate::helper::feature_neither_compile("native-tls", "rustls") } TransportType::Noise => { #[cfg(feature = "noise")] @@ -65,13 +66,13 @@ pub async fn run_client( crate::helper::feature_not_compile("noise") } TransportType::Websocket => { - #[cfg(feature = "websocket")] + #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] { let mut client = Client::::from(config).await?; client.run(shutdown_rx, update_rx).await } - #[cfg(not(feature = "websocket"))] - crate::helper::feature_not_compile("websocket") + #[cfg(not(any(feature = "websocket-native-tls", feature = "websocket-rustls")))] + crate::helper::feature_neither_compile("websocket-native-tls", "websocket-rustls") } } } diff --git a/src/helper.rs b/src/helper.rs index 7e1e5d3..a292969 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -43,6 +43,14 @@ pub fn feature_not_compile(feature: &str) -> ! { ) } +#[allow(dead_code)] +pub fn feature_neither_compile(feature1: &str, feature2: &str) -> ! { + panic!( + "Neither of the feature '{}' or '{}' is compiled in this binary. Please re-compile rathole", + feature1, feature2 + ) +} + pub async fn to_socket_addr(addr: A) -> Result { lookup_host(addr) .await? diff --git a/src/lib.rs b/src/lib.rs index 7fb2fa6..65beb7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ pub async fn run(args: Cli, shutdown_rx: broadcast::Receiver) -> Result<() if let Some((i, _)) = last_instance { info!("General configuration change detected. Restarting..."); shutdown_tx.send(true)?; - i.await?; + i.await??; } debug!("{:?}", config); @@ -119,8 +119,8 @@ async fn run_instance( args: Cli, shutdown_rx: broadcast::Receiver, service_update: mpsc::Receiver, -) { - let ret: Result<()> = match determine_run_mode(&config, &args) { +) -> Result<()> { + match determine_run_mode(&config, &args) { RunMode::Undetermine => panic!("Cannot determine running as a server or a client"), RunMode::Client => { #[cfg(not(feature = "client"))] @@ -134,8 +134,7 @@ async fn run_instance( #[cfg(feature = "server")] run_server(config, shutdown_rx, service_update).await } - }; - ret.unwrap(); + } } #[derive(PartialEq, Eq, Debug)] diff --git a/src/server.rs b/src/server.rs index 83ae976..a4c4948 100644 --- a/src/server.rs +++ b/src/server.rs @@ -25,9 +25,9 @@ use tracing::{debug, error, info, info_span, instrument, warn, Instrument, Span} #[cfg(feature = "noise")] use crate::transport::NoiseTransport; -#[cfg(feature = "tls")] +#[cfg(any(feature = "native-tls", feature = "rustls"))] use crate::transport::TlsTransport; -#[cfg(feature = "websocket")] +#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] use crate::transport::WebsocketTransport; type ServiceDigest = protocol::Digest; // SHA256 of a service name @@ -57,13 +57,13 @@ pub async fn run_server( server.run(shutdown_rx, update_rx).await?; } TransportType::Tls => { - #[cfg(feature = "tls")] + #[cfg(any(feature = "native-tls", feature = "rustls"))] { let mut server = Server::::from(config).await?; server.run(shutdown_rx, update_rx).await?; } - #[cfg(not(feature = "tls"))] - crate::helper::feature_not_compile("tls") + #[cfg(not(any(feature = "native-tls", feature = "rustls")))] + crate::helper::feature_neither_compile("native-tls", "rustls") } TransportType::Noise => { #[cfg(feature = "noise")] @@ -75,13 +75,13 @@ pub async fn run_server( crate::helper::feature_not_compile("noise") } TransportType::Websocket => { - #[cfg(feature = "websocket")] + #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] { let mut server = Server::::from(config).await?; server.run(shutdown_rx, update_rx).await?; } - #[cfg(not(feature = "websocket"))] - crate::helper::feature_not_compile("websocket") + #[cfg(not(any(feature = "websocket-native-tls", feature = "websocket-rustls")))] + crate::helper::feature_neither_compile("websocket-native-tls", "websocket-rustls") } } diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 38682a6..26d357f 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -69,19 +69,30 @@ pub trait Transport: Debug + Send + Sync { mod tcp; pub use tcp::TcpTransport; -#[cfg(feature = "tls")] -mod tls; -#[cfg(feature = "tls")] -pub use tls::TlsTransport; + +#[cfg(all(feature = "native-tls", feature = "rustls"))] +compile_error!("Only one of `native-tls` and `rustls` can be enabled"); + +#[cfg(feature = "native-tls")] +mod native_tls; +#[cfg(feature = "native-tls")] +use native_tls as tls; +#[cfg(feature = "rustls")] +mod rustls; +#[cfg(feature = "rustls")] +use rustls as tls; + +#[cfg(any(feature = "native-tls", feature = "rustls"))] +pub(crate) use tls::TlsTransport; #[cfg(feature = "noise")] mod noise; #[cfg(feature = "noise")] pub use noise::NoiseTransport; -#[cfg(feature = "websocket")] +#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] mod websocket; -#[cfg(feature = "websocket")] +#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] pub use websocket::WebsocketTransport; #[derive(Debug, Clone, Copy)] diff --git a/src/transport/tls.rs b/src/transport/native_tls.rs similarity index 91% rename from src/transport/tls.rs rename to src/transport/native_tls.rs index 918af04..40afd50 100644 --- a/src/transport/tls.rs +++ b/src/transport/native_tls.rs @@ -1,14 +1,14 @@ -use std::net::SocketAddr; - -use super::{AddrMaybeCached, SocketOpts, TcpTransport, Transport}; use crate::config::{TlsConfig, TransportConfig}; use crate::helper::host_port_pair; +use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use std::fs; +use std::net::SocketAddr; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio_native_tls::native_tls::{self, Certificate, Identity}; -use tokio_native_tls::{TlsAcceptor, TlsConnector, TlsStream}; +pub(crate) use tokio_native_tls::TlsStream; +use tokio_native_tls::{TlsAcceptor, TlsConnector}; #[derive(Debug)] pub struct TlsTransport { @@ -109,3 +109,8 @@ impl Transport for TlsTransport { .await?) } } + +#[cfg(feature = "websocket-native-tls")] +pub(crate) fn get_tcpstream(s: &TlsStream) -> &TcpStream { + s.get_ref().get_ref().get_ref() +} diff --git a/src/transport/rustls.rs b/src/transport/rustls.rs new file mode 100644 index 0000000..3ca4704 --- /dev/null +++ b/src/transport/rustls.rs @@ -0,0 +1,156 @@ +use crate::config::{TlsConfig, TransportConfig}; +use crate::helper::host_port_pair; +use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport}; +use std::fmt::Debug; +use std::fs; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; +use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer, ServerName}; + +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use p12::PFX; +use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig}; +pub(crate) use tokio_rustls::TlsStream; +use tokio_rustls::{TlsAcceptor, TlsConnector}; + +pub struct TlsTransport { + tcp: TcpTransport, + config: TlsConfig, + connector: Option, + tls_acceptor: Option, +} + +// workaround for TlsConnector and TlsAcceptor not implementing Debug +impl Debug for TlsTransport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TlsTransport") + .field("tcp", &self.tcp) + .field("config", &self.config) + .finish() + } +} + +fn load_server_config(config: &TlsConfig) -> Result> { + if let Some(pkcs12_path) = config.pkcs12.as_ref() { + let buf = fs::read(pkcs12_path)?; + let pfx = PFX::parse(buf.as_slice())?; + let pass = config.pkcs12_password.as_ref().unwrap(); + + let certs = pfx.cert_bags(pass)?; + let keys = pfx.key_bags(pass)?; + + let chain: Vec = certs.into_iter().map(CertificateDer::from).collect(); + let key = PrivatePkcs8KeyDer::from(keys.into_iter().next().unwrap()); + + Ok(Some( + ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(chain, key.into())?, + )) + } else { + Ok(None) + } +} + +fn load_client_config(config: &TlsConfig) -> Result> { + let cert = if let Some(path) = config.trusted_root.as_ref() { + rustls_pemfile::certs(&mut std::io::BufReader::new(fs::File::open(path).unwrap())) + .map(|cert| cert.unwrap()) + .next() + .with_context(|| "Failed to read certificate")? + } else { + // read from native + match rustls_native_certs::load_native_certs() { + Ok(certs) => certs.into_iter().next().unwrap(), + Err(e) => { + eprintln!("Failed to load native certs: {}", e); + return Ok(None); + } + } + }; + + let mut root_certs = RootCertStore::empty(); + root_certs.add(cert).unwrap(); + + Ok(Some( + ClientConfig::builder() + .with_root_certificates(root_certs) + .with_no_client_auth(), + )) +} + +#[async_trait] +impl Transport for TlsTransport { + type Acceptor = TcpListener; + type RawStream = TcpStream; + type Stream = TlsStream; + + fn new(config: &TransportConfig) -> Result { + let tcp = TcpTransport::new(config)?; + let config = config + .tls + .as_ref() + .ok_or_else(|| anyhow!("Missing tls config"))?; + + let connector = load_client_config(config) + .unwrap() + .map(|c| Arc::new(c).into()); + let tls_acceptor = load_server_config(config) + .unwrap() + .map(|c| Arc::new(c).into()); + + Ok(TlsTransport { + tcp, + config: config.clone(), + connector, + tls_acceptor, + }) + } + + fn hint(conn: &Self::Stream, opt: SocketOpts) { + opt.apply(conn.get_ref().0); + } + + async fn bind(&self, addr: A) -> Result { + let l = TcpListener::bind(addr) + .await + .with_context(|| "Failed to create tcp listener")?; + Ok(l) + } + + async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { + self.tcp + .accept(a) + .await + .with_context(|| "Failed to accept TCP connection") + } + + async fn handshake(&self, conn: Self::RawStream) -> Result { + let conn = self.tls_acceptor.as_ref().unwrap().accept(conn).await?; + Ok(tokio_rustls::TlsStream::Server(conn)) + } + + async fn connect(&self, addr: &AddrMaybeCached) -> Result { + let conn = self.tcp.connect(addr).await?; + + let connector = self.connector.as_ref().unwrap(); + + let host_name = self + .config + .hostname + .as_deref() + .unwrap_or(host_port_pair(&addr.addr)?.0); + + Ok(tokio_rustls::TlsStream::Client( + connector + .connect(ServerName::try_from(host_name)?.to_owned(), conn) + .await?, + )) + } +} + +pub(crate) fn get_tcpstream(s: &TlsStream) -> &TcpStream { + &s.get_ref().0 +} diff --git a/src/transport/websocket.rs b/src/transport/websocket.rs index ec6177d..228eff7 100644 --- a/src/transport/websocket.rs +++ b/src/transport/websocket.rs @@ -13,10 +13,14 @@ use futures_core::stream::Stream; use futures_sink::Sink; use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; -use tokio_native_tls::TlsStream; -use tokio_tungstenite::tungstenite::protocol::WebSocketConfig; -use tokio_tungstenite::{accept_async_with_config, client_async_with_config}; -use tokio_tungstenite::{tungstenite::protocol::Message, WebSocketStream}; + +#[cfg(any(feature = "native-tls", feature = "rustls"))] +use super::tls::get_tcpstream; +#[cfg(any(feature = "native-tls", feature = "rustls"))] +use super::tls::TlsStream; + +use tokio_tungstenite::tungstenite::protocol::{Message, WebSocketConfig}; +use tokio_tungstenite::{accept_async_with_config, client_async_with_config, WebSocketStream}; use tokio_util::io::StreamReader; use url::Url; @@ -30,7 +34,7 @@ impl TransportStream { fn get_tcpstream(&self) -> &TcpStream { match self { TransportStream::Insecure(s) => s, - TransportStream::Secure(s) => s.get_ref().get_ref().get_ref(), + TransportStream::Secure(s) => get_tcpstream(s), } } } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 3b8dec9..7b5d408 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Ok, Result}; use common::{run_rathole_client, PING, PONG}; use rand::Rng; use std::time::Duration; @@ -57,17 +57,17 @@ async fn tcp() -> Result<()> { test("tests/for_tcp/tcp_transport.toml", Type::Tcp).await?; // FIXME: Self-signed certificate on Mac requires mannual interference. Disable CI for now #[cfg(not(target_os = "macos"))] - #[cfg(feature="tls")] + #[cfg(any(feature = "native-tls", feature = "rustls"))] test("tests/for_tcp/tls_transport.toml", Type::Tcp).await?; - #[cfg(feature="noise")] + #[cfg(feature = "noise")] test("tests/for_tcp/noise_transport.toml", Type::Tcp).await?; - #[cfg(feature="websocket")] + #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] test("tests/for_tcp/websocket_transport.toml", Type::Tcp).await?; #[cfg(not(target_os = "macos"))] - #[cfg(feature="websocket")] + #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] test("tests/for_tcp/websocket_tls_transport.toml", Type::Tcp).await?; Ok(()) @@ -94,17 +94,17 @@ async fn udp() -> Result<()> { test("tests/for_udp/tcp_transport.toml", Type::Udp).await?; // See above #[cfg(not(target_os = "macos"))] - #[cfg(feature="tls")] + #[cfg(any(feature = "native-tls", feature = "rustls"))] test("tests/for_udp/tls_transport.toml", Type::Udp).await?; - #[cfg(feature="noise")] + #[cfg(feature = "noise")] test("tests/for_udp/noise_transport.toml", Type::Udp).await?; - #[cfg(feature="websocket")] + #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] test("tests/for_udp/websocket_transport.toml", Type::Udp).await?; #[cfg(not(target_os = "macos"))] - #[cfg(feature="websocket")] + #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] test("tests/for_udp/websocket_tls_transport.toml", Type::Udp).await?; Ok(()) @@ -112,6 +112,11 @@ async fn udp() -> Result<()> { #[instrument] async fn test(config_path: &'static str, t: Type) -> Result<()> { + if cfg!(not(all(feature = "client", feature = "server"))) { + // Skip the test if the client or the server is not enabled + return Ok(()); + } + let (client_shutdown_tx, client_shutdown_rx) = broadcast::channel(1); let (server_shutdown_tx, server_shutdown_rx) = broadcast::channel(1);