Skip to content

Commit

Permalink
minimum viable rustls v0.21 support (#3112)
Browse files Browse the repository at this point in the history
  • Loading branch information
robjtede committed Aug 26, 2023
1 parent 14355b9 commit 55c15f5
Show file tree
Hide file tree
Showing 13 changed files with 350 additions and 48 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
if: matrix.version.name != 'stable'
run: |
cargo update -p=clap --precise=4.3.24
cargo update -p=clap_lex --precise=0.5.0
- name: check minimal
run: cargo ci-check-min
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ members = [
"awc",
]

[workspace.package]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.68"

[profile.dev]
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
debug = 0
Expand Down
2 changes: 2 additions & 0 deletions actix-http/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added

- Add `rustls-0_20` crate feature.
- Add `{H1Service, H2Service, HttpService}::rustls_021()` and `HttpService::rustls_021_with_config()` service constructors.
- Add `body::to_body_limit()` function.
- Add `body::BodyLimitExceeded` error type.

Expand Down
29 changes: 20 additions & 9 deletions actix-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ categories = [
"web-programming::http-server",
"web-programming::websocket",
]
license = "MIT OR Apache-2.0"
edition = "2021"
license.workspace = true
edition.workspace = true
rust-version.workspace = true

[package.metadata.docs.rs]
# features that docs.rs will build with
Expand All @@ -43,15 +44,21 @@ ws = [
# TLS via OpenSSL
openssl = ["actix-tls/accept", "actix-tls/openssl"]

# TLS via Rustls
rustls = ["actix-tls/accept", "actix-tls/rustls"]
# TLS via Rustls v0.20
rustls = ["rustls-0_20"]

# TLS via Rustls v0.20
rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]

# TLS via Rustls v0.21
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]

# Compression codecs
compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"]
compress-zstd = ["__compress", "zstd"]

# Internal (PRIVATE!) features used to aid testing and cheking feature status.
# Internal (PRIVATE!) features used to aid testing and checking feature status.
# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
__compress = []

Expand Down Expand Up @@ -91,7 +98,7 @@ rand = { version = "0.8", optional = true }
sha1 = { version = "0.10", optional = true }

# openssl/rustls
actix-tls = { version = "3", default-features = false, optional = true }
actix-tls = { version = "3.1", default-features = false, optional = true }

# compress-*
brotli = { version = "3.3.3", optional = true }
Expand All @@ -101,7 +108,7 @@ zstd = { version = "0.12", optional = true }
[dev-dependencies]
actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2"
actix-tls = { version = "3", features = ["openssl"] }
actix-tls = { version = "3.1", features = ["openssl"] }
actix-web = "4"

async-stream = "0.3"
Expand All @@ -118,12 +125,16 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls = { package = "rustls", version = "0.20" }
tls-rustls_021 = { package = "rustls", version = "0.21" }
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }

[[example]]
name = "ws"
required-features = ["ws", "rustls"]
required-features = ["ws", "rustls-0_21"]

[[example]]
name = "tls_rustls"
required-features = ["http2", "rustls-0_21"]

[[bench]]
name = "response-body-compression"
Expand Down
73 changes: 73 additions & 0 deletions actix-http/examples/tls_rustls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! Demonstrates TLS configuration (via Rustls) for HTTP/1.1 and HTTP/2 connections.
//!
//! Test using cURL:
//!
//! ```console
//! $ curl --insecure https://127.0.0.1:8443
//! Hello World!
//! Protocol: HTTP/2.0
//!
//! $ curl --insecure --http1.1 https://127.0.0.1:8443
//! Hello World!
//! Protocol: HTTP/1.1
//! ```

extern crate tls_rustls_021 as rustls;

use std::io;

use actix_http::{Error, HttpService, Request, Response};
use actix_utils::future::ok;

#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

tracing::info!("starting HTTP server at https://127.0.0.1:8443");

