Expose SSL_CTX_set_info_callback (#266)

Model callback arguments as structs
This commit is contained in:
Evan Rittenhouse 2024-09-11 03:35:51 -05:00 committed by GitHub
parent 7324db2b75
commit b2525f2ed2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 118 additions and 2 deletions

View File

@ -2,8 +2,9 @@
use super::{
AlpnError, ClientHello, GetSessionPendingError, PrivateKeyMethod, PrivateKeyMethodError,
SelectCertError, SniError, Ssl, SslAlert, SslContext, SslContextRef, SslRef, SslSession,
SslSessionRef, SslSignatureAlgorithm, SslVerifyError, SESSION_CTX_INDEX,
SelectCertError, SniError, Ssl, SslAlert, SslContext, SslContextRef, SslInfoCallbackAlert,
SslInfoCallbackMode, SslInfoCallbackValue, SslRef, SslSession, SslSessionRef,
SslSignatureAlgorithm, SslVerifyError, SESSION_CTX_INDEX,
};
use crate::error::ErrorStack;
use crate::ffi;
@ -521,3 +522,32 @@ where
Err(err) => err.0,
}
}
pub(super) unsafe extern "C" fn raw_info_callback<F>(
ssl: *const ffi::SSL,
mode: c_int,
value: c_int,
) where
F: Fn(&SslRef, SslInfoCallbackMode, SslInfoCallbackValue) + Send + Sync + 'static,
{
// Due to FFI signature requirements we have to pass a *const SSL into this function, but
// foreign-types requires a *mut SSL to get the Rust SslRef
let mut_ref = ssl as *mut ffi::SSL;
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr(mut_ref) };
let ssl_context = ssl.ssl_context();
let callback = ssl_context
.ex_data(SslContext::cached_ex_index::<F>())
.expect("BUG: info callback missing");
let value = match mode {
ffi::SSL_CB_READ_ALERT | ffi::SSL_CB_WRITE_ALERT => {
SslInfoCallbackValue::Alert(SslInfoCallbackAlert(value))
}
_ => SslInfoCallbackValue::Unit,
};
callback(ssl, SslInfoCallbackMode(mode), value);
}

View File

@ -834,6 +834,66 @@ pub fn select_next_proto<'a>(server: &[u8], client: &'a [u8]) -> Option<&'a [u8]
}
}
/// Options controlling the behavior of the info callback.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub struct SslInfoCallbackMode(i32);
impl SslInfoCallbackMode {
/// Signaled for each alert received, warning or fatal.
pub const READ_ALERT: Self = Self(ffi::SSL_CB_READ_ALERT);
/// Signaled for each alert sent, warning or fatal.
pub const WRITE_ALERT: Self = Self(ffi::SSL_CB_WRITE_ALERT);
/// Signaled when a handshake begins.
pub const HANDSHAKE_START: Self = Self(ffi::SSL_CB_HANDSHAKE_START);
/// Signaled when a handshake completes successfully.
pub const HANDSHAKE_DONE: Self = Self(ffi::SSL_CB_HANDSHAKE_DONE);
/// Signaled when a handshake progresses to a new state.
pub const ACCEPT_LOOP: Self = Self(ffi::SSL_CB_ACCEPT_LOOP);
}
/// The `value` argument to an info callback. The most-significant byte is the alert level, while
/// the least significant byte is the alert itself.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub enum SslInfoCallbackValue {
/// The unit value (1). Some BoringSSL info callback modes, like ACCEPT_LOOP, always call the
/// callback with `value` set to the unit value. If the [`SslInfoCallbackValue`] is a
/// `Unit`, it can safely be disregarded.
Unit,
/// An alert. See [`SslInfoCallbackAlert`] for details on how to manipulate the alert. This
/// variant should only be present if the info callback was called with a `READ_ALERT` or
/// `WRITE_ALERT` mode.
Alert(SslInfoCallbackAlert),
}
#[derive(Hash, Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)]
pub struct SslInfoCallbackAlert(c_int);
impl SslInfoCallbackAlert {
/// The level of the SSL alert.
pub fn alert_level(&self) -> Ssl3AlertLevel {
let value = self.0 >> 8;
Ssl3AlertLevel(value)
}
/// The value of the SSL alert.
pub fn alert(&self) -> SslAlert {
let value = self.0 & (u8::MAX as i32);
SslAlert(value)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Ssl3AlertLevel(c_int);
impl Ssl3AlertLevel {
pub const WARNING: Ssl3AlertLevel = Self(ffi::SSL3_AL_WARNING);
pub const FATAL: Ssl3AlertLevel = Self(ffi::SSL3_AL_FATAL);
}
#[cfg(feature = "rpk")]
extern "C" fn rpk_verify_failure_callback(
_ssl: *mut ffi::SSL,
@ -1818,6 +1878,18 @@ impl SslContextBuilder {
unsafe { cvt_0i(ffi::SSL_CTX_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) }
}
/// Sets the context's info callback.
#[corresponds(SSL_CTX_set_info_callback)]
pub fn set_info_callback<F>(&mut self, callback: F)
where
F: Fn(&SslRef, SslInfoCallbackMode, SslInfoCallbackValue) + Send + Sync + 'static,
{
unsafe {
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
ffi::SSL_CTX_set_info_callback(self.as_ptr(), Some(callbacks::raw_info_callback::<F>));
}
}
/// Consumes the builder, returning a new `SslContext`.
pub fn build(self) -> SslContext {
self.ctx

View File

@ -1052,3 +1052,17 @@ fn drop_ex_data_in_ssl() {
assert_eq!(ssl.replace_ex_data(index, "camembert"), Some("comté"));
assert_eq!(ssl.replace_ex_data(index, "raclette"), Some("camembert"));
}
#[test]
fn test_info_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client();
client.ctx().set_info_callback(move |_, _, _| {
CALLED_BACK.store(true, Ordering::Relaxed);
});
client.connect();
assert!(CALLED_BACK.load(Ordering::Relaxed));
}