blob: 293bf8405e621152f97e111174e9a23a3db0d490 [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.
//! TLS certificate selection hook.
use alloc::{vec, vec::Vec};
use core::{
ffi::{c_int, c_void},
marker::PhantomData,
ptr::{NonNull, null},
task::Context,
};
use super::{
CryptoBufferIterator,
DistinguishedName, //
SignatureAlgorithm,
get_peer_certificate_type,
get_peer_raw_public_key,
};
use crate::{
CertCallback,
abort_on_panic,
config::ProtocolVersion,
connection::methods::waker_data_from_ssl,
context::TlsContext,
ffi::sanitize_slice, //
};
/// Result of certificate selection.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CertificateSelectionResult {
/// Certificate selection succeeded.
Success,
/// Certificate selection is pending (e.g. waiting for async operation).
/// The handshake will be paused and can be retried later.
Pending,
/// Certificate selection failed. The handshake will be aborted.
Error,
}
/// Server certificate selection.
///
/// Implementations must be `Send + Sync`. If the selector needs mutable state,
/// use interior mutability (e.g. `Mutex`).
pub trait ServerCertificateSelector<Mode>: Send + Sync {
/// This method should return `CertificateSelectionResult` to indicate the outcome of the selection.
fn select(
&self,
ctx: ServerCertificateSelectionContext<'_, Mode>,
waker: Option<&'_ mut Context<'_>>,
) -> CertificateSelectionResult;
}
/// Client certificate selection.
///
/// Implementations must be `Send + Sync`. If the selector needs mutable state,
/// use interior mutability (e.g. `Mutex`).
pub trait ClientCertificateSelector<M>: Send + Sync {
/// This method should return `CertificateSelectionResult` to indicate the outcome of the selection.
fn select(
&self,
ctx: ClientCertificateSelectionContext<'_, M>,
waker: Option<&'_ mut Context<'_>>,
) -> CertificateSelectionResult;
}
/// Certificate selection callback.
pub(crate) unsafe extern "C" fn select_cert_cb<M: CertCallback<Mode>, Mode>(
ssl: *mut bssl_sys::SSL,
_ctx: *mut c_void,
) -> c_int {
let is_server = unsafe {
// Safety: `ssl` is still valid by BoringSSL invariant.
bssl_sys::SSL_is_server(ssl) == 1
};
let Some(ssl) = NonNull::new(ssl) else {
return 0;
};
let Some(methods) = (unsafe {
// Safety: this callback is installed by this crate so the ex_data index must be valid.
M::from_ssl(ssl.as_ptr())
}) else {
return 0;
};
let waker = unsafe {
// Safety: this callback is installed by this crate so the associated data must have been
// set up correctly.
waker_data_from_ssl(ssl)
};
let waker = if let Some(waker) = &waker {
Some(&mut core::task::Context::from_waker(waker))
} else {
None
};
if is_server {
let ctx = ServerCertificateSelectionContext::new(ssl);
let Some(server_cert_cb) = methods.server_cert_cb() else {
return 1;
};
match abort_on_panic(move || server_cert_cb.select(ctx, waker)) {
CertificateSelectionResult::Success => 1,
CertificateSelectionResult::Pending => -1,
CertificateSelectionResult::Error => 0,
}
} else {
let ctx = ClientCertificateSelectionContext::new(ssl);
let Some(client_cert_cb) = methods.client_cert_cb() else {
return 1;
};
match abort_on_panic(move || client_cert_cb.select(ctx, waker)) {
CertificateSelectionResult::Success => 1,
CertificateSelectionResult::Pending => -1,
CertificateSelectionResult::Error => 0,
}
}
}
/// Server certificate selection context.
#[repr(transparent)]
pub struct ServerCertificateSelectionContext<'a, Mode>(
NonNull<bssl_sys::SSL>,
PhantomData<&'a fn() -> Mode>,
);
impl<'a, M> ServerCertificateSelectionContext<'a, M> {
pub(crate) fn new(ssl: NonNull<bssl_sys::SSL>) -> Self {
Self(ssl, PhantomData)
}
/// Switch context.
pub fn set_context(&mut self, ctx: &TlsContext<M>) {
unsafe {
// Safety: `self.0` is still valid by BoringSSL invariant.
bssl_sys::SSL_set_SSL_CTX(self.0.as_ptr(), ctx.ptr());
}
}
/// Append `credential` to the list of credentials of this connection.
///
/// Earlier calls to this method append a credential that is preferred over those added
/// in later calls.
pub fn add_credential(
&mut self,
credential: &super::TlsCredential,
) -> Result<(), super::super::errors::Error> {
crate::check_lib_error!(unsafe {
// Safety: both `self.0` and `credential` are still valid.
bssl_sys::SSL_add1_credential(self.0.as_ptr(), credential.ptr())
});
Ok(())
}
/// Get the peer's [`CertificateType`](super::CertificateType).
pub fn get_peer_certificate_type(&self) -> Option<super::CertificateType> {
get_peer_certificate_type(self.0.as_ptr())
}
/// Get the peer's raw public key as DER-encoded `SubjectPublicKeyInfo`.
pub fn get_peer_raw_public_key(&self) -> Option<Vec<u8>> {
get_peer_raw_public_key(self.0.as_ptr())
}
}
bssl_macros::bssl_enum! {
/// Requested certificate types defined in [RFC 5246] ยง7.4.4.
///
/// [RFC 5246]: <https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.4>
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RequestedCertificateType: u8 {
/// RSA signed certificate.
RsaSign = 1,
/// DSS signed certificate.
DssSign = 2,
/// RSA signed certificate with a static Diffie-Hellman key.
RsaFixedDh = 3,
/// DSS signed certificate with a static Diffie-Hellman key.
DssFixedDh = 4,
/// Reserved.
RsaEphemeralDh = 5,
/// Reserved.
DssEphemeralDh = 6,
/// Reserved.
FortezzaDms = 20,
}
}
/// Client Certificate Selection.
pub struct ClientCertificateSelectionContext<'a, Mode>(
NonNull<bssl_sys::SSL>,
PhantomData<&'a fn() -> Mode>,
);
impl<'a, M> ClientCertificateSelectionContext<'a, M> {
pub(crate) fn new(ssl: NonNull<bssl_sys::SSL>) -> Self {
Self(ssl, PhantomData)
}
pub(crate) fn ptr(&self) -> *mut bssl_sys::SSL {
self.0.as_ptr()
}
/// Protocol version selected.
///
/// This method returns [`None`] if the protocol version is unrecognised.
pub fn protocol_version(&self) -> Option<ProtocolVersion> {
ProtocolVersion::try_from(unsafe {
// Safety: `self.0` is still valid by BoringSSL invariant.
bssl_sys::SSL_version(self.ptr())
})
.ok()
}
/// Get verification algorithm from the peer.
///
/// If a signature algorithm is unrecognised, it will be reported as an `Err` value.
/// This method might return an empty list if the protocol version is not TLS 1.3.
pub fn get_tls13_peer_verification_algorithms(&self) -> Vec<Result<SignatureAlgorithm, u16>> {
let mut algs = null();
let len = unsafe {
// Safety: `self.0` is still valid by BoringSSL invariant.
bssl_sys::SSL_get0_peer_verify_algorithms(self.ptr(), &raw mut algs)
};
if let Some(slice) = unsafe {
// Safety: `algs` is only live within this call.
sanitize_slice(algs, len)
} {
slice
.iter()
.copied()
.map(SignatureAlgorithm::try_from)
.collect()
} else {
vec![]
}
}
/// Get TLS 1.2 certificate types.
///
/// If a certificate type is unrecognised, it will be reported as an `Err` value.
/// This method returns an empty list if the protocol version is TLS 1.3.
pub fn get_tls12_certificate_types(&self) -> Vec<Result<RequestedCertificateType, u8>> {
let mut types = null();
let len = unsafe {
// Safety: `self.0` is still valid by BoringSSL invariant.
bssl_sys::SSL_get0_certificate_types(self.ptr(), &raw mut types)
};
if let Some(slice) = unsafe {
// Safety: `types` is only live within this call.
sanitize_slice(types, len)
} {
slice
.iter()
.copied()
.map(RequestedCertificateType::try_from)
.collect()
} else {
vec![]
}
}
/// Get server requested CAs for client authentication.
///
/// These names are sent by the server and they are the acceptable CAs by server.
pub fn get_server_ca_list(&self) -> Vec<DistinguishedName> {
let names = unsafe {
// Safety: `self.ptr()` is still valid by BoringSSL invariant.
bssl_sys::SSL_get0_server_requested_CAs(self.ptr())
};
let names: CryptoBufferIterator<'_, DistinguishedName> = unsafe {
// Safety: `names` is owned by SSL and only live within this call.
CryptoBufferIterator::new(names)
};
names.collect()
}
/// Append `credential` to the list of credentials of this connection.
///
/// Earlier calls to this method append a credential that is preferred over those added
/// in later calls.
pub fn add_credential(
&mut self,
credential: &super::TlsCredential,
) -> Result<(), super::super::errors::Error> {
crate::check_lib_error!(unsafe {
// Safety: both `self.0` and `credential` are still valid.
bssl_sys::SSL_add1_credential(self.0.as_ptr(), credential.ptr())
});
Ok(())
}
/// Get the peer's [`CertificateType`](super::CertificateType).
pub fn get_peer_certificate_type(&self) -> Option<super::CertificateType> {
super::get_peer_certificate_type(self.ptr())
}
/// Get the peer's raw public key as DER-encoded `SubjectPublicKeyInfo`.
pub fn get_peer_raw_public_key(&self) -> Option<Vec<u8>> {
super::get_peer_raw_public_key(self.ptr())
}
}