Skip to content

Commit

Permalink
Implement SSL_CTX_set_alpn_select_cb and callbacks
Browse files Browse the repository at this point in the history
Calling callbacks is less simple than it appears, because
it is usual to re-enter libssl from a callback.  For our
locking scheme, that would imply deadlock if the same
SSL_CTX or SSL are in play.

Instead, we have a `Callbacks` object that just collects
up a list of things to call later, when the lock is dropped.
These things have to be standalone; they cannot borrow from
the locked data.
  • Loading branch information
ctz committed Apr 3, 2024
1 parent 2855677 commit 6651f59
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 18 deletions.
2 changes: 1 addition & 1 deletion rustls-libssl/MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
| `SSL_CTX_set1_param` | | | |
| `SSL_CTX_set_allow_early_data_cb` | | | |
| `SSL_CTX_set_alpn_protos` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `SSL_CTX_set_alpn_select_cb` | | :white_check_mark: | |
| `SSL_CTX_set_alpn_select_cb` | | :white_check_mark: | :white_check_mark: |
| `SSL_CTX_set_async_callback` | | | |
| `SSL_CTX_set_async_callback_arg` | | | |
| `SSL_CTX_set_block_padding` | | | |
Expand Down
1 change: 1 addition & 0 deletions rustls-libssl/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const ENTRYPOINTS: &[&str] = &[
"SSL_CTX_sess_set_new_cb",
"SSL_CTX_sess_set_remove_cb",
"SSL_CTX_set_alpn_protos",
"SSL_CTX_set_alpn_select_cb",
"SSL_CTX_set_cipher_list",
"SSL_CTX_set_ciphersuites",
"SSL_CTX_set_default_passwd_cb",
Expand Down
136 changes: 136 additions & 0 deletions rustls-libssl/src/callbacks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use core::ffi::{c_uchar, c_void};
use core::ptr;
use std::collections::VecDeque;

use openssl_sys::SSL_TLSEXT_ERR_OK;

use crate::entry::{
SSL_CTX_alpn_select_cb_func, _internal_SSL_complete_accept, _internal_SSL_set_alpn_choice, SSL,
};
use crate::error::Error;

/// Collects pending callbacks during a critical section.
///
/// Callback objects must be self-contained and own any neccessary data.
///
/// Callbacks are called in the same order they were `add()`ed.
///
/// This is because (eg) `SSL_accept` takes a lock on the `crate::Ssl`
/// it dispatches to, but this object knows when callbacks need to
/// happen. It therefore adds an item to this object, and then
/// `SSL_accept` execute these once the lock has been dropped.
/// This means the callbacks are free to call further functions that
/// themselves take locks.
pub struct Callbacks {
pending: VecDeque<Box<dyn PendingCallback>>,
ssl: *mut SSL,
}

impl Callbacks {
/// Make a new, empty callback collection.
pub fn new() -> Self {
Self {
pending: VecDeque::new(),
ssl: ptr::null_mut(),
}
}

/// Smuggle the original SSL* pointer via this object
pub fn with_ssl(mut self, ssl: *mut SSL) -> Self {
self.ssl = ssl;
self
}

/// Get the original SSL* pointer, or else `NULL`
pub fn ssl_ptr(&self) -> *mut SSL {
self.ssl
}

/// Add a pending callback.
pub fn add(&mut self, cb: Box<dyn PendingCallback>) {
self.pending.push_back(cb);
}

/// Call the callbacks, in order.
///
/// If one fails, the remainder are not called.
pub fn dispatch(&mut self) -> Result<(), Error> {
while let Some(callback) = self.pending.pop_front() {
callback.call()?;
}
Ok(())
}
}

/// A callback that should be called later.
pub trait PendingCallback {
fn call(self: Box<Self>) -> Result<(), Error>;
}

/// Configuration needed to create a `AlpnPendingCallback` later
#[derive(Debug, Clone)]
pub struct AlpnCallbackConfig {
pub cb: SSL_CTX_alpn_select_cb_func,
pub context: *mut c_void,
}

impl Default for AlpnCallbackConfig {
fn default() -> Self {
Self {
cb: None,
context: ptr::null_mut(),
}
}
}

pub struct AlpnPendingCallback {
pub config: AlpnCallbackConfig,
pub ssl: *mut SSL,
pub offer: Vec<u8>,
}

impl PendingCallback for AlpnPendingCallback {
fn call(self: Box<Self>) -> Result<(), Error> {
let callback = match self.config.cb {
Some(callback) => callback,
None => {
return Ok(());
}
};

let mut output_ptr: *const c_uchar = ptr::null();
let mut output_len = 0u8;
let result = unsafe {
callback(
self.ssl,
&mut output_ptr as *mut *const c_uchar,
&mut output_len as *mut u8,
self.offer.as_ptr(),
self.offer.len() as u32,
self.config.context,
)
};

if result == SSL_TLSEXT_ERR_OK {
_internal_SSL_set_alpn_choice(self.ssl, output_ptr, output_len);
}

Ok(())
}
}

/// The last callback during acceptance of a connection by a server.
///
/// This completes the creation of the `ServerConfig` and `ServerConnection`.
///
/// It doesn't actually call into a user-supplied callback, but instead runs
/// after them.
pub struct CompleteAcceptPendingCallback {
pub ssl: *mut SSL,
}

impl PendingCallback for CompleteAcceptPendingCallback {
fn call(self: Box<Self>) -> Result<(), Error> {
_internal_SSL_complete_accept(self.ssl)
}
}
55 changes: 51 additions & 4 deletions rustls-libssl/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ use openssl_sys::{
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};

use crate::bio::{Bio, BIO, BIO_METHOD};
use crate::callbacks::Callbacks;
use crate::error::{ffi_panic_boundary, Error, MysteriouslyOppositeReturnValue};
use crate::evp_pkey::EvpPkey;
use crate::ffi::{
free_arc, str_from_cstring, to_arc_mut_ptr, try_clone_arc, try_from, try_mut_slice_int,
try_ref_from_ptr, try_slice, try_slice_int, try_str, Castable, OwnershipArc, OwnershipRef,
clone_arc, free_arc, str_from_cstring, to_arc_mut_ptr, try_clone_arc, try_from,
try_mut_slice_int, try_ref_from_ptr, try_slice, try_slice_int, try_str, Castable, OwnershipArc,
OwnershipRef,
};
use crate::x509::OwnedX509;
use crate::ShutdownResult;
Expand Down Expand Up @@ -481,12 +483,36 @@ entry! {
}
}

