actix_tls/connect/
rustls_0_23.rs

1//! Rustls based connector service.
2//!
3//! See [`TlsConnector`] for main connector service factory docs.
4
5use std::{
6    future::Future,
7    io,
8    pin::Pin,
9    sync::Arc,
10    task::{Context, Poll},
11};
12
13use actix_rt::net::ActixStream;
14use actix_service::{Service, ServiceFactory};
15use actix_utils::future::{ok, Ready};
16use futures_core::ready;
17use rustls_pki_types_1::ServerName;
18use tokio_rustls::{
19    client::TlsStream as AsyncTlsStream, rustls::ClientConfig, Connect as RustlsConnect,
20    TlsConnector as RustlsTlsConnector,
21};
22use tokio_rustls_026 as tokio_rustls;
23
24use crate::connect::{Connection, Host};
25
26pub mod reexports {
27    //! Re-exports from the `rustls` v0.23 ecosystem that are useful for connectors.
28
29    pub use tokio_rustls_026::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig};
30    #[cfg(feature = "rustls-0_23-webpki-roots")]
31    pub use webpki_roots_026::TLS_SERVER_ROOTS;
32}
33
34/// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store.
35///
36/// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors.
37///
38/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_07::load_native_certs()
39#[cfg(feature = "rustls-0_23-native-roots")]
40pub fn native_roots_cert_store() -> io::Result<tokio_rustls::rustls::RootCertStore> {
41    let mut root_certs = tokio_rustls::rustls::RootCertStore::empty();
42
43    for cert in rustls_native_certs_07::load_native_certs()? {
44        root_certs.add(cert).unwrap();
45    }
46
47    Ok(root_certs)
48}
49
50/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store.
51#[cfg(feature = "rustls-0_23-webpki-roots")]
52pub fn webpki_roots_cert_store() -> tokio_rustls::rustls::RootCertStore {
53    let mut root_certs = tokio_rustls::rustls::RootCertStore::empty();
54    root_certs.extend(webpki_roots_026::TLS_SERVER_ROOTS.to_owned());
55    root_certs
56}
57
58/// Connector service factory using `rustls`.
59#[derive(Clone)]
60pub struct TlsConnector {
61    connector: Arc<ClientConfig>,
62}
63
64impl TlsConnector {
65    /// Constructs new connector service factory from a `rustls` client configuration.
66    pub fn new(connector: Arc<ClientConfig>) -> Self {
67        TlsConnector { connector }
68    }
69
70    /// Constructs new connector service from a `rustls` client configuration.
71    pub fn service(connector: Arc<ClientConfig>) -> TlsConnectorService {
72        TlsConnectorService { connector }
73    }
74}
75
76impl<R, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
77where
78    R: Host,
79    IO: ActixStream + 'static,
80{
81    type Response = Connection<R, AsyncTlsStream<IO>>;
82    type Error = io::Error;
83    type Config = ();
84    type Service = TlsConnectorService;
85    type InitError = ();
86    type Future = Ready<Result<Self::Service, Self::InitError>>;
87
88    fn new_service(&self, _: ()) -> Self::Future {
89        ok(TlsConnectorService {
90            connector: self.connector.clone(),
91        })
92    }
93}
94
95/// Connector service using `rustls`.
96#[derive(Clone)]
97pub struct TlsConnectorService {
98    connector: Arc<ClientConfig>,
99}
100
101impl<R, IO> Service<Connection<R, IO>> for TlsConnectorService
102where
103    R: Host,
104    IO: ActixStream,
105{
106    type Response = Connection<R, AsyncTlsStream<IO>>;
107    type Error = io::Error;
108    type Future = ConnectFut<R, IO>;
109
110    actix_service::always_ready!();
111
112    fn call(&self, connection: Connection<R, IO>) -> Self::Future {
113        tracing::trace!("TLS handshake start for: {:?}", connection.hostname());
114        let (stream, conn) = connection.replace_io(());
115
116        match ServerName::try_from(conn.hostname()) {
117            Ok(host) => ConnectFut::Future {
118                connect: RustlsTlsConnector::from(Arc::clone(&self.connector))
119                    .connect(host.to_owned(), stream),
120                connection: Some(conn),
121            },
122            Err(_) => ConnectFut::InvalidServerName,
123        }
124    }
125}
126
127/// Connect future for Rustls service.
128#[doc(hidden)]
129#[allow(clippy::large_enum_variant)]
130pub enum ConnectFut<R, IO> {
131    InvalidServerName,
132    Future {
133        connect: RustlsConnect<IO>,
134        connection: Option<Connection<R, ()>>,
135    },
136}
137
138impl<R, IO> Future for ConnectFut<R, IO>
139where
140    R: Host,
141    IO: ActixStream,
142{
143    type Output = io::Result<Connection<R, AsyncTlsStream<IO>>>;
144
145    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
146        match self.get_mut() {
147            Self::InvalidServerName => Poll::Ready(Err(io::Error::new(
148                io::ErrorKind::InvalidInput,
149                "connection parameters specified invalid server name",
150            ))),
151
152            Self::Future {
153                connect,
154                connection,
155            } => {
156                let stream = ready!(Pin::new(connect).poll(cx))?;
157                let connection = connection.take().unwrap();
158                tracing::trace!("TLS handshake success: {:?}", connection.hostname());
159                Poll::Ready(Ok(connection.replace_io(stream).1))
160            }
161        }
162    }
163}