diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index eb595019..64e7a05e 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -12,6 +12,7 @@ use std::sync::Arc; use error::ErrorStack; use ssl::AlpnError; +use ssl::{ClientHello, SelectCertError}; use ssl::{ SniError, Ssl, SslAlert, SslContext, SslContextRef, SslRef, SslSession, SslSessionRef, SESSION_CTX_INDEX, @@ -190,6 +191,25 @@ where } } +pub unsafe extern "C" fn raw_select_cert( + client_hello: *const ffi::SSL_CLIENT_HELLO, +) -> ffi::ssl_select_cert_result_t +where + F: Fn(&ClientHello) -> Result<(), SelectCertError> + Sync + Send + 'static, +{ + let ssl = SslRef::from_ptr_mut((*client_hello).ssl); + let client_hello = &*(client_hello as *const ClientHello); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: select cert callback missing") as *const F; + + match (*callback)(client_hello) { + Ok(()) => ffi::ssl_select_cert_result_t::ssl_select_cert_success, + Err(e) => e.0, + } +} + pub unsafe extern "C" fn raw_tlsext_status(ssl: *mut ffi::SSL, _: *mut c_void) -> c_int where F: Fn(&mut SslRef) -> Result + 'static + Sync + Send, diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 25e3f7ca..9e98812f 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -463,6 +463,15 @@ impl AlpnError { pub const NOACK: AlpnError = AlpnError(ffi::SSL_TLSEXT_ERR_NOACK); } +/// An error returned from a certificate selection callback. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SelectCertError(ffi::ssl_select_cert_result_t); + +impl SelectCertError { + /// A fatal error occured and the handshake should be terminated. + pub const ERROR: Self = Self(ffi::ssl_select_cert_result_t::ssl_select_cert_error); +} + /// An SSL/TLS protocol version. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct SslVersion(u16); @@ -1084,6 +1093,25 @@ impl SslContextBuilder { ); } } + /// Sets a callback that is called before most ClientHello processing and before the decision whether + /// to resume a session is made. The callback may inspect the ClientHello and configure the + /// connection. + /// + /// This corresponds to [`SSL_CTX_set_select_certificate_cb`]. + /// + /// [`SSL_CTX_set_select_certificate_cb`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set_select_certificate_cb.html + pub fn set_select_certificate_callback(&mut self, callback: F) + where + F: Fn(&ClientHello) -> Result<(), SelectCertError> + Sync + Send + 'static, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_select_certificate_cb( + self.as_ptr(), + Some(callbacks::raw_select_cert::), + ); + } + } /// Checks for consistency between the private key and certificate. /// @@ -1560,6 +1588,9 @@ pub struct CipherBits { pub algorithm: i32, } +#[repr(transparent)] +pub struct ClientHello(ffi::SSL_CLIENT_HELLO); + /// Information about a cipher. pub struct SslCipher(*mut ffi::SSL_CIPHER); diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 35e43dfd..0dc663f9 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -480,6 +480,31 @@ fn test_alpn_server_unilateral() { assert_eq!(None, s.ssl().selected_alpn_protocol()); } +#[test] +fn test_select_cert_ok() { + let mut server = Server::builder(); + server + .ctx() + .set_select_certificate_callback(|_client_hello| Ok(())); + let server = server.build(); + + let client = server.client(); + client.connect(); +} + +#[test] +fn test_select_cert_error() { + let mut server = Server::builder(); + server.should_error(); + server + .ctx() + .set_select_certificate_callback(|_client_hello| Err(ssl::SelectCertError::ERROR)); + let server = server.build(); + + let client = server.client(); + client.connect_err(); +} + #[test] #[should_panic(expected = "blammo")] fn write_panic() {