From 951e46186b0c8c37c70843f2f74aa13e6a2f9a8a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 6 Dec 2023 04:04:39 +0000 Subject: [PATCH] feat: add rustls v0.22 support (#513) --- .cargo/config.toml | 18 +-- actix-tls/CHANGES.md | 2 + actix-tls/Cargo.toml | 28 +++-- actix-tls/src/connect/mod.rs | 6 + actix-tls/src/connect/rustls_0_20.rs | 2 +- actix-tls/src/connect/rustls_0_21.rs | 2 +- actix-tls/src/connect/rustls_0_22.rs | 162 +++++++++++++++++++++++++++ actix-tls/tests/accept-openssl.rs | 61 ++++++---- actix-tls/tests/accept-rustls.rs | 8 +- actix-tls/tests/test_connect.rs | 8 +- 10 files changed, 248 insertions(+), 49 deletions(-) create mode 100644 actix-tls/src/connect/rustls_0_22.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index bade4d02..8f1ff8a7 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,20 +6,20 @@ ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocaptur # just check the library (without dev deps) ci-check-min = "hack --workspace check --no-default-features" -ci-check-lib = "hack --workspace --feature-powerset --depth=3 --exclude-features=io-uring check" -ci-check-lib-linux = "hack --workspace --feature-powerset --depth=3 check" +ci-check-lib = "hack --workspace --feature-powerset --depth=2 --exclude-features=io-uring check" +ci-check-lib-linux = "hack --workspace --feature-powerset --depth=2 check" # check everything -ci-check = "hack --workspace --feature-powerset --depth=3 --exclude-features=io-uring check --tests --examples" -ci-check-linux = "hack --workspace --feature-powerset --depth=3 check --tests --examples" +ci-check = "hack --workspace --feature-powerset --depth=2 --exclude-features=io-uring check --tests --examples" +ci-check-linux = "hack --workspace --feature-powerset --depth=2 check --tests --examples" # tests avoiding io-uring feature -ci-test = "hack --feature-powerset --depth=3 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture" -ci-test-rustls-020 = "hack --feature-powerset --depth=3 --exclude-features=io-uring,rustls-0_21 test --lib --tests --no-fail-fast -- --nocapture" -ci-test-rustls-021 = "hack --feature-powerset --depth=3 --exclude-features=io-uring,rustls-0_20 test --lib --tests --no-fail-fast -- --nocapture" +ci-test = "hack --feature-powerset --depth=2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture" +ci-test-rustls-020 = "hack --feature-powerset --depth=2 --exclude-features=io-uring,rustls-0_21 test --lib --tests --no-fail-fast -- --nocapture" +ci-test-rustls-021 = "hack --feature-powerset --depth=2 --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=3 --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 --depth=3 --exclude-features=rustls-0_20 test --lib --tests --no-fail-fast -- --nocapture" +ci-test-linux = "hack --feature-powerset --depth=2 --exclude-features=rustls-0_20 test --lib --tests --no-fail-fast -- --nocapture" diff --git a/actix-tls/CHANGES.md b/actix-tls/CHANGES.md index aa0d668f..2c49bebc 100644 --- a/actix-tls/CHANGES.md +++ b/actix-tls/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased +- Support Rustls v0.22. +- Added `{accept, connect}::rustls_0_22` modules. - Add `rustls-0_21-native-roots` and `rustls-0_20-native-roots` crate features which utilize the `rustls-native-certs` crate to enable a `native_roots_cert_store()` functions in each rustls-based `connect` module. - Implement `Host` for `http::Uri` (`http` crate version `1`). diff --git a/actix-tls/Cargo.toml b/actix-tls/Cargo.toml index 43235227..c3ed89b4 100755 --- a/actix-tls/Cargo.toml +++ b/actix-tls/Cargo.toml @@ -49,12 +49,16 @@ rustls = ["rustls-0_20"] # use rustls v0.20 impls rustls-0_20 = ["rustls-0_20-webpki-roots"] rustls-0_20-webpki-roots = ["tokio-rustls-023", "webpki-roots-022"] -rustls-0_20-native-roots = ["tokio-rustls-023", "dep:rustls-native-certs"] +rustls-0_20-native-roots = ["tokio-rustls-023", "dep:rustls-native-certs-06"] # use rustls v0.21 impls rustls-0_21 = ["rustls-0_21-webpki-roots"] rustls-0_21-webpki-roots = ["tokio-rustls-024", "webpki-roots-025"] -rustls-0_21-native-roots = ["tokio-rustls-024", "dep:rustls-native-certs"] +rustls-0_21-native-roots = ["tokio-rustls-024", "dep:rustls-native-certs-06"] + +# use rustls v0.22 impls +rustls-0_22-webpki-roots = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1", "dep:webpki-roots-026"] +rustls-0_22-native-roots = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1", "dep:rustls-native-certs-07"] # use native-tls impls native-tls = ["tokio-native-tls"] @@ -87,13 +91,19 @@ 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 -rustls-021 = { package = "rustls", version = "0.21.6" } -rustls-webpki-0101 = { package = "rustls-webpki", version = "0.101.4" } +rustls-021 = { package = "rustls", version = "0.21.6", optional = true } +rustls-webpki-0101 = { package = "rustls-webpki", version = "0.101.4", optional = true } tokio-rustls-024 = { package = "tokio-rustls", version = "0.24", optional = true } webpki-roots-025 = { package = "webpki-roots", version = "0.25", optional = true } -# native root certificates for both rustls impls -rustls-native-certs = { version = "0.6", optional = true } +# rustls v0.22 +rustls-pki-types-1 = { package = "rustls-pki-types", version = "1", optional = true } +tokio-rustls-025 = { package = "tokio-rustls", version = "0.25", optional = true } +webpki-roots-026 = { package = "webpki-roots", version = "0.26", optional = true } + +# native root certificates for rustls impls +rustls-native-certs-06 = { package = "rustls-native-certs", version = "0.6", optional = true } +rustls-native-certs-07 = { package = "rustls-native-certs", version = "0.7", optional = true } # native-tls tokio-native-tls = { version = "0.3", optional = true } @@ -106,10 +116,10 @@ bytes = "1" env_logger = "0.10" futures-util = { version = "0.3.17", default-features = false, features = ["sink"] } rcgen = "0.11" -rustls-pemfile = "1" -tokio-rustls-024 = { package = "tokio-rustls", version = "0.24", features = ["dangerous_configuration"] } +rustls-pemfile = "2" +tokio-rustls-025 = { package = "tokio-rustls", version = "0.25" } trust-dns-resolver = "0.23" [[example]] name = "accept-rustls" -required-features = ["accept", "rustls-0_21"] +required-features = ["accept", "rustls-0_22"] diff --git a/actix-tls/src/connect/mod.rs b/actix-tls/src/connect/mod.rs index 2e069c02..b742e76e 100644 --- a/actix-tls/src/connect/mod.rs +++ b/actix-tls/src/connect/mod.rs @@ -46,6 +46,12 @@ pub use rustls_0_20 as rustls; ))] pub mod rustls_0_21; +#[cfg(any( + feature = "rustls-0_22-webpki-roots", + feature = "rustls-0_22-native-roots", +))] +pub mod rustls_0_22; + #[cfg(feature = "native-tls")] pub mod native_tls; diff --git a/actix-tls/src/connect/rustls_0_20.rs b/actix-tls/src/connect/rustls_0_20.rs index 52e73028..19553044 100644 --- a/actix-tls/src/connect/rustls_0_20.rs +++ b/actix-tls/src/connect/rustls_0_20.rs @@ -39,7 +39,7 @@ pub mod reexports { pub fn native_roots_cert_store() -> io::Result { let mut root_certs = RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs()? { + for cert in rustls_native_certs_06::load_native_certs()? { root_certs .add(&tokio_rustls_023::rustls::Certificate(cert.0)) .unwrap(); diff --git a/actix-tls/src/connect/rustls_0_21.rs b/actix-tls/src/connect/rustls_0_21.rs index 7c3ab24b..7474327a 100644 --- a/actix-tls/src/connect/rustls_0_21.rs +++ b/actix-tls/src/connect/rustls_0_21.rs @@ -39,7 +39,7 @@ pub mod reexports { pub fn native_roots_cert_store() -> io::Result { let mut root_certs = RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs()? { + for cert in rustls_native_certs_06::load_native_certs()? { root_certs .add(&tokio_rustls_024::rustls::Certificate(cert.0)) .unwrap(); diff --git a/actix-tls/src/connect/rustls_0_22.rs b/actix-tls/src/connect/rustls_0_22.rs new file mode 100644 index 00000000..bb6f5f83 --- /dev/null +++ b/actix-tls/src/connect/rustls_0_22.rs @@ -0,0 +1,162 @@ +//! Rustls based connector service. +//! +//! See [`TlsConnector`] for main connector service factory docs. + +use std::{ + 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 rustls_pki_types_1::ServerName; +use tokio_rustls::{ + client::TlsStream as AsyncTlsStream, + rustls::{ClientConfig, RootCertStore}, + Connect as RustlsConnect, TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_025 as tokio_rustls; + +use crate::connect::{Connection, Host}; + +pub mod reexports { + //! Re-exports from the `rustls` v0.22 ecosystem that are useful for connectors. + + pub use tokio_rustls_025::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + #[cfg(feature = "rustls-0_22-webpki-roots")] + pub use webpki_roots_026::TLS_SERVER_ROOTS; +} + +/// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store. +/// +/// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors. +#[cfg(feature = "rustls-0_22-native-roots")] +pub fn native_roots_cert_store() -> io::Result { + let mut root_certs = RootCertStore::empty(); + + for cert in rustls_native_certs_07::load_native_certs()? { + root_certs.add(cert).unwrap(); + } + + Ok(root_certs) +} + +/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store. +#[cfg(feature = "rustls-0_22-webpki-roots")] +pub fn webpki_roots_cert_store() -> RootCertStore { + let mut root_certs = RootCertStore::empty(); + root_certs.extend(webpki_roots_026::TLS_SERVER_ROOTS.to_owned()); + 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, conn) = connection.replace_io(()); + + match ServerName::try_from(conn.hostname()) { + Ok(host) => ConnectFut::Future { + connect: RustlsTlsConnector::from(Arc::clone(&self.connector)) + .connect(host.to_owned(), stream), + connection: Some(conn), + }, + 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 ca57e17d..a3506b1b 100644 --- a/actix-tls/tests/accept-openssl.rs +++ b/actix-tls/tests/accept-openssl.rs @@ -3,19 +3,20 @@ #![cfg(all( feature = "accept", feature = "connect", - feature = "rustls-0_21", + feature = "rustls-0_22", feature = "openssl" ))] -use std::{convert::TryFrom, io::Write, sync::Arc}; +use std::{io::Write as _, sync::Arc}; use actix_rt::net::TcpStream; use actix_server::TestServer; 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; +use rustls_pki_types_1::ServerName; +use tokio_rustls::rustls::{ClientConfig, RootCertStore}; +use tokio_rustls_025 as tokio_rustls; fn new_cert_and_key() -> (String, String) { let cert = @@ -48,28 +49,45 @@ fn openssl_acceptor(cert: String, key: String) -> tls_openssl::ssl::SslAcceptor #[allow(dead_code)] mod danger { - use std::time::SystemTime; - - use tokio_rustls_024::rustls::{ - self, - client::{ServerCertVerified, ServerCertVerifier}, - }; - - use super::*; + use tokio_rustls_025::rustls; + #[derive(Debug)] pub struct NoCertificateVerification; - impl ServerCertVerifier for NoCertificateVerification { + impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _end_entity: &Certificate, - _intermediates: &[Certificate], - _server_name: &ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: SystemTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) + end_entity: &rustls_pki_types_1::CertificateDer::CertificateDer<'_>, + intermediates: &[rustls_pki_types_1::CertificateDer::CertificateDer<'_>], + server_name: &rustls_pki_types_1::CertificateDer::ServerName<'_>, + ocsp_response: &[u8], + now: rustls_pki_types_1::CertificateDer::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &rustls_pki_types_1::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &rustls_pki_types_1::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() } } } @@ -77,7 +95,6 @@ mod danger { #[allow(dead_code)] fn rustls_connector(_cert: String, _key: String) -> ClientConfig { let mut config = ClientConfig::builder() - .with_safe_defaults() .with_root_certificates(RootCertStore::empty()) .with_no_client_auth(); diff --git a/actix-tls/tests/accept-rustls.rs b/actix-tls/tests/accept-rustls.rs index 40d38b7d..554f0fc1 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-0_21", + feature = "rustls-0_22", feature = "openssl" ))] @@ -41,8 +41,10 @@ fn rustls_server_config(cert: String, key: String) -> rustls::ServerConfig { let cert = &mut BufReader::new(cert.as_bytes()); let key = &mut BufReader::new(key.as_bytes()); - let cert_chain = certs(cert).unwrap().into_iter().map(Certificate).collect(); - let mut keys = pkcs8_private_keys(key).unwrap(); + let cert_chain = certs(cert).collect::, _>>().unwrap(); + let mut keys = pkcs8_private_keys(key) + .collect::, _>>() + .unwrap(); let mut config = ServerConfig::builder() .with_safe_defaults() diff --git a/actix-tls/tests/test_connect.rs b/actix-tls/tests/test_connect.rs index 8cf8d614..58ffa4db 100644 --- a/actix-tls/tests/test_connect.rs +++ b/actix-tls/tests/test_connect.rs @@ -11,7 +11,7 @@ use actix_server::TestServer; use actix_service::{fn_service, Service, ServiceFactory}; use actix_tls::connect::{ConnectError, ConnectInfo, Connection, Connector, Host}; use bytes::Bytes; -use futures_util::sink::SinkExt; +use futures_util::sink::SinkExt as _; #[cfg(feature = "openssl")] #[actix_rt::test] @@ -30,7 +30,7 @@ async fn test_string() { assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(feature = "rustls-0_21")] +#[cfg(feature = "rustls-0_22")] #[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-0_21", feature = "uri"))] +#[cfg(all(feature = "rustls-0_22", feature = "uri"))] #[actix_rt::test] async fn test_rustls_uri_http1() { let srv = TestServer::start(|| { @@ -131,7 +131,7 @@ async fn test_rustls_uri_http1() { assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(all(feature = "rustls-0_21", feature = "uri"))] +#[cfg(all(feature = "rustls-0_22", feature = "uri"))] #[actix_rt::test] async fn test_rustls_uri() { use std::convert::TryFrom;