Skip to content

Commit

Permalink
feat(tls): add accept::rustls_0_22 module
Browse files Browse the repository at this point in the history
  • Loading branch information
robjtede committed Dec 16, 2023
1 parent 86b000f commit 02ac0bb
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 14 deletions.
3 changes: 2 additions & 1 deletion actix-tls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,12 @@ actix-server = "2"
bytes = "1"
env_logger = "0.10"
futures-util = { version = "0.3.17", default-features = false, features = ["sink"] }
itertools = "0.12"
rcgen = "0.11"
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_22"]
required-features = ["accept", "rustls-0_22-webpki-roots"]
24 changes: 11 additions & 13 deletions actix-tls/examples/accept-rustls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@
//! http --verify=false https://127.0.0.1:8443
//! ```

#[rustfmt::skip]
// this `use` is only exists because of how we have organised the crate
// it is not necessary for your actual code; you should import from `rustls` normally
use tokio_rustls_024::rustls;

use std::{
fs::File,
io::{self, BufReader},
Expand All @@ -33,10 +30,13 @@ use std::{
use actix_rt::net::TcpStream;
use actix_server::Server;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::rustls_0_21::{Acceptor as RustlsAcceptor, TlsStream};
use actix_tls::accept::rustls_0_22::{Acceptor as RustlsAcceptor, TlsStream};
use futures_util::future::ok;
use rustls::{server::ServerConfig, Certificate, PrivateKey};
use itertools::Itertools as _;
use rustls::server::ServerConfig;
use rustls_pemfile::{certs, rsa_private_keys};
use rustls_pki_types_1::PrivateKeyDer;
use tokio_rustls_025::rustls;
use tracing::info;

#[actix_rt::main]
Expand All @@ -54,17 +54,15 @@ async fn main() -> io::Result<()> {
let cert_file = &mut BufReader::new(File::open(cert_path).unwrap());
let key_file = &mut BufReader::new(File::open(key_path).unwrap());

let cert_chain = certs(cert_file)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let mut keys = rsa_private_keys(key_file).unwrap();
let cert_chain = certs(cert_file);
let mut keys = rsa_private_keys(key_file);

let tls_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
.with_single_cert(
cert_chain.try_collect::<_, Vec<_>, _>()?,
PrivateKeyDer::Pkcs1(keys.next().unwrap()?),
)
.unwrap();

let tls_acceptor = RustlsAcceptor::new(tls_config);
Expand Down
6 changes: 6 additions & 0 deletions actix-tls/src/accept/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ pub use rustls_0_20 as rustls;
#[cfg(feature = "rustls-0_21")]
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;

Expand Down
198 changes: 198 additions & 0 deletions actix-tls/src/accept/rustls_0_22.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
//! `rustls` v0.22 based TLS connection acceptor service.
//!
//! See [`Acceptor`] for main service factory docs.

use std::{
convert::Infallible,
future::Future,
io::{self, IoSlice},
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};

use actix_rt::{
net::{ActixStream, Ready},
time::{sleep, Sleep},
};
use actix_service::{Service, ServiceFactory};
use actix_utils::{
counter::{Counter, CounterGuard},
future::{ready, Ready as FutReady},
};
use pin_project_lite::pin_project;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio_rustls::{Accept, TlsAcceptor};
use tokio_rustls_025 as tokio_rustls;

use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Linux / msrv

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Linux / nightly

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Linux / stable

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / macOS / nightly

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / macOS / msrv

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Windows / nightly

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / macOS / stable

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Windows / msrv

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Windows / stable

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Windows (32-bit) / nightly

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Windows (32-bit) / msrv

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

Check failure on line 29 in actix-tls/src/accept/rustls_0_22.rs

View workflow job for this annotation

GitHub Actions / Windows (32-bit) / stable

unresolved import `super::DEFAULT_TLS_HANDSHAKE_TIMEOUT`

pub mod reexports {
//! Re-exports from `rustls` that are useful for acceptors.

pub use tokio_rustls_025::rustls::ServerConfig;
}

/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);

impl_more::impl_from!(<IO> in tokio_rustls::server::TlsStream<IO> => TlsStream<IO>);
impl_more::impl_deref_and_mut!(<IO> in TlsStream<IO> => tokio_rustls::server::TlsStream<IO>);

impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(&mut **self.get_mut()).poll_read(cx, buf)
}
}

impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut **self.get_mut()).poll_write(cx, buf)
}

fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut **self.get_mut()).poll_flush(cx)
}

fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut **self.get_mut()).poll_shutdown(cx)
}

fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<io::Result<usize>> {
Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs)
}

fn is_write_vectored(&self) -> bool {
(**self).is_write_vectored()
}
}

impl<IO: ActixStream> ActixStream for TlsStream<IO> {
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
IO::poll_read_ready((**self).get_ref().0, cx)
}

fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
IO::poll_write_ready((**self).get_ref().0, cx)
}
}

/// Accept TLS connections via the `rustls` crate.
pub struct Acceptor {
config: Arc<reexports::ServerConfig>,
handshake_timeout: Duration,
}

impl Acceptor {
/// Constructs `rustls` based acceptor service factory.
pub fn new(config: reexports::ServerConfig) -> Self {
Acceptor {
config: Arc::new(config),
handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
}
}

/// Limit the amount of time that the acceptor will wait for a TLS handshake to complete.
///
/// Default timeout is 3 seconds.
pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self {
self.handshake_timeout = handshake_timeout;
self
}
}

impl Clone for Acceptor {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
handshake_timeout: self.handshake_timeout,
}
}
}

impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
type Response = TlsStream<IO>;
type Error = TlsError<io::Error, Infallible>;
type Config = ();
type Service = AcceptorService;
type InitError = ();
type Future = FutReady<Result<Self::Service, Self::InitError>>;

fn new_service(&self, _: ()) -> Self::Future {
let res = MAX_CONN_COUNTER.with(|conns| {
Ok(AcceptorService {
acceptor: self.config.clone().into(),
conns: conns.clone(),
handshake_timeout: self.handshake_timeout,
})
});

ready(res)
}
}

/// Rustls based acceptor service.
pub struct AcceptorService {
acceptor: TlsAcceptor,
conns: Counter,
handshake_timeout: Duration,
}

impl<IO: ActixStream> Service<IO> for AcceptorService {
type Response = TlsStream<IO>;
type Error = TlsError<io::Error, Infallible>;
type Future = AcceptFut<IO>;

fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.conns.available(cx) {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}

fn call(&self, req: IO) -> Self::Future {
AcceptFut {
fut: self.acceptor.accept(req),
timeout: sleep(self.handshake_timeout),
_guard: self.conns.get(),
}
}
}

pin_project! {
/// Accept future for Rustls service.
#[doc(hidden)]
pub struct AcceptFut<IO: ActixStream> {
fut: Accept<IO>,
#[pin]
timeout: Sleep,
_guard: CounterGuard,
}
}

impl<IO: ActixStream> Future for AcceptFut<IO> {
type Output = Result<TlsStream<IO>, TlsError<io::Error, Infallible>>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
match Pin::new(&mut this.fut).poll(cx) {
Poll::Ready(Ok(stream)) => Poll::Ready(Ok(TlsStream(stream))),
Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))),
Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)),
}
}
}

0 comments on commit 02ac0bb

Please sign in to comment.