blob: 94352fd5afb18dc9c0414451db4211745fb91875 [file]
// Copyright 2026 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Select Certificate Callback types
use core::{
marker::PhantomData,
mem::transmute,
ptr::{
NonNull, //
null,
}, //
};
use crate::{
EarlyCallbackMethods,
abort_on_panic,
config::CipherSuite,
connection::{
Server,
lifecycle::TlsConnectionInHandshake, //
},
ffi::sanitize_slice, //
};
bssl_macros::bssl_enum! {
/// Results from selecting a certificate in the callback.
pub enum EarlyCallbackResult: i32 {
/// The certificate selection was successful.
Success = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_success as i32,
/// The operation could not be immediately completed and must be reattempted at a later point.
Retry = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_retry as i32,
/// A fatal error occurred and the handshake should be terminated.
ErrorResult = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error as i32,
/// Disable ECH.
/// The callback should return this result when an encrypted `ClientHelloInner` was
/// decrypted but should be discarded.
/// In this case, the callback will be called again with `ClientHelloOuter` as input
/// instead and the handshake will proceed with `retry_config`s to signal to the client that
/// ECH is disabled.
/// This value may only be returned when `SSL_ech_accepted` returns one.
/// It may be useful if the `ClientHelloInner` indicated lack of ECH support in the service,
/// for example if it is a TLS-1.2 only service.
DisableEch = bssl_sys::ssl_select_cert_result_t_ssl_select_cert_disable_ech as i32,
}
}
/// TLS Extension Types.
///
/// See [IANA assignment](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1)
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
pub struct ExtensionType(pub u16);
impl ExtensionType {
/// Server Name Indication
///
/// [RFC 6066, §3](https://datatracker.ietf.org/doc/html/rfc6066#section-3)
pub const SERVER_NAME: Self = Self(bssl_sys::TLSEXT_TYPE_server_name as u16);
/// Status Request
///
/// [RFC 6066, §8](https://datatracker.ietf.org/doc/html/rfc6066#section-8)
pub const STATUS_REQUEST: Self = Self(bssl_sys::TLSEXT_TYPE_status_request as u16);
/// Supported Groups
///
/// [RFC 8446, §4.2.7](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7)
pub const SUPPORTED_GROUPS: Self = Self(bssl_sys::TLSEXT_TYPE_supported_groups as u16);
/// Signature Algorithms
///
/// [RFC 8446, §4.2.3](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3)
pub const SIGNATURE_ALGORITHMS: Self = Self(bssl_sys::TLSEXT_TYPE_signature_algorithms as u16);
/// Application Layer Protocol Negotiation
///
/// [RFC 7301, §3](https://datatracker.ietf.org/doc/html/rfc7301#section-3)
pub const ALPN: Self =
Self(bssl_sys::TLSEXT_TYPE_application_layer_protocol_negotiation as u16);
/// Key Share
///
/// [RFC 8446, §4.2.8](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8)
pub const KEY_SHARE: Self = Self(bssl_sys::TLSEXT_TYPE_key_share as u16);
/// Supported Versions
///
/// [RFC 8446, §4.2.1](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.1)
pub const SUPPORTED_VERSIONS: Self = Self(bssl_sys::TLSEXT_TYPE_supported_versions as u16);
/// Pre-Shared Key
///
/// [RFC 8446, §4.2.11](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.11)
pub const PRE_SHARED_KEY: Self = Self(bssl_sys::TLSEXT_TYPE_pre_shared_key as u16);
/// Cookie
///
/// [RFC 8446, §4.2.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.2)
pub const COOKIE: Self = Self(bssl_sys::TLSEXT_TYPE_cookie as u16);
/// PSK Key Exchange Modes
///
/// [RFC 8446, §4.2.9](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.9)
pub const PSK_KEY_EXCHANGE_MODES: Self =
Self(bssl_sys::TLSEXT_TYPE_psk_key_exchange_modes as u16);
/// Certificate Compression
///
/// [RFC 8879](https://datatracker.ietf.org/doc/html/rfc8879)
pub const CERT_COMPRESSION: Self = Self(bssl_sys::TLSEXT_TYPE_cert_compression as u16);
/// Session Ticket
///
/// [RFC 5077](https://datatracker.ietf.org/doc/html/rfc5077)
pub const SESSION_TICKET: Self = Self(bssl_sys::TLSEXT_TYPE_session_ticket as u16);
/// Extended Master Secret
///
/// [RFC 7627](https://datatracker.ietf.org/doc/html/rfc7627)
pub const EXTENDED_MASTER_SECRET: Self =
Self(bssl_sys::TLSEXT_TYPE_extended_master_secret as u16);
/// Client Certificate Type
///
/// [RFC 7250](https://datatracker.ietf.org/doc/html/rfc7250)
pub const CLIENT_CERT_TYPE: Self = Self(bssl_sys::TLSEXT_TYPE_client_cert_type as u16);
/// Server Certificate Type
///
/// [RFC 7250](https://datatracker.ietf.org/doc/html/rfc7250)
pub const SERVER_CERT_TYPE: Self = Self(bssl_sys::TLSEXT_TYPE_server_cert_type as u16);
}
/// A wrapper around the `ClientHello` data passed to the select certificate callback.
///
/// The structure is prescribed in [RFC 8446] §4.1.2.
///
/// [RFC 8446]: <https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2>
pub struct ClientHello<'a, M> {
ptr: *const bssl_sys::SSL_CLIENT_HELLO,
conn: NonNull<bssl_sys::SSL>,
_p: PhantomData<&'a M>,
}
macro_rules! client_hello_getter {
($client_hello:expr, $data:ident, $len:ident) => {
unsafe {
// Safety: `$client_hello` is a valid pointer
sanitize_slice($client_hello.$data, $client_hello.$len)?
}
};
}
impl<'a, M> ClientHello<'a, M> {
/// Exposes a mutable reference to the associated server connection in handshake.
///
/// The return handle has full capability to configure your server-side connection for the
/// subsequent handshake.
/// This handle can be used to set connection-specific credentials or certificates during the
/// callback, for example.
pub fn connection_mut(&mut self) -> TlsConnectionInHandshake<'_, Server, M> {
unsafe {
// Safety: `self.conn` is a `repr(transparent)` wrapper around a `NonNull<SSL>`.
let conn_ref = transmute(&mut self.conn);
TlsConnectionInHandshake(conn_ref)
}
}
/// Get the client random bytes of length 32.
///
/// This method returns [`None`] if the random bytes are absent or of wrong length.
pub fn random(&self) -> Option<[u8; 32]> {
let random = client_hello_getter!(*self.ptr, random, random_len);
random.try_into().ok()
}
/// Get the legacy session ID bytes.
pub fn legacy_session_id(&self) -> Option<[u8; 32]> {
let session_id = client_hello_getter!(*self.ptr, session_id, session_id_len);
session_id.try_into().ok()
}
/// Get the cipher suites bytes.
///
/// This method returns [`None`] if the `cipher_suites` field is absent or contains error while
/// parsing.
pub fn cipher_suites(&self) -> Option<Vec<CipherSuite>> {
let cipher_suites = client_hello_getter!(*self.ptr, cipher_suites, cipher_suites_len);
if cipher_suites.len() % 2 != 0 {
return None;
}
let mut result = Vec::with_capacity(cipher_suites.len() / 2);
for suite in cipher_suites.chunks_exact(2) {
let suite = u16::from_be_bytes(
suite
.try_into()
.expect("we have checked that length of cipher_suites is multiple of 2"),
);
result.push(CipherSuite(suite));
}
Some(result)
}
/// Get the legacy compression methods bytes.
pub fn legacy_compression_methods(&self) -> &'a [u8] {
unsafe {
// Safety: `self.ptr` is a valid pointer to `SSL_CLIENT_HELLO` provided by BoringSSL.
sanitize_slice(
(*self.ptr).compression_methods,
(*self.ptr).compression_methods_len,
)
.unwrap_or(&[])
}
}
/// Get the extensions bytes.
pub fn extensions(&self) -> &'a [u8] {
unsafe {
// Safety: `self.ptr` is a valid pointer to `SSL_CLIENT_HELLO` provided by BoringSSL.
sanitize_slice((*self.ptr).extensions, (*self.ptr).extensions_len).unwrap_or(&[])
}
}
/// Extract a specific extension from the client hello.
///
/// The returned slice contains the raw extension data, excluding the
/// extension type and length headers. The format of these bytes depends
/// on the specific extension type as defined in the relevant RFCs.
pub fn get_extension(&self, extension_type: ExtensionType) -> Option<&'a [u8]> {
let mut out_data = null();
let mut out_len = 0;
let ret = unsafe {
// Safety: `self.ptr` is a valid pointer to `SSL_CLIENT_HELLO` provided by BoringSSL.
bssl_sys::SSL_early_callback_ctx_extension_get(
self.ptr,
extension_type.0,
&mut out_data,
&mut out_len,
)
};
if ret == 1 {
unsafe {
// Safety: `out_data` and `out_len` are valid if the function returns 1.
sanitize_slice(out_data, out_len)
}
} else {
None
}
}
}
/// Early callback handler.
///
/// This callback happens before TLS server state machine makes progress when [`ClientHello`] is
/// received.
/// A good use case is when a TLS server could make further configuration in response to the client
/// requests.
pub trait EarlyCallback<M>: Send + Sync {
/// Decide whether a certificate can be selected.
///
/// The connection handle is accessible through [`ClientHello::connection_mut`].
fn process(&self, client_hello: &mut ClientHello<'_, M>) -> EarlyCallbackResult;
}
pub(crate) unsafe extern "C" fn early_select_cert_cb<Mode, MethodsT>(
client_hello: *const bssl_sys::SSL_CLIENT_HELLO,
) -> bssl_sys::ssl_select_cert_result_t
where
MethodsT: EarlyCallbackMethods<Mode>,
{
let Some(client_hello_ptr) = NonNull::new(client_hello as *mut bssl_sys::SSL_CLIENT_HELLO)
else {
return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error;
};
let ssl = unsafe {
// Safety: By BoringSSL invariant the `SSL_CLIENT_HELLO` handle must be valid
// and only contains object handles that are managed by this crate or their derivatives.
(*client_hello_ptr.as_ptr()).ssl
};
let Some(ssl_ptr) = NonNull::new(ssl) else {
return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error;
};
let Some(methods) = (unsafe {
// Safety: the connection handle must be originated from this crate
MethodsT::from_ssl(ssl_ptr.as_ptr())
}) else {
return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_error;
};
let Some(handler) = methods.early_callback_handler() else {
return bssl_sys::ssl_select_cert_result_t_ssl_select_cert_success;
};
let mut wrapped_hello = ClientHello {
ptr: client_hello_ptr.as_ptr(),
conn: ssl_ptr,
_p: PhantomData,
};
let res = abort_on_panic(move || handler.process(&mut wrapped_hello));
res as bssl_sys::ssl_select_cert_result_t
}