diff --git a/bindings/rust/standard/integration/Cargo.toml b/bindings/rust/standard/integration/Cargo.toml index 9bd4e9007af..214a589423b 100644 --- a/bindings/rust/standard/integration/Cargo.toml +++ b/bindings/rust/standard/integration/Cargo.toml @@ -18,6 +18,8 @@ no-sensitive-tests = [] # can be disabled by turning off this feature. pq = [ "s2n-tls/pq" ] +boringssl = ["tls-harness/boringssl"] + [dependencies] s2n-tls = { path = "../../extended/s2n-tls", features = ["unstable-testing", "unstable-crl"]} s2n-tls-hyper = { path = "../s2n-tls-hyper" } @@ -48,6 +50,12 @@ hyper-util = "0.1" dhat = "0.3.3" tabled = "0.20.0" +# NOTE: BoringSSL is disabled on macOS to avoid symbol collisions with +# OpenSSL; see https://github.com/aws/s2n-tls/pull/5659 for details. +[target.'cfg(not(target_os = "macos"))'.dev-dependencies.boring] +git = "https://github.com/kaukabrizvi/boring.git" +features = ["prefix-symbols"] + [build-dependencies] # The ML-DSA tests require the ML-DSA support added in Openssl-3.5 # Since this overrides the dependency from the openssl-src crate, diff --git a/bindings/rust/standard/integration/src/mtls/mod.rs b/bindings/rust/standard/integration/src/mtls/mod.rs index c4d37ed780d..9200ae8a634 100644 --- a/bindings/rust/standard/integration/src/mtls/mod.rs +++ b/bindings/rust/standard/integration/src/mtls/mod.rs @@ -25,6 +25,10 @@ use std::{ }, }; +// NOTE: BoringSSL tests are disabled on macOS to avoid symbol collisions with +// OpenSSL; see https://github.com/aws/s2n-tls/pull/5659 for details. +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +use boring::ssl::SslVersion; use rustls::ClientConfig; use s2n_tls::{ @@ -46,6 +50,9 @@ use tls_harness::{ PemType, SigType, TlsConnPair, TlsConnection, }; +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +use tls_harness::cohort::{BoringSslConfig, BoringSslConnection}; + const APP_DATA_SIZE: usize = 100_000; /// A wrapper around a raw pointer to `s2n_cert_validation_info` that can be sent across threads. @@ -210,6 +217,58 @@ fn rustls_mtls_server( server.into() } +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +fn boringssl_mtls_client(sig_type: SigType, version: SslVersion) -> BoringSslConfig { + use tls_harness::harness::{Mode, TlsConfigBuilder}; + + let mut builder = boring::ssl::SslContextBuilder::new_test_config(Mode::Client); + builder.set_trust(sig_type); + + builder + .set_certificate_chain_file(tls_harness::get_cert_path( + PemType::ClientCertChain, + sig_type, + )) + .unwrap(); + builder + .set_private_key_file( + tls_harness::get_cert_path(PemType::ClientKey, sig_type), + boring::ssl::SslFiletype::PEM, + ) + .unwrap(); + builder.set_verify(boring::ssl::SslVerifyMode::PEER); + + // Pin the protocol version + builder.set_min_proto_version(Some(version)).unwrap(); + builder.set_max_proto_version(Some(version)).unwrap(); + + BoringSslConfig { + config: builder.build(), + session_ticket_storage: Default::default(), + } +} + +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +fn boringssl_mtls_server(sig_type: SigType, version: SslVersion) -> BoringSslConfig { + use tls_harness::harness::{Mode, TlsConfigBuilder}; + + let mut builder = boring::ssl::SslContextBuilder::new_test_config(Mode::Server); + builder.set_chain(sig_type); + builder.set_trust(sig_type); + builder.set_verify( + boring::ssl::SslVerifyMode::PEER | boring::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT, + ); + + // Pin the protocol version + builder.set_min_proto_version(Some(version)).unwrap(); + builder.set_max_proto_version(Some(version)).unwrap(); + + BoringSslConfig { + config: builder.build(), + session_ticket_storage: Default::default(), + } +} + // ============================================================================ // Basic mTLS tests // ============================================================================ @@ -227,7 +286,7 @@ where // s2n client, rustls server #[test] -fn s2n_client_basic() { +fn rustls_server_basic() { // TLS 1.2 let client = { let builder = s2n_mtls_base_builder(SigType::Rsa2048); @@ -252,7 +311,7 @@ fn s2n_client_basic() { // rustls client, s2n server #[test] -fn s2n_server_basic() { +fn rustls_client_basic() { // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let server = { @@ -275,6 +334,58 @@ fn s2n_server_basic() { ); } +// s2n client, boringssl server +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +#[test] +fn boringssl_server_basic() { + // TLS 1.2 + let client = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; + let server = boringssl_mtls_server(SigType::Rsa2048, SslVersion::TLS1_2); + test_basic::(&client, &server); + + // TLS 1.3 + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; + let server = boringssl_mtls_server(SigType::Rsa2048, SslVersion::TLS1_3); + test_basic::(&client, &server); + }, + ); +} + +// boringssl client, s2n server +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +#[test] +fn boringssl_client_basic() { + // TLS 1.2 + let client = boringssl_mtls_client(SigType::Rsa2048, SslVersion::TLS1_2); + let server = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; + test_basic::(&client, &server); + + // TLS 1.3 + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = boringssl_mtls_client(SigType::Rsa2048, SslVersion::TLS1_3); + let server = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; + test_basic::(&client, &server); + }, + ); +} + // ============================================================================ // Sync callback tests // ============================================================================ @@ -294,7 +405,7 @@ where // s2n client with sync callback, rustls server #[test] -fn s2n_client_sync_callback() { +fn rustls_server_sync_callback() { // TLS 1.2 let (client, handle) = { let mut builder = s2n_mtls_base_builder(SigType::Rsa2048); @@ -326,7 +437,7 @@ fn s2n_client_sync_callback() { // rustls client, s2n server with sync callback #[test] -fn s2n_server_sync_callback() { +fn rustls_client_sync_callback() { // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, handle) = { @@ -357,6 +468,70 @@ fn s2n_server_sync_callback() { ); } +// s2n client with sync callback, boringssl server +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +#[test] +fn boringssl_server_sync_callback() { + // TLS 1.2 + let (client, handle) = { + let mut builder = s2n_mtls_base_builder(SigType::Rsa2048); + let cb = TestCertValidationCallback::new_sync(); + let invoked = Arc::clone(cb.invoked_count()); + builder.set_cert_validation_callback_sync(cb).unwrap(); + (S2NConfig::from(builder.build().unwrap()), invoked) + }; + let server = boringssl_mtls_server(SigType::Rsa2048, SslVersion::TLS1_2); + test_sync_callback::(&client, &server, handle); + + // TLS 1.3 + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let (client, handle) = { + let mut builder = s2n_mtls_base_builder(SigType::Rsa2048); + let cb = TestCertValidationCallback::new_sync(); + let invoked = Arc::clone(cb.invoked_count()); + builder.set_cert_validation_callback_sync(cb).unwrap(); + (S2NConfig::from(builder.build().unwrap()), invoked) + }; + let server = boringssl_mtls_server(SigType::Rsa2048, SslVersion::TLS1_3); + test_sync_callback::(&client, &server, handle); + }, + ); +} + +// boringssl client, s2n server with sync callback +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +#[test] +fn boringssl_client_sync_callback() { + // TLS 1.2 + let client = boringssl_mtls_client(SigType::Rsa2048, SslVersion::TLS1_2); + let (server, handle) = { + let mut builder = s2n_mtls_base_builder(SigType::Rsa2048); + let cb = TestCertValidationCallback::new_sync(); + let invoked = Arc::clone(cb.invoked_count()); + builder.set_cert_validation_callback_sync(cb).unwrap(); + (S2NConfig::from(builder.build().unwrap()), invoked) + }; + test_sync_callback::(&client, &server, handle); + + // TLS 1.3 + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = boringssl_mtls_client(SigType::Rsa2048, SslVersion::TLS1_3); + let (server, handle) = { + let mut builder = s2n_mtls_base_builder(SigType::Rsa2048); + let cb = TestCertValidationCallback::new_sync(); + let invoked = Arc::clone(cb.invoked_count()); + builder.set_cert_validation_callback_sync(cb).unwrap(); + (S2NConfig::from(builder.build().unwrap()), invoked) + }; + test_sync_callback::(&client, &server, handle); + }, + ); +} + // ============================================================================ // Async callback tests // ============================================================================ @@ -430,7 +605,7 @@ where // s2n client with async callback, rustls server #[test] -fn s2n_client_async_callback() { +fn rustls_server_async_callback() { // TLS 1.2 let (client, handle, rx) = { let builder = s2n_mtls_base_builder(SigType::Rsa2048); @@ -462,7 +637,7 @@ fn s2n_client_async_callback() { // rustls client, s2n server with async callback #[test] -fn s2n_server_async_callback() { +fn rustls_client_async_callback() { // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, handle, rx) = { @@ -493,6 +668,74 @@ fn s2n_server_async_callback() { ); } +// s2n client with async callback, boringssl server +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +#[test] +fn boringssl_server_async_callback() { + // TLS 1.2 + let (client, handle, rx) = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); + let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); + (s2n_cfg, invoked, rx) + }; + let server = boringssl_mtls_server(SigType::Rsa2048, SslVersion::TLS1_2); + let _pair = test_async_client_callback::( + &client, &server, handle, rx, + ); + + // TLS 1.3 + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let (client, handle, rx) = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); + let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); + (s2n_cfg, invoked, rx) + }; + let server = boringssl_mtls_server(SigType::Rsa2048, SslVersion::TLS1_3); + let _pair = test_async_client_callback::( + &client, &server, handle, rx, + ); + }, + ); +} + +// boringssl client, s2n server with async callback +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +#[test] +fn boringssl_client_async_callback() { + // TLS 1.2 + let client = boringssl_mtls_client(SigType::Rsa2048, SslVersion::TLS1_2); + let (server, handle, rx) = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); + let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); + (s2n_cfg, invoked, rx) + }; + let _pair = test_async_server_callback::( + &client, &server, handle, rx, + ); + + // TLS 1.3 + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = boringssl_mtls_client(SigType::Rsa2048, SslVersion::TLS1_3); + let (server, handle, rx) = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); + let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); + (s2n_cfg, invoked, rx) + }; + let _pair = test_async_server_callback::( + &client, &server, handle, rx, + ); + }, + ); +} + // s2n client, s2n server with async callback #[test] fn s2n_s2n_mtls_async_callback() { diff --git a/bindings/rust/standard/tls-harness/Cargo.toml b/bindings/rust/standard/tls-harness/Cargo.toml index 45783415a4e..01fd06a3c23 100644 --- a/bindings/rust/standard/tls-harness/Cargo.toml +++ b/bindings/rust/standard/tls-harness/Cargo.toml @@ -1,3 +1,7 @@ +[features] +default = [] +boringssl = ["dep:boring"] + [package] name = "tls-harness" version = "0.1.0" @@ -14,10 +18,17 @@ rustls-pemfile = "2.2.0" openssl = { version = "0.10.73", features = ["vendored"] } openssl-sys = "0.9.109" byteorder = "1.5.0" +boring = { git = "https://github.com/kaukabrizvi/boring.git", features = ["prefix-symbols"], optional = true } brass-aphid-wire-decryption = "0.0.1" brass-aphid-wire-messages = "0.0.1" +# NOTE: BoringSSL is disabled on macOS to avoid symbol collisions with +# OpenSSL; see https://github.com/aws/s2n-tls/pull/5659 for details. +[target.'cfg(not(target_os = "macos"))'.dependencies.boring] +git = "https://github.com/kaukabrizvi/boring.git" +features = ["prefix-symbols"] + [dev-dependencies] # env_logger and log are used to enable logging for rustls, which can help with # debugging interop failures diff --git a/bindings/rust/standard/tls-harness/src/cohort/boringssl.rs b/bindings/rust/standard/tls-harness/src/cohort/boringssl.rs new file mode 100644 index 00000000000..a1536883bc4 --- /dev/null +++ b/bindings/rust/standard/tls-harness/src/cohort/boringssl.rs @@ -0,0 +1,223 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + get_cert_path, + harness::{self, Mode, TlsConfigBuilder, TlsConnection, TlsInfo, ViewIO}, + PemType, +}; +use boring::ssl::{ + ErrorCode, ShutdownResult, Ssl, SslContext, SslContextBuilder, SslFiletype, SslMethod, + SslSession, SslStream, SslVersion, +}; +use std::{ + error::Error, + io::{Read, Write}, + rc::Rc, + sync::{Arc, Mutex}, +}; + +// Creates session ticket callback handler +#[derive(Clone, Default)] +pub struct SessionTicketStorage { + pub stored_ticket: Arc>>, +} + +pub struct BoringSslConnection { + mode: Mode, + connection: SslStream, +} + +pub struct BoringSslConfig { + pub config: SslContext, + pub session_ticket_storage: SessionTicketStorage, +} + +impl From for BoringSslConfig { + fn from(ctx: SslContext) -> Self { + BoringSslConfig { + config: ctx, + session_ticket_storage: Default::default(), + } + } +} + +impl TlsConnection for BoringSslConnection { + type Config = BoringSslConfig; + + fn new_from_config( + mode: harness::Mode, + config: &Self::Config, + io: &Rc, + ) -> Result> { + // Check if there is a session ticket available. + // A session ticket will only be available if the Config was created + // with session resumption enabled (and a previous handshake stored it). + let maybe_ticket = config + .session_ticket_storage + .stored_ticket + .lock() + .unwrap() + .take(); + + // Populate the internal session cache (mirrors the OpenSSL harness pattern). + if let Some(ticket) = &maybe_ticket { + let _ = unsafe { config.config.add_session(ticket) }; + } + + let mut ssl = Ssl::new(&config.config)?; + + // If we have a ticket, attempt to resume with it. + if let Some(ticket) = &maybe_ticket { + unsafe { ssl.set_session(ticket)? }; + } + + let view = match mode { + Mode::Client => io.client_view(), + Mode::Server => io.server_view(), + }; + + let stream = SslStream::new(ssl, view)?; + Ok(Self { + mode, + connection: stream, + }) + } + + fn handshake(&mut self) -> Result<(), Box> { + // If the handshake is already complete, no further work is needed. + if self.connection.ssl().is_init_finished() { + return Ok(()); + } + + // Drive handshake based on configured mode. + let result = match self.mode { + Mode::Server => self.connection.accept(), + Mode::Client => self.connection.connect(), + }; + + match result { + // Completed a handshake step — not necessarily “done” yet. + Ok(_) => Ok(()), + + // Nonblocking WANT_READ / WANT_WRITE are normal while handshaking. + Err(err) => match err.code() { + ErrorCode::WANT_READ | ErrorCode::WANT_WRITE => Ok(()), + _ => Err(err.into()), + }, + } + } + + fn handshake_completed(&self) -> bool { + self.connection.ssl().is_init_finished() + } + + fn send(&mut self, data: &[u8]) { + let mut write_offset = 0; + while write_offset < data.len() { + write_offset += self.connection.write(&data[write_offset..]).unwrap(); + self.connection.flush().unwrap(); // make sure internal buffers don't fill up + } + } + + fn recv(&mut self, data: &mut [u8]) -> std::io::Result<()> { + let data_len = data.len(); + let mut read_offset = 0; + while read_offset < data_len { + read_offset += self.connection.read(&mut data[read_offset..data_len])? + } + Ok(()) + } + + fn shutdown_send(&mut self) { + // this method will not read in a CloseNotify + assert_eq!(self.connection.shutdown().unwrap(), ShutdownResult::Sent); + } + + fn shutdown_finish(&mut self) -> bool { + self.connection.shutdown().unwrap() == ShutdownResult::Received + } +} + +impl TlsInfo for BoringSslConnection { + fn name() -> String { + "boringssl".to_string() + } + + fn get_negotiated_cipher_suite(&self) -> String { + self.connection + .ssl() + .current_cipher() + .expect("Handshake not completed") + .name() + .to_string() + } + + fn negotiated_tls13(&self) -> bool { + self.connection + .ssl() + .version2() + .expect("Handshake not completed") + == SslVersion::TLS1_3 + } + + fn resumed_connection(&self) -> bool { + self.connection.ssl().session_reused() + } + + fn mutual_auth(&self) -> bool { + assert!(self.connection.ssl().is_server()); + self.connection.ssl().peer_certificate().is_some() + && self.connection.ssl().verify_result().is_ok() + } +} + +impl TlsConfigBuilder for SslContextBuilder { + type Config = BoringSslConfig; + + fn new_test_config(mode: Mode) -> Self { + match mode { + Mode::Client => SslContext::builder(SslMethod::tls_client()).unwrap(), + Mode::Server => SslContext::builder(SslMethod::tls_server()).unwrap(), + } + } + + fn set_chain(&mut self, sig_type: crate::SigType) { + self.set_certificate_chain_file(get_cert_path(PemType::ServerCertChain, sig_type)) + .unwrap(); + self.set_private_key_file( + get_cert_path(PemType::ServerKey, sig_type), + SslFiletype::PEM, + ) + .unwrap(); + } + + fn set_trust(&mut self, sig_type: crate::SigType) { + self.set_ca_file(get_cert_path(PemType::CACert, sig_type)) + .unwrap(); + } + + fn build(self) -> Self::Config { + BoringSslConfig { + config: self.build(), + session_ticket_storage: SessionTicketStorage::default(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::test_utilities; + + use super::*; + + #[test] + fn handshake() { + test_utilities::handshake::(); + } + + #[test] + fn transfer() { + test_utilities::transfer::(); + } +} diff --git a/bindings/rust/standard/tls-harness/src/cohort/mod.rs b/bindings/rust/standard/tls-harness/src/cohort/mod.rs index cd4e5e15e49..1864a93f5b3 100644 --- a/bindings/rust/standard/tls-harness/src/cohort/mod.rs +++ b/bindings/rust/standard/tls-harness/src/cohort/mod.rs @@ -13,6 +13,14 @@ pub mod openssl; pub mod rustls; pub mod s2n_tls; +// NOTE: BoringSSL is disabled on macOS to avoid symbol collisions with +// OpenSSL; see https://github.com/aws/s2n-tls/pull/5659 for details. +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +pub mod boringssl; + pub use openssl::{OpenSslConfig, OpenSslConnection}; pub use rustls::{RustlsConfig, RustlsConnection}; pub use s2n_tls::{S2NConfig, S2NConnection}; + +#[cfg(all(feature = "boringssl", not(target_os = "macos")))] +pub use boringssl::{BoringSslConfig, BoringSslConnection}; diff --git a/nix/devshells.nix b/nix/devshells.nix index cd0357b4856..0b4df5fca6d 100644 --- a/nix/devshells.nix +++ b/nix/devshells.nix @@ -44,6 +44,7 @@ let pkgs.rustc pkgs.rustup pkgs.cargo + pkgs.go ]; # Helper function to create base shell configurations diff --git a/nix/shell.sh b/nix/shell.sh index 96ca7cc9c75..938d555ea37 100644 --- a/nix/shell.sh +++ b/nix/shell.sh @@ -256,5 +256,5 @@ function rust_test {(set -e export S2N_TLS_LIB_DIR=$(pwd)/build/lib export S2N_TLS_INCLUDE_DIR=$(pwd)/api echo "rust_test: Running Rust integration tests" - cargo test --manifest-path bindings/rust/standard/integration/Cargo.toml + cargo test --manifest-path bindings/rust/standard/integration/Cargo.toml --features boringssl )}