Expose a safe Rust interface for the session resumption callback

This commit is contained in:
Apoorv Kothari 2025-09-30 14:44:17 -07:00 committed by Kornel
parent ac1d71cb54
commit ab8513ef8f
6 changed files with 153 additions and 77 deletions

38
boring/src/hmac.rs Normal file
View File

@ -0,0 +1,38 @@
use crate::cvt;
use crate::error::ErrorStack;
use crate::hash::MessageDigest;
use std::ffi::c_void;
use foreign_types::ForeignType;
foreign_type_and_impl_send_sync! {
type CType = ffi::HMAC_CTX;
fn drop = ffi::HMAC_CTX_free;
pub struct HmacCtx;
}
impl HmacCtx {
/// Configures HmacCtx to use `md` as the hash function and `key` as the key.
///
/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex
///
/// # Safety
///
/// The caller must ensure HMAC_CTX has been initalized.
pub unsafe fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> {
ffi::init();
unsafe {
cvt(ffi::HMAC_Init_ex(
self.as_ptr(),
key.as_ptr() as *const c_void,
key.len(),
md.as_ptr(),
// ENGINE api is deprecated
core::ptr::null_mut(),
))
.map(|_| ())
}
}
}

View File

@ -137,6 +137,7 @@ pub mod error;
pub mod ex_data; pub mod ex_data;
pub mod fips; pub mod fips;
pub mod hash; pub mod hash;
pub mod hmac;
pub mod hpke; pub mod hpke;
pub mod memcmp; pub mod memcmp;
pub mod nid; pub mod nid;

View File

