//! 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_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. /// /// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_07::load_native_certs() #[cfg(feature = "rustls-0_22-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_22-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)) } } } }