From 73b8553376fec147073f5b2eafdd08787a43d730 Mon Sep 17 00:00:00 2001 From: Victor Pirat Date: Wed, 19 May 2021 17:40:03 +0200 Subject: [PATCH] Extends Rustls ALPN protocols instead of replacing them when creating Rustls based services --- CHANGES.md | 2 + actix-http-test/CHANGES.md | 2 +- actix-http-test/Cargo.toml | 3 + actix-http-test/src/lib.rs | 27 +++++++++ actix-http/CHANGES.md | 1 + actix-http/Cargo.toml | 2 +- actix-http/src/h2/service.rs | 3 +- actix-http/src/service.rs | 4 +- actix-http/tests/test_rustls.rs | 104 ++++++++++++++++++++++++++++++++ 9 files changed, 144 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 162f9f61b..30d098c65 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Added * `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200] +* Added `TestServer::get_negotiated_alpn_protocol` method. ### Changed * `ServiceResponse::error_response` now uses body type of `Body`. [#2201] @@ -10,6 +11,7 @@ * Update `language-tags` to `0.3`. * `ServiceResponse::take_body`. [#2201] * `ServiceResponse::map_body` closure receives and returns `B` instead of `ResponseBody` types. [#2201] +* Extends Rustls ALPN protocols instead of replacing them when creating Rustls based services ### Removed * `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201] diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 1dbd9a15b..2a93a615d 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,7 +1,7 @@ # Changes ## Unreleased - 2021-xx-xx - +* Added `TestServer::get_negotiated_alpn_protocol` method. ## 3.0.0-beta.4 - 2021-04-02 * Added `TestServer::client_headers` method. [#2097] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 88b4bd04a..5afb0d697 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -27,6 +27,7 @@ default = [] # openssl openssl = ["tls-openssl", "awc/openssl"] +rustls = ["tls-rustls", "webpki"] [dependencies] actix-service = "2.0.0" @@ -49,6 +50,8 @@ slab = "0.4" serde_urlencoded = "0.7" time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } +tls-rustls = { version = "0.19", package = "rustls", optional = true } +webpki = { version = "0.21.0", optional = true } [dev-dependencies] actix-web = { version = "4.0.0-beta.6", default-features = false, features = ["cookies"] } diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 0f126c99a..36a72a643 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -7,8 +7,17 @@ #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + use std::sync::mpsc; use std::{net, thread, time}; +#[cfg(feature = "rustls")] +use { + rustls::Session, + std::{io::Write, net::TcpStream as StdTcpStream, sync::Arc}, + webpki::DNSNameRef, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; @@ -223,6 +232,24 @@ impl TestServer { self.client.request(method, path.as_ref()) } + #[cfg(feature = "rustls")] + /// Get the negotiated ALPN protocol with the server + pub fn get_negotiated_alpn_protocol(&self, client_alpn_protocol: &[u8]) -> Option> { + let mut config = rustls::ClientConfig::new(); + config.alpn_protocols.push(client_alpn_protocol.to_vec()); + let mut sess = rustls::ClientSession::new( + &Arc::new(config), + DNSNameRef::try_from_ascii_str("localhost").unwrap(), + ); + let mut sock = StdTcpStream::connect(self.addr).unwrap(); + let mut stream = rustls::Stream::new(&mut sess, &mut sock); + // The handshake will fails because the client will not be able to verify the server + // certificate, but it doesn't matter here as we are just interested in the negotiated ALPN + // protocol + let _ = stream.flush(); + sess.get_alpn_protocol().map(|proto| proto.to_vec()) + } + pub async fn load_body( &mut self, mut response: ClientResponse, diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ec7d8ee3b..bb9e5af12 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -18,6 +18,7 @@ * Update `language-tags` to `0.3`. * Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201] * `ResponseBuilder::message_body` now returns a `Result`. [#2201] +* Extends Rustls ALPN protocols instead of replacing them when creating Rustls based services ### Removed * Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1f7df39a6..e78d3273f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -81,7 +81,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" -actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.4", features = ["openssl", "rustls"] } actix-tls = { version = "3.0.0-beta.5", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index a75abef7d..86f1f8038 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -171,7 +171,8 @@ mod rustls { Error = TlsError, InitError = S::InitError, > { - let protos = vec!["h2".to_string().into()]; + let mut protos = vec!["h2".to_string().into()]; + protos.extend_from_slice(&config.alpn_protocols); config.set_protocols(&protos); Acceptor::new(config) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index d25a67a19..eb1bbb66b 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -305,7 +305,9 @@ mod rustls { Error = TlsError, InitError = (), > { - let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; + let mut protos = + vec!["h2".to_string().into(), "http/1.1".to_string().into()]; + protos.extend_from_slice(&config.alpn_protocols); config.set_protocols(&protos); Acceptor::new(config) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 2382d1ad3..c3dbd7e85 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -460,3 +460,107 @@ async fn test_h1_service_error() { let bytes = srv.load_body(response).await.unwrap(); assert_eq!(bytes, Bytes::from_static(b"error")); } + +const H2_ALPN_PROTOCOL: &[u8] = b"h2"; +const HTTP1_1_ALPN_PROTOCOL: &[u8] = b"http/1.1"; +const CUSTOM_ALPN_PROTOCOL: &[u8] = b"custom"; + +#[actix_rt::test] +async fn test_alpn_h1() -> io::Result<()> { + let srv = test_server(move || { + let mut config = tls_config(); + config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); + HttpService::build() + .h1(|_| ok::<_, Error>(Response::ok())) + .rustls(config) + }) + .await; + + assert_eq!( + srv.get_negotiated_alpn_protocol(CUSTOM_ALPN_PROTOCOL), + Some(CUSTOM_ALPN_PROTOCOL.to_vec()) + ); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + Ok(()) +} + +#[actix_rt::test] +async fn test_alpn_h2() -> io::Result<()> { + let srv = test_server(move || { + let mut config = tls_config(); + config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); + HttpService::build() + .h2(|_| ok::<_, Error>(Response::ok())) + .rustls(config) + }) + .await; + + assert_eq!( + srv.get_negotiated_alpn_protocol(H2_ALPN_PROTOCOL), + Some(H2_ALPN_PROTOCOL.to_vec()) + ); + assert_eq!( + srv.get_negotiated_alpn_protocol(CUSTOM_ALPN_PROTOCOL), + Some(CUSTOM_ALPN_PROTOCOL.to_vec()) + ); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + Ok(()) +} + +#[actix_rt::test] +async fn test_alpn_h1_1() -> io::Result<()> { + let srv = test_server(move || { + let mut config = tls_config(); + config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); + HttpService::build() + .h1(|_| ok::<_, Error>(Response::ok())) + .rustls(config) + }) + .await; + + assert_eq!( + srv.get_negotiated_alpn_protocol(CUSTOM_ALPN_PROTOCOL), + Some(CUSTOM_ALPN_PROTOCOL.to_vec()) + ); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + Ok(()) +} + +#[actix_rt::test] +async fn test_alpn_h2_1() -> io::Result<()> { + let srv = test_server(move || { + let mut config = tls_config(); + config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec()); + HttpService::build() + .finish(|_| ok::<_, Error>(Response::ok())) + .rustls(config) + }) + .await; + + assert_eq!( + srv.get_negotiated_alpn_protocol(H2_ALPN_PROTOCOL), + Some(H2_ALPN_PROTOCOL.to_vec()) + ); + assert_eq!( + srv.get_negotiated_alpn_protocol(HTTP1_1_ALPN_PROTOCOL), + Some(HTTP1_1_ALPN_PROTOCOL.to_vec()) + ); + assert_eq!( + srv.get_negotiated_alpn_protocol(CUSTOM_ALPN_PROTOCOL), + Some(CUSTOM_ALPN_PROTOCOL.to_vec()) + ); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + Ok(()) +}