From 618c2be457201955b63577f1854990717d235537 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 19 Nov 2025 22:44:02 +0000 Subject: [PATCH 01/29] Initial integration test import --- .../s2n-tls/src/callbacks/cert_validation.rs | 10 +- bindings/rust/standard/integration/Cargo.toml | 3 +- bindings/rust/standard/integration/src/lib.rs | 2 + .../rust/standard/integration/src/mtls.rs | 277 ++++++++++++++++++ 4 files changed, 286 insertions(+), 6 deletions(-) create mode 100644 bindings/rust/standard/integration/src/mtls.rs diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index 32ca3bbaf00..1439b999130 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -10,12 +10,12 @@ use crate::{ use std::{marker::PhantomData, ptr::NonNull}; pub struct CertValidationInfo<'a> { - info: NonNull, + pub info: NonNull, _lifetime: PhantomData<&'a s2n_cert_validation_info>, } impl CertValidationInfo<'_> { - pub(crate) fn from_ptr(info: *mut s2n_cert_validation_info) -> Self { + pub fn from_ptr(info: *mut s2n_cert_validation_info) -> Self { let info = NonNull::new(info).expect("info pointer should not be null"); CertValidationInfo { info, @@ -23,18 +23,18 @@ impl CertValidationInfo<'_> { } } - pub(crate) fn as_ptr(&mut self) -> *mut s2n_cert_validation_info { + pub fn as_ptr(&mut self) -> *mut s2n_cert_validation_info { self.info.as_ptr() } /// Corresponds to [s2n_cert_validation_accept]. - pub(crate) fn accept(&mut self) -> Result<(), Error> { + pub fn accept(&mut self) -> Result<(), Error> { unsafe { s2n_cert_validation_accept(self.as_ptr()).into_result() }?; Ok(()) } /// Corresponds to [s2n_cert_validation_reject]. - pub(crate) fn reject(&mut self) -> Result<(), Error> { + pub fn reject(&mut self) -> Result<(), Error> { unsafe { s2n_cert_validation_reject(self.as_ptr()).into_result() }?; Ok(()) } diff --git a/bindings/rust/standard/integration/Cargo.toml b/bindings/rust/standard/integration/Cargo.toml index ff51a7a4ab9..61a893db3b0 100644 --- a/bindings/rust/standard/integration/Cargo.toml +++ b/bindings/rust/standard/integration/Cargo.toml @@ -19,12 +19,13 @@ no-sensitive-tests = [] pq = [ "s2n-tls/pq" ] [dependencies] -s2n-tls = { path = "../../extended/s2n-tls", features = ["unstable-testing"]} +s2n-tls = { path = "../../extended/s2n-tls", features = ["unstable-testing", "unstable-crl"]} s2n-tls-hyper = { path = "../s2n-tls-hyper" } s2n-tls-tokio = { path = "../../extended/s2n-tls-tokio" } s2n-tls-sys = { path = "../../extended/s2n-tls-sys" } aws-lc-sys = "*" tls-harness = { path = "../tls-harness" } +rustls = "0.23.35" [dev-dependencies] openssl = { version = "0.10", features = ["vendored"] } diff --git a/bindings/rust/standard/integration/src/lib.rs b/bindings/rust/standard/integration/src/lib.rs index fd008ab0163..87b59fcc4fe 100644 --- a/bindings/rust/standard/integration/src/lib.rs +++ b/bindings/rust/standard/integration/src/lib.rs @@ -5,5 +5,7 @@ mod capability_check; #[cfg(test)] mod features; +#[cfg(test)] +mod mtls; #[cfg(all(not(feature = "no-sensitive-tests"), test))] mod network; diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs new file mode 100644 index 00000000000..2790767eb22 --- /dev/null +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -0,0 +1,277 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use rustls::ClientConfig; +use s2n_tls::{ + callbacks::{CertValidationCallbackSync, CertValidationInfo, VerifyHostNameCallback}, + connection::Connection, + enums::ClientAuthType, +}; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + mpsc::{Receiver, Sender}, + Arc, +}; +use tls_harness::{ + cohort::{ + rustls::RustlsConfigBuilder, RustlsConfig, RustlsConnection, S2NConfig, S2NConnection, + }, + harness::{read_to_bytes, TlsConfigBuilder, TlsConfigBuilderPair}, + PemType, SigType, TlsConnPair, TlsConnection, +}; + +/// Total application data size (chosen so the final record is always more than small size) +const APP_DATA_SIZE: usize = 100_000; + +#[test] +fn mtls_basic() { + let mut pair: TlsConnPair = { + let mut server_config = s2n_tls::config::Builder::new(); + server_config.set_chain(SigType::Rsa2048); + server_config + .set_client_auth_type(ClientAuthType::Required) + .unwrap(); + server_config.with_system_certs(false).unwrap(); + server_config + .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) + .unwrap(); + let server_config = server_config.build().unwrap(); + let server_config = S2NConfig::from(server_config); + + let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let client_config = ClientConfig::builder_with_provider(crypto_provider) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_root_certificates(RustlsConfig::get_root_cert_store(SigType::Rsa2048)) + .with_client_auth_cert( + RustlsConfig::get_cert_chain(PemType::ClientCertChain, SigType::Rsa2048), + RustlsConfig::get_key(PemType::ClientKey, SigType::Rsa2048), + ) + .unwrap(); + let client_config: RustlsConfig = client_config.into(); + + let mut configs = + TlsConfigBuilderPair::::default(); + configs + .server + .set_client_auth_type(ClientAuthType::Required) + .unwrap(); + TlsConnPair::from_configs(&client_config, &server_config) + }; + + pair.handshake().unwrap(); + pair.round_trip_assert(APP_DATA_SIZE).unwrap(); + pair.shutdown().unwrap(); +} +use s2n_tls::error::Error as S2NError; + +/// Wrapper to make the raw cert validation info pointer sendable across threads +struct SendableCertValidationInfo(*mut s2n_tls_sys::s2n_cert_validation_info); + +unsafe impl Send for SendableCertValidationInfo {} + +#[derive(Debug)] +struct SyncCallback { + invoked: Arc, + /// this is the number of time that the callback must be polled before it returns successfully + immediately_accept: bool, + callback_sender: Sender, +} + +impl SyncCallback { + fn new(immediately_accept: bool) -> (Self, Receiver) { + let (tx, rx) = std::sync::mpsc::channel(); + let value = Self { + invoked: Default::default(), + immediately_accept: immediately_accept, + callback_sender: tx + }; + (value, rx) + } +} + +impl CertValidationCallbackSync for SyncCallback { + fn handle_validation(&self, _conn: &mut Connection, info: &mut CertValidationInfo) -> Result { + self.invoked.fetch_add(1, Ordering::SeqCst); + self.callback_sender + .send(SendableCertValidationInfo(info.info.as_ptr())) + .unwrap(); + if self.immediately_accept { + info.accept()?; + Ok(true) + } else { + // Return false to indicate async validation - cert not yet accepted + Ok(false) + } + } +} + +#[test] +fn mtls_with_cert_verify() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .init(); + let (callback, _rx) = SyncCallback::new(true); + let callback_handle = Arc::clone(&callback.invoked); + let mut pair: TlsConnPair = { + let server_config = { + let mut server_config = s2n_tls::config::Builder::new(); + server_config.set_chain(SigType::Rsa2048); + server_config + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) + .unwrap() + .set_cert_validation_callback_sync(callback) + .unwrap(); + let server_config = server_config.build().unwrap(); + S2NConfig::from(server_config) + }; + + let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let client_config = ClientConfig::builder_with_provider(crypto_provider) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_root_certificates(RustlsConfig::get_root_cert_store(SigType::Rsa2048)) + .with_client_auth_cert( + RustlsConfig::get_cert_chain(PemType::ClientCertChain, SigType::Rsa2048), + RustlsConfig::get_key(PemType::ClientKey, SigType::Rsa2048), + ) + .unwrap(); + let client_config: RustlsConfig = client_config.into(); + + let mut configs = + TlsConfigBuilderPair::::default(); + configs + .server + .set_client_auth_type(ClientAuthType::Required) + .unwrap(); + TlsConnPair::from_configs(&client_config, &server_config) + }; + assert_eq!(callback_handle.load(Ordering::SeqCst), 0); + + pair.handshake().unwrap(); + pair.round_trip_assert(APP_DATA_SIZE).unwrap(); + pair.shutdown().unwrap(); + + assert_eq!(callback_handle.load(Ordering::SeqCst), 1); +} + +// gdb --args target/debug/deps/integration-a4a2dc78ba29c050 mtls_rustls_s2n_async_verify +#[test] +fn mtls_rustls_s2n_async_verify() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .init(); + let (callback, rx) = SyncCallback::new(false); + let callback_handle = Arc::clone(&callback.invoked); + let mut pair: TlsConnPair = { + let server_config = { + let mut server_config = s2n_tls::config::Builder::new(); + server_config.set_chain(SigType::Rsa2048); + server_config + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) + .unwrap() + .set_cert_validation_callback_sync(callback) + .unwrap(); + let server_config = server_config.build().unwrap(); + S2NConfig::from(server_config) + }; + + let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let client_config = ClientConfig::builder_with_provider(crypto_provider) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_root_certificates(RustlsConfig::get_root_cert_store(SigType::Rsa2048)) + .with_client_auth_cert( + RustlsConfig::get_cert_chain(PemType::ClientCertChain, SigType::Rsa2048), + RustlsConfig::get_key(PemType::ClientKey, SigType::Rsa2048), + ) + .unwrap(); + let client_config: RustlsConfig = client_config.into(); + + TlsConnPair::from_configs(&client_config, &server_config) + }; + pair.io.enable_recording(); + + pair.client.handshake().unwrap(); + pair.server.handshake().unwrap(); + pair.client.handshake().unwrap(); + assert_eq!(callback_handle.load(Ordering::SeqCst), 0); + pair.server.handshake().unwrap(); + assert_eq!(callback_handle.load(Ordering::SeqCst), 1); + pair.server.handshake().unwrap(); + assert_eq!(callback_handle.load(Ordering::SeqCst), 1); + + let ptr = rx.recv().unwrap().0; + let mut validation_info = CertValidationInfo::from_ptr(ptr); + validation_info.accept().unwrap(); + tracing::info!("cert accepted"); + pair.handshake().unwrap(); + + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); +} + +struct HostNameIgnorer; +impl VerifyHostNameCallback for HostNameIgnorer { + fn verify_host_name(&self, host_name: &str) -> bool { + println!("the host name: {host_name}"); + true + } +} + +/// control case: async cert validation stuff works in this case +#[test] +fn mtls_s2n_s2n_async_verify() { + let (callback, rx) = SyncCallback::new(false); + let callback_handle = Arc::clone(&callback.invoked); + let mut pair: TlsConnPair = { + let mut configs = + TlsConfigBuilderPair::::default(); + configs + .server + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) + .unwrap() + .set_cert_validation_callback_sync(callback) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + configs.client.set_chain(SigType::Rsa2048); + configs + .client + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + + configs.connection_pair() + }; + pair.io.enable_recording(); + + pair.client.handshake().unwrap(); + pair.server.handshake().unwrap(); + pair.client.handshake().unwrap(); + assert_eq!(callback_handle.load(Ordering::SeqCst), 0); + pair.server.handshake().unwrap(); + assert_eq!(callback_handle.load(Ordering::SeqCst), 1); + let ptr = rx.recv().unwrap().0; + let mut validation_info = CertValidationInfo::from_ptr(ptr); + validation_info.accept().unwrap(); + + pair.handshake().unwrap(); + + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); + +} \ No newline at end of file From 44c07e9baf21fbcbda12becb559f1e1944a760cf Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 19 Nov 2025 23:38:16 +0000 Subject: [PATCH 02/29] Fix mtls cert validation callback to return bool --- bindings/rust/standard/integration/src/mtls.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 2790767eb22..fa95750ea7f 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -96,13 +96,11 @@ impl CertValidationCallbackSync for SyncCallback { self.callback_sender .send(SendableCertValidationInfo(info.info.as_ptr())) .unwrap(); - if self.immediately_accept { - info.accept()?; - Ok(true) - } else { - // Return false to indicate async validation - cert not yet accepted - Ok(false) - } + + // Return true to accept, false for async validation + // The wrapper in config.rs will call info.accept() or info.reject() based on this return value + // Do NOT call info.accept() or info.reject() here - it will be called by the wrapper + Ok(self.immediately_accept) } } From f53822e0fb34b69e691ba8f57f82539830da7aa4 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Fri, 21 Nov 2025 00:53:13 +0000 Subject: [PATCH 03/29] Add helper methods to make module more extensible --- .../s2n-tls/src/callbacks/cert_validation.rs | 40 +- bindings/rust/extended/s2n-tls/src/config.rs | 41 +- .../rust/standard/integration/src/mtls.rs | 561 +++++++++++------- 3 files changed, 422 insertions(+), 220 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index 1439b999130..f756e9da9be 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -40,13 +40,34 @@ impl CertValidationInfo<'_> { } } -pub trait CertValidationCallbackSync: 'static + Send + Sync { - /// Return a boolean to indicate if the certificate chain passed the validation +/// Certificate validation callback that supports both synchronous and asynchronous validation. +/// +/// The callback can operate in three modes based on the return value: +/// - `Ok(Some(true))`: Accept the certificate immediately (synchronous) +/// - `Ok(Some(false))`: Reject the certificate immediately (synchronous) +/// - `Ok(None)`: Defer the decision (asynchronous) - the application must call +/// `validation_info.accept()` or `validation_info.reject()` later +/// +/// When returning `None`, the handshake will block (return S2N_ERR_T_BLOCKED with +/// S2N_BLOCKED_ON_APPLICATION_INPUT) until validation is completed by calling +/// `accept()` or `reject()` on the validation info. +/// +/// This is useful when validation requires: +/// - Consulting an external service (database, OCSP responder, etc.) +/// - User interaction +/// - Complex async operations +pub trait CertValidationCallback: 'static + Send + Sync { + /// Validate the certificate chain. + /// + /// Return: + /// - `Some(true)` to accept immediately + /// - `Some(false)` to reject immediately + /// - `None` to defer the decision (async mode) fn handle_validation( &self, connection: &mut Connection, validation_info: &mut CertValidationInfo, - ) -> Result; + ) -> Result, Error>; } #[cfg(test)] @@ -58,16 +79,17 @@ mod tests { accept: bool, } - struct SyncCallback(Counter); - impl CertValidationCallbackSync for SyncCallback { + struct TestCallback(Counter); + impl CertValidationCallback for TestCallback { fn handle_validation( &self, conn: &mut Connection, _info: &mut CertValidationInfo, - ) -> Result { + ) -> Result, Error> { self.0.increment(); let context = conn.application_context::().unwrap(); - Ok(context.accept) + // Return Some(accept) for synchronous validation + Ok(Some(context.accept)) } } @@ -75,11 +97,11 @@ mod tests { fn sync_cert_validation() -> Result<(), Box> { for accept in [true, false] { let counter = Counter::default(); - let callback = SyncCallback(counter.clone()); + let callback = TestCallback(counter.clone()); let config = { let mut config = config_builder(&security::DEFAULT_TLS13)?; - config.set_cert_validation_callback_sync(callback)?; + config.set_cert_validation_callback(callback)?; config.build()? }; diff --git a/bindings/rust/extended/s2n-tls/src/config.rs b/bindings/rust/extended/s2n-tls/src/config.rs index ab73c78bc95..fec6bcea58d 100644 --- a/bindings/rust/extended/s2n-tls/src/config.rs +++ b/bindings/rust/extended/s2n-tls/src/config.rs @@ -623,12 +623,20 @@ impl Builder { Ok(self) } - /// Set a callback function to perform custom cert validation synchronously. + /// Set a callback function to perform custom certificate validation. /// - /// Corresponds to [s2n_config_set_cert_validation_cb], but the rust callback - /// can only perform in synchronous mode. + /// The callback can operate in both synchronous and asynchronous modes: + /// - Return `Some(true)` to accept the certificate immediately + /// - Return `Some(false)` to reject the certificate immediately + /// - Return `None` to defer the decision - the application must call + /// `validation_info.accept()` or `validation_info.reject()` later + /// + /// When returning `None`, the handshake will block (return S2N_ERR_T_BLOCKED with + /// S2N_BLOCKED_ON_APPLICATION_INPUT) until validation is completed. + /// + /// Corresponds to [s2n_config_set_cert_validation_cb]. #[cfg(feature = "unstable-crl")] - pub fn set_cert_validation_callback_sync( + pub fn set_cert_validation_callback( &mut self, handler: T, ) -> Result<&mut Self, Error> { @@ -639,12 +647,21 @@ impl Builder { ) -> libc::c_int { let mut info = CertValidationInfo::from_ptr(validation_info); with_context(conn_ptr, |conn, context| { - let callback = context.cert_validation_callback_sync.as_ref(); + let callback = context.cert_validation_callback.as_ref(); callback.map(|callback| { - let accepted = callback.handle_validation(conn, &mut info).unwrap(); - match accepted { - true => info.accept().unwrap(), - false => info.reject().unwrap(), + match callback.handle_validation(conn, &mut info).unwrap() { + Some(true) => { + // Accept immediately (sync mode) + info.accept().unwrap(); + } + Some(false) => { + // Reject immediately (sync mode) + info.reject().unwrap(); + } + None => { + // Defer decision (async mode) - do nothing + // The application must call accept() or reject() later + } } }) }); @@ -657,7 +674,7 @@ impl Builder { // it is being built, the Builder is the only reference to the config. self.config.context_mut() }; - context.cert_validation_callback_sync = Some(handler); + context.cert_validation_callback = Some(handler); unsafe { s2n_config_set_cert_validation_cb( @@ -1095,7 +1112,7 @@ pub(crate) struct Context { #[cfg(feature = "unstable-cert_authorities")] pub(crate) cert_authorities: Option>, #[cfg(feature = "unstable-crl")] - pub(crate) cert_validation_callback_sync: Option>, + pub(crate) cert_validation_callback: Option>, } impl Default for Context { @@ -1119,7 +1136,7 @@ impl Default for Context { #[cfg(feature = "unstable-cert_authorities")] cert_authorities: None, #[cfg(feature = "unstable-crl")] - cert_validation_callback_sync: None, + cert_validation_callback: None, } } } diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index fa95750ea7f..d57d1270161 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -3,9 +3,10 @@ use rustls::ClientConfig; use s2n_tls::{ - callbacks::{CertValidationCallbackSync, CertValidationInfo, VerifyHostNameCallback}, + callbacks::{CertValidationCallback, CertValidationInfo, VerifyHostNameCallback}, connection::Connection, enums::ClientAuthType, + error::Error as S2NError, }; use std::sync::{ atomic::{AtomicU64, Ordering}, @@ -13,263 +14,425 @@ use std::sync::{ Arc, }; use tls_harness::{ - cohort::{ - rustls::RustlsConfigBuilder, RustlsConfig, RustlsConnection, S2NConfig, S2NConnection, - }, - harness::{read_to_bytes, TlsConfigBuilder, TlsConfigBuilderPair}, + cohort::{RustlsConfig, RustlsConnection, S2NConfig, S2NConnection}, + harness::{read_to_bytes, TlsConfigBuilder}, PemType, SigType, TlsConnPair, TlsConnection, }; /// Total application data size (chosen so the final record is always more than small size) const APP_DATA_SIZE: usize = 100_000; -#[test] -fn mtls_basic() { - let mut pair: TlsConnPair = { - let mut server_config = s2n_tls::config::Builder::new(); - server_config.set_chain(SigType::Rsa2048); - server_config - .set_client_auth_type(ClientAuthType::Required) - .unwrap(); - server_config.with_system_certs(false).unwrap(); - server_config - .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) - .unwrap(); - let server_config = server_config.build().unwrap(); - let server_config = S2NConfig::from(server_config); - - let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); - let client_config = ClientConfig::builder_with_provider(crypto_provider) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_root_certificates(RustlsConfig::get_root_cert_store(SigType::Rsa2048)) - .with_client_auth_cert( - RustlsConfig::get_cert_chain(PemType::ClientCertChain, SigType::Rsa2048), - RustlsConfig::get_key(PemType::ClientKey, SigType::Rsa2048), - ) - .unwrap(); - let client_config: RustlsConfig = client_config.into(); - - let mut configs = - TlsConfigBuilderPair::::default(); - configs - .server - .set_client_auth_type(ClientAuthType::Required) - .unwrap(); - TlsConnPair::from_configs(&client_config, &server_config) - }; - - pair.handshake().unwrap(); - pair.round_trip_assert(APP_DATA_SIZE).unwrap(); - pair.shutdown().unwrap(); -} -use s2n_tls::error::Error as S2NError; -/// Wrapper to make the raw cert validation info pointer sendable across threads struct SendableCertValidationInfo(*mut s2n_tls_sys::s2n_cert_validation_info); - unsafe impl Send for SendableCertValidationInfo {} +/// A test callback that can operate in sync or async mode. +/// +/// This callback is used to test certificate validation during the TLS handshake. +/// It can operate in two modes: +/// +/// - **Sync mode**: Immediately accepts the certificate (returns `Some(true)`) +/// - **Async mode**: Defers the decision (returns `None`) and sends the validation +/// info pointer through a channel so the test can manually accept/reject later #[derive(Debug)] -struct SyncCallback { +struct TestCertValidationCallback { invoked: Arc, - /// this is the number of time that the callback must be polled before it returns successfully immediately_accept: bool, - callback_sender: Sender, + callback_sender: Option>, } -impl SyncCallback { - fn new(immediately_accept: bool) -> (Self, Receiver) { +impl TestCertValidationCallback { + /// Create a callback that immediately accepts certificates (sync mode) + fn new_sync() -> Self { + Self { + invoked: Default::default(), + immediately_accept: true, + callback_sender: None, // No channel needed for sync mode + } + } + + /// Create a callback that defers the decision (async mode) + /// + /// Returns the callback and a receiver that will get the validation info pointer + /// when the callback is invoked during the handshake. + fn new_async() -> (Self, Receiver) { let (tx, rx) = std::sync::mpsc::channel(); - let value = Self { + let callback = Self { invoked: Default::default(), - immediately_accept: immediately_accept, - callback_sender: tx + immediately_accept: false, + callback_sender: Some(tx), }; - (value, rx) + (callback, rx) } } -impl CertValidationCallbackSync for SyncCallback { - fn handle_validation(&self, _conn: &mut Connection, info: &mut CertValidationInfo) -> Result { +impl CertValidationCallback for TestCertValidationCallback { + /// Called by s2n-tls during the handshake when a client certificate is received. + /// + /// Return value: + /// - `Ok(Some(true))`: Accept the certificate immediately (sync mode) + /// - `Ok(None)`: Defer the decision - the test must call accept()/reject() later (async mode) + fn handle_validation( + &self, + _conn: &mut Connection, + info: &mut CertValidationInfo, + ) -> Result, S2NError> { self.invoked.fetch_add(1, Ordering::SeqCst); - self.callback_sender - .send(SendableCertValidationInfo(info.info.as_ptr())) - .unwrap(); - // Return true to accept, false for async validation - // The wrapper in config.rs will call info.accept() or info.reject() based on this return value - // Do NOT call info.accept() or info.reject() here - it will be called by the wrapper - Ok(self.immediately_accept) + if let Some(sender) = &self.callback_sender { + sender + .send(SendableCertValidationInfo(info.info.as_ptr())) + .unwrap(); + } + + if self.immediately_accept { + Ok(Some(true)) + } else { + Ok(None) + } } } -#[test] -fn mtls_with_cert_verify() { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::TRACE) - .init(); - let (callback, _rx) = SyncCallback::new(true); - let callback_handle = Arc::clone(&callback.invoked); - let mut pair: TlsConnPair = { - let server_config = { - let mut server_config = s2n_tls::config::Builder::new(); - server_config.set_chain(SigType::Rsa2048); - server_config - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) - .unwrap() - .set_cert_validation_callback_sync(callback) - .unwrap(); - let server_config = server_config.build().unwrap(); - S2NConfig::from(server_config) - }; +/// A hostname verifier that accepts all hostnames. +/// +/// Hostname verification isn't the focus of the test so we ignore it. +struct HostNameIgnorer; +impl VerifyHostNameCallback for HostNameIgnorer { + fn verify_host_name(&self, _host_name: &str) -> bool { + true // Accept any hostname + } +} - let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); - let client_config = ClientConfig::builder_with_provider(crypto_provider) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_root_certificates(RustlsConfig::get_root_cert_store(SigType::Rsa2048)) - .with_client_auth_cert( - RustlsConfig::get_cert_chain(PemType::ClientCertChain, SigType::Rsa2048), - RustlsConfig::get_key(PemType::ClientKey, SigType::Rsa2048), - ) - .unwrap(); - let client_config: RustlsConfig = client_config.into(); - - let mut configs = - TlsConfigBuilderPair::::default(); - configs - .server - .set_client_auth_type(ClientAuthType::Required) - .unwrap(); - TlsConnPair::from_configs(&client_config, &server_config) - }; - assert_eq!(callback_handle.load(Ordering::SeqCst), 0); +struct MtlsClientConfig { + /// The signature algorithm type (RSA2048, ECDSA256, etc.) + sig_type: SigType, +} +impl Default for MtlsClientConfig { + fn default() -> Self { + Self { + sig_type: SigType::Rsa2048, + } + } +} + +enum MtlsServerCallback { + None, + Sync, + Async, +} + +/// Configuration for mTLS server. +/// +/// Specifies the server's certificate and optionally a custom cert validation callback. +struct MtlsServerConfig { + sig_type: SigType, + callback_mode: MtlsServerCallback, +} + +impl Default for MtlsServerConfig { + fn default() -> Self { + Self { + sig_type: SigType::Rsa2048, + callback_mode: MtlsServerCallback::None, // No custom callback by default + } + } +} + + + +/// Basic mTLS test: handshake, data transfer, and shutdown. +fn test_mtls_basic(client_config: &C::Config, server_config: &S::Config) +where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = TlsConnPair::::from_configs(client_config, server_config); pair.handshake().unwrap(); pair.round_trip_assert(APP_DATA_SIZE).unwrap(); pair.shutdown().unwrap(); +} +/// mTLS test with synchronous cert validation callback. +/// +/// This test verifies that a custom certificate validation callback is invoked +/// during the handshake and can immediately accept or reject the client certificate. +fn test_mtls_sync_callback( + client_config: &C::Config, + server_config: &S::Config, + callback_handle: Arc, +) where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = TlsConnPair::::from_configs(client_config, server_config); + assert_eq!(callback_handle.load(Ordering::SeqCst), 0); + pair.handshake().unwrap(); assert_eq!(callback_handle.load(Ordering::SeqCst), 1); -} -// gdb --args target/debug/deps/integration-a4a2dc78ba29c050 mtls_rustls_s2n_async_verify -#[test] -fn mtls_rustls_s2n_async_verify() { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::TRACE) - .init(); - let (callback, rx) = SyncCallback::new(false); - let callback_handle = Arc::clone(&callback.invoked); - let mut pair: TlsConnPair = { - let server_config = { - let mut server_config = s2n_tls::config::Builder::new(); - server_config.set_chain(SigType::Rsa2048); - server_config - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) - .unwrap() - .set_cert_validation_callback_sync(callback) - .unwrap(); - let server_config = server_config.build().unwrap(); - S2NConfig::from(server_config) - }; + pair.round_trip_assert(APP_DATA_SIZE).unwrap(); + pair.shutdown().unwrap(); +} - let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); - let client_config = ClientConfig::builder_with_provider(crypto_provider) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_root_certificates(RustlsConfig::get_root_cert_store(SigType::Rsa2048)) - .with_client_auth_cert( - RustlsConfig::get_cert_chain(PemType::ClientCertChain, SigType::Rsa2048), - RustlsConfig::get_key(PemType::ClientKey, SigType::Rsa2048), - ) - .unwrap(); - let client_config: RustlsConfig = client_config.into(); - - TlsConnPair::from_configs(&client_config, &server_config) - }; +/// mTLS test with asynchronous cert validation callback. +/// +/// This test verifies that certificate validation can be deferred - the callback +/// returns "false" to indicate validation is pending, and the test manually calls +/// accept() or reject() later to complete the handshake and verify the cert asynchronously. +fn test_mtls_async_callback( + client_config: &C::Config, + server_config: &S::Config, + callback_handle: Arc, + rx: Receiver, +) where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = TlsConnPair::::from_configs(client_config, server_config); pair.io.enable_recording(); + // Step through handshake manually to control when validation happens + // Client sends ClientHello pair.client.handshake().unwrap(); + + // Server responds with ServerHello, Certificate, etc. pair.server.handshake().unwrap(); + + // Client sends Certificate, CertificateVerify, Finished pair.client.handshake().unwrap(); + + // Callback hasn't been invoked yet (server hasn't processed client cert) assert_eq!(callback_handle.load(Ordering::SeqCst), 0); + + // Server processes client certificate - this triggers the callback + // The callback returns false (async mode), so validation is pending pair.server.handshake().unwrap(); + + // Verify callback was invoked exactly once assert_eq!(callback_handle.load(Ordering::SeqCst), 1); + + // Calling handshake again should NOT invoke the callback again + // (validation is still pending from the first invocation) pair.server.handshake().unwrap(); assert_eq!(callback_handle.load(Ordering::SeqCst), 1); + // Now manually accept the certificate (this is the "async" part) + // Receive the validation info pointer that was sent through the channel let ptr = rx.recv().unwrap().0; let mut validation_info = CertValidationInfo::from_ptr(ptr); + + // Accept the certificate - this unblocks the handshake validation_info.accept().unwrap(); - tracing::info!("cert accepted"); - pair.handshake().unwrap(); + // Complete the handshake now that validation is done + pair.handshake().unwrap(); + + // Verify the connection works pair.round_trip_assert(10).unwrap(); pair.shutdown().unwrap(); } -struct HostNameIgnorer; -impl VerifyHostNameCallback for HostNameIgnorer { - fn verify_host_name(&self, host_name: &str) -> bool { - println!("the host name: {host_name}"); - true - } +/// Create a Rustls client configured for mTLS. +fn create_rustls_mtls_client_config(config: MtlsClientConfig) -> RustlsConfig { + let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + + let client_config = ClientConfig::builder_with_provider(crypto_provider) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_root_certificates(RustlsConfig::get_root_cert_store(config.sig_type)) + .with_client_auth_cert( + RustlsConfig::get_cert_chain(PemType::ClientCertChain, config.sig_type), + RustlsConfig::get_key(PemType::ClientKey, config.sig_type), + ) + .unwrap(); + + client_config.into() } -/// control case: async cert validation stuff works in this case -#[test] -fn mtls_s2n_s2n_async_verify() { - let (callback, rx) = SyncCallback::new(false); - let callback_handle = Arc::clone(&callback.invoked); - let mut pair: TlsConnPair = { - let mut configs = - TlsConfigBuilderPair::::default(); - configs - .server - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, SigType::Rsa2048)) - .unwrap() - .set_cert_validation_callback_sync(callback) - .unwrap() - .set_verify_host_callback(HostNameIgnorer) - .unwrap(); - configs.client.set_chain(SigType::Rsa2048); - configs - .client - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .set_verify_host_callback(HostNameIgnorer) - .unwrap(); - - configs.connection_pair() + +/// Create an S2N server configured for mTLS with optional cert validation callback. +fn create_s2n_mtls_server_config( + config: MtlsServerConfig, +) -> ( + S2NConfig, + Option>, + Option>, +) { + let mut server_config = s2n_tls::config::Builder::new(); + + // Load the server's certificate and private key + server_config.set_chain(config.sig_type); + + server_config + // Require client to present a certificate + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + // Don't load system CA certificates (use only our test CA) + .with_system_certs(false) + .unwrap() + // Load the test CA certificate to verify client certificates + .trust_pem(&read_to_bytes(PemType::CACert, config.sig_type)) + .unwrap(); + + // Optionally install a custom cert validation callback + let (callback_handle, rx) = match config.callback_mode { + MtlsServerCallback::None => { + (None, None) + } + MtlsServerCallback::Sync => { + let callback = TestCertValidationCallback::new_sync(); + let handle = Arc::clone(&callback.invoked); + server_config + .set_cert_validation_callback(callback) + .unwrap(); + (Some(handle), None) + } + MtlsServerCallback::Async => { + let (callback, rx) = TestCertValidationCallback::new_async(); + let handle = Arc::clone(&callback.invoked); + server_config + .set_cert_validation_callback(callback) + .unwrap(); + (Some(handle), Some(rx)) + } }; - pair.io.enable_recording(); - pair.client.handshake().unwrap(); - pair.server.handshake().unwrap(); - pair.client.handshake().unwrap(); - assert_eq!(callback_handle.load(Ordering::SeqCst), 0); - pair.server.handshake().unwrap(); - assert_eq!(callback_handle.load(Ordering::SeqCst), 1); - let ptr = rx.recv().unwrap().0; - let mut validation_info = CertValidationInfo::from_ptr(ptr); - validation_info.accept().unwrap(); + let server_config = S2NConfig::from(server_config.build().unwrap()); + (server_config, callback_handle, rx) +} - pair.handshake().unwrap(); - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); +/// Test basic mTLS with Rustls client and S2N server. +#[test] +fn rustls_s2n_mtls_basic() { + let client_config = create_rustls_mtls_client_config(MtlsClientConfig::default()); + + let (server_config, _, _) = create_s2n_mtls_server_config(MtlsServerConfig::default()); + + test_mtls_basic::(&client_config, &server_config); +} + +/// Test mTLS with synchronous cert validation callback. +#[test] +fn rustls_s2n_mtls_sync_callback() { + let client_config = create_rustls_mtls_client_config(MtlsClientConfig::default()); + + // Create S2N server with a synchronous validation callback + let (server_config, callback_handle, _) = create_s2n_mtls_server_config(MtlsServerConfig { + callback_mode: MtlsServerCallback::Sync, + ..Default::default() + }); + + // Run the sync callback test + test_mtls_sync_callback::( + &client_config, + &server_config, + callback_handle.unwrap(), + ); +} + +/// Test mTLS with asynchronous cert validation callback. +/// +#[test] +fn rustls_s2n_mtls_async_callback() { + let client_config = create_rustls_mtls_client_config(MtlsClientConfig::default()); + + // Create S2N server with an asynchronous validation callback + let (server_config, callback_handle, rx) = create_s2n_mtls_server_config(MtlsServerConfig { + callback_mode: MtlsServerCallback::Async, + ..Default::default() + }); + + // Run the async callback test + test_mtls_async_callback::( + &client_config, + &server_config, + callback_handle.unwrap(), + rx.unwrap(), + ); +} + +/// Create an S2N client configured for mTLS. +/// - Requires explicit client auth type setting +fn create_s2n_mtls_client_config(config: MtlsClientConfig) -> S2NConfig { + let mut client_config = s2n_tls::config::Builder::new(); + client_config.set_chain(config.sig_type); + + client_config + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, config.sig_type)) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + + S2NConfig::from(client_config.build().unwrap()) +} + +/// Create an S2N server with hostname verifier (for S2N-to-S2N tests). +fn create_s2n_mtls_server_config_with_hostname_verifier( + config: MtlsServerConfig, +) -> ( + S2NConfig, + Option>, + Option>, +) { + let mut server_config = s2n_tls::config::Builder::new(); + + server_config.set_chain(config.sig_type); + + server_config + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, config.sig_type)) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + + let (callback_handle, rx) = match config.callback_mode { + MtlsServerCallback::None => (None, None), + MtlsServerCallback::Sync => { + let callback = TestCertValidationCallback::new_sync(); + let handle = Arc::clone(&callback.invoked); + server_config + .set_cert_validation_callback(callback) + .unwrap(); + (Some(handle), None) + } + MtlsServerCallback::Async => { + let (callback, rx) = TestCertValidationCallback::new_async(); + let handle = Arc::clone(&callback.invoked); + server_config + .set_cert_validation_callback(callback) + .unwrap(); + (Some(handle), Some(rx)) + } + }; + + let server_config = S2NConfig::from(server_config.build().unwrap()); + (server_config, callback_handle, rx) +} + +/// Test mTLS with asynchronous cert validation using S2N for both client and server. +#[test] +fn s2n_s2n_mtls_async_callback() { + let client_config = create_s2n_mtls_client_config(MtlsClientConfig::default()); + + let (server_config, callback_handle, rx) = + create_s2n_mtls_server_config_with_hostname_verifier(MtlsServerConfig { + callback_mode: MtlsServerCallback::Async, + ..Default::default() + }); + + test_mtls_async_callback::( + &client_config, + &server_config, + callback_handle.unwrap(), + rx.unwrap(), + ); } \ No newline at end of file From 7ec5306b3109807ca240e54c1bd77c00f3d0fa49 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Mon, 24 Nov 2025 22:06:36 +0000 Subject: [PATCH 04/29] Add TLS 1.2 rustls client test --- .../rust/standard/integration/src/mtls.rs | 502 ++++++++---------- 1 file changed, 222 insertions(+), 280 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index d57d1270161..cb392a19b9e 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -1,4 +1,4 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright Amazon.com, Inc. or its affiliates. // SPDX-License-Identifier: Apache-2.0 use rustls::ClientConfig; @@ -19,21 +19,11 @@ use tls_harness::{ PemType, SigType, TlsConnPair, TlsConnection, }; -/// Total application data size (chosen so the final record is always more than small size) const APP_DATA_SIZE: usize = 100_000; - struct SendableCertValidationInfo(*mut s2n_tls_sys::s2n_cert_validation_info); unsafe impl Send for SendableCertValidationInfo {} -/// A test callback that can operate in sync or async mode. -/// -/// This callback is used to test certificate validation during the TLS handshake. -/// It can operate in two modes: -/// -/// - **Sync mode**: Immediately accepts the certificate (returns `Some(true)`) -/// - **Async mode**: Defers the decision (returns `None`) and sends the validation -/// info pointer through a channel so the test can manually accept/reject later #[derive(Debug)] struct TestCertValidationCallback { invoked: Arc, @@ -42,397 +32,349 @@ struct TestCertValidationCallback { } impl TestCertValidationCallback { - /// Create a callback that immediately accepts certificates (sync mode) - fn new_sync() -> Self { - Self { - invoked: Default::default(), - immediately_accept: true, - callback_sender: None, // No channel needed for sync mode - } + fn new_sync() -> (Self, Arc, Option>) { + let invoked = Arc::new(AtomicU64::new(0)); + ( + Self { + invoked: Arc::clone(&invoked), + immediately_accept: true, + callback_sender: None, + }, + invoked, + None, + ) } - /// Create a callback that defers the decision (async mode) - /// - /// Returns the callback and a receiver that will get the validation info pointer - /// when the callback is invoked during the handshake. - fn new_async() -> (Self, Receiver) { + fn new_async() -> (Self, Arc, Receiver) { + let invoked = Arc::new(AtomicU64::new(0)); let (tx, rx) = std::sync::mpsc::channel(); - let callback = Self { - invoked: Default::default(), - immediately_accept: false, - callback_sender: Some(tx), - }; - (callback, rx) + ( + Self { + invoked: Arc::clone(&invoked), + immediately_accept: false, + callback_sender: Some(tx), + }, + invoked, + rx, + ) } } impl CertValidationCallback for TestCertValidationCallback { - /// Called by s2n-tls during the handshake when a client certificate is received. - /// - /// Return value: - /// - `Ok(Some(true))`: Accept the certificate immediately (sync mode) - /// - `Ok(None)`: Defer the decision - the test must call accept()/reject() later (async mode) fn handle_validation( &self, _conn: &mut Connection, info: &mut CertValidationInfo, ) -> Result, S2NError> { self.invoked.fetch_add(1, Ordering::SeqCst); - + if let Some(sender) = &self.callback_sender { sender .send(SendableCertValidationInfo(info.info.as_ptr())) - .unwrap(); + .expect("sending CertValidationInfo ptr"); } - if self.immediately_accept { - Ok(Some(true)) - } else { - Ok(None) - } + Ok(if self.immediately_accept { Some(true) } else { None }) } } -/// A hostname verifier that accepts all hostnames. -/// -/// Hostname verification isn't the focus of the test so we ignore it. +#[derive(Default)] struct HostNameIgnorer; impl VerifyHostNameCallback for HostNameIgnorer { fn verify_host_name(&self, _host_name: &str) -> bool { - true // Accept any hostname + true } } +#[derive(Clone, Copy)] struct MtlsClientConfig { - /// The signature algorithm type (RSA2048, ECDSA256, etc.) sig_type: SigType, + tls_version: &'static rustls::SupportedProtocolVersion, } impl Default for MtlsClientConfig { fn default() -> Self { Self { sig_type: SigType::Rsa2048, + tls_version: &rustls::version::TLS13, } } } +#[derive(Clone, Copy, Default)] enum MtlsServerCallback { + #[default] None, Sync, Async, } -/// Configuration for mTLS server. -/// -/// Specifies the server's certificate and optionally a custom cert validation callback. +#[derive(Clone, Copy, Default)] struct MtlsServerConfig { sig_type: SigType, callback_mode: MtlsServerCallback, + with_hostname_verifier: bool, } -impl Default for MtlsServerConfig { - fn default() -> Self { - Self { - sig_type: SigType::Rsa2048, - callback_mode: MtlsServerCallback::None, // No custom callback by default - } +// ---------- Small helpers ---------- + +fn rustls_mtls_client(cfg: MtlsClientConfig) -> RustlsConfig { + let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let client = ClientConfig::builder_with_provider(provider) + .with_protocol_versions(&[cfg.tls_version]) + .unwrap() + .with_root_certificates(RustlsConfig::get_root_cert_store(cfg.sig_type)) + .with_client_auth_cert( + RustlsConfig::get_cert_chain(PemType::ClientCertChain, cfg.sig_type), + RustlsConfig::get_key(PemType::ClientKey, cfg.sig_type), + ) + .unwrap(); + client.into() +} + +fn s2n_mtls_server( + cfg: MtlsServerConfig, +) -> ( + S2NConfig, + Option>, + Option>, +) { + let mut builder = s2n_tls::config::Builder::new(); + builder.set_chain(cfg.sig_type); + builder + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) + .unwrap(); + + if cfg.with_hostname_verifier { + builder.set_verify_host_callback(HostNameIgnorer).unwrap(); } + + let (handle, rx) = match cfg.callback_mode { + MtlsServerCallback::None => (None, None), + MtlsServerCallback::Sync => { + let (cb, invoked, _) = TestCertValidationCallback::new_sync(); + builder.set_cert_validation_callback(cb).unwrap(); + (Some(invoked), None) + } + MtlsServerCallback::Async => { + let (cb, invoked, rx) = TestCertValidationCallback::new_async(); + builder.set_cert_validation_callback(cb).unwrap(); + (Some(invoked), Some(rx)) + } + }; + + (S2NConfig::from(builder.build().unwrap()), handle, rx) } +fn s2n_mtls_client(cfg: MtlsClientConfig) -> S2NConfig { + let mut builder = s2n_tls::config::Builder::new(); + builder.set_chain(cfg.sig_type); + builder + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + S2NConfig::from(builder.build().unwrap()) +} +// ---------- Core test logic ---------- -/// Basic mTLS test: handshake, data transfer, and shutdown. -fn test_mtls_basic(client_config: &C::Config, server_config: &S::Config) +fn test_mtls_basic(client_cfg: &C::Config, server_cfg: &S::Config) where C: TlsConnection, S: TlsConnection, { - let mut pair = TlsConnPair::::from_configs(client_config, server_config); + let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); pair.handshake().unwrap(); pair.round_trip_assert(APP_DATA_SIZE).unwrap(); pair.shutdown().unwrap(); } -/// mTLS test with synchronous cert validation callback. -/// -/// This test verifies that a custom certificate validation callback is invoked -/// during the handshake and can immediately accept or reject the client certificate. fn test_mtls_sync_callback( - client_config: &C::Config, - server_config: &S::Config, - callback_handle: Arc, + client_cfg: &C::Config, + server_cfg: &S::Config, + handle: Arc, ) where C: TlsConnection, S: TlsConnection, { - let mut pair = TlsConnPair::::from_configs(client_config, server_config); - assert_eq!(callback_handle.load(Ordering::SeqCst), 0); + let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); + assert_eq!(handle.load(Ordering::SeqCst), 0); pair.handshake().unwrap(); - assert_eq!(callback_handle.load(Ordering::SeqCst), 1); - + assert_eq!(handle.load(Ordering::SeqCst), 1); pair.round_trip_assert(APP_DATA_SIZE).unwrap(); pair.shutdown().unwrap(); } -/// mTLS test with asynchronous cert validation callback. -/// -/// This test verifies that certificate validation can be deferred - the callback -/// returns "false" to indicate validation is pending, and the test manually calls -/// accept() or reject() later to complete the handshake and verify the cert asynchronously. -fn test_mtls_async_callback( - client_config: &C::Config, - server_config: &S::Config, - callback_handle: Arc, - rx: Receiver, -) where +/// Drive handshake to the point where async cert validation is pending and +/// the callback has been invoked exactly once. +fn drive_until_async_pending( + client_cfg: &C::Config, + server_cfg: &S::Config, + handle: &Arc, +) -> TlsConnPair +where C: TlsConnection, S: TlsConnection, { - let mut pair = TlsConnPair::::from_configs(client_config, server_config); + let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); pair.io.enable_recording(); - // Step through handshake manually to control when validation happens - // Client sends ClientHello + // ClientHello pair.client.handshake().unwrap(); - - // Server responds with ServerHello, Certificate, etc. + // ServerHello + server cert flight (1.2 or 1.3 depending on config) pair.server.handshake().unwrap(); - - // Client sends Certificate, CertificateVerify, Finished + // client cert + finished flight pair.client.handshake().unwrap(); - - // Callback hasn't been invoked yet (server hasn't processed client cert) - assert_eq!(callback_handle.load(Ordering::SeqCst), 0); - - // Server processes client certificate - this triggers the callback - // The callback returns false (async mode), so validation is pending + + // callback has not fired yet + assert_eq!(handle.load(Ordering::SeqCst), 0); + + // server processes client cert → async callback fires and returns None pair.server.handshake().unwrap(); - - // Verify callback was invoked exactly once - assert_eq!(callback_handle.load(Ordering::SeqCst), 1); - - // Calling handshake again should NOT invoke the callback again - // (validation is still pending from the first invocation) + assert_eq!(handle.load(Ordering::SeqCst), 1); + + // second call should not re-invoke callback pair.server.handshake().unwrap(); - assert_eq!(callback_handle.load(Ordering::SeqCst), 1); + assert_eq!(handle.load(Ordering::SeqCst), 1); + + pair +} + +// TLS 1.3: bug shows up after accept, when we try to finish the handshake. +fn test_mtls_async_callback_tls13_core( + client_cfg: &C::Config, + server_cfg: &S::Config, + handle: Arc, + rx: Receiver, +) where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = drive_until_async_pending::(client_cfg, server_cfg, &handle); - // Now manually accept the certificate (this is the "async" part) - // Receive the validation info pointer that was sent through the channel - let ptr = rx.recv().unwrap().0; - let mut validation_info = CertValidationInfo::from_ptr(ptr); - - // Accept the certificate - this unblocks the handshake - validation_info.accept().unwrap(); + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + let mut info = CertValidationInfo::from_ptr(ptr); + info.accept().unwrap(); - // Complete the handshake now that validation is done + // BUG (TLS 1.3): currently hangs here due to multi-message + error blinding. pair.handshake().unwrap(); - - // Verify the connection works + pair.round_trip_assert(10).unwrap(); pair.shutdown().unwrap(); } -/// Create a Rustls client configured for mTLS. -fn create_rustls_mtls_client_config(config: MtlsClientConfig) -> RustlsConfig { - let crypto_provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); - - let client_config = ClientConfig::builder_with_provider(crypto_provider) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_root_certificates(RustlsConfig::get_root_cert_store(config.sig_type)) - .with_client_auth_cert( - RustlsConfig::get_cert_chain(PemType::ClientCertChain, config.sig_type), - RustlsConfig::get_key(PemType::ClientKey, config.sig_type), - ) - .unwrap(); - - client_config.into() -} - +// TLS 1.2: bug shows up when we drive more handshake *while* validation is pending. +fn test_mtls_async_callback_tls12_core( + client_cfg: &C::Config, + server_cfg: &S::Config, + handle: Arc, + rx: Receiver, +) where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = drive_until_async_pending::(client_cfg, server_cfg, &handle); -/// Create an S2N server configured for mTLS with optional cert validation callback. -fn create_s2n_mtls_server_config( - config: MtlsServerConfig, -) -> ( - S2NConfig, - Option>, - Option>, -) { - let mut server_config = s2n_tls::config::Builder::new(); - - // Load the server's certificate and private key - server_config.set_chain(config.sig_type); - - server_config - // Require client to present a certificate - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - // Don't load system CA certificates (use only our test CA) - .with_system_certs(false) - .unwrap() - // Load the test CA certificate to verify client certificates - .trust_pem(&read_to_bytes(PemType::CACert, config.sig_type)) - .unwrap(); + // BUG (TLS 1.2): currently hangs here due to multi-message + error blinding. + pair.handshake().unwrap(); - // Optionally install a custom cert validation callback - let (callback_handle, rx) = match config.callback_mode { - MtlsServerCallback::None => { - (None, None) - } - MtlsServerCallback::Sync => { - let callback = TestCertValidationCallback::new_sync(); - let handle = Arc::clone(&callback.invoked); - server_config - .set_cert_validation_callback(callback) - .unwrap(); - (Some(handle), None) - } - MtlsServerCallback::Async => { - let (callback, rx) = TestCertValidationCallback::new_async(); - let handle = Arc::clone(&callback.invoked); - server_config - .set_cert_validation_callback(callback) - .unwrap(); - (Some(handle), Some(rx)) - } - }; + // Intended flow once bug is fixed: + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + let mut info = CertValidationInfo::from_ptr(ptr); + info.accept().unwrap(); - let server_config = S2NConfig::from(server_config.build().unwrap()); - (server_config, callback_handle, rx) + pair.handshake().unwrap(); + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); } +// ---------- Tests ---------- -/// Test basic mTLS with Rustls client and S2N server. #[test] fn rustls_s2n_mtls_basic() { - let client_config = create_rustls_mtls_client_config(MtlsClientConfig::default()); - - let (server_config, _, _) = create_s2n_mtls_server_config(MtlsServerConfig::default()); - - test_mtls_basic::(&client_config, &server_config); + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); + test_mtls_basic::(&client, &server); } -/// Test mTLS with synchronous cert validation callback. #[test] fn rustls_s2n_mtls_sync_callback() { - let client_config = create_rustls_mtls_client_config(MtlsClientConfig::default()); - - // Create S2N server with a synchronous validation callback - let (server_config, callback_handle, _) = create_s2n_mtls_server_config(MtlsServerConfig { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Sync, ..Default::default() }); - - // Run the sync callback test + test_mtls_sync_callback::( - &client_config, - &server_config, - callback_handle.unwrap(), + &client, + &server, + handle.expect("sync callback handle"), ); } -/// Test mTLS with asynchronous cert validation callback. -/// +// TLS 1.3 async mTLS – currently hangs; ignored until bug is fixed. #[test] -fn rustls_s2n_mtls_async_callback() { - let client_config = create_rustls_mtls_client_config(MtlsClientConfig::default()); - - // Create S2N server with an asynchronous validation callback - let (server_config, callback_handle, rx) = create_s2n_mtls_server_config(MtlsServerConfig { +#[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.3)"] +fn rustls_s2n_mtls_async_callback_tls13() { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Async, ..Default::default() }); - - // Run the async callback test - test_mtls_async_callback::( - &client_config, - &server_config, - callback_handle.unwrap(), - rx.unwrap(), - ); -} -/// Create an S2N client configured for mTLS. -/// - Requires explicit client auth type setting -fn create_s2n_mtls_client_config(config: MtlsClientConfig) -> S2NConfig { - let mut client_config = s2n_tls::config::Builder::new(); - - client_config.set_chain(config.sig_type); - - client_config - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, config.sig_type)) - .unwrap() - .set_verify_host_callback(HostNameIgnorer) - .unwrap(); - - S2NConfig::from(client_config.build().unwrap()) + test_mtls_async_callback_tls13_core::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), + ); } -/// Create an S2N server with hostname verifier (for S2N-to-S2N tests). -fn create_s2n_mtls_server_config_with_hostname_verifier( - config: MtlsServerConfig, -) -> ( - S2NConfig, - Option>, - Option>, -) { - let mut server_config = s2n_tls::config::Builder::new(); - - server_config.set_chain(config.sig_type); - - server_config - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, config.sig_type)) - .unwrap() - .set_verify_host_callback(HostNameIgnorer) - .unwrap(); +// TLS 1.2 async mTLS – same multi-message bug; ignored until bug is fixed. +#[test] +#[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.2)"] +fn rustls_s2n_mtls_async_callback_tls12() { + let client = rustls_mtls_client(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); - let (callback_handle, rx) = match config.callback_mode { - MtlsServerCallback::None => (None, None), - MtlsServerCallback::Sync => { - let callback = TestCertValidationCallback::new_sync(); - let handle = Arc::clone(&callback.invoked); - server_config - .set_cert_validation_callback(callback) - .unwrap(); - (Some(handle), None) - } - MtlsServerCallback::Async => { - let (callback, rx) = TestCertValidationCallback::new_async(); - let handle = Arc::clone(&callback.invoked); - server_config - .set_cert_validation_callback(callback) - .unwrap(); - (Some(handle), Some(rx)) - } - }; + let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { + callback_mode: MtlsServerCallback::Async, + ..Default::default() + }); - let server_config = S2NConfig::from(server_config.build().unwrap()); - (server_config, callback_handle, rx) + test_mtls_async_callback_tls12_core::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), + ); } -/// Test mTLS with asynchronous cert validation using S2N for both client and server. #[test] fn s2n_s2n_mtls_async_callback() { - let client_config = create_s2n_mtls_client_config(MtlsClientConfig::default()); - - let (server_config, callback_handle, rx) = - create_s2n_mtls_server_config_with_hostname_verifier(MtlsServerConfig { - callback_mode: MtlsServerCallback::Async, - ..Default::default() - }); - - test_mtls_async_callback::( - &client_config, - &server_config, - callback_handle.unwrap(), - rx.unwrap(), + let client = s2n_mtls_client(MtlsClientConfig::default()); + let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { + callback_mode: MtlsServerCallback::Async, + with_hostname_verifier: true, + ..Default::default() + }); + + // This follows the TLS 1.3 flow by default (client cfg defaults to TLS 1.3). + test_mtls_async_callback_tls13_core::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), ); } \ No newline at end of file From ea2ead8224c6fa0eb27c9d9b28f25ccef3f9bf3d Mon Sep 17 00:00:00 2001 From: Kaukab Date: Mon, 24 Nov 2025 22:08:37 +0000 Subject: [PATCH 05/29] Formatting --- bindings/rust/standard/integration/src/mtls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index cb392a19b9e..b28cf677545 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -377,4 +377,4 @@ fn s2n_s2n_mtls_async_callback() { handle.expect("async callback handle"), rx.expect("async callback receiver"), ); -} \ No newline at end of file +} From b3de43a08fdb2dfa88e503e0c558dd343d944bf8 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Mon, 24 Nov 2025 22:22:36 +0000 Subject: [PATCH 06/29] Use TLS1.2 as default for basic and sync callback tests --- bindings/rust/standard/integration/src/mtls.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index b28cf677545..427afa9bf45 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -301,14 +301,22 @@ fn test_mtls_async_callback_tls12_core( #[test] fn rustls_s2n_mtls_basic() { - let client = rustls_mtls_client(MtlsClientConfig::default()); + // Use TLS 1.2 for compatibility with OpenSSL 1.0.2 + let client = rustls_mtls_client(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); test_mtls_basic::(&client, &server); } #[test] fn rustls_s2n_mtls_sync_callback() { - let client = rustls_mtls_client(MtlsClientConfig::default()); + // Use TLS 1.2 for compatibility with OpenSSL 1.0.2 + let client = rustls_mtls_client(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Sync, ..Default::default() From 0ab8b4b5971dabd835fd1c16ec2c72a6b28ec518 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Mon, 24 Nov 2025 22:28:24 +0000 Subject: [PATCH 07/29] Add capability gating for TLS 1.2 tests --- .../rust/standard/integration/src/mtls.rs | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 427afa9bf45..2770136e2c6 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -299,9 +299,10 @@ fn test_mtls_async_callback_tls12_core( // ---------- Tests ---------- +// TLS 1.2 tests (run on all libcrypto versions including OpenSSL 1.0.2) + #[test] -fn rustls_s2n_mtls_basic() { - // Use TLS 1.2 for compatibility with OpenSSL 1.0.2 +fn rustls_s2n_mtls_basic_tls12() { let client = rustls_mtls_client(MtlsClientConfig { tls_version: &rustls::version::TLS12, ..MtlsClientConfig::default() @@ -311,8 +312,7 @@ fn rustls_s2n_mtls_basic() { } #[test] -fn rustls_s2n_mtls_sync_callback() { - // Use TLS 1.2 for compatibility with OpenSSL 1.0.2 +fn rustls_s2n_mtls_sync_callback_tls12() { let client = rustls_mtls_client(MtlsClientConfig { tls_version: &rustls::version::TLS12, ..MtlsClientConfig::default() @@ -329,22 +329,54 @@ fn rustls_s2n_mtls_sync_callback() { ); } +// TLS 1.3 tests (require TLS 1.3 support, not available in OpenSSL 1.0.2) + +#[test] +fn rustls_s2n_mtls_basic_tls13() { + crate::capability_check::required_capability(&[crate::capability_check::Capability::Tls13], || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); + test_mtls_basic::(&client, &server); + }); +} + +#[test] +fn rustls_s2n_mtls_sync_callback_tls13() { + crate::capability_check::required_capability(&[crate::capability_check::Capability::Tls13], || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { + callback_mode: MtlsServerCallback::Sync, + ..Default::default() + }); + + test_mtls_sync_callback::( + &client, + &server, + handle.expect("sync callback handle"), + ); + }); +} + +// Async callback tests - currently hang due to error blinding bug, kept as ignored + // TLS 1.3 async mTLS – currently hangs; ignored until bug is fixed. #[test] #[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.3)"] fn rustls_s2n_mtls_async_callback_tls13() { - let client = rustls_mtls_client(MtlsClientConfig::default()); - let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Async, - ..Default::default() + crate::capability_check::required_capability(&[crate::capability_check::Capability::Tls13], || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { + callback_mode: MtlsServerCallback::Async, + ..Default::default() + }); + + test_mtls_async_callback_tls13_core::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), + ); }); - - test_mtls_async_callback_tls13_core::( - &client, - &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), - ); } // TLS 1.2 async mTLS – same multi-message bug; ignored until bug is fixed. From 5453524a38502491a990bf54f38fb149490495f0 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Mon, 24 Nov 2025 23:49:01 +0000 Subject: [PATCH 08/29] Add clarifying comments --- .../s2n-tls/src/callbacks/cert_validation.rs | 17 +++++++++++-- bindings/rust/extended/s2n-tls/src/config.rs | 2 ++ .../rust/standard/integration/src/mtls.rs | 25 ++++++++++++++++--- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index f756e9da9be..5682cb60c3f 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -10,12 +10,22 @@ use crate::{ use std::{marker::PhantomData, ptr::NonNull}; pub struct CertValidationInfo<'a> { - pub info: NonNull, + info: NonNull, _lifetime: PhantomData<&'a s2n_cert_validation_info>, } impl CertValidationInfo<'_> { - pub fn from_ptr(info: *mut s2n_cert_validation_info) -> Self { + /// Creates a `CertValidationInfo` from a raw pointer. + /// + /// # Safety + /// + /// The caller must ensure that: + /// - `info` is a non-null pointer to a valid `s2n_cert_validation_info` structure + /// - The pointed-to structure is owned by s2n-tls and remains valid for the lifetime + /// of this `CertValidationInfo` (typically until the handshake completes or the + /// connection is freed) + /// - The pointer is not used to create multiple mutable references + pub unsafe fn from_ptr(info: *mut s2n_cert_validation_info) -> Self { let info = NonNull::new(info).expect("info pointer should not be null"); CertValidationInfo { info, @@ -23,6 +33,9 @@ impl CertValidationInfo<'_> { } } + /// Returns the raw pointer to the underlying `s2n_cert_validation_info`. + /// + /// This is primarily useful for passing to FFI functions or storing for later use. pub fn as_ptr(&mut self) -> *mut s2n_cert_validation_info { self.info.as_ptr() } diff --git a/bindings/rust/extended/s2n-tls/src/config.rs b/bindings/rust/extended/s2n-tls/src/config.rs index fec6bcea58d..0810da0568e 100644 --- a/bindings/rust/extended/s2n-tls/src/config.rs +++ b/bindings/rust/extended/s2n-tls/src/config.rs @@ -645,6 +645,8 @@ impl Builder { validation_info: *mut s2n_cert_validation_info, _context: *mut core::ffi::c_void, ) -> libc::c_int { + // SAFETY: validation_info is provided by s2n-tls and guaranteed to be valid + // for the duration of this callback let mut info = CertValidationInfo::from_ptr(validation_info); with_context(conn_ptr, |conn, context| { let callback = context.cert_validation_callback.as_ref(); diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 2770136e2c6..822a409f0a8 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -21,7 +21,16 @@ use tls_harness::{ const APP_DATA_SIZE: usize = 100_000; +/// A wrapper around a raw pointer to `s2n_cert_validation_info` that can be sent across threads. +/// +/// This is used in tests to simulate async certificate validation where the validation +/// decision is deferred and made on a different thread or after some async operation. struct SendableCertValidationInfo(*mut s2n_tls_sys::s2n_cert_validation_info); + +// SAFETY: The pointer is owned by s2n-tls and remains valid for the duration of the +// pending async validation (until accept() or reject() is called, or the connection is freed). +// The test mimics the intended usage pattern where an application hands off the pointer +// to a worker thread that later calls accept()/reject(). unsafe impl Send for SendableCertValidationInfo {} #[derive(Debug)] @@ -70,7 +79,7 @@ impl CertValidationCallback for TestCertValidationCallback { if let Some(sender) = &self.callback_sender { sender - .send(SendableCertValidationInfo(info.info.as_ptr())) + .send(SendableCertValidationInfo(info.as_ptr())) .expect("sending CertValidationInfo ptr"); } @@ -262,7 +271,9 @@ fn test_mtls_async_callback_tls13_core( let mut pair = drive_until_async_pending::(client_cfg, server_cfg, &handle); let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - let mut info = CertValidationInfo::from_ptr(ptr); + // SAFETY: The pointer comes from the cert validation callback which guarantees + // it points to a valid s2n_cert_validation_info owned by s2n-tls + let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; info.accept().unwrap(); // BUG (TLS 1.3): currently hangs here due to multi-message + error blinding. @@ -289,7 +300,9 @@ fn test_mtls_async_callback_tls12_core( // Intended flow once bug is fixed: let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - let mut info = CertValidationInfo::from_ptr(ptr); + // SAFETY: The pointer comes from the cert validation callback which guarantees + // it points to a valid s2n_cert_validation_info owned by s2n-tls + let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; info.accept().unwrap(); pair.handshake().unwrap(); @@ -360,6 +373,9 @@ fn rustls_s2n_mtls_sync_callback_tls13() { // Async callback tests - currently hang due to error blinding bug, kept as ignored // TLS 1.3 async mTLS – currently hangs; ignored until bug is fixed. +// As of 2024-11-24: This test hangs because error blinding wipes buffered messages +// when async cert validation returns None. Once the C library is fixed to preserve +// messages during async validation, remove the #[ignore] attribute. #[test] #[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.3)"] fn rustls_s2n_mtls_async_callback_tls13() { @@ -380,6 +396,9 @@ fn rustls_s2n_mtls_async_callback_tls13() { } // TLS 1.2 async mTLS – same multi-message bug; ignored until bug is fixed. +// As of 2024-11-24: This test hangs because error blinding wipes buffered messages +// when async cert validation returns None. Once the C library is fixed to preserve +// messages during async validation, remove the #[ignore] attribute. #[test] #[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.2)"] fn rustls_s2n_mtls_async_callback_tls12() { From 8af8a9fa83c843d2aa519bff9a8bd8a93618a9e0 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 25 Nov 2025 00:30:08 +0000 Subject: [PATCH 09/29] More comments --- .../extended/s2n-tls/src/callbacks/cert_validation.rs | 4 ---- bindings/rust/standard/integration/src/mtls.rs | 8 ++------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index 5682cb60c3f..452fa7b21f1 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -65,10 +65,6 @@ impl CertValidationInfo<'_> { /// S2N_BLOCKED_ON_APPLICATION_INPUT) until validation is completed by calling /// `accept()` or `reject()` on the validation info. /// -/// This is useful when validation requires: -/// - Consulting an external service (database, OCSP responder, etc.) -/// - User interaction -/// - Complex async operations pub trait CertValidationCallback: 'static + Send + Sync { /// Validate the certificate chain. /// diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 822a409f0a8..bcf9b4cb0b9 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -125,8 +125,6 @@ struct MtlsServerConfig { with_hostname_verifier: bool, } -// ---------- Small helpers ---------- - fn rustls_mtls_client(cfg: MtlsClientConfig) -> RustlsConfig { let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); let client = ClientConfig::builder_with_provider(provider) @@ -194,8 +192,6 @@ fn s2n_mtls_client(cfg: MtlsClientConfig) -> S2NConfig { S2NConfig::from(builder.build().unwrap()) } -// ---------- Core test logic ---------- - fn test_mtls_basic(client_cfg: &C::Config, server_cfg: &S::Config) where C: TlsConnection, @@ -224,7 +220,8 @@ fn test_mtls_sync_callback( } /// Drive handshake to the point where async cert validation is pending and -/// the callback has been invoked exactly once. +/// the callback has been invoked exactly once. This behavior is consistent +/// across TLS 1.2 and TLS 1.3 tests. fn drive_until_async_pending( client_cfg: &C::Config, server_cfg: &S::Config, @@ -258,7 +255,6 @@ where pair } -// TLS 1.3: bug shows up after accept, when we try to finish the handshake. fn test_mtls_async_callback_tls13_core( client_cfg: &C::Config, server_cfg: &S::Config, From b3af9dc4112911db92e778d617e063e17e0c69c5 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 25 Nov 2025 00:50:02 +0000 Subject: [PATCH 10/29] Apply cargo format --- .../rust/standard/integration/src/mtls.rs | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index bcf9b4cb0b9..ccfd33b5df2 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -41,7 +41,11 @@ struct TestCertValidationCallback { } impl TestCertValidationCallback { - fn new_sync() -> (Self, Arc, Option>) { + fn new_sync() -> ( + Self, + Arc, + Option>, + ) { let invoked = Arc::new(AtomicU64::new(0)); ( Self { @@ -83,7 +87,11 @@ impl CertValidationCallback for TestCertValidationCallback { .expect("sending CertValidationInfo ptr"); } - Ok(if self.immediately_accept { Some(true) } else { None }) + Ok(if self.immediately_accept { + Some(true) + } else { + None + }) } } @@ -220,7 +228,7 @@ fn test_mtls_sync_callback( } /// Drive handshake to the point where async cert validation is pending and -/// the callback has been invoked exactly once. This behavior is consistent +/// the callback has been invoked exactly once. This behavior is consistent /// across TLS 1.2 and TLS 1.3 tests. fn drive_until_async_pending( client_cfg: &C::Config, @@ -342,28 +350,34 @@ fn rustls_s2n_mtls_sync_callback_tls12() { #[test] fn rustls_s2n_mtls_basic_tls13() { - crate::capability_check::required_capability(&[crate::capability_check::Capability::Tls13], || { - let client = rustls_mtls_client(MtlsClientConfig::default()); - let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); - test_mtls_basic::(&client, &server); - }); + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); + test_mtls_basic::(&client, &server); + }, + ); } #[test] fn rustls_s2n_mtls_sync_callback_tls13() { - crate::capability_check::required_capability(&[crate::capability_check::Capability::Tls13], || { - let client = rustls_mtls_client(MtlsClientConfig::default()); - let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Sync, - ..Default::default() - }); - - test_mtls_sync_callback::( - &client, - &server, - handle.expect("sync callback handle"), - ); - }); + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { + callback_mode: MtlsServerCallback::Sync, + ..Default::default() + }); + + test_mtls_sync_callback::( + &client, + &server, + handle.expect("sync callback handle"), + ); + }, + ); } // Async callback tests - currently hang due to error blinding bug, kept as ignored @@ -375,20 +389,23 @@ fn rustls_s2n_mtls_sync_callback_tls13() { #[test] #[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.3)"] fn rustls_s2n_mtls_async_callback_tls13() { - crate::capability_check::required_capability(&[crate::capability_check::Capability::Tls13], || { - let client = rustls_mtls_client(MtlsClientConfig::default()); - let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Async, - ..Default::default() - }); - - test_mtls_async_callback_tls13_core::( - &client, - &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), - ); - }); + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { + callback_mode: MtlsServerCallback::Async, + ..Default::default() + }); + + test_mtls_async_callback_tls13_core::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), + ); + }, + ); } // TLS 1.2 async mTLS – same multi-message bug; ignored until bug is fixed. From 7ee997700c86aeec64046ed275f2e9180a41940e Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 25 Nov 2025 18:51:32 +0000 Subject: [PATCH 11/29] Simplify helper method logic --- .../rust/standard/integration/src/mtls.rs | 157 +++++++++--------- 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index ccfd33b5df2..8956fa56477 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -41,36 +41,29 @@ struct TestCertValidationCallback { } impl TestCertValidationCallback { - fn new_sync() -> ( - Self, - Arc, - Option>, - ) { - let invoked = Arc::new(AtomicU64::new(0)); - ( - Self { - invoked: Arc::clone(&invoked), - immediately_accept: true, - callback_sender: None, - }, - invoked, - None, - ) + fn new_sync() -> Self { + Self { + invoked: Arc::new(AtomicU64::new(0)), + immediately_accept: true, + callback_sender: None, + } } - fn new_async() -> (Self, Arc, Receiver) { - let invoked = Arc::new(AtomicU64::new(0)); + fn new_async() -> (Self, Receiver) { let (tx, rx) = std::sync::mpsc::channel(); ( Self { - invoked: Arc::clone(&invoked), + invoked: Arc::new(AtomicU64::new(0)), immediately_accept: false, callback_sender: Some(tx), }, - invoked, rx, ) } + + fn invoked_count(&self) -> &Arc { + &self.invoked + } } impl CertValidationCallback for TestCertValidationCallback { @@ -171,12 +164,14 @@ fn s2n_mtls_server( let (handle, rx) = match cfg.callback_mode { MtlsServerCallback::None => (None, None), MtlsServerCallback::Sync => { - let (cb, invoked, _) = TestCertValidationCallback::new_sync(); + let cb = TestCertValidationCallback::new_sync(); + let invoked = Arc::clone(cb.invoked_count()); builder.set_cert_validation_callback(cb).unwrap(); (Some(invoked), None) } MtlsServerCallback::Async => { - let (cb, invoked, rx) = TestCertValidationCallback::new_async(); + let (cb, rx) = TestCertValidationCallback::new_async(); + let invoked = Arc::clone(cb.invoked_count()); builder.set_cert_validation_callback(cb).unwrap(); (Some(invoked), Some(rx)) } @@ -229,7 +224,7 @@ fn test_mtls_sync_callback( /// Drive handshake to the point where async cert validation is pending and /// the callback has been invoked exactly once. This behavior is consistent -/// across TLS 1.2 and TLS 1.3 tests. +/// across TLS 1.2 and TLS 1.3 tests so we can reuse the logic. fn drive_until_async_pending( client_cfg: &C::Config, server_cfg: &S::Config, @@ -263,57 +258,6 @@ where pair } -fn test_mtls_async_callback_tls13_core( - client_cfg: &C::Config, - server_cfg: &S::Config, - handle: Arc, - rx: Receiver, -) where - C: TlsConnection, - S: TlsConnection, -{ - let mut pair = drive_until_async_pending::(client_cfg, server_cfg, &handle); - - let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: The pointer comes from the cert validation callback which guarantees - // it points to a valid s2n_cert_validation_info owned by s2n-tls - let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; - info.accept().unwrap(); - - // BUG (TLS 1.3): currently hangs here due to multi-message + error blinding. - pair.handshake().unwrap(); - - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); -} - -// TLS 1.2: bug shows up when we drive more handshake *while* validation is pending. -fn test_mtls_async_callback_tls12_core( - client_cfg: &C::Config, - server_cfg: &S::Config, - handle: Arc, - rx: Receiver, -) where - C: TlsConnection, - S: TlsConnection, -{ - let mut pair = drive_until_async_pending::(client_cfg, server_cfg, &handle); - - // BUG (TLS 1.2): currently hangs here due to multi-message + error blinding. - pair.handshake().unwrap(); - - // Intended flow once bug is fixed: - let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: The pointer comes from the cert validation callback which guarantees - // it points to a valid s2n_cert_validation_info owned by s2n-tls - let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; - info.accept().unwrap(); - - pair.handshake().unwrap(); - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); -} - // ---------- Tests ---------- // TLS 1.2 tests (run on all libcrypto versions including OpenSSL 1.0.2) @@ -398,12 +342,28 @@ fn rustls_s2n_mtls_async_callback_tls13() { ..Default::default() }); - test_mtls_async_callback_tls13_core::( + let handle = handle.expect("async callback handle"); + let rx = rx.expect("async callback receiver"); + + // Drive handshake until async cert validation is pending + let mut pair = drive_until_async_pending::( &client, &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), + &handle, ); + + // Accept the certificate + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + // SAFETY: The pointer comes from the cert validation callback which guarantees + // it points to a valid s2n_cert_validation_info owned by s2n-tls + let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; + info.accept().unwrap(); + + // BUG (TLS 1.3): currently hangs here due to multi-message + error blinding. + pair.handshake().unwrap(); + + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); }, ); } @@ -425,12 +385,29 @@ fn rustls_s2n_mtls_async_callback_tls12() { ..Default::default() }); - test_mtls_async_callback_tls12_core::( + let handle = handle.expect("async callback handle"); + let rx = rx.expect("async callback receiver"); + + // Drive handshake until async cert validation is pending + let mut pair = drive_until_async_pending::( &client, &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), + &handle, ); + + // BUG (TLS 1.2): currently hangs here due to multi-message + error blinding. + pair.handshake().unwrap(); + + // Intended flow once bug is fixed: + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + // SAFETY: The pointer comes from the cert validation callback which guarantees + // it points to a valid s2n_cert_validation_info owned by s2n-tls + let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; + info.accept().unwrap(); + + pair.handshake().unwrap(); + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); } #[test] @@ -442,11 +419,27 @@ fn s2n_s2n_mtls_async_callback() { ..Default::default() }); + let handle = handle.expect("async callback handle"); + let rx = rx.expect("async callback receiver"); + + // Drive handshake until async cert validation is pending // This follows the TLS 1.3 flow by default (client cfg defaults to TLS 1.3). - test_mtls_async_callback_tls13_core::( + let mut pair = drive_until_async_pending::( &client, &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), + &handle, ); + + // Accept the certificate + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + // SAFETY: The pointer comes from the cert validation callback which guarantees + // it points to a valid s2n_cert_validation_info owned by s2n-tls + let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; + info.accept().unwrap(); + + // Complete the handshake + pair.handshake().unwrap(); + + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); } From d9a8bb89e718cefc6fe31e443a94d56f5d5fed1c Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 25 Nov 2025 20:19:50 +0000 Subject: [PATCH 12/29] Comment cleanup --- .../rust/standard/integration/src/mtls.rs | 55 ++++--------------- 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 8956fa56477..601e180ad2b 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -75,6 +75,8 @@ impl CertValidationCallback for TestCertValidationCallback { self.invoked.fetch_add(1, Ordering::SeqCst); if let Some(sender) = &self.callback_sender { + // We send a raw pointer to the underlying s2n_cert_validation_info so the test + // can later reconstruct CertValidationInfo and call accept()/reject(). sender .send(SendableCertValidationInfo(info.as_ptr())) .expect("sending CertValidationInfo ptr"); @@ -222,9 +224,8 @@ fn test_mtls_sync_callback( pair.shutdown().unwrap(); } -/// Drive handshake to the point where async cert validation is pending and -/// the callback has been invoked exactly once. This behavior is consistent -/// across TLS 1.2 and TLS 1.3 tests so we can reuse the logic. +/// Drives handshake until async cert validation callback fires and returns None (pending). +/// Verifies the callback is invoked exactly once and subsequent calls don't re-invoke it. fn drive_until_async_pending( client_cfg: &C::Config, server_cfg: &S::Config, @@ -237,31 +238,22 @@ where let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); pair.io.enable_recording(); - // ClientHello pair.client.handshake().unwrap(); - // ServerHello + server cert flight (1.2 or 1.3 depending on config) pair.server.handshake().unwrap(); - // client cert + finished flight pair.client.handshake().unwrap(); - // callback has not fired yet + // At this point, the server has not processed the client cert yet. assert_eq!(handle.load(Ordering::SeqCst), 0); - // server processes client cert → async callback fires and returns None pair.server.handshake().unwrap(); assert_eq!(handle.load(Ordering::SeqCst), 1); - // second call should not re-invoke callback pair.server.handshake().unwrap(); assert_eq!(handle.load(Ordering::SeqCst), 1); pair } -// ---------- Tests ---------- - -// TLS 1.2 tests (run on all libcrypto versions including OpenSSL 1.0.2) - #[test] fn rustls_s2n_mtls_basic_tls12() { let client = rustls_mtls_client(MtlsClientConfig { @@ -290,8 +282,6 @@ fn rustls_s2n_mtls_sync_callback_tls12() { ); } -// TLS 1.3 tests (require TLS 1.3 support, not available in OpenSSL 1.0.2) - #[test] fn rustls_s2n_mtls_basic_tls13() { crate::capability_check::required_capability( @@ -324,12 +314,9 @@ fn rustls_s2n_mtls_sync_callback_tls13() { ); } -// Async callback tests - currently hang due to error blinding bug, kept as ignored - -// TLS 1.3 async mTLS – currently hangs; ignored until bug is fixed. -// As of 2024-11-24: This test hangs because error blinding wipes buffered messages -// when async cert validation returns None. Once the C library is fixed to preserve -// messages during async validation, remove the #[ignore] attribute. +// As of 2024-11-24: Hangs due to the multi-message async cert validation bug. +// s2n incorrectly clears queued handshake messages, causing +// poll_negotiate() to spin forever. Remove #[ignore] once fixed in C. #[test] #[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.3)"] fn rustls_s2n_mtls_async_callback_tls13() { @@ -345,21 +332,18 @@ fn rustls_s2n_mtls_async_callback_tls13() { let handle = handle.expect("async callback handle"); let rx = rx.expect("async callback receiver"); - // Drive handshake until async cert validation is pending let mut pair = drive_until_async_pending::( &client, &server, &handle, ); - // Accept the certificate let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: The pointer comes from the cert validation callback which guarantees - // it points to a valid s2n_cert_validation_info owned by s2n-tls + // SAFETY: Pointer from cert validation callback, valid until accept/reject called let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; info.accept().unwrap(); - // BUG (TLS 1.3): currently hangs here due to multi-message + error blinding. + // BUG: Hangs here - s2n has wiped CertificateVerify and Finished messages pair.handshake().unwrap(); pair.round_trip_assert(10).unwrap(); @@ -368,12 +352,7 @@ fn rustls_s2n_mtls_async_callback_tls13() { ); } -// TLS 1.2 async mTLS – same multi-message bug; ignored until bug is fixed. -// As of 2024-11-24: This test hangs because error blinding wipes buffered messages -// when async cert validation returns None. Once the C library is fixed to preserve -// messages during async validation, remove the #[ignore] attribute. #[test] -#[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.2)"] fn rustls_s2n_mtls_async_callback_tls12() { let client = rustls_mtls_client(MtlsClientConfig { tls_version: &rustls::version::TLS12, @@ -388,20 +367,15 @@ fn rustls_s2n_mtls_async_callback_tls12() { let handle = handle.expect("async callback handle"); let rx = rx.expect("async callback receiver"); - // Drive handshake until async cert validation is pending let mut pair = drive_until_async_pending::( &client, &server, &handle, ); - // BUG (TLS 1.2): currently hangs here due to multi-message + error blinding. - pair.handshake().unwrap(); - // Intended flow once bug is fixed: let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: The pointer comes from the cert validation callback which guarantees - // it points to a valid s2n_cert_validation_info owned by s2n-tls + // SAFETY: Pointer from cert validation callback, valid until accept/reject called let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; info.accept().unwrap(); @@ -422,22 +396,17 @@ fn s2n_s2n_mtls_async_callback() { let handle = handle.expect("async callback handle"); let rx = rx.expect("async callback receiver"); - // Drive handshake until async cert validation is pending - // This follows the TLS 1.3 flow by default (client cfg defaults to TLS 1.3). let mut pair = drive_until_async_pending::( &client, &server, &handle, ); - // Accept the certificate let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: The pointer comes from the cert validation callback which guarantees - // it points to a valid s2n_cert_validation_info owned by s2n-tls + // SAFETY: Pointer from cert validation callback, valid until accept/reject called let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; info.accept().unwrap(); - // Complete the handshake pair.handshake().unwrap(); pair.round_trip_assert(10).unwrap(); From 4b4b086744116604c5c4e50f71cd5955e81eebfc Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 25 Nov 2025 20:21:13 +0000 Subject: [PATCH 13/29] apply cargo fmt --- bindings/rust/standard/integration/src/mtls.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 601e180ad2b..72cc5df7a10 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -333,9 +333,7 @@ fn rustls_s2n_mtls_async_callback_tls13() { let rx = rx.expect("async callback receiver"); let mut pair = drive_until_async_pending::( - &client, - &server, - &handle, + &client, &server, &handle, ); let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; @@ -367,11 +365,8 @@ fn rustls_s2n_mtls_async_callback_tls12() { let handle = handle.expect("async callback handle"); let rx = rx.expect("async callback receiver"); - let mut pair = drive_until_async_pending::( - &client, - &server, - &handle, - ); + let mut pair = + drive_until_async_pending::(&client, &server, &handle); // Intended flow once bug is fixed: let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; @@ -396,11 +391,8 @@ fn s2n_s2n_mtls_async_callback() { let handle = handle.expect("async callback handle"); let rx = rx.expect("async callback receiver"); - let mut pair = drive_until_async_pending::( - &client, - &server, - &handle, - ); + let mut pair = + drive_until_async_pending::(&client, &server, &handle); let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; // SAFETY: Pointer from cert validation callback, valid until accept/reject called From a0ce7c004741890bc94e19ab8b98eb69f0372c3d Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 25 Nov 2025 21:13:38 +0000 Subject: [PATCH 14/29] Reuse repeated async cert verify logic --- .../rust/standard/integration/src/mtls.rs | 86 +++++++------------ 1 file changed, 29 insertions(+), 57 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 72cc5df7a10..39e061b3d04 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -224,14 +224,12 @@ fn test_mtls_sync_callback( pair.shutdown().unwrap(); } -/// Drives handshake until async cert validation callback fires and returns None (pending). -/// Verifies the callback is invoked exactly once and subsequent calls don't re-invoke it. -fn drive_until_async_pending( +fn test_mtls_async_callback( client_cfg: &C::Config, server_cfg: &S::Config, - handle: &Arc, -) -> TlsConnPair -where + handle: Arc, + rx: Receiver, +) where C: TlsConnection, S: TlsConnection, { @@ -242,16 +240,18 @@ where pair.server.handshake().unwrap(); pair.client.handshake().unwrap(); - // At this point, the server has not processed the client cert yet. assert_eq!(handle.load(Ordering::SeqCst), 0); - pair.server.handshake().unwrap(); assert_eq!(handle.load(Ordering::SeqCst), 1); - pair.server.handshake().unwrap(); - assert_eq!(handle.load(Ordering::SeqCst), 1); + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + // SAFETY: Pointer from cert validation callback, valid until accept/reject called + let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; + info.accept().unwrap(); - pair + pair.handshake().unwrap(); + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); } #[test] @@ -329,23 +329,13 @@ fn rustls_s2n_mtls_async_callback_tls13() { ..Default::default() }); - let handle = handle.expect("async callback handle"); - let rx = rx.expect("async callback receiver"); - - let mut pair = drive_until_async_pending::( - &client, &server, &handle, + // BUG: Hangs in test_mtls_async_callback - s2n wiped CertificateVerify and Finished + test_mtls_async_callback::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), ); - - let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: Pointer from cert validation callback, valid until accept/reject called - let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; - info.accept().unwrap(); - - // BUG: Hangs here - s2n has wiped CertificateVerify and Finished messages - pair.handshake().unwrap(); - - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); }, ); } @@ -362,21 +352,12 @@ fn rustls_s2n_mtls_async_callback_tls12() { ..Default::default() }); - let handle = handle.expect("async callback handle"); - let rx = rx.expect("async callback receiver"); - - let mut pair = - drive_until_async_pending::(&client, &server, &handle); - - // Intended flow once bug is fixed: - let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: Pointer from cert validation callback, valid until accept/reject called - let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; - info.accept().unwrap(); - - pair.handshake().unwrap(); - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); + test_mtls_async_callback::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), + ); } #[test] @@ -388,19 +369,10 @@ fn s2n_s2n_mtls_async_callback() { ..Default::default() }); - let handle = handle.expect("async callback handle"); - let rx = rx.expect("async callback receiver"); - - let mut pair = - drive_until_async_pending::(&client, &server, &handle); - - let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: Pointer from cert validation callback, valid until accept/reject called - let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; - info.accept().unwrap(); - - pair.handshake().unwrap(); - - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); + test_mtls_async_callback::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), + ); } From af9d473cb903bdd8dce03432aa9cdf15d5e36fe5 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 25 Nov 2025 21:22:53 +0000 Subject: [PATCH 15/29] Apply cargo fmt --- bindings/rust/standard/integration/src/mtls.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 39e061b3d04..0775f1687a3 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -229,7 +229,8 @@ fn test_mtls_async_callback( server_cfg: &S::Config, handle: Arc, rx: Receiver, -) where +) -> TlsConnPair +where C: TlsConnection, S: TlsConnection, { @@ -252,6 +253,7 @@ fn test_mtls_async_callback( pair.handshake().unwrap(); pair.round_trip_assert(10).unwrap(); pair.shutdown().unwrap(); + pair } #[test] @@ -330,7 +332,7 @@ fn rustls_s2n_mtls_async_callback_tls13() { }); // BUG: Hangs in test_mtls_async_callback - s2n wiped CertificateVerify and Finished - test_mtls_async_callback::( + let _pair = test_mtls_async_callback::( &client, &server, handle.expect("async callback handle"), @@ -352,12 +354,14 @@ fn rustls_s2n_mtls_async_callback_tls12() { ..Default::default() }); - test_mtls_async_callback::( + let pair = test_mtls_async_callback::( &client, &server, handle.expect("async callback handle"), rx.expect("async callback receiver"), ); + + assert!(!pair.negotiated_tls13(), "Expected TLS 1.2, got TLS 1.3"); } #[test] @@ -369,7 +373,7 @@ fn s2n_s2n_mtls_async_callback() { ..Default::default() }); - test_mtls_async_callback::( + let _pair = test_mtls_async_callback::( &client, &server, handle.expect("async callback handle"), From fee32b28b04f1dced71446619d5d92ac6939fd4f Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 26 Nov 2025 00:28:10 +0000 Subject: [PATCH 16/29] Add s2n as client test cases --- .../rust/standard/integration/src/mtls.rs | 263 ++++++++++++++++-- 1 file changed, 234 insertions(+), 29 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 0775f1687a3..474b0b6c1f2 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -142,6 +142,26 @@ fn rustls_mtls_client(cfg: MtlsClientConfig) -> RustlsConfig { client.into() } +fn rustls_mtls_server(cfg: MtlsClientConfig) -> RustlsConfig { + let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let client_cert_verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new( + RustlsConfig::get_root_cert_store(cfg.sig_type), + )) + .build() + .unwrap(); + + let server = rustls::ServerConfig::builder_with_provider(provider) + .with_protocol_versions(&[cfg.tls_version]) + .unwrap() + .with_client_cert_verifier(client_cert_verifier) + .with_single_cert( + RustlsConfig::get_cert_chain(PemType::ServerCertChain, cfg.sig_type), + RustlsConfig::get_key(PemType::ServerKey, cfg.sig_type), + ) + .unwrap(); + server.into() +} + fn s2n_mtls_server( cfg: MtlsServerConfig, ) -> ( @@ -197,6 +217,52 @@ fn s2n_mtls_client(cfg: MtlsClientConfig) -> S2NConfig { S2NConfig::from(builder.build().unwrap()) } +fn s2n_mtls_client_with_sync_callback(cfg: MtlsClientConfig) -> (S2NConfig, Arc) { + let mut builder = s2n_tls::config::Builder::new(); + builder.set_chain(cfg.sig_type); + builder + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + + let cb = TestCertValidationCallback::new_sync(); + let invoked = Arc::clone(cb.invoked_count()); + builder.set_cert_validation_callback(cb).unwrap(); + + (S2NConfig::from(builder.build().unwrap()), invoked) +} + +fn s2n_mtls_client_with_async_callback( + cfg: MtlsClientConfig, +) -> ( + S2NConfig, + Arc, + Receiver, +) { + let mut builder = s2n_tls::config::Builder::new(); + builder.set_chain(cfg.sig_type); + builder + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + + let (cb, rx) = TestCertValidationCallback::new_async(); + let invoked = Arc::clone(cb.invoked_count()); + builder.set_cert_validation_callback(cb).unwrap(); + + (S2NConfig::from(builder.build().unwrap()), invoked, rx) +} + fn test_mtls_basic(client_cfg: &C::Config, server_cfg: &S::Config) where C: TlsConnection, @@ -256,6 +322,68 @@ where pair } +fn test_mtls_async_callback_client( + client_cfg: &C::Config, + server_cfg: &S::Config, + handle: Arc, + rx: Receiver, +) -> TlsConnPair +where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); + pair.io.enable_recording(); + + pair.client.handshake().unwrap(); + pair.server.handshake().unwrap(); + + assert_eq!(handle.load(Ordering::SeqCst), 0); + pair.client.handshake().unwrap(); + assert_eq!(handle.load(Ordering::SeqCst), 1); + + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + // SAFETY: Pointer from cert validation callback, valid until accept/reject called + let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; + info.accept().unwrap(); + + pair.handshake().unwrap(); + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); + pair +} + +// ============================================================================ +// Basic mTLS tests +// ============================================================================ + +// s2n client, rustls server +#[test] +fn s2n_rustls_mtls_basic_tls12() { + let client = s2n_mtls_client(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); + let server = rustls_mtls_server(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); + test_mtls_basic::(&client, &server); +} + +#[test] +fn s2n_rustls_mtls_basic_tls13() { + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = s2n_mtls_client(MtlsClientConfig::default()); + let server = rustls_mtls_server(MtlsClientConfig::default()); + test_mtls_basic::(&client, &server); + }, + ); +} + +// rustls client, s2n server #[test] fn rustls_s2n_mtls_basic_tls12() { let client = rustls_mtls_client(MtlsClientConfig { @@ -266,6 +394,51 @@ fn rustls_s2n_mtls_basic_tls12() { test_mtls_basic::(&client, &server); } +#[test] +fn rustls_s2n_mtls_basic_tls13() { + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); + test_mtls_basic::(&client, &server); + }, + ); +} + +// ============================================================================ +// Sync callback tests +// ============================================================================ + +// s2n client with sync callback, rustls server +#[test] +fn s2n_rustls_mtls_sync_callback_tls12() { + let (client, handle) = s2n_mtls_client_with_sync_callback(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); + let server = rustls_mtls_server(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); + + test_mtls_sync_callback::(&client, &server, handle); +} + +#[test] +fn s2n_rustls_mtls_sync_callback_tls13() { + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let (client, handle) = s2n_mtls_client_with_sync_callback(MtlsClientConfig::default()); + let server = rustls_mtls_server(MtlsClientConfig::default()); + + test_mtls_sync_callback::(&client, &server, handle); + }, + ); +} + +// rustls client, s2n server with sync callback #[test] fn rustls_s2n_mtls_sync_callback_tls12() { let client = rustls_mtls_client(MtlsClientConfig { @@ -284,18 +457,6 @@ fn rustls_s2n_mtls_sync_callback_tls12() { ); } -#[test] -fn rustls_s2n_mtls_basic_tls13() { - crate::capability_check::required_capability( - &[crate::capability_check::Capability::Tls13], - || { - let client = rustls_mtls_client(MtlsClientConfig::default()); - let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); - test_mtls_basic::(&client, &server); - }, - ); -} - #[test] fn rustls_s2n_mtls_sync_callback_tls13() { crate::capability_check::required_capability( @@ -316,32 +477,55 @@ fn rustls_s2n_mtls_sync_callback_tls13() { ); } -// As of 2024-11-24: Hangs due to the multi-message async cert validation bug. +// ============================================================================ +// Async callback tests +// ============================================================================ + +// As of 2025-11-24: s2n as client (TLS 1.2, 1.3) and s2n as +// server (TLS 1.3) hang due to a multi-message async cert validation bug. // s2n incorrectly clears queued handshake messages, causing -// poll_negotiate() to spin forever. Remove #[ignore] once fixed in C. +// poll_negotiate() to spin forever. Remove #[ignore] once fixed. +// s2n client with async callback, rustls server #[test] -#[ignore = "Hangs due to multi-message bug in async cert validation (TLS 1.3)"] -fn rustls_s2n_mtls_async_callback_tls13() { +#[ignore = "Hangs due to multi-message bug in async cert validation"] +fn s2n_rustls_mtls_async_callback_tls12() { + let (client, handle, rx) = s2n_mtls_client_with_async_callback(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); + + let server = rustls_mtls_server(MtlsClientConfig { + tls_version: &rustls::version::TLS12, + ..MtlsClientConfig::default() + }); + + let _pair = test_mtls_async_callback_client::( + &client, &server, handle, rx, + ); +} + +#[test] +#[ignore = "Hangs due to multi-message bug in async cert validation"] +fn s2n_rustls_mtls_async_callback_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let client = rustls_mtls_client(MtlsClientConfig::default()); - let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Async, - ..Default::default() - }); + let (client, handle, rx) = + s2n_mtls_client_with_async_callback(MtlsClientConfig::default()); - // BUG: Hangs in test_mtls_async_callback - s2n wiped CertificateVerify and Finished - let _pair = test_mtls_async_callback::( - &client, - &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), + let server = rustls_mtls_server(MtlsClientConfig::default()); + + let _pair = test_mtls_async_callback_client::( + &client, &server, handle, rx, ); }, ); } +// rustls client, s2n server with async callback +// Rustls TLS 1.2 clients do not send multiple handshake messages in a +// single record, so s2n never hits the multi-message async-callback +// bug that appears in TLS 1.3.ß #[test] fn rustls_s2n_mtls_async_callback_tls12() { let client = rustls_mtls_client(MtlsClientConfig { @@ -354,16 +538,37 @@ fn rustls_s2n_mtls_async_callback_tls12() { ..Default::default() }); - let pair = test_mtls_async_callback::( + let _pair = test_mtls_async_callback::( &client, &server, handle.expect("async callback handle"), rx.expect("async callback receiver"), ); +} + +#[test] +#[ignore = "Hangs due to multi-message bug in async cert validation"] +fn rustls_s2n_mtls_async_callback_tls13() { + crate::capability_check::required_capability( + &[crate::capability_check::Capability::Tls13], + || { + let client = rustls_mtls_client(MtlsClientConfig::default()); + let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { + callback_mode: MtlsServerCallback::Async, + ..Default::default() + }); - assert!(!pair.negotiated_tls13(), "Expected TLS 1.2, got TLS 1.3"); + let _pair = test_mtls_async_callback::( + &client, + &server, + handle.expect("async callback handle"), + rx.expect("async callback receiver"), + ); + }, + ); } +// s2n client, s2n server with async callback #[test] fn s2n_s2n_mtls_async_callback() { let client = s2n_mtls_client(MtlsClientConfig::default()); From 0d227170ddb06a9db34899d5a91e3699d4f1ca1c Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 26 Nov 2025 19:04:28 +0000 Subject: [PATCH 17/29] Typo fix --- bindings/rust/standard/integration/src/mtls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 474b0b6c1f2..ea4944d6a36 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -525,7 +525,7 @@ fn s2n_rustls_mtls_async_callback_tls13() { // rustls client, s2n server with async callback // Rustls TLS 1.2 clients do not send multiple handshake messages in a // single record, so s2n never hits the multi-message async-callback -// bug that appears in TLS 1.3.ß +// bug that appears in TLS 1.3 #[test] fn rustls_s2n_mtls_async_callback_tls12() { let client = rustls_mtls_client(MtlsClientConfig { From b955439154246405d0b0846e02859b519a226e2d Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 26 Nov 2025 23:55:57 +0000 Subject: [PATCH 18/29] separate sync/async cert validation APIs and gate async behind unstable feature --- bindings/rust/extended/s2n-tls/Cargo.toml | 2 + .../s2n-tls/src/callbacks/cert_validation.rs | 37 +++++---- bindings/rust/extended/s2n-tls/src/config.rs | 82 +++++++++++++++++-- bindings/rust/standard/integration/Cargo.toml | 5 +- .../rust/standard/integration/src/mtls.rs | 23 ++++-- 5 files changed, 119 insertions(+), 30 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/Cargo.toml b/bindings/rust/extended/s2n-tls/Cargo.toml index 4e066c12e94..833adb1eeda 100644 --- a/bindings/rust/extended/s2n-tls/Cargo.toml +++ b/bindings/rust/extended/s2n-tls/Cargo.toml @@ -20,6 +20,8 @@ quic = ["s2n-tls-sys/quic"] fips = ["s2n-tls-sys/fips"] pq = ["s2n-tls-sys/pq"] unstable-testing = [] +# Internal feature for testing async cert validation - not for public use +unstable-async-cert = ["unstable-crl"] [dependencies] errno = { version = "0.3" } diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index 452fa7b21f1..b40200b4254 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -53,7 +53,23 @@ impl CertValidationInfo<'_> { } } -/// Certificate validation callback that supports both synchronous and asynchronous validation. +/// Certificate validation callback for synchronous validation. +/// +/// The callback should return: +/// - `Ok(true)`: Accept the certificate +/// - `Ok(false)`: Reject the certificate +/// +pub trait CertValidationCallbackSync: 'static + Send + Sync { + /// Return a boolean to indicate if the certificate chain passed the validation + fn handle_validation( + &self, + connection: &mut Connection, + validation_info: &mut CertValidationInfo, + ) -> Result; +} +/// Certificate validation callback that supports asynchronous validation. +/// +/// This trait is only available for integration testing purposes. /// /// The callback can operate in three modes based on the return value: /// - `Ok(Some(true))`: Accept the certificate immediately (synchronous) @@ -64,14 +80,8 @@ impl CertValidationInfo<'_> { /// When returning `None`, the handshake will block (return S2N_ERR_T_BLOCKED with /// S2N_BLOCKED_ON_APPLICATION_INPUT) until validation is completed by calling /// `accept()` or `reject()` on the validation info. -/// -pub trait CertValidationCallback: 'static + Send + Sync { - /// Validate the certificate chain. - /// - /// Return: - /// - `Some(true)` to accept immediately - /// - `Some(false)` to reject immediately - /// - `None` to defer the decision (async mode) +#[cfg(feature = "unstable-async-cert")] +pub trait CertValidationCallbackAsync: 'static + Send + Sync { fn handle_validation( &self, connection: &mut Connection, @@ -88,17 +98,16 @@ mod tests { accept: bool, } - struct TestCallback(Counter); - impl CertValidationCallback for TestCallback { + struct SyncCallback(Counter); + impl CertValidationCallbackSync for SyncCallback { fn handle_validation( &self, conn: &mut Connection, _info: &mut CertValidationInfo, - ) -> Result, Error> { + ) -> Result { self.0.increment(); let context = conn.application_context::().unwrap(); - // Return Some(accept) for synchronous validation - Ok(Some(context.accept)) + Ok(context.accept) } } diff --git a/bindings/rust/extended/s2n-tls/src/config.rs b/bindings/rust/extended/s2n-tls/src/config.rs index 0810da0568e..a54fb808f9b 100644 --- a/bindings/rust/extended/s2n-tls/src/config.rs +++ b/bindings/rust/extended/s2n-tls/src/config.rs @@ -623,7 +623,66 @@ impl Builder { Ok(self) } - /// Set a callback function to perform custom certificate validation. + /// Set a callback function to perform custom cert validation synchronously. + /// + /// The callback should return: + /// - `true` to accept the certificate + /// - `false` to reject the certificate + /// + /// Corresponds to [s2n_config_set_cert_validation_cb], but the rust callback + /// can only perform in synchronous mode. + #[cfg(feature = "unstable-crl")] + pub fn set_cert_validation_callback_sync( + &mut self, + handler: T, + ) -> Result<&mut Self, Error> { + unsafe extern "C" fn cert_validation_cb( + conn_ptr: *mut s2n_connection, + validation_info: *mut s2n_cert_validation_info, + _context: *mut core::ffi::c_void, + ) -> libc::c_int { + // SAFETY: validation_info is provided by s2n-tls and guaranteed to be valid + // for the duration of this callback + let mut info = CertValidationInfo::from_ptr(validation_info); + with_context(conn_ptr, |conn, context| { + let callback = context.cert_validation_callback_sync.as_ref(); + callback.map(|callback| { + let accepted = callback.handle_validation(conn, &mut info).unwrap(); + match accepted { + true => info.accept().unwrap(), + false => info.reject().unwrap(), + } + }) + }); + CallbackResult::Success.into() + } + + let handler = Box::new(handler); + let context = unsafe { + // SAFETY: usage of context_mut is safe in the builder, because while + // it is being built, the Builder is the only reference to the config. + self.config.context_mut() + }; + context.cert_validation_callback_sync = Some(handler); + + unsafe { + s2n_config_set_cert_validation_cb( + self.as_mut_ptr(), + Some(cert_validation_cb), + core::ptr::null_mut(), + ) + .into_result()?; + } + + Ok(self) + } + + /// Set a callback function to perform custom asynchronous certificate validation. + /// + /// # Warning + /// + /// This is an unstable API intended for testing purposes only. It should not be used + /// in production code. The API may change or be removed in future versions. /// /// The callback can operate in both synchronous and asynchronous modes: /// - Return `Some(true)` to accept the certificate immediately @@ -635,12 +694,13 @@ impl Builder { /// S2N_BLOCKED_ON_APPLICATION_INPUT) until validation is completed. /// /// Corresponds to [s2n_config_set_cert_validation_cb]. - #[cfg(feature = "unstable-crl")] - pub fn set_cert_validation_callback( + #[cfg(feature = "unstable-async-cert")] + #[doc(hidden)] + pub fn set_cert_validation_callback_async( &mut self, handler: T, ) -> Result<&mut Self, Error> { - unsafe extern "C" fn cert_validation_cb( + unsafe extern "C" fn async_cert_validation_cb( conn_ptr: *mut s2n_connection, validation_info: *mut s2n_cert_validation_info, _context: *mut core::ffi::c_void, @@ -649,7 +709,7 @@ impl Builder { // for the duration of this callback let mut info = CertValidationInfo::from_ptr(validation_info); with_context(conn_ptr, |conn, context| { - let callback = context.cert_validation_callback.as_ref(); + let callback = context.cert_validation_callback_async.as_ref(); callback.map(|callback| { match callback.handle_validation(conn, &mut info).unwrap() { Some(true) => { @@ -676,12 +736,12 @@ impl Builder { // it is being built, the Builder is the only reference to the config. self.config.context_mut() }; - context.cert_validation_callback = Some(handler); + context.cert_validation_callback_async = Some(handler); unsafe { s2n_config_set_cert_validation_cb( self.as_mut_ptr(), - Some(cert_validation_cb), + Some(async_cert_validation_cb), core::ptr::null_mut(), ) .into_result()?; @@ -1114,7 +1174,9 @@ pub(crate) struct Context { #[cfg(feature = "unstable-cert_authorities")] pub(crate) cert_authorities: Option>, #[cfg(feature = "unstable-crl")] - pub(crate) cert_validation_callback: Option>, + pub(crate) cert_validation_callback_sync: Option>, + #[cfg(feature = "unstable-async-cert")] + pub(crate) cert_validation_callback_async: Option>, } impl Default for Context { @@ -1138,7 +1200,9 @@ impl Default for Context { #[cfg(feature = "unstable-cert_authorities")] cert_authorities: None, #[cfg(feature = "unstable-crl")] - cert_validation_callback: None, + cert_validation_callback_sync: None, + #[cfg(feature = "unstable-async-cert")] + cert_validation_callback_async: None, } } } diff --git a/bindings/rust/standard/integration/Cargo.toml b/bindings/rust/standard/integration/Cargo.toml index 61a893db3b0..3f7444d4a07 100644 --- a/bindings/rust/standard/integration/Cargo.toml +++ b/bindings/rust/standard/integration/Cargo.toml @@ -18,8 +18,11 @@ no-sensitive-tests = [] # can be disabled by turning off this feature. pq = [ "s2n-tls/pq" ] +# Unstable feature for testing async cert validation - not for production use +unstable-async-cert = [ "s2n-tls/unstable-async-cert" ] + [dependencies] -s2n-tls = { path = "../../extended/s2n-tls", features = ["unstable-testing", "unstable-crl"]} +s2n-tls = { path = "../../extended/s2n-tls", features = ["unstable-testing", "unstable-crl", "unstable-async-cert"]} s2n-tls-hyper = { path = "../s2n-tls-hyper" } s2n-tls-tokio = { path = "../../extended/s2n-tls-tokio" } s2n-tls-sys = { path = "../../extended/s2n-tls-sys" } diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index ea4944d6a36..0c25602bcd2 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -3,7 +3,7 @@ use rustls::ClientConfig; use s2n_tls::{ - callbacks::{CertValidationCallback, CertValidationInfo, VerifyHostNameCallback}, + callbacks::{CertValidationCallbackAsync, CertValidationCallbackSync, CertValidationInfo, VerifyHostNameCallback}, connection::Connection, enums::ClientAuthType, error::Error as S2NError, @@ -66,7 +66,18 @@ impl TestCertValidationCallback { } } -impl CertValidationCallback for TestCertValidationCallback { +impl CertValidationCallbackSync for TestCertValidationCallback { + fn handle_validation( + &self, + _conn: &mut Connection, + _info: &mut CertValidationInfo, + ) -> Result { + self.invoked.fetch_add(1, Ordering::SeqCst); + Ok(self.immediately_accept) + } +} + +impl CertValidationCallbackAsync for TestCertValidationCallback { fn handle_validation( &self, _conn: &mut Connection, @@ -188,13 +199,13 @@ fn s2n_mtls_server( MtlsServerCallback::Sync => { let cb = TestCertValidationCallback::new_sync(); let invoked = Arc::clone(cb.invoked_count()); - builder.set_cert_validation_callback(cb).unwrap(); + builder.set_cert_validation_callback_sync(cb).unwrap(); (Some(invoked), None) } MtlsServerCallback::Async => { let (cb, rx) = TestCertValidationCallback::new_async(); let invoked = Arc::clone(cb.invoked_count()); - builder.set_cert_validation_callback(cb).unwrap(); + builder.set_cert_validation_callback_async(cb).unwrap(); (Some(invoked), Some(rx)) } }; @@ -232,7 +243,7 @@ fn s2n_mtls_client_with_sync_callback(cfg: MtlsClientConfig) -> (S2NConfig, Arc< let cb = TestCertValidationCallback::new_sync(); let invoked = Arc::clone(cb.invoked_count()); - builder.set_cert_validation_callback(cb).unwrap(); + builder.set_cert_validation_callback_sync(cb).unwrap(); (S2NConfig::from(builder.build().unwrap()), invoked) } @@ -258,7 +269,7 @@ fn s2n_mtls_client_with_async_callback( let (cb, rx) = TestCertValidationCallback::new_async(); let invoked = Arc::clone(cb.invoked_count()); - builder.set_cert_validation_callback(cb).unwrap(); + builder.set_cert_validation_callback_async(cb).unwrap(); (S2NConfig::from(builder.build().unwrap()), invoked, rx) } From 114c0195109b243ce04266cf199f54aee0d2605b Mon Sep 17 00:00:00 2001 From: Kaukab Date: Thu, 27 Nov 2025 00:00:47 +0000 Subject: [PATCH 19/29] Apply cargo fmt --- bindings/rust/standard/integration/src/mtls.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 0c25602bcd2..9f2ac31c9f2 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -3,7 +3,10 @@ use rustls::ClientConfig; use s2n_tls::{ - callbacks::{CertValidationCallbackAsync, CertValidationCallbackSync, CertValidationInfo, VerifyHostNameCallback}, + callbacks::{ + CertValidationCallbackAsync, CertValidationCallbackSync, CertValidationInfo, + VerifyHostNameCallback, + }, connection::Connection, enums::ClientAuthType, error::Error as S2NError, From 7b01e43f02f10c02fa57dda46b30cb977ae58a0c Mon Sep 17 00:00:00 2001 From: Kaukab Date: Thu, 27 Nov 2025 00:16:30 +0000 Subject: [PATCH 20/29] Address PR feedback, CI fix --- .../rust/extended/s2n-tls/src/callbacks/cert_validation.rs | 4 ++-- bindings/rust/standard/integration/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index b40200b4254..c3de6542d4b 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -115,11 +115,11 @@ mod tests { fn sync_cert_validation() -> Result<(), Box> { for accept in [true, false] { let counter = Counter::default(); - let callback = TestCallback(counter.clone()); + let callback = SyncCallback(counter.clone()); let config = { let mut config = config_builder(&security::DEFAULT_TLS13)?; - config.set_cert_validation_callback(callback)?; + config.set_cert_validation_callback_sync(callback)?; config.build()? }; diff --git a/bindings/rust/standard/integration/Cargo.toml b/bindings/rust/standard/integration/Cargo.toml index 3f7444d4a07..f36b0ac503f 100644 --- a/bindings/rust/standard/integration/Cargo.toml +++ b/bindings/rust/standard/integration/Cargo.toml @@ -28,12 +28,12 @@ s2n-tls-tokio = { path = "../../extended/s2n-tls-tokio" } s2n-tls-sys = { path = "../../extended/s2n-tls-sys" } aws-lc-sys = "*" tls-harness = { path = "../tls-harness" } -rustls = "0.23.35" [dev-dependencies] openssl = { version = "0.10", features = ["vendored"] } tokio = { version = "1", features = ["macros", "test-util"] } tokio-openssl = { version = "0.6.5" } +rustls = "0.23" tracing = "0.1" tracing-subscriber = "0.3" From 29ed45d8a160a54b6cb70f32fb14e1cff739c6e6 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Mon, 1 Dec 2025 21:05:00 +0000 Subject: [PATCH 21/29] Address PR feedback --- bindings/rust/extended/s2n-tls/Cargo.toml | 2 - .../s2n-tls/src/callbacks/cert_validation.rs | 45 +-- bindings/rust/extended/s2n-tls/src/config.rs | 79 ---- bindings/rust/standard/integration/Cargo.toml | 5 +- .../rust/standard/integration/src/mtls.rs | 343 ++++++++---------- 5 files changed, 162 insertions(+), 312 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/Cargo.toml b/bindings/rust/extended/s2n-tls/Cargo.toml index 833adb1eeda..4e066c12e94 100644 --- a/bindings/rust/extended/s2n-tls/Cargo.toml +++ b/bindings/rust/extended/s2n-tls/Cargo.toml @@ -20,8 +20,6 @@ quic = ["s2n-tls-sys/quic"] fips = ["s2n-tls-sys/fips"] pq = ["s2n-tls-sys/pq"] unstable-testing = [] -# Internal feature for testing async cert validation - not for public use -unstable-async-cert = ["unstable-crl"] [dependencies] errno = { version = "0.3" } diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index c3de6542d4b..12b93e9a713 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -15,17 +15,7 @@ pub struct CertValidationInfo<'a> { } impl CertValidationInfo<'_> { - /// Creates a `CertValidationInfo` from a raw pointer. - /// - /// # Safety - /// - /// The caller must ensure that: - /// - `info` is a non-null pointer to a valid `s2n_cert_validation_info` structure - /// - The pointed-to structure is owned by s2n-tls and remains valid for the lifetime - /// of this `CertValidationInfo` (typically until the handshake completes or the - /// connection is freed) - /// - The pointer is not used to create multiple mutable references - pub unsafe fn from_ptr(info: *mut s2n_cert_validation_info) -> Self { + pub(crate) fn from_ptr(info: *mut s2n_cert_validation_info) -> Self { let info = NonNull::new(info).expect("info pointer should not be null"); CertValidationInfo { info, @@ -36,29 +26,23 @@ impl CertValidationInfo<'_> { /// Returns the raw pointer to the underlying `s2n_cert_validation_info`. /// /// This is primarily useful for passing to FFI functions or storing for later use. - pub fn as_ptr(&mut self) -> *mut s2n_cert_validation_info { + pub(crate) fn as_ptr(&mut self) -> *mut s2n_cert_validation_info { self.info.as_ptr() } /// Corresponds to [s2n_cert_validation_accept]. - pub fn accept(&mut self) -> Result<(), Error> { + pub(crate) fn accept(&mut self) -> Result<(), Error> { unsafe { s2n_cert_validation_accept(self.as_ptr()).into_result() }?; Ok(()) } /// Corresponds to [s2n_cert_validation_reject]. - pub fn reject(&mut self) -> Result<(), Error> { + pub(crate) fn reject(&mut self) -> Result<(), Error> { unsafe { s2n_cert_validation_reject(self.as_ptr()).into_result() }?; Ok(()) } } -/// Certificate validation callback for synchronous validation. -/// -/// The callback should return: -/// - `Ok(true)`: Accept the certificate -/// - `Ok(false)`: Reject the certificate -/// pub trait CertValidationCallbackSync: 'static + Send + Sync { /// Return a boolean to indicate if the certificate chain passed the validation fn handle_validation( @@ -67,27 +51,6 @@ pub trait CertValidationCallbackSync: 'static + Send + Sync { validation_info: &mut CertValidationInfo, ) -> Result; } -/// Certificate validation callback that supports asynchronous validation. -/// -/// This trait is only available for integration testing purposes. -/// -/// The callback can operate in three modes based on the return value: -/// - `Ok(Some(true))`: Accept the certificate immediately (synchronous) -/// - `Ok(Some(false))`: Reject the certificate immediately (synchronous) -/// - `Ok(None)`: Defer the decision (asynchronous) - the application must call -/// `validation_info.accept()` or `validation_info.reject()` later -/// -/// When returning `None`, the handshake will block (return S2N_ERR_T_BLOCKED with -/// S2N_BLOCKED_ON_APPLICATION_INPUT) until validation is completed by calling -/// `accept()` or `reject()` on the validation info. -#[cfg(feature = "unstable-async-cert")] -pub trait CertValidationCallbackAsync: 'static + Send + Sync { - fn handle_validation( - &self, - connection: &mut Connection, - validation_info: &mut CertValidationInfo, - ) -> Result, Error>; -} #[cfg(test)] mod tests { diff --git a/bindings/rust/extended/s2n-tls/src/config.rs b/bindings/rust/extended/s2n-tls/src/config.rs index a54fb808f9b..8ba260c9518 100644 --- a/bindings/rust/extended/s2n-tls/src/config.rs +++ b/bindings/rust/extended/s2n-tls/src/config.rs @@ -625,10 +625,6 @@ impl Builder { /// Set a callback function to perform custom cert validation synchronously. /// - /// The callback should return: - /// - `true` to accept the certificate - /// - `false` to reject the certificate - /// /// Corresponds to [s2n_config_set_cert_validation_cb], but the rust callback /// can only perform in synchronous mode. #[cfg(feature = "unstable-crl")] @@ -677,78 +673,7 @@ impl Builder { Ok(self) } - /// Set a callback function to perform custom asynchronous certificate validation. - /// - /// # Warning - /// - /// This is an unstable API intended for testing purposes only. It should not be used - /// in production code. The API may change or be removed in future versions. - /// - /// The callback can operate in both synchronous and asynchronous modes: - /// - Return `Some(true)` to accept the certificate immediately - /// - Return `Some(false)` to reject the certificate immediately - /// - Return `None` to defer the decision - the application must call - /// `validation_info.accept()` or `validation_info.reject()` later - /// - /// When returning `None`, the handshake will block (return S2N_ERR_T_BLOCKED with - /// S2N_BLOCKED_ON_APPLICATION_INPUT) until validation is completed. - /// - /// Corresponds to [s2n_config_set_cert_validation_cb]. - #[cfg(feature = "unstable-async-cert")] - #[doc(hidden)] - pub fn set_cert_validation_callback_async( - &mut self, - handler: T, - ) -> Result<&mut Self, Error> { - unsafe extern "C" fn async_cert_validation_cb( - conn_ptr: *mut s2n_connection, - validation_info: *mut s2n_cert_validation_info, - _context: *mut core::ffi::c_void, - ) -> libc::c_int { - // SAFETY: validation_info is provided by s2n-tls and guaranteed to be valid - // for the duration of this callback - let mut info = CertValidationInfo::from_ptr(validation_info); - with_context(conn_ptr, |conn, context| { - let callback = context.cert_validation_callback_async.as_ref(); - callback.map(|callback| { - match callback.handle_validation(conn, &mut info).unwrap() { - Some(true) => { - // Accept immediately (sync mode) - info.accept().unwrap(); - } - Some(false) => { - // Reject immediately (sync mode) - info.reject().unwrap(); - } - None => { - // Defer decision (async mode) - do nothing - // The application must call accept() or reject() later - } - } - }) - }); - CallbackResult::Success.into() - } - - let handler = Box::new(handler); - let context = unsafe { - // SAFETY: usage of context_mut is safe in the builder, because while - // it is being built, the Builder is the only reference to the config. - self.config.context_mut() - }; - context.cert_validation_callback_async = Some(handler); - unsafe { - s2n_config_set_cert_validation_cb( - self.as_mut_ptr(), - Some(async_cert_validation_cb), - core::ptr::null_mut(), - ) - .into_result()?; - } - - Ok(self) - } /// Set a custom callback function which is run after parsing the client hello. /// @@ -1175,8 +1100,6 @@ pub(crate) struct Context { pub(crate) cert_authorities: Option>, #[cfg(feature = "unstable-crl")] pub(crate) cert_validation_callback_sync: Option>, - #[cfg(feature = "unstable-async-cert")] - pub(crate) cert_validation_callback_async: Option>, } impl Default for Context { @@ -1201,8 +1124,6 @@ impl Default for Context { cert_authorities: None, #[cfg(feature = "unstable-crl")] cert_validation_callback_sync: None, - #[cfg(feature = "unstable-async-cert")] - cert_validation_callback_async: None, } } } diff --git a/bindings/rust/standard/integration/Cargo.toml b/bindings/rust/standard/integration/Cargo.toml index f36b0ac503f..88cfc7811b2 100644 --- a/bindings/rust/standard/integration/Cargo.toml +++ b/bindings/rust/standard/integration/Cargo.toml @@ -18,11 +18,8 @@ no-sensitive-tests = [] # can be disabled by turning off this feature. pq = [ "s2n-tls/pq" ] -# Unstable feature for testing async cert validation - not for production use -unstable-async-cert = [ "s2n-tls/unstable-async-cert" ] - [dependencies] -s2n-tls = { path = "../../extended/s2n-tls", features = ["unstable-testing", "unstable-crl", "unstable-async-cert"]} +s2n-tls = { path = "../../extended/s2n-tls", features = ["unstable-testing", "unstable-crl"]} s2n-tls-hyper = { path = "../s2n-tls-hyper" } s2n-tls-tokio = { path = "../../extended/s2n-tls-tokio" } s2n-tls-sys = { path = "../../extended/s2n-tls-sys" } diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 9f2ac31c9f2..757e62990fb 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -1,21 +1,33 @@ // Copyright Amazon.com, Inc. or its affiliates. // SPDX-License-Identifier: Apache-2.0 +// This test suite exercises mTLS interoperability between s2n-tls and rustls, +// including: +// - basic mTLS handshakes (TLS 1.2 and 1.3) +// - sync certificate validation callbacks +// - async certificate validation callbacks wired directly via the C FFI +// +// Async callbacks are registered via s2n_tls_sys instead of the Rust bindings +// to avoid exposing an unstable async callback API in the public Rust surface. + use rustls::ClientConfig; use s2n_tls::{ - callbacks::{ - CertValidationCallbackAsync, CertValidationCallbackSync, CertValidationInfo, - VerifyHostNameCallback, - }, + callbacks::{CertValidationCallbackSync, CertValidationInfo, VerifyHostNameCallback}, + config::{Builder, Config}, connection::Connection, enums::ClientAuthType, error::Error as S2NError, }; +use s2n_tls_sys::{ + s2n_cert_validation_accept, s2n_cert_validation_info, s2n_config, + s2n_config_set_cert_validation_cb, s2n_connection, s2n_status_code, +}; use std::sync::{ atomic::{AtomicU64, Ordering}, mpsc::{Receiver, Sender}, Arc, }; +use std::{mem, os::raw::c_void, ptr::NonNull}; use tls_harness::{ cohort::{RustlsConfig, RustlsConnection, S2NConfig, S2NConnection}, harness::{read_to_bytes, TlsConfigBuilder}, @@ -28,7 +40,7 @@ const APP_DATA_SIZE: usize = 100_000; /// /// This is used in tests to simulate async certificate validation where the validation /// decision is deferred and made on a different thread or after some async operation. -struct SendableCertValidationInfo(*mut s2n_tls_sys::s2n_cert_validation_info); +struct SendableCertValidationInfo(*mut s2n_cert_validation_info); // SAFETY: The pointer is owned by s2n-tls and remains valid for the duration of the // pending async validation (until accept() or reject() is called, or the connection is freed). @@ -36,11 +48,19 @@ struct SendableCertValidationInfo(*mut s2n_tls_sys::s2n_cert_validation_info); // to a worker thread that later calls accept()/reject(). unsafe impl Send for SendableCertValidationInfo {} +/// Get the raw s2n_config pointer from S2NConfig +/// SAFETY: S2NConfig wraps Config, which is a thin NonNull +/// wrapper. This is test-only functionality relying on that internal layout. +unsafe fn raw_config(cfg: &mut S2NConfig) -> *mut s2n_config { + let config: &mut Config = &mut cfg.config; + let nn: &mut NonNull = mem::transmute(config); + nn.as_ptr() +} + #[derive(Debug)] struct TestCertValidationCallback { invoked: Arc, immediately_accept: bool, - callback_sender: Option>, } impl TestCertValidationCallback { @@ -48,22 +68,9 @@ impl TestCertValidationCallback { Self { invoked: Arc::new(AtomicU64::new(0)), immediately_accept: true, - callback_sender: None, } } - fn new_async() -> (Self, Receiver) { - let (tx, rx) = std::sync::mpsc::channel(); - ( - Self { - invoked: Arc::new(AtomicU64::new(0)), - immediately_accept: false, - callback_sender: Some(tx), - }, - rx, - ) - } - fn invoked_count(&self) -> &Arc { &self.invoked } @@ -80,28 +87,26 @@ impl CertValidationCallbackSync for TestCertValidationCallback { } } -impl CertValidationCallbackAsync for TestCertValidationCallback { - fn handle_validation( - &self, - _conn: &mut Connection, - info: &mut CertValidationInfo, - ) -> Result, S2NError> { - self.invoked.fetch_add(1, Ordering::SeqCst); +// Async callback context for C FFI +struct AsyncCertCtx { + invoked: Arc, + sender: Sender, +} - if let Some(sender) = &self.callback_sender { - // We send a raw pointer to the underlying s2n_cert_validation_info so the test - // can later reconstruct CertValidationInfo and call accept()/reject(). - sender - .send(SendableCertValidationInfo(info.as_ptr())) - .expect("sending CertValidationInfo ptr"); - } +// C-style async cert validation callback +extern "C" fn test_async_cert_cb( + _conn: *mut s2n_connection, + info: *mut s2n_cert_validation_info, + ctx: *mut c_void, +) -> i32 { + let ctx = unsafe { &*(ctx as *mut AsyncCertCtx) }; - Ok(if self.immediately_accept { - Some(true) - } else { - None - }) - } + ctx.invoked.fetch_add(1, Ordering::SeqCst); + ctx.sender + .send(SendableCertValidationInfo(info)) + .expect("send async cert validation info"); + + s2n_status_code::SUCCESS } #[derive(Default)] @@ -112,21 +117,6 @@ impl VerifyHostNameCallback for HostNameIgnorer { } } -#[derive(Clone, Copy)] -struct MtlsClientConfig { - sig_type: SigType, - tls_version: &'static rustls::SupportedProtocolVersion, -} - -impl Default for MtlsClientConfig { - fn default() -> Self { - Self { - sig_type: SigType::Rsa2048, - tls_version: &rustls::version::TLS13, - } - } -} - #[derive(Clone, Copy, Default)] enum MtlsServerCallback { #[default] @@ -139,38 +129,88 @@ enum MtlsServerCallback { struct MtlsServerConfig { sig_type: SigType, callback_mode: MtlsServerCallback, - with_hostname_verifier: bool, } -fn rustls_mtls_client(cfg: MtlsClientConfig) -> RustlsConfig { +/// Helper which creates a base s2n-tls builder configured for mTLS +fn s2n_mtls_base_builder(sig_type: SigType) -> Builder { + let mut builder = Builder::new(); + builder.set_chain(sig_type); + builder + .set_client_auth_type(ClientAuthType::Required) + .unwrap() + .with_system_certs(false) + .unwrap() + .trust_pem(&read_to_bytes(PemType::CACert, sig_type)) + .unwrap() + .set_verify_host_callback(HostNameIgnorer) + .unwrap(); + builder +} + +/// Helper which registers an async cert validation callback via C FFI +fn register_async_cert_callback( + s2n_cfg: &mut S2NConfig, +) -> (Arc, Receiver) { + let invoked = Arc::new(AtomicU64::new(0)); + let (tx, rx) = std::sync::mpsc::channel(); + + let ctx = Box::new(AsyncCertCtx { + invoked: Arc::clone(&invoked), + sender: tx, + }); + let ctx_ptr = Box::into_raw(ctx) as *mut c_void; + + // SAFETY: s2n stores this context pointer and later returns it in the async + // callback. Because s2n never frees it, we intentionally leak the Box so the + // memory stays valid for the lifetime of the config (test-only). + unsafe { + let raw = raw_config(s2n_cfg); + let rc = s2n_config_set_cert_validation_cb(raw, Some(test_async_cert_cb), ctx_ptr); + assert_eq!( + rc, + s2n_status_code::SUCCESS, + "s2n_config_set_cert_validation_cb failed" + ); + } + + (invoked, rx) +} + +fn rustls_mtls_client( + sig_type: SigType, + tls_version: &'static rustls::SupportedProtocolVersion, +) -> RustlsConfig { let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); let client = ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&[cfg.tls_version]) + .with_protocol_versions(&[tls_version]) .unwrap() - .with_root_certificates(RustlsConfig::get_root_cert_store(cfg.sig_type)) + .with_root_certificates(RustlsConfig::get_root_cert_store(sig_type)) .with_client_auth_cert( - RustlsConfig::get_cert_chain(PemType::ClientCertChain, cfg.sig_type), - RustlsConfig::get_key(PemType::ClientKey, cfg.sig_type), + RustlsConfig::get_cert_chain(PemType::ClientCertChain, sig_type), + RustlsConfig::get_key(PemType::ClientKey, sig_type), ) .unwrap(); client.into() } -fn rustls_mtls_server(cfg: MtlsClientConfig) -> RustlsConfig { +fn rustls_mtls_server( + sig_type: SigType, + tls_version: &'static rustls::SupportedProtocolVersion, +) -> RustlsConfig { let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); let client_cert_verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new( - RustlsConfig::get_root_cert_store(cfg.sig_type), + RustlsConfig::get_root_cert_store(sig_type), )) .build() .unwrap(); let server = rustls::ServerConfig::builder_with_provider(provider) - .with_protocol_versions(&[cfg.tls_version]) + .with_protocol_versions(&[tls_version]) .unwrap() .with_client_cert_verifier(client_cert_verifier) .with_single_cert( - RustlsConfig::get_cert_chain(PemType::ServerCertChain, cfg.sig_type), - RustlsConfig::get_key(PemType::ServerKey, cfg.sig_type), + RustlsConfig::get_cert_chain(PemType::ServerCertChain, sig_type), + RustlsConfig::get_key(PemType::ServerKey, sig_type), ) .unwrap(); server.into() @@ -183,66 +223,35 @@ fn s2n_mtls_server( Option>, Option>, ) { - let mut builder = s2n_tls::config::Builder::new(); - builder.set_chain(cfg.sig_type); - builder - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) - .unwrap(); + let mut builder = s2n_mtls_base_builder(cfg.sig_type); - if cfg.with_hostname_verifier { - builder.set_verify_host_callback(HostNameIgnorer).unwrap(); - } - - let (handle, rx) = match cfg.callback_mode { - MtlsServerCallback::None => (None, None), + match cfg.callback_mode { + MtlsServerCallback::None => (S2NConfig::from(builder.build().unwrap()), None, None), MtlsServerCallback::Sync => { let cb = TestCertValidationCallback::new_sync(); let invoked = Arc::clone(cb.invoked_count()); builder.set_cert_validation_callback_sync(cb).unwrap(); - (Some(invoked), None) + ( + S2NConfig::from(builder.build().unwrap()), + Some(invoked), + None, + ) } MtlsServerCallback::Async => { - let (cb, rx) = TestCertValidationCallback::new_async(); - let invoked = Arc::clone(cb.invoked_count()); - builder.set_cert_validation_callback_async(cb).unwrap(); - (Some(invoked), Some(rx)) + let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); + let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); + (s2n_cfg, Some(invoked), Some(rx)) } - }; - - (S2NConfig::from(builder.build().unwrap()), handle, rx) + } } -fn s2n_mtls_client(cfg: MtlsClientConfig) -> S2NConfig { - let mut builder = s2n_tls::config::Builder::new(); - builder.set_chain(cfg.sig_type); - builder - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) - .unwrap() - .set_verify_host_callback(HostNameIgnorer) - .unwrap(); +fn s2n_mtls_client(sig_type: SigType) -> S2NConfig { + let builder = s2n_mtls_base_builder(sig_type); S2NConfig::from(builder.build().unwrap()) } -fn s2n_mtls_client_with_sync_callback(cfg: MtlsClientConfig) -> (S2NConfig, Arc) { - let mut builder = s2n_tls::config::Builder::new(); - builder.set_chain(cfg.sig_type); - builder - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) - .unwrap() - .set_verify_host_callback(HostNameIgnorer) - .unwrap(); +fn s2n_mtls_client_with_sync_callback(sig_type: SigType) -> (S2NConfig, Arc) { + let mut builder = s2n_mtls_base_builder(sig_type); let cb = TestCertValidationCallback::new_sync(); let invoked = Arc::clone(cb.invoked_count()); @@ -252,29 +261,16 @@ fn s2n_mtls_client_with_sync_callback(cfg: MtlsClientConfig) -> (S2NConfig, Arc< } fn s2n_mtls_client_with_async_callback( - cfg: MtlsClientConfig, + sig_type: SigType, ) -> ( S2NConfig, Arc, Receiver, ) { - let mut builder = s2n_tls::config::Builder::new(); - builder.set_chain(cfg.sig_type); - builder - .set_client_auth_type(ClientAuthType::Required) - .unwrap() - .with_system_certs(false) - .unwrap() - .trust_pem(&read_to_bytes(PemType::CACert, cfg.sig_type)) - .unwrap() - .set_verify_host_callback(HostNameIgnorer) - .unwrap(); - - let (cb, rx) = TestCertValidationCallback::new_async(); - let invoked = Arc::clone(cb.invoked_count()); - builder.set_cert_validation_callback_async(cb).unwrap(); - - (S2NConfig::from(builder.build().unwrap()), invoked, rx) + let builder = s2n_mtls_base_builder(sig_type); + let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); + let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); + (s2n_cfg, invoked, rx) } fn test_mtls_basic(client_cfg: &C::Config, server_cfg: &S::Config) @@ -315,7 +311,6 @@ where S: TlsConnection, { let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); - pair.io.enable_recording(); pair.client.handshake().unwrap(); pair.server.handshake().unwrap(); @@ -326,9 +321,12 @@ where assert_eq!(handle.load(Ordering::SeqCst), 1); let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: Pointer from cert validation callback, valid until accept/reject called - let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; - info.accept().unwrap(); + + // SAFETY: Pointer from cert validation callback, valid until accept/reject called. + unsafe { + let rc = s2n_cert_validation_accept(ptr); + assert_eq!(rc, 0, "s2n_cert_validation_accept failed"); + } pair.handshake().unwrap(); pair.round_trip_assert(10).unwrap(); @@ -347,7 +345,6 @@ where S: TlsConnection, { let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); - pair.io.enable_recording(); pair.client.handshake().unwrap(); pair.server.handshake().unwrap(); @@ -357,9 +354,12 @@ where assert_eq!(handle.load(Ordering::SeqCst), 1); let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - // SAFETY: Pointer from cert validation callback, valid until accept/reject called - let mut info = unsafe { CertValidationInfo::from_ptr(ptr) }; - info.accept().unwrap(); + + // SAFETY: Pointer from cert validation callback, valid until accept/reject called. + unsafe { + let rc = s2n_cert_validation_accept(ptr); + assert_eq!(rc, 0, "s2n_cert_validation_accept failed"); + } pair.handshake().unwrap(); pair.round_trip_assert(10).unwrap(); @@ -374,14 +374,8 @@ where // s2n client, rustls server #[test] fn s2n_rustls_mtls_basic_tls12() { - let client = s2n_mtls_client(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); - let server = rustls_mtls_server(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); + let client = s2n_mtls_client(SigType::Rsa2048); + let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); test_mtls_basic::(&client, &server); } @@ -390,8 +384,8 @@ fn s2n_rustls_mtls_basic_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let client = s2n_mtls_client(MtlsClientConfig::default()); - let server = rustls_mtls_server(MtlsClientConfig::default()); + let client = s2n_mtls_client(SigType::Rsa2048); + let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); test_mtls_basic::(&client, &server); }, ); @@ -400,10 +394,7 @@ fn s2n_rustls_mtls_basic_tls13() { // rustls client, s2n server #[test] fn rustls_s2n_mtls_basic_tls12() { - let client = rustls_mtls_client(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); + let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); test_mtls_basic::(&client, &server); } @@ -413,7 +404,7 @@ fn rustls_s2n_mtls_basic_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let client = rustls_mtls_client(MtlsClientConfig::default()); + let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS13); let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); test_mtls_basic::(&client, &server); }, @@ -427,14 +418,8 @@ fn rustls_s2n_mtls_basic_tls13() { // s2n client with sync callback, rustls server #[test] fn s2n_rustls_mtls_sync_callback_tls12() { - let (client, handle) = s2n_mtls_client_with_sync_callback(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); - let server = rustls_mtls_server(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); + let (client, handle) = s2n_mtls_client_with_sync_callback(SigType::Rsa2048); + let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); test_mtls_sync_callback::(&client, &server, handle); } @@ -444,8 +429,8 @@ fn s2n_rustls_mtls_sync_callback_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let (client, handle) = s2n_mtls_client_with_sync_callback(MtlsClientConfig::default()); - let server = rustls_mtls_server(MtlsClientConfig::default()); + let (client, handle) = s2n_mtls_client_with_sync_callback(SigType::Rsa2048); + let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); test_mtls_sync_callback::(&client, &server, handle); }, @@ -455,10 +440,7 @@ fn s2n_rustls_mtls_sync_callback_tls13() { // rustls client, s2n server with sync callback #[test] fn rustls_s2n_mtls_sync_callback_tls12() { - let client = rustls_mtls_client(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); + let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Sync, ..Default::default() @@ -476,7 +458,7 @@ fn rustls_s2n_mtls_sync_callback_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let client = rustls_mtls_client(MtlsClientConfig::default()); + let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS13); let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Sync, ..Default::default() @@ -503,15 +485,9 @@ fn rustls_s2n_mtls_sync_callback_tls13() { #[test] #[ignore = "Hangs due to multi-message bug in async cert validation"] fn s2n_rustls_mtls_async_callback_tls12() { - let (client, handle, rx) = s2n_mtls_client_with_async_callback(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); + let (client, handle, rx) = s2n_mtls_client_with_async_callback(SigType::Rsa2048); - let server = rustls_mtls_server(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); + let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); let _pair = test_mtls_async_callback_client::( &client, &server, handle, rx, @@ -524,10 +500,9 @@ fn s2n_rustls_mtls_async_callback_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let (client, handle, rx) = - s2n_mtls_client_with_async_callback(MtlsClientConfig::default()); + let (client, handle, rx) = s2n_mtls_client_with_async_callback(SigType::Rsa2048); - let server = rustls_mtls_server(MtlsClientConfig::default()); + let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); let _pair = test_mtls_async_callback_client::( &client, &server, handle, rx, @@ -542,10 +517,7 @@ fn s2n_rustls_mtls_async_callback_tls13() { // bug that appears in TLS 1.3 #[test] fn rustls_s2n_mtls_async_callback_tls12() { - let client = rustls_mtls_client(MtlsClientConfig { - tls_version: &rustls::version::TLS12, - ..MtlsClientConfig::default() - }); + let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Async, @@ -566,7 +538,7 @@ fn rustls_s2n_mtls_async_callback_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let client = rustls_mtls_client(MtlsClientConfig::default()); + let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS13); let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Async, ..Default::default() @@ -585,10 +557,9 @@ fn rustls_s2n_mtls_async_callback_tls13() { // s2n client, s2n server with async callback #[test] fn s2n_s2n_mtls_async_callback() { - let client = s2n_mtls_client(MtlsClientConfig::default()); + let client = s2n_mtls_client(SigType::Rsa2048); let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { callback_mode: MtlsServerCallback::Async, - with_hostname_verifier: true, ..Default::default() }); From a2acae78a30ee8f5513e7dcc19c78c120735dac6 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 2 Dec 2025 20:13:58 +0000 Subject: [PATCH 22/29] Applyc agro fmt --- bindings/rust/extended/s2n-tls/src/config.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/src/config.rs b/bindings/rust/extended/s2n-tls/src/config.rs index 8ba260c9518..876dd63ceea 100644 --- a/bindings/rust/extended/s2n-tls/src/config.rs +++ b/bindings/rust/extended/s2n-tls/src/config.rs @@ -673,8 +673,6 @@ impl Builder { Ok(self) } - - /// Set a custom callback function which is run after parsing the client hello. /// /// Corresponds to [s2n_config_set_client_hello_cb]. From 54112e557e1c87d292bf6c5bed41940adde0cc64 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 2 Dec 2025 20:50:03 +0000 Subject: [PATCH 23/29] Apply cargo fmt --- .../rust/standard/integration/src/mtls.rs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 757e62990fb..47b6d18eeb5 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -10,7 +10,19 @@ // Async callbacks are registered via s2n_tls_sys instead of the Rust bindings // to avoid exposing an unstable async callback API in the public Rust surface. +use std::{ + mem, + os::raw::c_void, + ptr::NonNull, + sync::{ + atomic::{AtomicU64, Ordering}, + mpsc::{Receiver, Sender}, + Arc, + }, +}; + use rustls::ClientConfig; + use s2n_tls::{ callbacks::{CertValidationCallbackSync, CertValidationInfo, VerifyHostNameCallback}, config::{Builder, Config}, @@ -18,16 +30,12 @@ use s2n_tls::{ enums::ClientAuthType, error::Error as S2NError, }; + use s2n_tls_sys::{ s2n_cert_validation_accept, s2n_cert_validation_info, s2n_config, s2n_config_set_cert_validation_cb, s2n_connection, s2n_status_code, }; -use std::sync::{ - atomic::{AtomicU64, Ordering}, - mpsc::{Receiver, Sender}, - Arc, -}; -use std::{mem, os::raw::c_void, ptr::NonNull}; + use tls_harness::{ cohort::{RustlsConfig, RustlsConnection, S2NConfig, S2NConnection}, harness::{read_to_bytes, TlsConfigBuilder}, From 2bd7d2c728222e52828b86d09a28996795754116 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Tue, 2 Dec 2025 20:59:30 +0000 Subject: [PATCH 24/29] Remove stale comments --- .../rust/extended/s2n-tls/src/callbacks/cert_validation.rs | 3 --- bindings/rust/extended/s2n-tls/src/config.rs | 2 -- 2 files changed, 5 deletions(-) diff --git a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs index 12b93e9a713..32ca3bbaf00 100644 --- a/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs +++ b/bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs @@ -23,9 +23,6 @@ impl CertValidationInfo<'_> { } } - /// Returns the raw pointer to the underlying `s2n_cert_validation_info`. - /// - /// This is primarily useful for passing to FFI functions or storing for later use. pub(crate) fn as_ptr(&mut self) -> *mut s2n_cert_validation_info { self.info.as_ptr() } diff --git a/bindings/rust/extended/s2n-tls/src/config.rs b/bindings/rust/extended/s2n-tls/src/config.rs index 876dd63ceea..ab73c78bc95 100644 --- a/bindings/rust/extended/s2n-tls/src/config.rs +++ b/bindings/rust/extended/s2n-tls/src/config.rs @@ -637,8 +637,6 @@ impl Builder { validation_info: *mut s2n_cert_validation_info, _context: *mut core::ffi::c_void, ) -> libc::c_int { - // SAFETY: validation_info is provided by s2n-tls and guaranteed to be valid - // for the duration of this callback let mut info = CertValidationInfo::from_ptr(validation_info); with_context(conn_ptr, |conn, context| { let callback = context.cert_validation_callback_sync.as_ref(); From d94caad750fed95cb1fe5ba03e5c6540afc610e4 Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 3 Dec 2025 01:23:56 +0000 Subject: [PATCH 25/29] Simplify test body for protocol versions and inline server/client config setup --- .../rust/standard/integration/src/mtls.rs | 250 ++++++++---------- 1 file changed, 111 insertions(+), 139 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 47b6d18eeb5..828358c1332 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -125,20 +125,6 @@ impl VerifyHostNameCallback for HostNameIgnorer { } } -#[derive(Clone, Copy, Default)] -enum MtlsServerCallback { - #[default] - None, - Sync, - Async, -} - -#[derive(Clone, Copy, Default)] -struct MtlsServerConfig { - sig_type: SigType, - callback_mode: MtlsServerCallback, -} - /// Helper which creates a base s2n-tls builder configured for mTLS fn s2n_mtls_base_builder(sig_type: SigType) -> Builder { let mut builder = Builder::new(); @@ -224,62 +210,7 @@ fn rustls_mtls_server( server.into() } -fn s2n_mtls_server( - cfg: MtlsServerConfig, -) -> ( - S2NConfig, - Option>, - Option>, -) { - let mut builder = s2n_mtls_base_builder(cfg.sig_type); - - match cfg.callback_mode { - MtlsServerCallback::None => (S2NConfig::from(builder.build().unwrap()), None, None), - MtlsServerCallback::Sync => { - 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()), - Some(invoked), - None, - ) - } - MtlsServerCallback::Async => { - let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); - let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); - (s2n_cfg, Some(invoked), Some(rx)) - } - } -} - -fn s2n_mtls_client(sig_type: SigType) -> S2NConfig { - let builder = s2n_mtls_base_builder(sig_type); - S2NConfig::from(builder.build().unwrap()) -} - -fn s2n_mtls_client_with_sync_callback(sig_type: SigType) -> (S2NConfig, Arc) { - let mut builder = s2n_mtls_base_builder(sig_type); - 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) -} - -fn s2n_mtls_client_with_async_callback( - sig_type: SigType, -) -> ( - S2NConfig, - Arc, - Receiver, -) { - let builder = s2n_mtls_base_builder(sig_type); - let mut s2n_cfg = S2NConfig::from(builder.build().unwrap()); - let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); - (s2n_cfg, invoked, rx) -} fn test_mtls_basic(client_cfg: &C::Config, server_cfg: &S::Config) where @@ -381,18 +312,24 @@ where // s2n client, rustls server #[test] -fn s2n_rustls_mtls_basic_tls12() { - let client = s2n_mtls_client(SigType::Rsa2048); +fn s2n_client_basic() { + + // TLS 1.2 + let client = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); test_mtls_basic::(&client, &server); -} -#[test] -fn s2n_rustls_mtls_basic_tls13() { + // TLS 1.3 crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let client = s2n_mtls_client(SigType::Rsa2048); + let client = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); test_mtls_basic::(&client, &server); }, @@ -401,19 +338,25 @@ fn s2n_rustls_mtls_basic_tls13() { // rustls client, s2n server #[test] -fn rustls_s2n_mtls_basic_tls12() { +fn s2n_server_basic() { + + // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); - let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); + let server = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; test_mtls_basic::(&client, &server); -} -#[test] -fn rustls_s2n_mtls_basic_tls13() { + // TLS 1.3 crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS13); - let (server, _, _) = s2n_mtls_server(MtlsServerConfig::default()); + let server = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; test_mtls_basic::(&client, &server); }, ); @@ -425,19 +368,30 @@ fn rustls_s2n_mtls_basic_tls13() { // s2n client with sync callback, rustls server #[test] -fn s2n_rustls_mtls_sync_callback_tls12() { - let (client, handle) = s2n_mtls_client_with_sync_callback(SigType::Rsa2048); +fn s2n_client_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 = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); - test_mtls_sync_callback::(&client, &server, handle); -} -#[test] -fn s2n_rustls_mtls_sync_callback_tls13() { + // TLS 1.3 crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let (client, handle) = s2n_mtls_client_with_sync_callback(SigType::Rsa2048); + 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 = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); test_mtls_sync_callback::(&client, &server, handle); @@ -447,35 +401,41 @@ fn s2n_rustls_mtls_sync_callback_tls13() { // rustls client, s2n server with sync callback #[test] -fn rustls_s2n_mtls_sync_callback_tls12() { +fn s2n_server_sync_callback() { + + // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); - let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Sync, - ..Default::default() - }); + 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_mtls_sync_callback::( &client, &server, - handle.expect("sync callback handle"), + handle, ); -} -#[test] -fn rustls_s2n_mtls_sync_callback_tls13() { + // TLS 1.3 crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS13); - let (server, handle, _) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Sync, - ..Default::default() - }); + 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_mtls_sync_callback::( &client, &server, - handle.expect("sync callback handle"), + handle, ); }, ); @@ -492,26 +452,31 @@ fn rustls_s2n_mtls_sync_callback_tls13() { // s2n client with async callback, rustls server #[test] #[ignore = "Hangs due to multi-message bug in async cert validation"] -fn s2n_rustls_mtls_async_callback_tls12() { - let (client, handle, rx) = s2n_mtls_client_with_async_callback(SigType::Rsa2048); - +fn s2n_client_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 = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); - let _pair = test_mtls_async_callback_client::( &client, &server, handle, rx, ); -} -#[test] -#[ignore = "Hangs due to multi-message bug in async cert validation"] -fn s2n_rustls_mtls_async_callback_tls13() { + // TLS 1.3 crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { - let (client, handle, rx) = s2n_mtls_client_with_async_callback(SigType::Rsa2048); - + 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 = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); - let _pair = test_mtls_async_callback_client::( &client, &server, handle, rx, ); @@ -524,39 +489,41 @@ fn s2n_rustls_mtls_async_callback_tls13() { // single record, so s2n never hits the multi-message async-callback // bug that appears in TLS 1.3 #[test] -fn rustls_s2n_mtls_async_callback_tls12() { - let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); - - let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Async, - ..Default::default() - }); +#[ignore = "Hangs due to multi-message bug in async cert validation"] +fn s2n_server_async_callback() { + // TLS 1.2 + let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); + 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_mtls_async_callback::( &client, &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), + handle, + rx, ); -} -#[test] -#[ignore = "Hangs due to multi-message bug in async cert validation"] -fn rustls_s2n_mtls_async_callback_tls13() { + // TLS 1.3 crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS13); - let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Async, - ..Default::default() - }); + 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_mtls_async_callback::( &client, &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), + handle, + rx, ); }, ); @@ -565,16 +532,21 @@ fn rustls_s2n_mtls_async_callback_tls13() { // s2n client, s2n server with async callback #[test] fn s2n_s2n_mtls_async_callback() { - let client = s2n_mtls_client(SigType::Rsa2048); - let (server, handle, rx) = s2n_mtls_server(MtlsServerConfig { - callback_mode: MtlsServerCallback::Async, - ..Default::default() - }); + let client = { + let builder = s2n_mtls_base_builder(SigType::Rsa2048); + S2NConfig::from(builder.build().unwrap()) + }; + 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_mtls_async_callback::( &client, &server, - handle.expect("async callback handle"), - rx.expect("async callback receiver"), + handle, + rx, ); } From 3aab05991d53dcc1f97e7f0476cb08f3701688cf Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 3 Dec 2025 18:54:36 +0000 Subject: [PATCH 26/29] Restructure tests for readability --- .../rust/standard/integration/src/mtls.rs | 244 ++++++++---------- 1 file changed, 110 insertions(+), 134 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 828358c1332..2531e751ffc 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -125,7 +125,7 @@ impl VerifyHostNameCallback for HostNameIgnorer { } } -/// Helper which creates a base s2n-tls builder configured for mTLS +/// Creates a base s2n-tls builder configured for mTLS. fn s2n_mtls_base_builder(sig_type: SigType) -> Builder { let mut builder = Builder::new(); builder.set_chain(sig_type); @@ -170,6 +170,7 @@ fn register_async_cert_callback( (invoked, rx) } +/// Builds a rustls mTLS client config for the given TLS version. fn rustls_mtls_client( sig_type: SigType, tls_version: &'static rustls::SupportedProtocolVersion, @@ -187,6 +188,7 @@ fn rustls_mtls_client( client.into() } +/// Builds a rustls mTLS server config for the given TLS version. fn rustls_mtls_server( sig_type: SigType, tls_version: &'static rustls::SupportedProtocolVersion, @@ -210,9 +212,12 @@ fn rustls_mtls_server( server.into() } +// ============================================================================ +// Basic mTLS tests +// ============================================================================ - -fn test_mtls_basic(client_cfg: &C::Config, server_cfg: &S::Config) +// Helper for basic test case +fn test_basic(client_cfg: &C::Config, server_cfg: &S::Config) where C: TlsConnection, S: TlsConnection, @@ -223,104 +228,16 @@ where pair.shutdown().unwrap(); } -fn test_mtls_sync_callback( - client_cfg: &C::Config, - server_cfg: &S::Config, - handle: Arc, -) where - C: TlsConnection, - S: TlsConnection, -{ - let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); - assert_eq!(handle.load(Ordering::SeqCst), 0); - pair.handshake().unwrap(); - assert_eq!(handle.load(Ordering::SeqCst), 1); - pair.round_trip_assert(APP_DATA_SIZE).unwrap(); - pair.shutdown().unwrap(); -} - -fn test_mtls_async_callback( - client_cfg: &C::Config, - server_cfg: &S::Config, - handle: Arc, - rx: Receiver, -) -> TlsConnPair -where - C: TlsConnection, - S: TlsConnection, -{ - let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); - - pair.client.handshake().unwrap(); - pair.server.handshake().unwrap(); - pair.client.handshake().unwrap(); - - assert_eq!(handle.load(Ordering::SeqCst), 0); - pair.server.handshake().unwrap(); - assert_eq!(handle.load(Ordering::SeqCst), 1); - - let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - - // SAFETY: Pointer from cert validation callback, valid until accept/reject called. - unsafe { - let rc = s2n_cert_validation_accept(ptr); - assert_eq!(rc, 0, "s2n_cert_validation_accept failed"); - } - - pair.handshake().unwrap(); - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); - pair -} - -fn test_mtls_async_callback_client( - client_cfg: &C::Config, - server_cfg: &S::Config, - handle: Arc, - rx: Receiver, -) -> TlsConnPair -where - C: TlsConnection, - S: TlsConnection, -{ - let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); - - pair.client.handshake().unwrap(); - pair.server.handshake().unwrap(); - - assert_eq!(handle.load(Ordering::SeqCst), 0); - pair.client.handshake().unwrap(); - assert_eq!(handle.load(Ordering::SeqCst), 1); - - let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; - - // SAFETY: Pointer from cert validation callback, valid until accept/reject called. - unsafe { - let rc = s2n_cert_validation_accept(ptr); - assert_eq!(rc, 0, "s2n_cert_validation_accept failed"); - } - - pair.handshake().unwrap(); - pair.round_trip_assert(10).unwrap(); - pair.shutdown().unwrap(); - pair -} - -// ============================================================================ -// Basic mTLS tests -// ============================================================================ - // s2n client, rustls server #[test] fn s2n_client_basic() { - // TLS 1.2 let client = { let builder = s2n_mtls_base_builder(SigType::Rsa2048); S2NConfig::from(builder.build().unwrap()) }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); - test_mtls_basic::(&client, &server); + test_basic::(&client, &server); // TLS 1.3 crate::capability_check::required_capability( @@ -331,7 +248,7 @@ fn s2n_client_basic() { S2NConfig::from(builder.build().unwrap()) }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); - test_mtls_basic::(&client, &server); + test_basic::(&client, &server); }, ); } @@ -339,14 +256,13 @@ fn s2n_client_basic() { // rustls client, s2n server #[test] fn s2n_server_basic() { - // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let server = { let builder = s2n_mtls_base_builder(SigType::Rsa2048); S2NConfig::from(builder.build().unwrap()) }; - test_mtls_basic::(&client, &server); + test_basic::(&client, &server); // TLS 1.3 crate::capability_check::required_capability( @@ -357,7 +273,7 @@ fn s2n_server_basic() { let builder = s2n_mtls_base_builder(SigType::Rsa2048); S2NConfig::from(builder.build().unwrap()) }; - test_mtls_basic::(&client, &server); + test_basic::(&client, &server); }, ); } @@ -366,10 +282,23 @@ fn s2n_server_basic() { // Sync callback tests // ============================================================================ +// Helper for synchronous callback tests +fn test_sync_callback(client_cfg: &C::Config, server_cfg: &S::Config, handle: Arc) +where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); + assert_eq!(handle.load(Ordering::SeqCst), 0); + pair.handshake().unwrap(); + assert_eq!(handle.load(Ordering::SeqCst), 1); + pair.round_trip_assert(APP_DATA_SIZE).unwrap(); + pair.shutdown().unwrap(); +} + // s2n client with sync callback, rustls server #[test] fn s2n_client_sync_callback() { - // TLS 1.2 let (client, handle) = { let mut builder = s2n_mtls_base_builder(SigType::Rsa2048); @@ -379,7 +308,7 @@ fn s2n_client_sync_callback() { (S2NConfig::from(builder.build().unwrap()), invoked) }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); - test_mtls_sync_callback::(&client, &server, handle); + test_sync_callback::(&client, &server, handle); // TLS 1.3 crate::capability_check::required_capability( @@ -394,7 +323,7 @@ fn s2n_client_sync_callback() { }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); - test_mtls_sync_callback::(&client, &server, handle); + test_sync_callback::(&client, &server, handle); }, ); } @@ -402,7 +331,6 @@ fn s2n_client_sync_callback() { // rustls client, s2n server with sync callback #[test] fn s2n_server_sync_callback() { - // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, handle) = { @@ -413,11 +341,7 @@ fn s2n_server_sync_callback() { (S2NConfig::from(builder.build().unwrap()), invoked) }; - test_mtls_sync_callback::( - &client, - &server, - handle, - ); + test_sync_callback::(&client, &server, handle); // TLS 1.3 crate::capability_check::required_capability( @@ -432,11 +356,7 @@ fn s2n_server_sync_callback() { (S2NConfig::from(builder.build().unwrap()), invoked) }; - test_mtls_sync_callback::( - &client, - &server, - handle, - ); + test_sync_callback::(&client, &server, handle); }, ); } @@ -445,6 +365,75 @@ fn s2n_server_sync_callback() { // Async callback tests // ============================================================================ +// Helper for async server-side cert validation tests. +fn test_async_server_callback( + client_cfg: &C::Config, + server_cfg: &S::Config, + handle: Arc, + rx: Receiver, +) -> TlsConnPair +where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); + + pair.client.handshake().unwrap(); + pair.server.handshake().unwrap(); + pair.client.handshake().unwrap(); + + assert_eq!(handle.load(Ordering::SeqCst), 0); + pair.server.handshake().unwrap(); + assert_eq!(handle.load(Ordering::SeqCst), 1); + + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + + // SAFETY: Pointer from cert validation callback, valid until accept/reject called. + unsafe { + let rc = s2n_cert_validation_accept(ptr); + assert_eq!(rc, 0, "s2n_cert_validation_accept failed"); + } + + pair.handshake().unwrap(); + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); + pair +} + +// Helper for async client-side cert validation tests. +fn test_async_client_callback( + client_cfg: &C::Config, + server_cfg: &S::Config, + handle: Arc, + rx: Receiver, +) -> TlsConnPair +where + C: TlsConnection, + S: TlsConnection, +{ + let mut pair = TlsConnPair::::from_configs(client_cfg, server_cfg); + + pair.client.handshake().unwrap(); + pair.server.handshake().unwrap(); + + assert_eq!(handle.load(Ordering::SeqCst), 0); + pair.client.handshake().unwrap(); + assert_eq!(handle.load(Ordering::SeqCst), 1); + + let ptr = rx.recv().expect("recv CertValidationInfo ptr").0; + + // SAFETY: Pointer from cert validation callback, valid until accept/reject called. + unsafe { + let rc = s2n_cert_validation_accept(ptr); + assert_eq!(rc, 0, "s2n_cert_validation_accept failed"); + } + + pair.handshake().unwrap(); + pair.round_trip_assert(10).unwrap(); + pair.shutdown().unwrap(); + pair +} + // As of 2025-11-24: s2n as client (TLS 1.2, 1.3) and s2n as // server (TLS 1.3) hang due to a multi-message async cert validation bug. // s2n incorrectly clears queued handshake messages, causing @@ -453,7 +442,6 @@ fn s2n_server_sync_callback() { #[test] #[ignore = "Hangs due to multi-message bug in async cert validation"] fn s2n_client_async_callback() { - // TLS 1.2 let (client, handle, rx) = { let builder = s2n_mtls_base_builder(SigType::Rsa2048); @@ -462,9 +450,8 @@ fn s2n_client_async_callback() { (s2n_cfg, invoked, rx) }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS12); - let _pair = test_mtls_async_callback_client::( - &client, &server, handle, rx, - ); + let _pair = + test_async_client_callback::(&client, &server, handle, rx); // TLS 1.3 crate::capability_check::required_capability( @@ -477,7 +464,7 @@ fn s2n_client_async_callback() { (s2n_cfg, invoked, rx) }; let server = rustls_mtls_server(SigType::Rsa2048, &rustls::version::TLS13); - let _pair = test_mtls_async_callback_client::( + let _pair = test_async_client_callback::( &client, &server, handle, rx, ); }, @@ -487,11 +474,11 @@ fn s2n_client_async_callback() { // rustls client, s2n server with async callback // Rustls TLS 1.2 clients do not send multiple handshake messages in a // single record, so s2n never hits the multi-message async-callback -// bug that appears in TLS 1.3 +// bug that appears in TLS 1.3 but both variants are ignored for now +// for simplicity. #[test] #[ignore = "Hangs due to multi-message bug in async cert validation"] fn s2n_server_async_callback() { - // TLS 1.2 let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, handle, rx) = { @@ -500,12 +487,8 @@ fn s2n_server_async_callback() { let (invoked, rx) = register_async_cert_callback(&mut s2n_cfg); (s2n_cfg, invoked, rx) }; - let _pair = test_mtls_async_callback::( - &client, - &server, - handle, - rx, - ); + let _pair = + test_async_server_callback::(&client, &server, handle, rx); // TLS 1.3 crate::capability_check::required_capability( @@ -519,11 +502,8 @@ fn s2n_server_async_callback() { (s2n_cfg, invoked, rx) }; - let _pair = test_mtls_async_callback::( - &client, - &server, - handle, - rx, + let _pair = test_async_server_callback::( + &client, &server, handle, rx, ); }, ); @@ -543,10 +523,6 @@ fn s2n_s2n_mtls_async_callback() { (s2n_cfg, invoked, rx) }; - let _pair = test_mtls_async_callback::( - &client, - &server, - handle, - rx, - ); + let _pair = + test_async_server_callback::(&client, &server, handle, rx); } From 8a41338eac566c5e880c8059da87ddf75986dedd Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 3 Dec 2025 19:09:11 +0000 Subject: [PATCH 27/29] Edit doc comments --- .../rust/standard/integration/src/mtls.rs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 2531e751ffc..f8336c4cf2b 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -125,7 +125,6 @@ impl VerifyHostNameCallback for HostNameIgnorer { } } -/// Creates a base s2n-tls builder configured for mTLS. fn s2n_mtls_base_builder(sig_type: SigType) -> Builder { let mut builder = Builder::new(); builder.set_chain(sig_type); @@ -141,7 +140,7 @@ fn s2n_mtls_base_builder(sig_type: SigType) -> Builder { builder } -/// Helper which registers an async cert validation callback via C FFI +/// Registers an async cert validation callback and returns (invoked_counter, info_receiver). fn register_async_cert_callback( s2n_cfg: &mut S2NConfig, ) -> (Arc, Receiver) { @@ -170,7 +169,6 @@ fn register_async_cert_callback( (invoked, rx) } -/// Builds a rustls mTLS client config for the given TLS version. fn rustls_mtls_client( sig_type: SigType, tls_version: &'static rustls::SupportedProtocolVersion, @@ -188,7 +186,6 @@ fn rustls_mtls_client( client.into() } -/// Builds a rustls mTLS server config for the given TLS version. fn rustls_mtls_server( sig_type: SigType, tls_version: &'static rustls::SupportedProtocolVersion, @@ -216,7 +213,6 @@ fn rustls_mtls_server( // Basic mTLS tests // ============================================================================ -// Helper for basic test case fn test_basic(client_cfg: &C::Config, server_cfg: &S::Config) where C: TlsConnection, @@ -282,7 +278,6 @@ fn s2n_server_basic() { // Sync callback tests // ============================================================================ -// Helper for synchronous callback tests fn test_sync_callback(client_cfg: &C::Config, server_cfg: &S::Config, handle: Arc) where C: TlsConnection, @@ -365,7 +360,6 @@ fn s2n_server_sync_callback() { // Async callback tests // ============================================================================ -// Helper for async server-side cert validation tests. fn test_async_server_callback( client_cfg: &C::Config, server_cfg: &S::Config, @@ -400,7 +394,6 @@ where pair } -// Helper for async client-side cert validation tests. fn test_async_client_callback( client_cfg: &C::Config, server_cfg: &S::Config, @@ -474,12 +467,10 @@ fn s2n_client_async_callback() { // rustls client, s2n server with async callback // Rustls TLS 1.2 clients do not send multiple handshake messages in a // single record, so s2n never hits the multi-message async-callback -// bug that appears in TLS 1.3 but both variants are ignored for now -// for simplicity. +// bug that appears in TLS 1.3. These tests are split by protocol +// version until the multi-message bug is fixed. #[test] -#[ignore = "Hangs due to multi-message bug in async cert validation"] -fn s2n_server_async_callback() { - // TLS 1.2 +fn s2n_server_async_callback_tls12() { let client = rustls_mtls_client(SigType::Rsa2048, &rustls::version::TLS12); let (server, handle, rx) = { let builder = s2n_mtls_base_builder(SigType::Rsa2048); @@ -489,8 +480,11 @@ fn s2n_server_async_callback() { }; let _pair = test_async_server_callback::(&client, &server, handle, rx); +} - // TLS 1.3 +#[test] +#[ignore = "Hangs due to multi-message bug in async cert validation"] +fn s2n_server_async_callback_tls13() { crate::capability_check::required_capability( &[crate::capability_check::Capability::Tls13], || { From 6318435652601b7ec39b8b031f6b97ee7dc5fe21 Mon Sep 17 00:00:00 2001 From: kaukabrizvi <100529019+kaukabrizvi@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:48:27 -0800 Subject: [PATCH 28/29] Update bindings/rust/standard/integration/src/mtls.rs Co-authored-by: James Mayclin --- bindings/rust/standard/integration/src/mtls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index f8336c4cf2b..190c79721b0 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -158,9 +158,9 @@ fn register_async_cert_callback( // memory stays valid for the lifetime of the config (test-only). unsafe { let raw = raw_config(s2n_cfg); - let rc = s2n_config_set_cert_validation_cb(raw, Some(test_async_cert_cb), ctx_ptr); + let result = s2n_config_set_cert_validation_cb(raw, Some(test_async_cert_cb), ctx_ptr); assert_eq!( - rc, + result, s2n_status_code::SUCCESS, "s2n_config_set_cert_validation_cb failed" ); From e8ecd646cec5f739a84dd41b69a6c0a03d8104ac Mon Sep 17 00:00:00 2001 From: Kaukab Date: Wed, 3 Dec 2025 22:56:29 +0000 Subject: [PATCH 29/29] Remove immediately accept field in sync callback --- bindings/rust/standard/integration/src/mtls.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bindings/rust/standard/integration/src/mtls.rs b/bindings/rust/standard/integration/src/mtls.rs index 190c79721b0..bd9e3e4434a 100644 --- a/bindings/rust/standard/integration/src/mtls.rs +++ b/bindings/rust/standard/integration/src/mtls.rs @@ -68,14 +68,12 @@ unsafe fn raw_config(cfg: &mut S2NConfig) -> *mut s2n_config { #[derive(Debug)] struct TestCertValidationCallback { invoked: Arc, - immediately_accept: bool, } impl TestCertValidationCallback { fn new_sync() -> Self { Self { invoked: Arc::new(AtomicU64::new(0)), - immediately_accept: true, } } @@ -91,7 +89,7 @@ impl CertValidationCallbackSync for TestCertValidationCallback { _info: &mut CertValidationInfo, ) -> Result { self.invoked.fetch_add(1, Ordering::SeqCst); - Ok(self.immediately_accept) + Ok(true) } }