@ -8,13 +8,15 @@ use super::{
}; };
use crate::error::ErrorStack; use crate::error::ErrorStack;
use crate::ffi; use crate::ffi;
use crate::hmac::HmacCtx;
use crate::ssl::TicketKeyCallbackResult; use crate::ssl::TicketKeyCallbackResult;
use crate::symm::CipherCtx;
use crate::x509::{X509StoreContext, X509StoreContextRef}; use crate::x509::{X509StoreContext, X509StoreContextRef};
use foreign_types::ForeignType; use foreign_types::ForeignType;
use foreign_types::ForeignTypeRef; use foreign_types::ForeignTypeRef;
use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use libc::{c_char, c_int, c_uchar, c_uint, c_void};
use std::ffi::CStr; use std::ffi::CStr;
use std::mem::MaybeUninit; use std::mem::{ManuallyDrop, MaybeUninit};
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use std::str; use std::str;
@ -288,8 +290,8 @@ where
&SslRef, &SslRef,
&mut [u8; 16], &mut [u8; 16],
&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
*mut ffi::EVP_CIPHER_CTX, &mut CipherCtx,
*mut ffi::HMAC_CTX, &mut HmacCtx,
bool, bool,
) -> TicketKeyCallbackResult ) -> TicketKeyCallbackResult
+ 'static + 'static
@ -325,7 +327,11 @@ where
let key_name = unsafe { key_name.assume_init_mut() }; let key_name = unsafe { key_name.assume_init_mut() };
let iv = unsafe { iv.assume_init_mut() }; let iv = unsafe { iv.assume_init_mut() };
callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() // The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL.
let mut evp_ctx = ManuallyDrop::new(unsafe { CipherCtx::from_ptr(evp_ctx) });
let mut hmac_ctx = ManuallyDrop::new(unsafe { HmacCtx::from_ptr(hmac_ctx) });
callback(ssl, key_name, iv, &mut evp_ctx, &mut hmac_ctx, encrypt).into()
} }
pub(super) unsafe extern "C" fn raw_alpn_select<F>( pub(super) unsafe extern "C" fn raw_alpn_select<F>(

View File

@ -81,6 +81,7 @@ use crate::dh::DhRef;
use crate::ec::EcKeyRef; use crate::ec::EcKeyRef;
use crate::error::ErrorStack; use crate::error::ErrorStack;
use crate::ex_data::Index; use crate::ex_data::Index;
use crate::hmac::HmacCtx;
use crate::nid::Nid; use crate::nid::Nid;
use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::pkey::{HasPrivate, PKeyRef, Params, Private};
use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef};
@ -88,6 +89,7 @@ use crate::ssl::bio::BioMethod;
use crate::ssl::callbacks::*; use crate::ssl::callbacks::*;
use crate::ssl::error::InnerError; use crate::ssl::error::InnerError;
use crate::stack::{Stack, StackRef, Stackable}; use crate::stack::{Stack, StackRef, Stackable};
use crate::symm::CipherCtx;
use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef};
use crate::x509::verify::X509VerifyParamRef; use crate::x509::verify::X509VerifyParamRef;
use crate::x509::{ use crate::x509::{
@ -1137,6 +1139,8 @@ impl SslContextBuilder {
/// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that /// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that
/// ticket key. Thus ticket keys must be regularly rotated for forward secrecy. /// ticket key. Thus ticket keys must be regularly rotated for forward secrecy.
/// ///
/// CipherCtx and HmacCtx are guaranteed to be initialized.
///
/// # Panics /// # Panics
/// ///
/// This method panics if this `Ssl` is associated with a RPK context. /// This method panics if this `Ssl` is associated with a RPK context.
@ -1148,14 +1152,14 @@ impl SslContextBuilder {
/// ///
/// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb /// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb
#[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)]
pub unsafe fn set_ticket_key_callback_unsafe<F>(&mut self, callback: F) pub unsafe fn set_ticket_key_callback<F>(&mut self, callback: F)
where where
F: Fn( F: Fn(
&SslRef, &SslRef,
&mut [u8; 16], &mut [u8; 16],
&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
*mut ffi::EVP_CIPHER_CTX, &mut CipherCtx,
*mut ffi::HMAC_CTX, &mut HmacCtx,
bool, bool,
) -> TicketKeyCallbackResult ) -> TicketKeyCallbackResult
+ 'static + 'static

View File

@ -1,11 +1,12 @@
use super::server::Server; use super::server::Server;
use crate::ssl::test::MessageDigest; use crate::ssl::test::MessageDigest;
use crate::ssl::HmacCtx;
use crate::ssl::SslRef; use crate::ssl::SslRef;
use crate::ssl::SslSession; use crate::ssl::SslSession;
use crate::ssl::SslSessionCacheMode; use crate::ssl::SslSessionCacheMode;
use crate::ssl::TicketKeyCallbackResult; use crate::ssl::TicketKeyCallbackResult;
use crate::symm::Cipher; use crate::symm::Cipher;
use std::ffi::c_void; use crate::symm::CipherCtx;
use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::OnceLock; use std::sync::OnceLock;
@ -60,7 +61,7 @@ fn custom_callback_success() {
unsafe { unsafe {
server server
.ctx() .ctx()
.set_ticket_key_callback_unsafe(test_success_tickey_key_callback) .set_ticket_key_callback(test_success_tickey_key_callback)
}; };
let server = server.build(); let server = server.build();
@ -105,7 +106,7 @@ fn custom_callback_unrecognized_decryption_ticket() {
unsafe { unsafe {
server server
.ctx() .ctx()
.set_ticket_key_callback_unsafe(test_noop_tickey_key_callback) .set_ticket_key_callback(test_noop_tickey_key_callback)
}; };
let server = server.build(); let server = server.build();
@ -147,8 +148,8 @@ fn test_noop_tickey_key_callback(
_ssl: &SslRef, _ssl: &SslRef,
key_name: &mut [u8; 16], key_name: &mut [u8; 16],
iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
evp_ctx: *mut ffi::EVP_CIPHER_CTX, evp_ctx: &mut CipherCtx,
hmac_ctx: *mut ffi::HMAC_CTX, hmac_ctx: &mut HmacCtx,
encrypt: bool, encrypt: bool,
) -> TicketKeyCallbackResult { ) -> TicketKeyCallbackResult {
// These should only be used for testing purposes. // These should only be used for testing purposes.
@ -164,31 +165,16 @@ fn test_noop_tickey_key_callback(
assert_eq!(iv, &[0; 16]); assert_eq!(iv, &[0; 16]);
NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
// Set the encryption context. // Set the encryption context.
let ret = unsafe { unsafe {
ffi::EVP_EncryptInit_ex( evp_ctx
evp_ctx, .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV)
cipher.as_ptr(), .unwrap()
// ENGINE api is deprecated
core::ptr::null_mut(),
TEST_AES_128_CBC_KEY.as_ptr(),
TEST_CBC_IV.as_ptr(),
)
}; };
assert!(ret == 1);
// Set the hmac context. // Set the hmac context.
let ret = unsafe { unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() };
ffi::HMAC_Init_ex(
hmac_ctx,
TEST_HMAC_KEY.as_ptr() as *const c_void,
TEST_HMAC_KEY.len(),
digest.as_ptr(),
// ENGINE api is deprecated
core::ptr::null_mut(),
)
};
assert!(ret == 1);
TicketKeyCallbackResult::Success TicketKeyCallbackResult::Success
} else { } else {
@ -202,8 +188,8 @@ fn test_success_tickey_key_callback(
_ssl: &SslRef, _ssl: &SslRef,
key_name: &mut [u8; 16], key_name: &mut [u8; 16],
iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
evp_ctx: *mut ffi::EVP_CIPHER_CTX, evp_ctx: &mut CipherCtx,
hmac_ctx: *mut ffi::HMAC_CTX, hmac_ctx: &mut HmacCtx,
encrypt: bool, encrypt: bool,
) -> TicketKeyCallbackResult { ) -> TicketKeyCallbackResult {
// These should only be used for testing purposes. // These should only be used for testing purposes.
@ -219,58 +205,27 @@ fn test_success_tickey_key_callback(
assert_eq!(iv, &[0; 16]); assert_eq!(iv, &[0; 16]);
SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
// Set the encryption context. // Set the encryption context.
let ret = unsafe { unsafe {
ffi::EVP_EncryptInit_ex( evp_ctx
evp_ctx, .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV)
cipher.as_ptr(), .unwrap()
// ENGINE api is deprecated
core::ptr::null_mut(),
TEST_AES_128_CBC_KEY.as_ptr(),
TEST_CBC_IV.as_ptr(),
)
}; };
assert!(ret == 1);
// Set the hmac context. // Set the hmac context.
let ret = unsafe { unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() };
ffi::HMAC_Init_ex(
hmac_ctx,
TEST_HMAC_KEY.as_ptr() as *const c_void,
TEST_HMAC_KEY.len(),
digest.as_ptr(),
// ENGINE api is deprecated
core::ptr::null_mut(),
)
};
assert!(ret == 1);
} else { } else {
SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
// Set the decryption context. // Set the decryption context.
let ret = unsafe { unsafe {
ffi::EVP_DecryptInit_ex( evp_ctx
evp_ctx, .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV)
cipher.as_ptr(), .unwrap()
// ENGINE api is deprecated
core::ptr::null_mut(),
TEST_AES_128_CBC_KEY.as_ptr(),
TEST_CBC_IV.as_ptr(),
)
}; };
assert!(ret == 1);
// Set the hmac context. // Set the hmac context.
let ret = unsafe { unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() };
ffi::HMAC_Init_ex(
hmac_ctx,
TEST_HMAC_KEY.as_ptr() as *const c_void,
TEST_HMAC_KEY.len(),
digest.as_ptr(),
// ENGINE api is deprecated
core::ptr::null_mut(),
)
};
assert!(ret == 1);
} }
TicketKeyCallbackResult::Success TicketKeyCallbackResult::Success

View File

@ -53,6 +53,7 @@
//! ``` //! ```
use crate::ffi; use crate::ffi;
use foreign_types::ForeignType;
use libc::{c_int, c_uint}; use libc::{c_int, c_uint};
use openssl_macros::corresponds; use openssl_macros::corresponds;
use std::cmp; use std::cmp;
@ -68,6 +69,77 @@ pub enum Mode {
Decrypt, Decrypt,
} }
foreign_type_and_impl_send_sync! {
type CType = ffi::EVP_CIPHER_CTX;
fn drop = ffi::EVP_CIPHER_CTX_free;
pub struct CipherCtx;
}
impl CipherCtx {
/// Configures CipherCtx for a fresh encryption operation using `cipher`.
///
/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex
///
/// # Safety
///
/// The caller must ensure EVP_CIPHER_CTX has been initalized.
///
/// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the
/// chosen Cipher.
pub unsafe fn init_encrypt(
&mut self,
cipher: &Cipher,
key: &[u8],
iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize],
) -> Result<(), ErrorStack> {
ffi::init();
unsafe {
cvt(ffi::EVP_EncryptInit_ex(
self.as_ptr(),
cipher.as_ptr(),
// ENGINE api is deprecated
ptr::null_mut(),
key.as_ptr(),
iv.as_ptr(),
))
.map(|_| ())
}
}
/// Configures CipherCtx for a fresh decryption operation using `cipher`.
///
/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_DecryptInit_ex
///
/// # Safety
///
/// The caller must ensure EVP_CIPHER_CTX has been initalized.
///
/// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the
/// chosen Cipher.
pub unsafe fn init_decrypt(
&mut self,
cipher: &Cipher,
key: &[u8],
iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize],
) -> Result<(), ErrorStack> {
ffi::init();
unsafe {
cvt(ffi::EVP_DecryptInit_ex(
self.as_ptr(),
cipher.as_ptr(),
// ENGINE api is deprecated
ptr::null_mut(),
key.as_ptr(),
iv.as_ptr(),
))
.map(|_| ())
}
}
}
/// Represents a particular cipher algorithm. /// Represents a particular cipher algorithm.
/// ///
/// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms. /// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms.