From 2125aca2c5e0bba99172ce1a00f72531a58cee2a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 3 Feb 2024 23:55:01 +0000 Subject: [PATCH] Rustls v0.22 support (#3275) --- .cargo/config.toml | 4 +- .github/workflows/ci-post-merge.yml | 23 ++-- .github/workflows/ci.yml | 23 ++-- actix-files/src/lib.rs | 1 + actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 31 +++-- actix-http/examples/tls_rustls.rs | 20 ++-- actix-http/examples/ws.rs | 21 ++-- actix-http/src/h1/service.rs | 65 +++++++++- actix-http/src/h2/service.rs | 55 ++++++++- actix-http/src/lib.rs | 7 +- actix-http/src/service.rs | 124 ++++++++++++++++++-- actix-http/tests/test_rustls.rs | 63 +++++----- actix-test/CHANGES.md | 2 + actix-test/Cargo.toml | 3 + actix-test/src/lib.rs | 83 +++++++++++-- actix-web/CHANGES.md | 3 + actix-web/Cargo.toml | 19 ++- actix-web/src/server.rs | 85 ++++++++++++++ actix-web/tests/test_server.rs | 2 +- awc/CHANGES.md | 3 + awc/Cargo.toml | 30 +++-- awc/src/client/connector.rs | 176 ++++++++++++++++++++-------- awc/tests/test_rustls_client.rs | 70 +++++++---- 24 files changed, 719 insertions(+), 196 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 931b20b2c..350a924de 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,6 @@ [alias] -lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo" -lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo" +lint = "clippy --workspace --all-targets -- -Dclippy::todo" +lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo" # lib checking ci-check-min = "hack --workspace check --no-default-features" diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d2f952ec7..de9b30a59 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -32,22 +32,22 @@ jobs: - name: Install OpenSSL if: matrix.target.os == 'windows-latest' - run: choco install openssl -y --forcex64 --no-progress - - name: Set OpenSSL dir in env - if: matrix.target.os == 'windows-latest' + shell: bash run: | - echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append - echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append + set -e + choco install openssl --version=1.1.1.2100 -y --no-progress + echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV + echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV - name: Install Rust (${{ matrix.version.name }}) uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 with: toolchain: ${{ matrix.version.version }} - - name: Install cargo-hack + - name: Install cargo-hack and cargo-ci-cache-clean uses: taiki-e/install-action@v2.26.8 with: - tool: cargo-hack + tool: cargo-hack,cargo-ci-cache-clean - name: check minimal run: cargo ci-check-min @@ -57,10 +57,12 @@ jobs: - name: tests timeout-minutes: 60 + shell: bash run: | + set -e cargo test --lib --tests -p=actix-router --all-features cargo test --lib --tests -p=actix-http --all-features - cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls + cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls cargo test --lib --tests -p=actix-web-codegen --all-features cargo test --lib --tests -p=awc --all-features cargo test --lib --tests -p=actix-http-test --all-features @@ -69,11 +71,6 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features - - name: Install cargo-ci-cache-clean - uses: taiki-e/install-action@v2.26.8 - with: - tool: cargo-ci-cache-clean - - name: CI cache clean run: cargo-ci-cache-clean diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d2f5bb1c..79c581b81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,22 +37,22 @@ jobs: - name: Install OpenSSL if: matrix.target.os == 'windows-latest' - run: choco install openssl -y --forcex64 --no-progress --version=3.2.1 - - name: Set OpenSSL dir in env - if: matrix.target.os == 'windows-latest' + shell: bash run: | - echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append - echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append + set -e + choco install openssl --version=1.1.1.2100 -y --no-progress + echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV + echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV - name: Install Rust (${{ matrix.version.name }}) uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 with: toolchain: ${{ matrix.version.version }} - - name: Install cargo-hack + - name: Install cargo-hack and cargo-ci-cache-clean uses: taiki-e/install-action@v2.26.8 with: - tool: cargo-hack + tool: cargo-hack,cargo-ci-cache-clean - name: workaround MSRV issues if: matrix.version.name == 'msrv' @@ -71,10 +71,12 @@ jobs: - name: tests timeout-minutes: 60 + shell: bash run: | + set -e cargo test --lib --tests -p=actix-router --all-features cargo test --lib --tests -p=actix-http --all-features - cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls + cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls cargo test --lib --tests -p=actix-web-codegen --all-features cargo test --lib --tests -p=awc --all-features cargo test --lib --tests -p=actix-http-test --all-features @@ -83,11 +85,6 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features - - name: Install cargo-ci-cache-clean - uses: taiki-e/install-action@v2.26.8 - with: - tool: cargo-ci-cache-clean - - name: CI cache clean run: cargo-ci-cache-clean diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 5b1f14eed..8381519aa 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -568,6 +568,7 @@ mod tests { assert_eq!(bytes, data); } + #[cfg(not(target_os = "windows"))] #[actix_rt::test] async fn test_static_files_with_special_characters() { // Create the file we want to test against ad-hoc. We can't check it in as otherwise diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3ce06442e..aca2a2573 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,8 @@ ### Added +- Add `rustls-0_22` crate feature. +- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_22()` and `HttpService::rustls_0_22_with_config()` service constructors. - Implement `From<&HeaderMap>` for `http::HeaderMap`. ## 3.5.1 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e7d50c313..3a3000169 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -20,8 +20,18 @@ edition.workspace = true rust-version.workspace = true [package.metadata.docs.rs] -# features that docs.rs will build with -features = ["http2", "ws", "openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd"] +rustdoc-args = ["--cfg", "docsrs"] +features = [ + "http2", + "ws", + "openssl", + "rustls-0_20", + "rustls-0_21", + "rustls-0_22", + "compress-brotli", + "compress-gzip", + "compress-zstd", +] [lib] name = "actix_http" @@ -53,6 +63,9 @@ rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"] # TLS via Rustls v0.21 rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"] +# TLS via Rustls v0.22 +rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"] + # Compression codecs compress-brotli = ["__compress", "brotli"] compress-gzip = ["__compress", "flate2"] @@ -98,7 +111,7 @@ rand = { version = "0.8", optional = true } sha1 = { version = "0.10", optional = true } # openssl/rustls -actix-tls = { version = "3.1", default-features = false, optional = true } +actix-tls = { version = "3.3", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } @@ -108,7 +121,7 @@ zstd = { version = "0.13", optional = true } [dev-dependencies] actix-http-test = { version = "3", features = ["openssl"] } actix-server = "2" -actix-tls = { version = "3.1", features = ["openssl"] } +actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22-webpki-roots"] } actix-web = "4" async-stream = "0.3" @@ -117,24 +130,24 @@ env_logger = "0.10" futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } memchr = "2.4" once_cell = "1.9" -rcgen = "0.11" +rcgen = "0.12" regex = "1.3" rustversion = "1" -rustls-pemfile = "1" +rustls-pemfile = "2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.55" } -tls-rustls_021 = { package = "rustls", version = "0.21" } +tls-rustls_022 = { package = "rustls", version = "0.22" } tokio = { version = "1.24.2", features = ["net", "rt", "macros"] } [[example]] name = "ws" -required-features = ["ws", "rustls-0_21"] +required-features = ["ws", "rustls-0_22"] [[example]] name = "tls_rustls" -required-features = ["http2", "rustls-0_21"] +required-features = ["http2", "rustls-0_22"] [[bench]] name = "response-body-compression" diff --git a/actix-http/examples/tls_rustls.rs b/actix-http/examples/tls_rustls.rs index fbb55c6a4..47ff061cd 100644 --- a/actix-http/examples/tls_rustls.rs +++ b/actix-http/examples/tls_rustls.rs @@ -12,7 +12,7 @@ //! Protocol: HTTP/1.1 //! ``` -extern crate tls_rustls_021 as rustls; +extern crate tls_rustls_022 as rustls; use std::io; @@ -36,7 +36,7 @@ async fn main() -> io::Result<()> { ); ok::<_, Error>(Response::ok().set_body(body)) }) - .rustls_021(rustls_config()) + .rustls_0_22(rustls_config()) })? .run() .await @@ -51,16 +51,18 @@ fn rustls_config() -> rustls::ServerConfig { let key_file = &mut io::BufReader::new(key_file.as_bytes()); let cert_chain = rustls_pemfile::certs(cert_file) - .unwrap() - .into_iter() - .map(rustls::Certificate) - .collect(); - let mut keys = rustls_pemfile::pkcs8_private_keys(key_file).unwrap(); + .collect::, _>>() + .unwrap(); + let mut keys = rustls_pemfile::pkcs8_private_keys(key_file) + .collect::, _>>() + .unwrap(); let mut config = rustls::ServerConfig::builder() - .with_safe_defaults() .with_no_client_auth() - .with_single_cert(cert_chain, rustls::PrivateKey(keys.remove(0))) + .with_single_cert( + cert_chain, + rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)), + ) .unwrap(); const H1_ALPN: &[u8] = b"http/1.1"; diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index 241175ae2..55085fd73 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -1,7 +1,7 @@ //! Sets up a WebSocket server over TCP and TLS. //! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames. -extern crate tls_rustls_021 as rustls; +extern crate tls_rustls_022 as rustls; use std::{ io, @@ -30,7 +30,7 @@ async fn main() -> io::Result<()> { .bind("tls", ("127.0.0.1", 8443), || { HttpService::build() .finish(handler) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) })? .run() .await @@ -85,7 +85,6 @@ impl Stream for Heartbeat { fn tls_config() -> rustls::ServerConfig { use std::io::BufReader; - use rustls::{Certificate, PrivateKey}; use rustls_pemfile::{certs, pkcs8_private_keys}; let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); @@ -95,17 +94,17 @@ fn tls_config() -> rustls::ServerConfig { let cert_file = &mut BufReader::new(cert_file.as_bytes()); let key_file = &mut BufReader::new(key_file.as_bytes()); - let cert_chain = certs(cert_file) - .unwrap() - .into_iter() - .map(Certificate) - .collect(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); + let cert_chain = certs(cert_file).collect::, _>>().unwrap(); + let mut keys = pkcs8_private_keys(key_file) + .collect::, _>>() + .unwrap(); let mut config = rustls::ServerConfig::builder() - .with_safe_defaults() .with_no_client_auth() - .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .with_single_cert( + cert_chain, + rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)), + ) .unwrap(); config.alpn_protocols.push(b"http/1.1".to_vec()); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 3b27e3db5..64eb39c82 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -153,7 +153,7 @@ mod openssl { } #[cfg(feature = "rustls-0_20")] -mod rustls_020 { +mod rustls_0_20 { use std::io; use actix_service::ServiceFactoryExt as _; @@ -214,7 +214,7 @@ mod rustls_020 { } #[cfg(feature = "rustls-0_21")] -mod rustls_021 { +mod rustls_0_21 { use std::io; use actix_service::ServiceFactoryExt as _; @@ -274,6 +274,67 @@ mod rustls_021 { } } +#[cfg(feature = "rustls-0_22")] +mod rustls_0_22 { + use std::io; + + use actix_service::ServiceFactoryExt as _; + use actix_tls::accept::{ + rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream}, + TlsError, + }; + + use super::*; + + impl H1Service, S, B, X, U> + where + S: ServiceFactory, + S::Future: 'static, + S::Error: Into>, + S::InitError: fmt::Debug, + S::Response: Into>, + + B: MessageBody, + + X: ServiceFactory, + X::Future: 'static, + X::Error: Into>, + X::InitError: fmt::Debug, + + U: ServiceFactory< + (Request, Framed, Codec>), + Config = (), + Response = (), + >, + U::Future: 'static, + U::Error: fmt::Display + Into>, + U::InitError: fmt::Debug, + { + /// Create Rustls v0.22 based service. + pub fn rustls_0_22( + self, + config: ServerConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = (), + > { + Acceptor::new(config) + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .map(|io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + (io, peer_addr) + }) + .and_then(self.map_err(TlsError::Service)) + } + } +} + impl H1Service where S: ServiceFactory, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 0ae7ea682..d50ffc4e3 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -141,7 +141,7 @@ mod openssl { } #[cfg(feature = "rustls-0_20")] -mod rustls_020 { +mod rustls_0_20 { use std::io; use actix_service::ServiceFactoryExt as _; @@ -192,7 +192,7 @@ mod rustls_020 { } #[cfg(feature = "rustls-0_21")] -mod rustls_021 { +mod rustls_0_21 { use std::io; use actix_service::ServiceFactoryExt as _; @@ -242,6 +242,57 @@ mod rustls_021 { } } +#[cfg(feature = "rustls-0_22")] +mod rustls_0_22 { + use std::io; + + use actix_service::ServiceFactoryExt as _; + use actix_tls::accept::{ + rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream}, + TlsError, + }; + + use super::*; + + impl H2Service, S, B> + where + S: ServiceFactory, + S::Future: 'static, + S::Error: Into> + 'static, + S::Response: Into> + 'static, + >::Future: 'static, + + B: MessageBody + 'static, + { + /// Create Rustls v0.22 based service. + pub fn rustls_0_22( + self, + mut config: ServerConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = S::InitError, + > { + let mut protos = vec![b"h2".to_vec()]; + protos.extend_from_slice(&config.alpn_protocols); + config.alpn_protocols = protos; + + Acceptor::new(config) + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .map(|io: TlsStream| { + let peer_addr = io.get_ref().0.peer_addr().ok(); + (io, peer_addr) + }) + .and_then(self.map_err(TlsError::Service)) + } + } +} + impl ServiceFactory<(T, Option)> for H2Service where T: AsyncRead + AsyncWrite + Unpin + 'static, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 888c3e06f..cb82ced00 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -58,7 +58,12 @@ pub mod ws; #[allow(deprecated)] pub use self::payload::PayloadStream; -#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))] +#[cfg(any( + feature = "openssl", + feature = "rustls-0_20", + feature = "rustls-0_21", + feature = "rustls-0_22", +))] pub use self::service::TlsAcceptorConfig; pub use self::{ builder::HttpServiceBuilder, diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index fb38ba636..e24387182 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -241,13 +241,23 @@ where } /// Configuration options used when accepting TLS connection. -#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))] +#[cfg(any( + feature = "openssl", + feature = "rustls-0_20", + feature = "rustls-0_21", + feature = "rustls-0_22", +))] #[derive(Debug, Default)] pub struct TlsAcceptorConfig { pub(crate) handshake_timeout: Option, } -#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))] +#[cfg(any( + feature = "openssl", + feature = "rustls-0_20", + feature = "rustls-0_21", + feature = "rustls-0_22", +))] impl TlsAcceptorConfig { /// Set TLS handshake timeout duration. pub fn handshake_timeout(self, dur: std::time::Duration) -> Self { @@ -353,12 +363,12 @@ mod openssl { } #[cfg(feature = "rustls-0_20")] -mod rustls_020 { +mod rustls_0_20 { use std::io; use actix_service::ServiceFactoryExt as _; use actix_tls::accept::{ - rustls::{reexports::ServerConfig, Acceptor, TlsStream}, + rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream}, TlsError, }; @@ -389,7 +399,7 @@ mod rustls_020 { U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { - /// Create Rustls based service. + /// Create Rustls v0.20 based service. pub fn rustls( self, config: ServerConfig, @@ -403,7 +413,7 @@ mod rustls_020 { self.rustls_with_config(config, TlsAcceptorConfig::default()) } - /// Create Rustls based service with custom TLS acceptor configuration. + /// Create Rustls v0.20 based service with custom TLS acceptor configuration. pub fn rustls_with_config( self, mut config: ServerConfig, @@ -449,7 +459,7 @@ mod rustls_020 { } #[cfg(feature = "rustls-0_21")] -mod rustls_021 { +mod rustls_0_21 { use std::io; use actix_service::ServiceFactoryExt as _; @@ -485,7 +495,7 @@ mod rustls_021 { U::Error: fmt::Display + Into>, U::InitError: fmt::Debug, { - /// Create Rustls based service. + /// Create Rustls v0.21 based service. pub fn rustls_021( self, config: ServerConfig, @@ -499,7 +509,7 @@ mod rustls_021 { self.rustls_021_with_config(config, TlsAcceptorConfig::default()) } - /// Create Rustls based service with custom TLS acceptor configuration. + /// Create Rustls v0.21 based service with custom TLS acceptor configuration. pub fn rustls_021_with_config( self, mut config: ServerConfig, @@ -544,6 +554,102 @@ mod rustls_021 { } } +#[cfg(feature = "rustls-0_22")] +mod rustls_0_22 { + use std::io; + + use actix_service::ServiceFactoryExt as _; + use actix_tls::accept::{ + rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream}, + TlsError, + }; + + use super::*; + + impl HttpService, S, B, X, U> + where + S: ServiceFactory, + S::Future: 'static, + S::Error: Into> + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + >::Future: 'static, + + B: MessageBody + 'static, + + X: ServiceFactory, + X::Future: 'static, + X::Error: Into>, + X::InitError: fmt::Debug, + + U: ServiceFactory< + (Request, Framed, h1::Codec>), + Config = (), + Response = (), + >, + U::Future: 'static, + U::Error: fmt::Display + Into>, + U::InitError: fmt::Debug, + { + /// Create Rustls v0.22 based service. + pub fn rustls_0_22( + self, + config: ServerConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = (), + > { + self.rustls_0_22_with_config(config, TlsAcceptorConfig::default()) + } + + /// Create Rustls v0.22 based service with custom TLS acceptor configuration. + pub fn rustls_0_22_with_config( + self, + mut config: ServerConfig, + tls_acceptor_config: TlsAcceptorConfig, + ) -> impl ServiceFactory< + TcpStream, + Config = (), + Response = (), + Error = TlsError, + InitError = (), + > { + let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + protos.extend_from_slice(&config.alpn_protocols); + config.alpn_protocols = protos; + + let mut acceptor = Acceptor::new(config); + + if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout { + acceptor.set_handshake_timeout(handshake_timeout); + } + + acceptor + .map_init_err(|_| { + unreachable!("TLS acceptor service factory does not error on init") + }) + .map_err(TlsError::into_service_error) + .and_then(|io: TlsStream| async { + let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() { + if protos.windows(2).any(|window| window == b"h2") { + Protocol::Http2 + } else { + Protocol::Http1 + } + } else { + Protocol::Http1 + }; + let peer_addr = io.get_ref().0.peer_addr().ok(); + Ok((io, proto, peer_addr)) + }) + .and_then(self.map_err(TlsError::Service)) + } + } +} + impl ServiceFactory<(T, Protocol, Option)> for HttpService where diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index c94e579e5..08b3a249b 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -1,6 +1,6 @@ -#![cfg(feature = "rustls-0_21")] +#![cfg(feature = "rustls-0_22")] -extern crate tls_rustls_021 as rustls; +extern crate tls_rustls_022 as rustls; use std::{ convert::Infallible, @@ -20,13 +20,13 @@ use actix_http::{ use actix_http_test::test_server; use actix_rt::pin; use actix_service::{fn_factory_with_config, fn_service}; -use actix_tls::connect::rustls_0_21::webpki_roots_cert_store; +use actix_tls::connect::rustls_0_22::webpki_roots_cert_store; use actix_utils::future::{err, ok, poll_fn}; use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; use futures_core::{ready, Stream}; use futures_util::stream::once; -use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName}; +use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig}; use rustls_pemfile::{certs, pkcs8_private_keys}; async fn load_body(stream: S) -> Result @@ -59,17 +59,17 @@ fn tls_config() -> RustlsServerConfig { let cert_file = &mut BufReader::new(cert_file.as_bytes()); let key_file = &mut BufReader::new(key_file.as_bytes()); - let cert_chain = certs(cert_file) - .unwrap() - .into_iter() - .map(Certificate) - .collect(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); + let cert_chain = certs(cert_file).collect::, _>>().unwrap(); + let mut keys = pkcs8_private_keys(key_file) + .collect::, _>>() + .unwrap(); let mut config = RustlsServerConfig::builder() - .with_safe_defaults() .with_no_client_auth() - .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .with_single_cert( + cert_chain, + rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)), + ) .unwrap(); config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec()); @@ -83,7 +83,6 @@ pub fn get_negotiated_alpn_protocol( client_alpn_protocol: &[u8], ) -> Option> { let mut config = rustls::ClientConfig::builder() - .with_safe_defaults() .with_root_certificates(webpki_roots_cert_store()) .with_no_client_auth(); @@ -109,7 +108,7 @@ async fn h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h1(|_| ok::<_, Error>(Response::ok())) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -123,7 +122,7 @@ async fn h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Error>(Response::ok())) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -141,7 +140,7 @@ async fn h1_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_11); ok::<_, Error>(Response::ok()) }) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -159,7 +158,7 @@ async fn h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); ok::<_, Error>(Response::ok()) }) - .rustls_021_with_config( + .rustls_0_22_with_config( tls_config(), TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)), ) @@ -180,7 +179,7 @@ async fn h2_body1() -> io::Result<()> { let body = load_body(req.take_payload()).await?; Ok::<_, Error>(Response::ok().set_body(body)) }) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -206,7 +205,7 @@ async fn h2_content_length() { ]; ok::<_, Infallible>(Response::new(statuses[indx])) }) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -278,7 +277,7 @@ async fn h2_headers() { } ok::<_, Infallible>(config.body(data.clone())) }) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -317,7 +316,7 @@ async fn h2_body2() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -334,7 +333,7 @@ async fn h2_head_empty() { let mut srv = test_server(move || { HttpService::build() .finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -360,7 +359,7 @@ async fn h2_head_binary() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -385,7 +384,7 @@ async fn h2_head_binary2() { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR))) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -411,7 +410,7 @@ async fn h2_body_length() { Response::ok().set_body(SizedStream::new(STR.len() as u64, body)), ) }) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -435,7 +434,7 @@ async fn h2_body_chunked_explicit() { .body(BodyStream::new(body)), ) }) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -464,7 +463,7 @@ async fn h2_response_http_error_handling() { ) })) })) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -494,7 +493,7 @@ async fn h2_service_error() { let mut srv = test_server(move || { HttpService::build() .h2(|_| err::, _>(BadRequest)) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -511,7 +510,7 @@ async fn h1_service_error() { let mut srv = test_server(move || { HttpService::build() .h1(|_| err::, _>(BadRequest)) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) }) .await; @@ -534,7 +533,7 @@ async fn alpn_h1() -> io::Result<()> { config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); HttpService::build() .h1(|_| ok::<_, Error>(Response::ok())) - .rustls_021(config) + .rustls_0_22(config) }) .await; @@ -556,7 +555,7 @@ async fn alpn_h2() -> io::Result<()> { config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); HttpService::build() .h2(|_| ok::<_, Error>(Response::ok())) - .rustls_021(config) + .rustls_0_22(config) }) .await; @@ -582,7 +581,7 @@ async fn alpn_h2_1() -> io::Result<()> { config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); HttpService::build() .finish(|_| ok::<_, Error>(Response::ok())) - .rustls_021(config) + .rustls_0_22(config) }) .await; diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index a3ca7fe10..bdacc8b2d 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased +- Add `TestServerConfig::rustls_0_22()` method for Rustls v0.22 support behind new `rustls-0_22` crate feature. + ## 0.1.2 - Add `TestServerConfig::rustls_021()` method for Rustls v0.21 support behind new `rustls-0_21` crate feature. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 46d65abdc..d5be25384 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -27,6 +27,8 @@ rustls = ["rustls-0_20"] rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"] # TLS via Rustls v0.21 rustls-0_21 = ["tls-rustls-0_21", "actix-http/rustls-0_21", "awc/rustls-0_21"] +# TLS via Rustls v0.22 +rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"] # TLS via OpenSSL openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] @@ -50,4 +52,5 @@ serde_urlencoded = "0.7" tls-openssl = { package = "openssl", version = "0.10.55", optional = true } tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true } tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true } +tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true } tokio = { version = "1.24.2", features = ["sync"] } diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index e570bb266..b7aeddad2 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -143,6 +143,8 @@ where StreamType::Rustls020(_) => true, #[cfg(feature = "rustls-0_21")] StreamType::Rustls021(_) => true, + #[cfg(feature = "rustls-0_22")] + StreamType::Rustls022(_) => true, }; // run server in separate orphaned thread @@ -327,6 +329,48 @@ where .rustls_021(config.clone()) }), }, + #[cfg(feature = "rustls-0_22")] + StreamType::Rustls022(config) => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + HttpService::build() + .client_request_timeout(timeout) + .h1(map_config(fac, move |_| app_cfg.clone())) + .rustls_0_22(config.clone()) + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + HttpService::build() + .client_request_timeout(timeout) + .h2(map_config(fac, move |_| app_cfg.clone())) + .rustls_0_22(config.clone()) + }), + HttpVer::Both => builder.listen("test", tcp, move || { + let app_cfg = + AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + HttpService::build() + .client_request_timeout(timeout) + .finish(map_config(fac, move |_| app_cfg.clone())) + .rustls_0_22(config.clone()) + }), + }, } .expect("test server could not be created"); @@ -401,6 +445,8 @@ enum StreamType { Rustls020(tls_rustls_0_20::ServerConfig), #[cfg(feature = "rustls-0_21")] Rustls021(tls_rustls_0_21::ServerConfig), + #[cfg(feature = "rustls-0_22")] + Rustls022(tls_rustls_0_22::ServerConfig), } /// Create default test server config. @@ -424,7 +470,7 @@ impl Default for TestServerConfig { } impl TestServerConfig { - /// Create default server configuration + /// Constructs default server configuration. pub(crate) fn new() -> TestServerConfig { TestServerConfig { tp: HttpVer::Both, @@ -435,40 +481,63 @@ impl TestServerConfig { } } - /// Accept HTTP/1.1 only. + /// Accepts HTTP/1.1 only. pub fn h1(mut self) -> Self { self.tp = HttpVer::Http1; self } - /// Accept HTTP/2 only. + /// Accepts HTTP/2 only. pub fn h2(mut self) -> Self { self.tp = HttpVer::Http2; self } - /// Accept secure connections via OpenSSL. + /// Accepts secure connections via OpenSSL. #[cfg(feature = "openssl")] pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self { self.stream = StreamType::Openssl(acceptor); self } - /// Accept secure connections via Rustls. + #[doc(hidden)] + #[deprecated(note = "Renamed to `rustls_0_20()`.")] #[cfg(feature = "rustls-0_20")] pub fn rustls(mut self, config: tls_rustls_0_20::ServerConfig) -> Self { self.stream = StreamType::Rustls020(config); self } - /// Accept secure connections via Rustls. + /// Accepts secure connections via Rustls v0.20. + #[cfg(feature = "rustls-0_20")] + pub fn rustls_0_20(mut self, config: tls_rustls_0_20::ServerConfig) -> Self { + self.stream = StreamType::Rustls020(config); + self + } + + #[doc(hidden)] + #[deprecated(note = "Renamed to `rustls_0_21()`.")] #[cfg(feature = "rustls-0_21")] pub fn rustls_021(mut self, config: tls_rustls_0_21::ServerConfig) -> Self { self.stream = StreamType::Rustls021(config); self } - /// Set client timeout for first request. + /// Accepts secure connections via Rustls v0.21. + #[cfg(feature = "rustls-0_21")] + pub fn rustls_0_21(mut self, config: tls_rustls_0_21::ServerConfig) -> Self { + self.stream = StreamType::Rustls021(config); + self + } + + /// Accepts secure connections via Rustls v0.22. + #[cfg(feature = "rustls-0_22")] + pub fn rustls_0_22(mut self, config: tls_rustls_0_22::ServerConfig) -> Self { + self.stream = StreamType::Rustls022(config); + self + } + + /// Sets client timeout for first request. pub fn client_request_timeout(mut self, dur: Duration) -> Self { self.client_request_timeout = dur; self diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 674c834ad..072cdc012 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased +- Add `rustls-0_22` crate feature. +- Add `HttpServer::{bind_rustls_0_22, listen_rustls_0_22}()` builder methods. + ## 4.4.1 ### Changed diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index d1fc9c916..37299721f 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -20,9 +20,20 @@ edition.workspace = true rust-version.workspace = true [package.metadata.docs.rs] -# features that docs.rs will build with -features = ["macros", "openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"] rustdoc-args = ["--cfg", "docsrs"] +features = [ + "macros", + "openssl", + "rustls-0_20", + "rustls-0_21", + "rustls-0_22", + "compress-brotli", + "compress-gzip", + "compress-zstd", + "cookies", + "secure-cookies", +] + [lib] name = "actix_web" @@ -58,6 +69,8 @@ rustls = ["rustls-0_20"] rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"] # TLS via Rustls v0.21 rustls-0_21 = ["http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"] +# TLS via Rustls v0.22 +rustls-0_22 = ["http2", "actix-http/rustls-0_22", "actix-tls/accept", "actix-tls/rustls-0_22"] # Internal (PRIVATE!) features used to aid testing and checking feature status. # Don't rely on these whatsoever. They may disappear at anytime. @@ -73,7 +86,7 @@ actix-rt = { version = "2.6", default-features = false } actix-server = "2" actix-service = "2" actix-utils = "3" -actix-tls = { version = "3.1", default-features = false, optional = true } +actix-tls = { version = "3.3", default-features = false, optional = true } actix-http = { version = "3.5", features = ["ws"] } actix-router = "0.5" diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 2cc00cb9f..193a7cce9 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -442,6 +442,25 @@ where Ok(self) } + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections + /// using Rustls v0.22. + /// + /// See [`bind()`](Self::bind()) for more details on `addrs` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "rustls-0_22")] + pub fn bind_rustls_0_22( + mut self, + addrs: A, + config: actix_tls::accept::rustls_0_22::reexports::ServerConfig, + ) -> io::Result { + let sockets = bind_addrs(addrs, self.backlog)?; + for lst in sockets { + self = self.listen_rustls_0_22_inner(lst, config.clone())?; + } + Ok(self) + } + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections /// using OpenSSL. /// @@ -685,6 +704,72 @@ where Ok(self) } + /// Binds to existing listener for accepting incoming TLS connection requests using Rustls + /// v0.22. + /// + /// See [`listen()`](Self::listen) for more details on the `lst` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. + #[cfg(feature = "rustls-0_22")] + pub fn listen_rustls_0_22( + self, + lst: net::TcpListener, + config: actix_tls::accept::rustls_0_22::reexports::ServerConfig, + ) -> io::Result { + self.listen_rustls_0_22_inner(lst, config) + } + + #[cfg(feature = "rustls-0_22")] + fn listen_rustls_0_22_inner( + mut self, + lst: net::TcpListener, + config: actix_tls::accept::rustls_0_22::reexports::ServerConfig, + ) -> io::Result { + let factory = self.factory.clone(); + let cfg = self.config.clone(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + scheme: "https", + }); + + let on_connect_fn = self.on_connect_fn.clone(); + + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) + } else { + svc + }; + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + + svc.finish(map_config(fac, move |_| { + AppConfig::new(true, host.clone(), addr) + })) + .rustls_0_22_with_config(config.clone(), acceptor_config) + })?; + + Ok(self) + } + /// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL. /// /// See [`listen()`](Self::listen) for more details on the `lst` argument. diff --git a/actix-web/tests/test_server.rs b/actix-web/tests/test_server.rs index a268cb6e3..0df6e2124 100644 --- a/actix-web/tests/test_server.rs +++ b/actix-web/tests/test_server.rs @@ -743,7 +743,7 @@ mod plus_rustls { .map(char::from) .collect::(); - let srv = actix_test::start_with(actix_test::config().rustls_021(tls_config()), || { + let srv = actix_test::start_with(actix_test::config().rustls_0_21(tls_config()), || { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| async { // echo decompressed request body back in response HttpResponse::Ok() diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 6f4212f90..3cef0e139 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased +- Add `rustls-0_22-webpki-roots` and `rustls-0_22-native-roots` crate feature. +- Add `awc::Connector::rustls_0_22()` method. + ## 3.3.0 - Update `trust-dns-resolver` dependency to `0.23`. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 5c09400a0..369119c25 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -20,8 +20,17 @@ name = "awc" path = "src/lib.rs" [package.metadata.docs.rs] -# features that docs.rs will build with -features = ["openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd", "cookies"] +rustdoc-args = ["--cfg", "docsrs"] +features = [ + "cookies", + "openssl", + "rustls-0_20", + "rustls-0_21", + "rustls-0_22-webpki-roots", + "compress-brotli", + "compress-gzip", + "compress-zstd", +] [features] default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"] @@ -35,6 +44,10 @@ rustls = ["rustls-0_20"] rustls-0_20 = ["tls-rustls-0_20", "actix-tls/rustls-0_20"] # TLS via Rustls v0.21 rustls-0_21 = ["tls-rustls-0_21", "actix-tls/rustls-0_21"] +# TLS via Rustls v0.22 (WebPKI roots) +rustls-0_22-webpki-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-webpki-roots"] +# TLS via Rustls v0.22 (Native roots) +rustls-0_22-native-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-native-roots"] # Brotli algorithm content-encoding support compress-brotli = ["actix-http/compress-brotli", "__compress"] @@ -63,7 +76,7 @@ actix-codec = "0.5" actix-service = "2" actix-http = { version = "3.5", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } -actix-tls = { version = "3.1", features = ["connect", "uri"] } +actix-tls = { version = "3.3", features = ["connect", "uri"] } actix-utils = "3" base64 = "0.21" @@ -90,6 +103,7 @@ cookie = { version = "0.16", features = ["percent-encode"], optional = true } tls-openssl = { package = "openssl", version = "0.10.55", optional = true } tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true, features = ["dangerous_configuration"] } tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true, features = ["dangerous_configuration"] } +tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true } trust-dns-resolver = { version = "0.23", optional = true } @@ -97,8 +111,8 @@ trust-dns-resolver = { version = "0.23", optional = true } actix-http = { version = "3.5", features = ["openssl"] } actix-http-test = { version = "3", features = ["openssl"] } actix-server = "2" -actix-test = { version = "0.1", features = ["openssl", "rustls-0_21"] } -actix-tls = { version = "3", features = ["openssl", "rustls-0_21"] } +actix-test = { version = "0.1", features = ["openssl", "rustls-0_22"] } +actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22"] } actix-utils = "3" actix-web = { version = "4", features = ["openssl"] } @@ -108,11 +122,11 @@ env_logger = "0.10" flate2 = "1.0.13" futures-util = { version = "0.3.17", default-features = false } static_assertions = "1.1" -rcgen = "0.11" -rustls-pemfile = "1" +rcgen = "0.12" +rustls-pemfile = "2" tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] } zstd = "0.13" [[example]] name = "client" -required-features = ["rustls-0_21"] +required-features = ["rustls-0_22-webpki-roots"] diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 879d1895b..df6e422f3 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -40,14 +40,23 @@ enum OurTlsConnector { /// Provided because building the OpenSSL context on newer versions can be very slow. /// This prevents unnecessary calls to `.build()` while constructing the client connector. #[cfg(feature = "openssl")] - #[allow(dead_code)] // false positive; used in build_ssl + #[allow(dead_code)] // false positive; used in build_tls OpensslBuilder(actix_tls::connect::openssl::reexports::SslConnectorBuilder), #[cfg(feature = "rustls-0_20")] + #[allow(dead_code)] // false positive; used in build_tls Rustls020(std::sync::Arc), #[cfg(feature = "rustls-0_21")] + #[allow(dead_code)] // false positive; used in build_tls Rustls021(std::sync::Arc), + + #[cfg(any( + feature = "rustls-0_22-webpki-roots", + feature = "rustls-0_22-native-roots", + ))] + #[allow(dead_code)] // false positive; used in build_tls + Rustls022(std::sync::Arc), } /// Manages HTTP client network connectivity. @@ -86,67 +95,83 @@ impl Connector<()> { } } - /// Provides an empty TLS connector when no TLS feature is enabled. - #[cfg(not(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21")))] - fn build_tls(_: Vec>) -> OurTlsConnector { - OurTlsConnector::None - } + cfg_if::cfg_if! { + if #[cfg(any(feature = "rustls-0_22-webpki-roots", feature = "rustls-0_22-webpki-roots"))] { + /// Build TLS connector with Rustls v0.22, based on supplied ALPN protocols. + /// + /// Note that if other TLS crate features are enabled, Rustls v0.22 will be used. + fn build_tls(protocols: Vec>) -> OurTlsConnector { + use actix_tls::connect::rustls_0_22::{self, reexports::ClientConfig}; - /// Build TLS connector with Rustls v0.21, based on supplied ALPN protocols - /// - /// Note that if other TLS crate features are enabled, Rustls v0.21 will be used. - #[cfg(feature = "rustls-0_21")] - fn build_tls(protocols: Vec>) -> OurTlsConnector { - use actix_tls::connect::rustls_0_21::{reexports::ClientConfig, webpki_roots_cert_store}; + cfg_if::cfg_if! { + if #[cfg(feature = "rustls-0_22-webpki-roots")] { + let certs = rustls_0_22::webpki_roots_cert_store(); + } else if #[cfg(feature = "rustls-0_22-native-roots")] { + let certs = rustls_0_22::native_roots_cert_store(); + } + } - let mut config = ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(webpki_roots_cert_store()) - .with_no_client_auth(); + let mut config = ClientConfig::builder() + .with_root_certificates(certs) + .with_no_client_auth(); - config.alpn_protocols = protocols; + config.alpn_protocols = protocols; - OurTlsConnector::Rustls021(std::sync::Arc::new(config)) - } + OurTlsConnector::Rustls022(std::sync::Arc::new(config)) + } + } else if #[cfg(feature = "rustls-0_21")] { + /// Build TLS connector with Rustls v0.21, based on supplied ALPN protocols. + fn build_tls(protocols: Vec>) -> OurTlsConnector { + use actix_tls::connect::rustls_0_21::{reexports::ClientConfig, webpki_roots_cert_store}; - /// Build TLS connector with Rustls v0.20, based on supplied ALPN protocols - /// - /// Note that if other TLS crate features are enabled, Rustls v0.21 will be used. - #[cfg(all(feature = "rustls-0_20", not(feature = "rustls-0_21")))] - fn build_tls(protocols: Vec>) -> OurTlsConnector { - use actix_tls::connect::rustls_0_20::{reexports::ClientConfig, webpki_roots_cert_store}; + let mut config = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(webpki_roots_cert_store()) + .with_no_client_auth(); - let mut config = ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(webpki_roots_cert_store()) - .with_no_client_auth(); + config.alpn_protocols = protocols; - config.alpn_protocols = protocols; + OurTlsConnector::Rustls021(std::sync::Arc::new(config)) + } + } else if #[cfg(feature = "rustls-0_20")] { + /// Build TLS connector with Rustls v0.20, based on supplied ALPN protocols. + fn build_tls(protocols: Vec>) -> OurTlsConnector { + use actix_tls::connect::rustls_0_20::{reexports::ClientConfig, webpki_roots_cert_store}; - OurTlsConnector::Rustls020(std::sync::Arc::new(config)) - } + let mut config = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(webpki_roots_cert_store()) + .with_no_client_auth(); - /// Build TLS connector with OpenSSL, based on supplied ALPN protocols - #[cfg(all( - feature = "openssl", - not(any(feature = "rustls-0_20", feature = "rustls-0_21")), - ))] - fn build_tls(protocols: Vec>) -> OurTlsConnector { - use actix_tls::connect::openssl::reexports::{SslConnector, SslMethod}; - use bytes::{BufMut, BytesMut}; + config.alpn_protocols = protocols; - let mut alpn = BytesMut::with_capacity(20); - for proto in &protocols { - alpn.put_u8(proto.len() as u8); - alpn.put(proto.as_slice()); + OurTlsConnector::Rustls020(std::sync::Arc::new(config)) + } + } else if #[cfg(feature = "openssl")] { + /// Build TLS connector with OpenSSL, based on supplied ALPN protocols. + fn build_tls(protocols: Vec>) -> OurTlsConnector { + use actix_tls::connect::openssl::reexports::{SslConnector, SslMethod}; + use bytes::{BufMut, BytesMut}; + + let mut alpn = BytesMut::with_capacity(20); + for proto in &protocols { + alpn.put_u8(proto.len() as u8); + alpn.put(proto.as_slice()); + } + + let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); + if let Err(err) = ssl.set_alpn_protos(&alpn) { + log::error!("Can not set ALPN protocol: {err:?}"); + } + + OurTlsConnector::OpensslBuilder(ssl) + } + } else { + /// Provides an empty TLS connector when no TLS feature is enabled. + fn build_tls(_: Vec>) -> OurTlsConnector { + OurTlsConnector::None + } } - - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); - if let Err(err) = ssl.set_alpn_protos(&alpn) { - log::error!("Can not set ALPN protocol: {:?}", err); - } - - OurTlsConnector::OpensslBuilder(ssl) } } @@ -240,6 +265,19 @@ where self } + /// Sets custom Rustls v0.22 `ClientConfig` instance. + #[cfg(any( + feature = "rustls-0_22-webpki-roots", + feature = "rustls-0_22-native-roots", + ))] + pub fn rustls_0_22( + mut self, + connector: std::sync::Arc, + ) -> Self { + self.tls = OurTlsConnector::Rustls022(connector); + self + } + /// Sets maximum supported HTTP major version. /// /// Supported versions are HTTP/1.1 and HTTP/2. @@ -509,6 +547,42 @@ where Some(actix_service::boxed::rc_service(tls_service)) } + + #[cfg(any( + feature = "rustls-0_22-webpki-roots", + feature = "rustls-0_22-native-roots", + ))] + OurTlsConnector::Rustls022(tls) => { + const H2: &[u8] = b"h2"; + + use actix_tls::connect::rustls_0_22::{reexports::AsyncTlsStream, TlsConnector}; + + impl IntoConnectionIo for TcpConnection> { + fn into_connection_io(self) -> (Box, Protocol) { + let sock = self.into_parts().0; + let h2 = sock + .get_ref() + .1 + .alpn_protocol() + .map_or(false, |protos| protos.windows(2).any(|w| w == H2)); + if h2 { + (Box::new(sock), Protocol::Http2) + } else { + (Box::new(sock), Protocol::Http1) + } + } + } + + let handshake_timeout = self.config.handshake_timeout; + + let tls_service = TlsConnectorService { + tcp_service: tcp_service_inner, + tls_service: TlsConnector::service(tls), + timeout: handshake_timeout, + }; + + Some(actix_service::boxed::rc_service(tls_service)) + } }; let tcp_config = self.config.no_disconnect_timeout(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index d758f93d8..1cc3e8c48 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -1,6 +1,6 @@ -#![cfg(feature = "rustls-0_21")] +#![cfg(feature = "rustls-0_22-webpki-roots")] -extern crate tls_rustls_0_21 as rustls; +extern crate tls_rustls_0_22 as rustls; use std::{ io::BufReader, @@ -8,18 +8,17 @@ use std::{ atomic::{AtomicUsize, Ordering}, Arc, }, - time::SystemTime, }; use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{fn_service, map_config, ServiceFactoryExt}; -use actix_tls::connect::rustls_0_21::webpki_roots_cert_store; +use actix_tls::connect::rustls_0_22::webpki_roots_cert_store; use actix_utils::future::ok; use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use rustls::{ - client::{ServerCertVerified, ServerCertVerifier}, - Certificate, ClientConfig, PrivateKey, ServerConfig, ServerName, + pki_types::{CertificateDer, PrivateKeyDer, ServerName}, + ClientConfig, ServerConfig, }; use rustls_pemfile::{certs, pkcs8_private_keys}; @@ -31,36 +30,62 @@ fn tls_config() -> ServerConfig { let cert_file = &mut BufReader::new(cert_file.as_bytes()); let key_file = &mut BufReader::new(key_file.as_bytes()); - let cert_chain = certs(cert_file) - .unwrap() - .into_iter() - .map(Certificate) - .collect(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); + let cert_chain = certs(cert_file).collect::, _>>().unwrap(); + let mut keys = pkcs8_private_keys(key_file) + .collect::, _>>() + .unwrap(); ServerConfig::builder() - .with_safe_defaults() .with_no_client_auth() - .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0))) .unwrap() } mod danger { + use rustls::{ + client::danger::{ServerCertVerified, ServerCertVerifier}, + pki_types::UnixTime, + }; + use super::*; + #[derive(Debug)] pub struct NoCertificateVerification; impl ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _end_entity: &Certificate, - _intermediates: &[Certificate], - _server_name: &ServerName, - _scts: &mut dyn Iterator, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, _ocsp_response: &[u8], - _now: SystemTime, + _now: UnixTime, ) -> Result { - Ok(ServerCertVerified::assertion()) + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() } } } @@ -82,14 +107,13 @@ async fn test_connection_reuse_h2() { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .rustls_021(tls_config()) + .rustls_0_22(tls_config()) .map_err(|_| ()), ) }) .await; let mut config = ClientConfig::builder() - .with_safe_defaults() .with_root_certificates(webpki_roots_cert_store()) .with_no_client_auth(); @@ -102,7 +126,7 @@ async fn test_connection_reuse_h2() { .set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); let client = awc::Client::builder() - .connector(awc::Connector::new().rustls_021(Arc::new(config))) + .connector(awc::Connector::new().rustls_0_22(Arc::new(config))) .finish(); // req 1