Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
618c2be
Initial integration test import
kaukabrizvi Nov 19, 2025
44c07e9
Fix mtls cert validation callback to return bool
kaukabrizvi Nov 19, 2025
f53822e
Add helper methods to make module more extensible
kaukabrizvi Nov 21, 2025
7ec5306
Add TLS 1.2 rustls client test
kaukabrizvi Nov 24, 2025
ea2ead8
Formatting
kaukabrizvi Nov 24, 2025
b3de43a
Use TLS1.2 as default for basic and sync callback tests
kaukabrizvi Nov 24, 2025
0ab8b4b
Add capability gating for TLS 1.2 tests
kaukabrizvi Nov 24, 2025
5453524
Add clarifying comments
kaukabrizvi Nov 24, 2025
8af8a9f
More comments
kaukabrizvi Nov 25, 2025
b3af9dc
Apply cargo format
kaukabrizvi Nov 25, 2025
7ee9977
Simplify helper method logic
kaukabrizvi Nov 25, 2025
d9a8bb8
Comment cleanup
kaukabrizvi Nov 25, 2025
4b4b086
apply cargo fmt
kaukabrizvi Nov 25, 2025
a0ce7c0
Reuse repeated async cert verify logic
kaukabrizvi Nov 25, 2025
af9d473
Apply cargo fmt
kaukabrizvi Nov 25, 2025
fee32b2
Add s2n as client test cases
kaukabrizvi Nov 26, 2025
0d22717
Typo fix
kaukabrizvi Nov 26, 2025
b955439
separate sync/async cert validation APIs and gate async behind unstab…
kaukabrizvi Nov 26, 2025
114c019
Apply cargo fmt
kaukabrizvi Nov 27, 2025
7b01e43
Address PR feedback, CI fix
kaukabrizvi Nov 27, 2025
29ed45d
Address PR feedback
kaukabrizvi Dec 1, 2025
a2acae7
Applyc agro fmt
kaukabrizvi Dec 2, 2025
54112e5
Apply cargo fmt
kaukabrizvi Dec 2, 2025
2bd7d2c
Remove stale comments
kaukabrizvi Dec 2, 2025
d94caad
Simplify test body for protocol versions and inline server/client con…
kaukabrizvi Dec 3, 2025
3aab059
Restructure tests for readability
kaukabrizvi Dec 3, 2025
8a41338
Edit doc comments
kaukabrizvi Dec 3, 2025
6318435
Update bindings/rust/standard/integration/src/mtls.rs
kaukabrizvi Dec 3, 2025
e8ecd64
Remove immediately accept field in sync callback
kaukabrizvi Dec 3, 2025
6cc1cfe
Merge branch 'main' into mTLS-integ-rust
kaukabrizvi Dec 3, 2025
3a3e85d
Merge branch 'main' into mTLS-integ-rust
kaukabrizvi Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bindings/rust/extended/s2n-tls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
48 changes: 44 additions & 4 deletions bindings/rust/extended/s2n-tls/src/callbacks/cert_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,50 @@ pub struct CertValidationInfo<'a> {
}

impl CertValidationInfo<'_> {
pub(crate) 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,
_lifetime: PhantomData,
}
}

pub(crate) fn as_ptr(&mut self) -> *mut s2n_cert_validation_info {
/// 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()
}

/// 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(())
}
}

/// 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(
Expand All @@ -48,6 +67,27 @@ pub trait CertValidationCallbackSync: 'static + Send + Sync {
validation_info: &mut CertValidationInfo,
) -> Result<bool, Error>;
}
/// 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<Option<bool>, Error>;
}

#[cfg(test)]
mod tests {
Expand Down
83 changes: 83 additions & 0 deletions bindings/rust/extended/s2n-tls/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,10 @@ 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")]
Expand All @@ -637,6 +641,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_sync.as_ref();
Expand Down Expand Up @@ -671,6 +677,79 @@ 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<T: 'static + CertValidationCallbackAsync>(
&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.
///
/// Corresponds to [s2n_config_set_client_hello_cb].
Expand Down Expand Up @@ -1096,6 +1175,8 @@ pub(crate) struct Context {
pub(crate) cert_authorities: Option<Box<dyn CertificateRequestCallback>>,
#[cfg(feature = "unstable-crl")]
pub(crate) cert_validation_callback_sync: Option<Box<dyn CertValidationCallbackSync>>,
#[cfg(feature = "unstable-async-cert")]
pub(crate) cert_validation_callback_async: Option<Box<dyn CertValidationCallbackAsync>>,
}

impl Default for Context {
Expand All @@ -1120,6 +1201,8 @@ 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,
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion bindings/rust/standard/integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
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" }
Expand All @@ -30,6 +33,7 @@ tls-harness = { path = "../tls-harness" }
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"
Expand Down
2 changes: 2 additions & 0 deletions bindings/rust/standard/integration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading
Loading