actix_server::Server::build()
.bind("echo", ("127.0.0.1", 8443), || {
HttpService::build()
.finish(|req: Request| {
let body = format!(
"Hello World!\n\
Protocol: {:?}",
req.head().version
);
ok::<_, Error>(Response::ok().set_body(body))
})
.rustls_021(rustls_config())
})?
.run()
.await
}

fn rustls_config() -> rustls::ServerConfig {
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem();

let cert_file = &mut io::BufReader::new(cert_file.as_bytes());
let key_file = &mut io::BufReader::new(key_file.as_bytes());

let cert_chain = rustls_pemfile::certs(cert_file)
.unwrap()
.into_iter()
.map(rustls::Certificate)
.collect();
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file).unwrap();

let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(cert_chain, rustls::PrivateKey(keys.remove(0)))
.unwrap();

const H1_ALPN: &[u8] = b"http/1.1";
const H2_ALPN: &[u8] = b"h2";

config.alpn_protocols.push(H2_ALPN.to_vec());
config.alpn_protocols.push(H1_ALPN.to_vec());

config
}
6 changes: 4 additions & 2 deletions actix-http/examples/ws.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Sets up a WebSocket server over TCP and TLS.
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.

extern crate tls_rustls as rustls;
extern crate tls_rustls_021 as rustls;

use std::{
io,
Expand All @@ -28,7 +28,9 @@ async fn main() -> io::Result<()> {
HttpService::build().h1(handler).tcp()
})?
.bind("tls", ("127.0.0.1", 8443), || {
HttpService::build().finish(handler).rustls(tls_config())
HttpService::build()
.finish(handler)
.rustls_021(tls_config())
})?
.run()
.await
Expand Down
69 changes: 65 additions & 4 deletions actix-http/src/h1/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,13 @@ mod openssl {
}
}

#[cfg(feature = "rustls")]
mod rustls {
#[cfg(feature = "rustls-0_20")]
mod rustls_020 {
use std::io;

use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls::{reexports::ServerConfig, Acceptor, TlsStream},
rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};

Expand Down Expand Up @@ -188,7 +188,7 @@ mod rustls {
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls based service.
/// Create Rustls v0.20 based service.
pub fn rustls(
self,
config: ServerConfig,
Expand All @@ -213,6 +213,67 @@ mod rustls {
}
}

#[cfg(feature = "rustls-0_21")]
mod rustls_021 {
use std::io;

use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_21::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};

use super::*;

impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,

B: MessageBody,

X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,

U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls v0.21 based service.
pub fn rustls_021(
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}

impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
Expand Down
57 changes: 54 additions & 3 deletions actix-http/src/h2/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ mod openssl {
}
}

#[cfg(feature = "rustls")]
mod rustls {
#[cfg(feature = "rustls-0_20")]
mod rustls_020 {
use std::io;

use actix_service::ServiceFactoryExt as _;
Expand All @@ -162,7 +162,7 @@ mod rustls {

B: MessageBody + 'static,
{
/// Create Rustls based service.
/// Create Rustls v0.20 based service.
pub fn rustls(
self,
mut config: ServerConfig,
Expand Down Expand Up @@ -191,6 +191,57 @@ mod rustls {
}
}

#[cfg(feature = "rustls-0_21")]
mod rustls_021 {
use std::io;

use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_21::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};

use super::*;

impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,

B: MessageBody + 'static,
{
/// Create Rustls v0.21 based service.
pub fn rustls_021(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = S::InitError,
> {
let mut protos = vec![b"h2".to_vec()];
protos.extend_from_slice(&config.alpn_protocols);
config.alpn_protocols = protos;

Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}

impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
Expand Down
2 changes: 1 addition & 1 deletion actix-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub mod ws;

#[allow(deprecated)]
pub use self::payload::PayloadStream;
#[cfg(any(feature = "openssl", feature = "rustls"))]
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
pub use self::service::TlsAcceptorConfig;
pub use self::{
builder::HttpServiceBuilder,
Expand Down
Loading

0 comments on commit 55c15f5

Please sign in to comment.