pub type SSL_CTX_alpn_select_cb_func = Option<
unsafe extern "C" fn(
ssl: *mut SSL,
out: *mut *const c_uchar,
outlen: *mut c_uchar,
in_: *const c_uchar,
inlen: c_uint,
arg: *mut c_void,
) -> c_int,
>;

entry! {
pub fn _SSL_CTX_set_alpn_select_cb(
ctx: *mut SSL_CTX,
cb: SSL_CTX_alpn_select_cb_func,
arg: *mut c_void,
) {
let ctx = try_clone_arc!(ctx);
ctx.lock()
.ok()
.map(|mut ctx| ctx.set_alpn_select_cb(cb, arg));
}
}

impl Castable for SSL_CTX {
type Ownership = OwnershipArc;
type RustType = Mutex<SSL_CTX>;
}

type SSL = crate::Ssl;
pub type SSL = crate::Ssl;

entry! {
pub fn _SSL_new(ctx: *mut SSL_CTX) -> *mut SSL {
Expand Down Expand Up @@ -604,6 +630,25 @@ entry! {
}
}

/// Tail end of ALPN selection callback
pub fn _internal_SSL_set_alpn_choice(ssl: *mut SSL, proto: *const c_uchar, len: c_uchar) {
let ssl = try_clone_arc!(ssl);
let slice = try_slice!(proto, len as usize);

ssl.lock()
.ok()
.map(|mut ssl| ssl.set_alpn_offer(vec![slice.to_vec()]));
}

/// Tail end of server acceptance callbacks
pub fn _internal_SSL_complete_accept(ssl: *mut SSL) -> Result<(), Error> {
// called by ourselves, `ssl` is known to be non-NULL
let ssl = clone_arc(ssl).unwrap();
ssl.lock()
.map_err(|_| Error::cannot_lock())
.and_then(|mut ssl| ssl.complete_accept())
}

entry! {
pub fn _SSL_set_connect_state(ssl: *mut SSL) {
let ssl = try_clone_arc!(ssl);
Expand Down Expand Up @@ -727,13 +772,15 @@ entry! {

entry! {
pub fn _SSL_accept(ssl: *mut SSL) -> c_int {
let mut callbacks = Callbacks::new().with_ssl(ssl);
let ssl = try_clone_arc!(ssl);

match ssl
.lock()
.map_err(|_| Error::cannot_lock())
.and_then(|mut ssl| ssl.accept())
.and_then(|mut ssl| ssl.accept(&mut callbacks))
.map_err(|err| err.raise())
.and_then(|()| callbacks.dispatch())
{
Err(e) => e.into(),
Ok(()) => C_INT_SUCCESS,
Expand Down
Loading

0 comments on commit 6651f59

Please sign in to comment.