From b88733d84f20831c790eb1359bc4a49347079d4a Mon Sep 17 00:00:00 2001 From: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com> Date: Thu, 2 May 2024 00:53:37 -0400 Subject: [PATCH] Add feature for using rustls 0.23 --- actix-tls/CHANGES.md | 4 +- actix-tls/Cargo.toml | 12 +- actix-tls/src/accept/mod.rs | 3 + actix-tls/src/accept/rustls_0_23.rs | 161 +++++++++++++++++++++++++++ actix-tls/src/connect/mod.rs | 3 + actix-tls/src/connect/rustls_0_23.rs | 161 +++++++++++++++++++++++++++ actix-tls/tests/accept-openssl.rs | 6 +- actix-tls/tests/accept-rustls.rs | 4 +- actix-tls/tests/test_connect.rs | 6 +- 9 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 actix-tls/src/accept/rustls_0_23.rs create mode 100644 actix-tls/src/connect/rustls_0_23.rs diff --git a/actix-tls/CHANGES.md b/actix-tls/CHANGES.md index 57932a30..494d6d0a 100644 --- a/actix-tls/CHANGES.md +++ b/actix-tls/CHANGES.md @@ -2,9 +2,11 @@ ## Unreleased +- Add `rustls-0_23` crate feature which excludes any root certificate methods or re-exports. + ## 3.3.0 -- Add `rustls-0_22` create feature which excludes any root certificate methods or re-exports. +- Add `rustls-0_22` crate feature which excludes any root certificate methods or re-exports. ## 3.2.0 diff --git a/actix-tls/Cargo.toml b/actix-tls/Cargo.toml index 7078efeb..e2a58487 100755 --- a/actix-tls/Cargo.toml +++ b/actix-tls/Cargo.toml @@ -61,6 +61,11 @@ rustls-0_22 = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1"] rustls-0_22-webpki-roots = ["rustls-0_22", "dep:webpki-roots-026"] rustls-0_22-native-roots = ["rustls-0_22", "dep:rustls-native-certs-07"] +# use rustls v0.23 impls +rustls-0_23 = ["dep:tokio-rustls-026", "dep:rustls-pki-types-1"] +rustls-0_23-webpki-roots = ["rustls-0_23", "dep:webpki-roots-026"] +rustls-0_23-native-roots = ["rustls-0_23", "dep:rustls-native-certs-07"] + # use native-tls impls native-tls = ["dep:tokio-native-tls"] @@ -98,9 +103,12 @@ tokio-rustls-024 = { package = "tokio-rustls", version = "0.24", optional = true webpki-roots-025 = { package = "webpki-roots", version = "0.25", optional = true } # rustls v0.22 -rustls-pki-types-1 = { package = "rustls-pki-types", version = "1", optional = true } +rustls-pki-types-1 = { package = "rustls-pki-types", version = "1", optional = true } # Also used for rustls v0.23 tokio-rustls-025 = { package = "tokio-rustls", version = "0.25", optional = true } -webpki-roots-026 = { package = "webpki-roots", version = "0.26", optional = true } +webpki-roots-026 = { package = "webpki-roots", version = "0.26", optional = true } # Also used for rustls v0.23 + +# rustls v0.23 +tokio-rustls-026 = { package = "tokio-rustls", version = "0.26", optional = true } # native root certificates for rustls impls rustls-native-certs-06 = { package = "rustls-native-certs", version = "0.6", optional = true } diff --git a/actix-tls/src/accept/mod.rs b/actix-tls/src/accept/mod.rs index 2302723e..39fd72e6 100644 --- a/actix-tls/src/accept/mod.rs +++ b/actix-tls/src/accept/mod.rs @@ -25,6 +25,9 @@ pub mod rustls_0_21; #[cfg(feature = "rustls-0_22")] pub mod rustls_0_22; +#[cfg(feature = "rustls-0_23")] +pub mod rustls_0_23; + #[cfg(feature = "native-tls")] pub mod native_tls; diff --git a/actix-tls/src/accept/rustls_0_23.rs b/actix-tls/src/accept/rustls_0_23.rs new file mode 100644 index 00000000..64ab680b --- /dev/null +++ b/actix-tls/src/accept/rustls_0_23.rs @@ -0,0 +1,161 @@ +//! 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, Connect as RustlsConnect, + TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_026 as tokio_rustls; + +use crate::connect::{Connection, Host}; + +pub mod reexports { + //! Re-exports from the `rustls` v0.23 ecosystem that are useful for connectors. + + pub use tokio_rustls_026::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + #[cfg(feature = "rustls-0_23-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_23-native-roots")] +pub fn native_roots_cert_store() -> io::Result { + let mut root_certs = tokio_rustls::rustls::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_23-webpki-roots")] +pub fn webpki_roots_cert_store() -> tokio_rustls::rustls::RootCertStore { + let mut root_certs = tokio_rustls::rustls::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/src/connect/mod.rs b/actix-tls/src/connect/mod.rs index 1e77d98a..04337daa 100644 --- a/actix-tls/src/connect/mod.rs +++ b/actix-tls/src/connect/mod.rs @@ -49,6 +49,9 @@ pub mod rustls_0_21; #[cfg(feature = "rustls-0_22")] pub mod rustls_0_22; +#[cfg(feature = "rustls-0_23")] +pub mod rustls_0_23; + #[cfg(feature = "native-tls")] pub mod native_tls; diff --git a/actix-tls/src/connect/rustls_0_23.rs b/actix-tls/src/connect/rustls_0_23.rs new file mode 100644 index 00000000..18a523e5 --- /dev/null +++ b/actix-tls/src/connect/rustls_0_23.rs @@ -0,0 +1,161 @@ +//! 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, Connect as RustlsConnect, + TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_026 as tokio_rustls; + +use crate::connect::{Connection, Host}; + +pub mod reexports { + //! Re-exports from the `rustls` v0.23 ecosystem that are useful for connectors. + + pub use tokio_rustls_025::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + #[cfg(feature = "rustls-0_23-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_23-native-roots")] +pub fn native_roots_cert_store() -> io::Result { + let mut root_certs = tokio_rustls::rustls::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_23-webpki-roots")] +pub fn webpki_roots_cert_store() -> tokio_rustls::rustls::RootCertStore { + let mut root_certs = tokio_rustls::rustls::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 e571a282..3ae29c0c 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-0_22", + feature = "rustls-0_23", feature = "openssl" ))] @@ -14,11 +14,11 @@ use actix_server::TestServer; use actix_service::ServiceFactoryExt as _; use actix_tls::{ accept::openssl::{Acceptor, TlsStream}, - connect::rustls_0_22::reexports::ClientConfig, + connect::rustls_0_23::reexports::ClientConfig, }; use actix_utils::future::ok; use rustls_pki_types_1::ServerName; -use tokio_rustls_025::rustls::RootCertStore; +use tokio_rustls_026::rustls::RootCertStore; fn new_cert_and_key() -> (String, String) { let cert = diff --git a/actix-tls/tests/accept-rustls.rs b/actix-tls/tests/accept-rustls.rs index bed2f6c3..03be6d93 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_22", + feature = "rustls-0_23", feature = "openssl" ))] @@ -15,7 +15,7 @@ use actix_rt::net::TcpStream; use actix_server::TestServer; use actix_service::ServiceFactoryExt as _; use actix_tls::{ - accept::rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream}, + accept::rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream}, connect::openssl::reexports::SslConnector, }; use actix_utils::future::ok; diff --git a/actix-tls/tests/test_connect.rs b/actix-tls/tests/test_connect.rs index 820df7e3..c0a7fa76 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-0_22")] +#[cfg(feature = "rustls-0_23")] #[actix_rt::test] async fn test_rustls_string() { let srv = TestServer::start(|| { @@ -112,7 +112,7 @@ async fn test_openssl_uri() { assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(all(feature = "rustls-0_22", feature = "uri"))] +#[cfg(all(feature = "rustls-0_23", feature = "uri"))] #[actix_rt::test] async fn test_rustls_uri_http1() { let srv = TestServer::start(|| { @@ -129,7 +129,7 @@ async fn test_rustls_uri_http1() { assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(all(feature = "rustls-0_22", feature = "uri"))] +#[cfg(all(feature = "rustls-0_23", feature = "uri"))] #[actix_rt::test] async fn test_rustls_uri() { let srv = TestServer::start(|| {