From 0bc310a656160d8425764bbef46ff9afe92f28bc Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Aug 2023 14:59:51 +0100 Subject: [PATCH] Rustls v0.21 support (#480) --- .cargo/config.toml | 6 +- .github/workflows/ci-post-merge.yml | 16 +- .github/workflows/ci.yml | 16 +- Cargo.toml | 1 + actix-server/src/worker.rs | 3 + actix-tls/CHANGES.md | 49 +++-- actix-tls/Cargo.toml | 30 ++- actix-tls/examples/accept-rustls.rs | 20 +- actix-tls/src/accept/mod.rs | 18 +- .../src/accept/{rustls.rs => rustls_0_20.rs} | 5 +- actix-tls/src/accept/rustls_0_21.rs | 198 ++++++++++++++++++ actix-tls/src/connect/mod.rs | 11 +- .../src/connect/{rustls.rs => rustls_0_20.rs} | 33 +-- actix-tls/src/connect/rustls_0_21.rs | 154 ++++++++++++++ actix-tls/tests/accept-openssl.rs | 5 +- actix-tls/tests/accept-rustls.rs | 5 +- actix-tls/tests/test_connect.rs | 4 +- actix-utils/src/counter.rs | 4 +- actix-utils/src/future/poll_fn.rs | 1 + 19 files changed, 504 insertions(+), 75 deletions(-) rename actix-tls/src/accept/{rustls.rs => rustls_0_20.rs} (97%) create mode 100644 actix-tls/src/accept/rustls_0_21.rs rename actix-tls/src/connect/{rustls.rs => rustls_0_20.rs} (78%) create mode 100644 actix-tls/src/connect/rustls_0_21.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index c4f6e3f6..a114083f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -15,9 +15,11 @@ ci-check-linux = "hack --workspace --feature-powerset check --tests --examples" # tests avoiding io-uring feature ci-test = "hack --feature-powerset --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture" +ci-test-rustls-020 = "hack --feature-powerset --exclude-features=io-uring,rustls-0_21 test --lib --tests --no-fail-fast -- --nocapture" +ci-test-rustls-021 = "hack --feature-powerset --exclude-features=io-uring,rustls-0_20 test --lib --tests --no-fail-fast -- --nocapture" # tests avoiding io-uring feature on Windows -ci-test-win = "hack --feature-powerset --depth 2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture" +ci-test-win = "hack --feature-powerset --depth=2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture" # test with io-uring feature -ci-test-linux = "hack --feature-powerset test --lib --tests --no-fail-fast -- --nocapture" +ci-test-linux = "hack --feature-powerset --exclude-features=rustls-0_20 test --lib --tests --no-fail-fast -- --nocapture" diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 27f541a3..30bca39e 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -16,6 +16,7 @@ jobs: strategy: fail-fast: false matrix: + # prettier-ignore target: - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } @@ -37,6 +38,10 @@ jobs: - uses: actions/checkout@v3 + - name: Free Disk Space + if: matrix.target.os == 'ubuntu-latest' + run: ./scripts/free-disk-space.sh + - name: Install OpenSSL if: matrix.target.os == 'windows-latest' run: choco install openssl -y --forcex64 --no-progress @@ -83,8 +88,15 @@ jobs: run: cargo ci-test - name: tests if: matrix.target.os == 'ubuntu-latest' - run: | - sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux" + run: >- + sudo bash -c " + ulimit -Sl 512 + && ulimit -Hl 512 + && PATH=$PATH:/usr/share/rust/.cargo/bin + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-020 + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-021 + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux + " - name: Clear the cargo caches run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20632f63..74315e6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: +on: pull_request: {} push: { branches: [master] } @@ -16,6 +16,7 @@ jobs: strategy: fail-fast: false matrix: + # prettier-ignore target: - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } @@ -37,7 +38,7 @@ jobs: run: sudo ifconfig lo0 alias 127.0.0.3 - uses: actions/checkout@v3 - + - name: Free Disk Space if: matrix.target.os == 'ubuntu-latest' run: ./scripts/free-disk-space.sh @@ -99,8 +100,15 @@ jobs: run: cargo ci-test-win - name: tests if: matrix.target.os == 'ubuntu-latest' - run: | - sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux" + run: >- + sudo bash -c " + ulimit -Sl 512 + && ulimit -Hl 512 + && PATH=$PATH:/usr/share/rust/.cargo/bin + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-020 + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-021 + && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux + " - name: Clear the cargo caches run: | diff --git a/Cargo.toml b/Cargo.toml index 62897764..696de4a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ resolver = "2" [workspace.package] +license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.65" diff --git a/actix-server/src/worker.rs b/actix-server/src/worker.rs index d72cd2db..7050fcd2 100644 --- a/actix-server/src/worker.rs +++ b/actix-server/src/worker.rs @@ -625,6 +625,7 @@ impl Future for ServerWorker { self.poll(cx) } }, + WorkerState::Restarting(ref mut restart) => { let factory_id = restart.factory_id; let token = restart.token; @@ -649,6 +650,7 @@ impl Future for ServerWorker { self.poll(cx) } + WorkerState::Shutdown(ref mut shutdown) => { // drop all pending connections in rx channel. while let Poll::Ready(Some(conn)) = this.conn_rx.poll_recv(cx) { @@ -682,6 +684,7 @@ impl Future for ServerWorker { shutdown.timer.as_mut().poll(cx) } } + // actively poll stream and handle worker command WorkerState::Available => loop { match this.check_readiness(cx) { diff --git a/actix-tls/CHANGES.md b/actix-tls/CHANGES.md index 0381e6de..556d498b 100644 --- a/actix-tls/CHANGES.md +++ b/actix-tls/CHANGES.md @@ -1,40 +1,43 @@ # Changes -## Unreleased - 2023-xx-xx +## Unreleased +- Support Rustls v0.21. +- Added `{accept, connect}::rustls_0_21` modules. +- Added `{accept, connect}::rustls_0_20` alias for `{accept, connect}::rustls` modules. - Minimum supported Rust version (MSRV) is now 1.65. -## 3.0.4 - 2022-03-15 +## 3.0.4 - Logs emitted now use the `tracing` crate with `log` compatibility. [#451] [#451]: https://github.com/actix/actix-net/pull/451 -## 3.0.3 - 2022-02-15 +## 3.0.3 - No significant changes since `3.0.2`. -## 3.0.2 - 2022-01-28 +## 3.0.2 - Expose `connect::Connection::new`. [#439] [#439]: https://github.com/actix/actix-net/pull/439 -## 3.0.1 - 2022-01-11 +## 3.0.1 - No significant changes since `3.0.0`. -## 3.0.0 - 2021-12-26 +## 3.0.0 - No significant changes since `3.0.0-rc.2`. -## 3.0.0-rc.2 - 2021-12-10 +## 3.0.0-rc.2 - Re-export `openssl::SslConnectorBuilder` in `connect::openssl::reexports`. [#429] [#429]: https://github.com/actix/actix-net/pull/429 -## 3.0.0-rc.1 - 2021-11-29 +## 3.0.0-rc.1 ### Added @@ -72,7 +75,7 @@ [#422]: https://github.com/actix/actix-net/pull/422 [#423]: https://github.com/actix/actix-net/pull/423 -## 3.0.0-beta.9 - 2021-11-22 +## 3.0.0-beta.9 - Add configurable timeout for accepting TLS connection. [#393] - Added `TlsError::Timeout` variant. [#393] @@ -82,20 +85,20 @@ [#393]: https://github.com/actix/actix-net/pull/393 [#420]: https://github.com/actix/actix-net/pull/420 -## 3.0.0-beta.8 - 2021-11-15 +## 3.0.0-beta.8 - Add `Connect::request` for getting a reference to the connection request. [#415] [#415]: https://github.com/actix/actix-net/pull/415 -## 3.0.0-beta.7 - 2021-10-20 +## 3.0.0-beta.7 - Add `webpki_roots_cert_store()` to get rustls compatible webpki roots cert store. [#401] - Alias `connect::ssl` to `connect::tls`. [#401] [#401]: https://github.com/actix/actix-net/pull/401 -## 3.0.0-beta.6 - 2021-10-19 +## 3.0.0-beta.6 - Update `tokio-rustls` to `0.23` which uses `rustls` `0.20`. [#396] - Removed a re-export of `Session` from `rustls` as it no longer exist. [#396] @@ -103,7 +106,7 @@ [#396]: https://github.com/actix/actix-net/pull/396 -## 3.0.0-beta.5 - 2021-03-29 +## 3.0.0-beta.5 - Changed `connect::ssl::rustls::RustlsConnectorService` to return error when `DNSNameRef` generation failed instead of panic. [#296] - Remove `connect::ssl::openssl::OpensslConnectServiceFactory`. [#297] @@ -117,7 +120,7 @@ [#297]: https://github.com/actix/actix-net/pull/297 [#299]: https://github.com/actix/actix-net/pull/299 -## 3.0.0-beta.4 - 2021-02-24 +## 3.0.0-beta.4 - Rename `accept::openssl::{SslStream => TlsStream}`. - Add `connect::Connect::set_local_addr` to attach local `IpAddr`. [#282] @@ -125,7 +128,7 @@ [#282]: https://github.com/actix/actix-net/pull/282 -## 3.0.0-beta.3 - 2021-02-06 +## 3.0.0-beta.3 - Remove `trust-dns-proto` and `trust-dns-resolver`. [#248] - Use `std::net::ToSocketAddrs` as simple and basic default resolver. [#248] @@ -139,13 +142,13 @@ [#248]: https://github.com/actix/actix-net/pull/248 [#273]: https://github.com/actix/actix-net/pull/273 -## 3.0.0-beta.2 - 2022-xx-xx +## 3.0.0-beta.2 - Depend on stable trust-dns packages. [#204] [#204]: https://github.com/actix/actix-net/pull/204 -## 3.0.0-beta.1 - 2020-12-29 +## 3.0.0-beta.1 - Move acceptors under `accept` module. [#238] - Merge `actix-connect` crate under `connect` module. [#238] @@ -153,7 +156,7 @@ [#238]: https://github.com/actix/actix-net/pull/238 -## 2.0.0 - 2020-09-03 +## 2.0.0 - `nativetls::NativeTlsAcceptor` is renamed to `nativetls::Acceptor`. - Where possible, "SSL" terminology is replaced with "TLS". @@ -161,28 +164,28 @@ - `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`. - `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`. -## 2.0.0-alpha.2 - 2020-08-17 +## 2.0.0-alpha.2 - Update `rustls` dependency to 0.18 - Update `tokio-rustls` dependency to 0.14 - Update `webpki-roots` dependency to 0.20 -## [2.0.0-alpha.1] - 2020-03-03 +## [2.0.0-alpha.1] - Update `rustls` dependency to 0.17 - Update `tokio-rustls` dependency to 0.13 - Update `webpki-roots` dependency to 0.19 -## [1.0.0] - 2019-12-11 +## [1.0.0] - 1.0.0 release -## [1.0.0-alpha.3] - 2019-12-07 +## [1.0.0-alpha.3] - Migrate to tokio 0.2 - Enable rustls acceptor service - Enable native-tls acceptor service -## [1.0.0-alpha.1] - 2019-12-02 +## [1.0.0-alpha.1] - Split openssl acceptor from actix-server package diff --git a/actix-tls/Cargo.toml b/actix-tls/Cargo.toml index d65d675b..0c5a148e 100755 --- a/actix-tls/Cargo.toml +++ b/actix-tls/Cargo.toml @@ -9,7 +9,7 @@ description = "TLS acceptor and connector services for Actix ecosystem" keywords = ["network", "tls", "ssl", "async", "transport"] repository = "https://github.com/actix/actix-net.git" categories = ["network-programming", "asynchronous", "cryptography"] -license = "MIT OR Apache-2.0" +license.workspace = true edition.workspace = true rust-version.workspace = true @@ -29,8 +29,14 @@ connect = [] # use openssl impls openssl = ["tls-openssl", "tokio-openssl"] -# use rustls impls -rustls = ["tokio-rustls", "webpki-roots"] +# alias for backwards compat +rustls = ["rustls-0_20"] + +# use rustls v0.20 impls +rustls-0_20 = ["tokio-rustls-023", "webpki-roots-022"] + +# use rustls v0.21 impls +rustls-0_21 = ["tokio-rustls-024", "webpki-roots-025"] # use native-tls impls native-tls = ["tokio-native-tls"] @@ -57,9 +63,13 @@ http = { version = "0.2.3", optional = true } tls-openssl = { package = "openssl", version = "0.10.48", optional = true } tokio-openssl = { version = "0.6", optional = true } -# rustls -tokio-rustls = { version = "0.23", optional = true } -webpki-roots = { version = "0.22", optional = true } +# rustls v0.20 +tokio-rustls-023 = { package = "tokio-rustls", version = "0.23", optional = true } +webpki-roots-022 = { package = "webpki-roots", version = "0.22", optional = true } + +# rustls v0.21 +tokio-rustls-024 = { package = "tokio-rustls", version = "0.24", optional = true } +webpki-roots-025 = { package = "webpki-roots", version = "0.25", optional = true } # native-tls tokio-native-tls = { version = "0.3", optional = true } @@ -72,11 +82,11 @@ bytes = "1" env_logger = "0.10" futures-util = { version = "0.3.17", default-features = false, features = ["sink"] } log = "0.4" -rcgen = "0.10" +rcgen = "0.11" rustls-pemfile = "1" -tokio-rustls = { version = "0.23", features = ["dangerous_configuration"] } -trust-dns-resolver = "0.22" +tokio-rustls-024 = { package = "tokio-rustls", version = "0.24", features = ["dangerous_configuration"] } +trust-dns-resolver = "0.23" [[example]] name = "accept-rustls" -required-features = ["accept", "rustls"] +required-features = ["accept", "rustls-0_21"] diff --git a/actix-tls/examples/accept-rustls.rs b/actix-tls/examples/accept-rustls.rs index 6424e64a..40f51753 100644 --- a/actix-tls/examples/accept-rustls.rs +++ b/actix-tls/examples/accept-rustls.rs @@ -17,12 +17,13 @@ #[rustfmt::skip] // this `use` is only exists because of how we have organised the crate -// it is not necessary for your actual code; you should import from `rustls` directly -use tokio_rustls::rustls; +// it is not necessary for your actual code; you should import from `rustls` normally +use tokio_rustls_024::rustls; use std::{ fs::File, io::{self, BufReader}, + path::PathBuf, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -32,7 +33,7 @@ use std::{ use actix_rt::net::TcpStream; use actix_server::Server; use actix_service::ServiceFactoryExt as _; -use actix_tls::accept::rustls::{Acceptor as RustlsAcceptor, TlsStream}; +use actix_tls::accept::rustls_0_21::{Acceptor as RustlsAcceptor, TlsStream}; use futures_util::future::ok; use rustls::{server::ServerConfig, Certificate, PrivateKey}; use rustls_pemfile::{certs, rsa_private_keys}; @@ -42,9 +43,16 @@ use tracing::info; async fn main() -> io::Result<()> { env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); + let root_path = env!("CARGO_MANIFEST_DIR") + .parse::() + .unwrap() + .join("examples"); + let cert_path = root_path.clone().join("cert.pem"); + let key_path = root_path.clone().join("key.pem"); + // Load TLS key and cert files - let cert_file = &mut BufReader::new(File::open("./examples/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("./examples/key.pem").unwrap()); + let cert_file = &mut BufReader::new(File::open(cert_path).unwrap()); + let key_file = &mut BufReader::new(File::open(key_path).unwrap()); let cert_chain = certs(cert_file) .unwrap() @@ -64,7 +72,7 @@ async fn main() -> io::Result<()> { let count = Arc::new(AtomicUsize::new(0)); let addr = ("127.0.0.1", 8443); - info!("starting server on port: {}", &addr.0); + info!("starting server at: {addr:?}"); Server::build() .bind("tls-example", addr, move || { diff --git a/actix-tls/src/accept/mod.rs b/actix-tls/src/accept/mod.rs index 1eee6720..18585970 100644 --- a/actix-tls/src/accept/mod.rs +++ b/actix-tls/src/accept/mod.rs @@ -12,15 +12,27 @@ use actix_utils::counter::Counter; #[cfg(feature = "openssl")] pub mod openssl; -#[cfg(feature = "rustls")] -pub mod rustls; +#[cfg(feature = "rustls-0_20")] +pub mod rustls_0_20; + +#[doc(hidden)] +#[cfg(feature = "rustls-0_20")] +pub use rustls_0_20 as rustls; + +#[cfg(feature = "rustls-0_21")] +pub mod rustls_0_21; #[cfg(feature = "native-tls")] pub mod native_tls; pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256); -#[cfg(any(feature = "openssl", feature = "rustls", feature = "native-tls"))] +#[cfg(any( + feature = "openssl", + feature = "rustls-0_20", + feature = "rustls-0_21", + feature = "native-tls", +))] pub(crate) const DEFAULT_TLS_HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3); diff --git a/actix-tls/src/accept/rustls.rs b/actix-tls/src/accept/rustls_0_20.rs similarity index 97% rename from actix-tls/src/accept/rustls.rs rename to actix-tls/src/accept/rustls_0_20.rs index 85cf5b00..d06f1799 100644 --- a/actix-tls/src/accept/rustls.rs +++ b/actix-tls/src/accept/rustls_0_20.rs @@ -1,4 +1,4 @@ -//! `rustls` based TLS connection acceptor service. +//! `rustls` v0.20 based TLS connection acceptor service. //! //! See [`Acceptor`] for main service factory docs. @@ -24,13 +24,14 @@ use actix_utils::{ use pin_project_lite::pin_project; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_rustls::{rustls::ServerConfig, Accept, TlsAcceptor}; +use tokio_rustls_023 as tokio_rustls; use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; pub mod reexports { //! Re-exports from `rustls` that are useful for acceptors. - pub use tokio_rustls::rustls::ServerConfig; + pub use tokio_rustls_023::rustls::ServerConfig; } /// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`]. diff --git a/actix-tls/src/accept/rustls_0_21.rs b/actix-tls/src/accept/rustls_0_21.rs new file mode 100644 index 00000000..27db8cdd --- /dev/null +++ b/actix-tls/src/accept/rustls_0_21.rs @@ -0,0 +1,198 @@ +//! `rustls` v0.21 based TLS connection acceptor service. +//! +//! See [`Acceptor`] for main service factory docs. + +use std::{ + convert::Infallible, + future::Future, + io::{self, IoSlice}, + pin::Pin, + sync::Arc, + task::{Context, Poll}, + time::Duration, +}; + +use actix_rt::{ + net::{ActixStream, Ready}, + time::{sleep, Sleep}, +}; +use actix_service::{Service, ServiceFactory}; +use actix_utils::{ + counter::{Counter, CounterGuard}, + future::{ready, Ready as FutReady}, +}; +use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio_rustls::{rustls::ServerConfig, Accept, TlsAcceptor}; +use tokio_rustls_024 as tokio_rustls; + +use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; + +pub mod reexports { + //! Re-exports from `rustls` that are useful for acceptors. + + pub use tokio_rustls_024::rustls::ServerConfig; +} + +/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`]. +pub struct TlsStream(tokio_rustls::server::TlsStream); + +impl_more::impl_from!( in tokio_rustls::server::TlsStream => TlsStream); +impl_more::impl_deref_and_mut!( in TlsStream => tokio_rustls::server::TlsStream); + +impl AsyncRead for TlsStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_read(cx, buf) + } +} + +impl AsyncWrite for TlsStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } +} + +impl ActixStream for TlsStream { + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_read_ready((**self).get_ref().0, cx) + } + + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_write_ready((**self).get_ref().0, cx) + } +} + +/// Accept TLS connections via the `rustls` crate. +pub struct Acceptor { + config: Arc, + handshake_timeout: Duration, +} + +impl Acceptor { + /// Constructs `rustls` based acceptor service factory. + pub fn new(config: ServerConfig) -> Self { + Acceptor { + config: Arc::new(config), + handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT, + } + } + + /// Limit the amount of time that the acceptor will wait for a TLS handshake to complete. + /// + /// Default timeout is 3 seconds. + pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self { + self.handshake_timeout = handshake_timeout; + self + } +} + +impl Clone for Acceptor { + fn clone(&self) -> Self { + Self { + config: self.config.clone(), + handshake_timeout: self.handshake_timeout, + } + } +} + +impl ServiceFactory for Acceptor { + type Response = TlsStream; + type Error = TlsError; + type Config = (); + type Service = AcceptorService; + type InitError = (); + type Future = FutReady>; + + fn new_service(&self, _: ()) -> Self::Future { + let res = MAX_CONN_COUNTER.with(|conns| { + Ok(AcceptorService { + acceptor: self.config.clone().into(), + conns: conns.clone(), + handshake_timeout: self.handshake_timeout, + }) + }); + + ready(res) + } +} + +/// Rustls based acceptor service. +pub struct AcceptorService { + acceptor: TlsAcceptor, + conns: Counter, + handshake_timeout: Duration, +} + +impl Service for AcceptorService { + type Response = TlsStream; + type Error = TlsError; + type Future = AcceptFut; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + if self.conns.available(cx) { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + fn call(&self, req: IO) -> Self::Future { + AcceptFut { + fut: self.acceptor.accept(req), + timeout: sleep(self.handshake_timeout), + _guard: self.conns.get(), + } + } +} + +pin_project! { + /// Accept future for Rustls service. + #[doc(hidden)] + pub struct AcceptFut { + fut: Accept, + #[pin] + timeout: Sleep, + _guard: CounterGuard, + } +} + +impl Future for AcceptFut { + type Output = Result, TlsError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + match Pin::new(&mut this.fut).poll(cx) { + Poll::Ready(Ok(stream)) => Poll::Ready(Ok(TlsStream(stream))), + Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))), + Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)), + } + } +} diff --git a/actix-tls/src/connect/mod.rs b/actix-tls/src/connect/mod.rs index dae1f7b1..79cbb295 100644 --- a/actix-tls/src/connect/mod.rs +++ b/actix-tls/src/connect/mod.rs @@ -27,8 +27,15 @@ mod uri; #[cfg(feature = "openssl")] pub mod openssl; -#[cfg(feature = "rustls")] -pub mod rustls; +#[cfg(feature = "rustls-0_20")] +pub mod rustls_0_20; + +#[doc(hidden)] +#[cfg(feature = "rustls-0_20")] +pub use rustls_0_20 as rustls; + +#[cfg(feature = "rustls-0_21")] +pub mod rustls_0_21; #[cfg(feature = "native-tls")] pub mod native_tls; diff --git a/actix-tls/src/connect/rustls.rs b/actix-tls/src/connect/rustls_0_20.rs similarity index 78% rename from actix-tls/src/connect/rustls.rs rename to actix-tls/src/connect/rustls_0_20.rs index 5706047d..4547854e 100644 --- a/actix-tls/src/connect/rustls.rs +++ b/actix-tls/src/connect/rustls_0_20.rs @@ -20,22 +20,21 @@ use tokio_rustls::{ rustls::{client::ServerName, ClientConfig, OwnedTrustAnchor, RootCertStore}, Connect as RustlsConnect, TlsConnector as RustlsTlsConnector, }; -use tracing::trace; -use webpki_roots::TLS_SERVER_ROOTS; +use tokio_rustls_023 as tokio_rustls; use crate::connect::{Connection, Host}; pub mod reexports { //! Re-exports from `rustls` and `webpki_roots` that are useful for connectors. - pub use tokio_rustls::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; - pub use webpki_roots::TLS_SERVER_ROOTS; + pub use tokio_rustls_023::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + pub use webpki_roots_022::TLS_SERVER_ROOTS; } /// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store. pub fn webpki_roots_cert_store() -> RootCertStore { let mut root_certs = RootCertStore::empty(); - for cert in TLS_SERVER_ROOTS.0 { + for cert in webpki_roots_022::TLS_SERVER_ROOTS.0 { let cert = OwnedTrustAnchor::from_subject_spki_name_constraints( cert.subject, cert.spki, @@ -102,12 +101,13 @@ where actix_service::always_ready!(); fn call(&self, connection: Connection) -> Self::Future { - trace!("TLS handshake start for: {:?}", connection.hostname()); + tracing::trace!("TLS handshake start for: {:?}", connection.hostname()); let (stream, connection) = connection.replace_io(()); match ServerName::try_from(connection.hostname()) { Ok(host) => ConnectFut::Future { - connect: RustlsTlsConnector::from(self.connector.clone()).connect(host, stream), + connect: RustlsTlsConnector::from(Arc::clone(&self.connector)) + .connect(host, stream), connection: Some(connection), }, Err(_) => ConnectFut::InvalidDns, @@ -117,6 +117,7 @@ where /// Connect future for Rustls service. #[doc(hidden)] +#[allow(clippy::large_enum_variant)] pub enum ConnectFut { /// See issue InvalidDns, @@ -131,17 +132,23 @@ where R: Host, IO: ActixStream, { - type Output = Result>, io::Error>; + type Output = io::Result>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.get_mut() { - Self::InvalidDns => Poll::Ready(Err( - io::Error::new(io::ErrorKind::Other, "rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54") - )), - Self::Future { connect, connection } => { + Self::InvalidDns => Poll::Ready(Err(io::Error::new( + io::ErrorKind::Other, + "Rustls v0.20 can only handle hostname-based connections. Enable the `rustls-0_21` \ + feature and use the Rustls v0.21 utilities to gain this feature.", + ))), + + Self::Future { + connect, + connection, + } => { let stream = ready!(Pin::new(connect).poll(cx))?; let connection = connection.take().unwrap(); - trace!("TLS handshake success: {:?}", connection.hostname()); + tracing::trace!("TLS handshake success: {:?}", connection.hostname()); Poll::Ready(Ok(connection.replace_io(stream).1)) } } diff --git a/actix-tls/src/connect/rustls_0_21.rs b/actix-tls/src/connect/rustls_0_21.rs new file mode 100644 index 00000000..cc0d6de3 --- /dev/null +++ b/actix-tls/src/connect/rustls_0_21.rs @@ -0,0 +1,154 @@ +//! Rustls based connector service. +//! +//! See [`TlsConnector`] for main connector service factory docs. + +use std::{ + convert::TryFrom, + future::Future, + io, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +use actix_rt::net::ActixStream; +use actix_service::{Service, ServiceFactory}; +use actix_utils::future::{ok, Ready}; +use futures_core::ready; +use tokio_rustls::{ + client::TlsStream as AsyncTlsStream, + rustls::{client::ServerName, ClientConfig, OwnedTrustAnchor, RootCertStore}, + Connect as RustlsConnect, TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_024 as tokio_rustls; + +use crate::connect::{Connection, Host}; + +pub mod reexports { + //! Re-exports from `rustls` and `webpki_roots` that are useful for connectors. + + pub use tokio_rustls_024::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + pub use webpki_roots_025::TLS_SERVER_ROOTS; +} + +/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store. +pub fn webpki_roots_cert_store() -> RootCertStore { + let mut root_certs = RootCertStore::empty(); + for cert in webpki_roots_025::TLS_SERVER_ROOTS { + let cert = OwnedTrustAnchor::from_subject_spki_name_constraints( + cert.subject, + cert.spki, + cert.name_constraints, + ); + let certs = vec![cert].into_iter(); + root_certs.add_trust_anchors(certs); + } + root_certs +} + +/// Connector service factory using `rustls`. +#[derive(Clone)] +pub struct TlsConnector { + connector: Arc, +} + +impl TlsConnector { + /// Constructs new connector service factory from a `rustls` client configuration. + pub fn new(connector: Arc) -> Self { + TlsConnector { connector } + } + + /// Constructs new connector service from a `rustls` client configuration. + pub fn service(connector: Arc) -> TlsConnectorService { + TlsConnectorService { connector } + } +} + +impl ServiceFactory> for TlsConnector +where + R: Host, + IO: ActixStream + 'static, +{ + type Response = Connection>; + type Error = io::Error; + type Config = (); + type Service = TlsConnectorService; + type InitError = (); + type Future = Ready>; + + fn new_service(&self, _: ()) -> Self::Future { + ok(TlsConnectorService { + connector: self.connector.clone(), + }) + } +} + +/// Connector service using `rustls`. +#[derive(Clone)] +pub struct TlsConnectorService { + connector: Arc, +} + +impl Service> for TlsConnectorService +where + R: Host, + IO: ActixStream, +{ + type Response = Connection>; + type Error = io::Error; + type Future = ConnectFut; + + actix_service::always_ready!(); + + fn call(&self, connection: Connection) -> Self::Future { + tracing::trace!("TLS handshake start for: {:?}", connection.hostname()); + let (stream, connection) = connection.replace_io(()); + + match ServerName::try_from(connection.hostname()) { + Ok(host) => ConnectFut::Future { + connect: RustlsTlsConnector::from(Arc::clone(&self.connector)) + .connect(host, stream), + connection: Some(connection), + }, + Err(_) => ConnectFut::InvalidServerName, + } + } +} + +/// Connect future for Rustls service. +#[doc(hidden)] +#[allow(clippy::large_enum_variant)] +pub enum ConnectFut { + InvalidServerName, + Future { + connect: RustlsConnect, + connection: Option>, + }, +} + +impl Future for ConnectFut +where + R: Host, + IO: ActixStream, +{ + type Output = io::Result>>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut() { + Self::InvalidServerName => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "connection parameters specified invalid server name", + ))), + + Self::Future { + connect, + connection, + } => { + let stream = ready!(Pin::new(connect).poll(cx))?; + let connection = connection.take().unwrap(); + tracing::trace!("TLS handshake success: {:?}", connection.hostname()); + Poll::Ready(Ok(connection.replace_io(stream).1)) + } + } + } +} diff --git a/actix-tls/tests/accept-openssl.rs b/actix-tls/tests/accept-openssl.rs index ff707f65..ca57e17d 100644 --- a/actix-tls/tests/accept-openssl.rs +++ b/actix-tls/tests/accept-openssl.rs @@ -3,7 +3,7 @@ #![cfg(all( feature = "accept", feature = "connect", - feature = "rustls", + feature = "rustls-0_21", feature = "openssl" ))] @@ -15,6 +15,7 @@ use actix_service::ServiceFactoryExt as _; use actix_tls::accept::openssl::{Acceptor, TlsStream}; use actix_utils::future::ok; use tokio_rustls::rustls::{Certificate, ClientConfig, RootCertStore, ServerName}; +use tokio_rustls_024 as tokio_rustls; fn new_cert_and_key() -> (String, String) { let cert = @@ -49,7 +50,7 @@ fn openssl_acceptor(cert: String, key: String) -> tls_openssl::ssl::SslAcceptor mod danger { use std::time::SystemTime; - use tokio_rustls::rustls::{ + use tokio_rustls_024::rustls::{ self, client::{ServerCertVerified, ServerCertVerifier}, }; diff --git a/actix-tls/tests/accept-rustls.rs b/actix-tls/tests/accept-rustls.rs index 7955b36f..40d38b7d 100644 --- a/actix-tls/tests/accept-rustls.rs +++ b/actix-tls/tests/accept-rustls.rs @@ -3,7 +3,7 @@ #![cfg(all( feature = "accept", feature = "connect", - feature = "rustls", + feature = "rustls-0_21", feature = "openssl" ))] @@ -15,13 +15,14 @@ use actix_rt::net::TcpStream; use actix_server::TestServer; use actix_service::ServiceFactoryExt as _; use actix_tls::{ - accept::rustls::{Acceptor, TlsStream}, + accept::rustls_0_21::{Acceptor, TlsStream}, connect::openssl::reexports::SslConnector, }; use actix_utils::future::ok; use rustls_pemfile::{certs, pkcs8_private_keys}; use tls_openssl::ssl::SslVerifyMode; use tokio_rustls::rustls::{self, Certificate, PrivateKey, ServerConfig}; +use tokio_rustls_024 as tokio_rustls; fn new_cert_and_key() -> (String, String) { let cert = diff --git a/actix-tls/tests/test_connect.rs b/actix-tls/tests/test_connect.rs index 5e73fd2d..1c969fec 100644 --- a/actix-tls/tests/test_connect.rs +++ b/actix-tls/tests/test_connect.rs @@ -30,7 +30,7 @@ async fn test_string() { assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-0_21")] #[actix_rt::test] async fn test_rustls_string() { let srv = TestServer::start(|| { @@ -114,7 +114,7 @@ async fn test_openssl_uri() { assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(all(feature = "rustls", feature = "uri"))] +#[cfg(all(feature = "rustls-0_21", feature = "uri"))] #[actix_rt::test] async fn test_rustls_uri() { use std::convert::TryFrom; diff --git a/actix-utils/src/counter.rs b/actix-utils/src/counter.rs index a61ef90e..4259fd6d 100644 --- a/actix-utils/src/counter.rs +++ b/actix-utils/src/counter.rs @@ -29,7 +29,7 @@ impl Counter { /// Returns true if counter is below capacity. Otherwise, register to wake task when it is. #[inline] - pub fn available(&self, cx: &mut task::Context<'_>) -> bool { + pub fn available(&self, cx: &task::Context<'_>) -> bool { self.0.available(cx) } @@ -59,7 +59,7 @@ impl CounterInner { } } - fn available(&self, cx: &mut task::Context<'_>) -> bool { + fn available(&self, cx: &task::Context<'_>) -> bool { if self.count.get() < self.capacity { true } else { diff --git a/actix-utils/src/future/poll_fn.rs b/actix-utils/src/future/poll_fn.rs index 15d93dbd..0c798f46 100644 --- a/actix-utils/src/future/poll_fn.rs +++ b/actix-utils/src/future/poll_fn.rs @@ -62,6 +62,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // SAFETY: we are not moving out of the pinned field // see https://github.com/rust-lang/rust/pull/102737 + #[allow(clippy::needless_borrow)] (unsafe { &mut self.get_unchecked_mut().f })(cx) } }