| // 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; | |
| use core::{ | |
| ffi::{ | |
| c_int, | |
| c_void, // | |
| }, | |
| marker::PhantomData, | |
| ptr::{ | |
| NonNull, | |
| null, // | |
| }, | |
| task::Context, | |
| }; | |
| use bssl_crypto::FromFfiSlice; | |
| 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, // | |
| }; | |
| /// 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)] | |
| #[non_exhaustive] | |
| 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) | |
| }; | |
| let slice = unsafe { | |
| // Safety: | |
| // - `slice` is only live within this call. | |
| // - `slice` is generated by BoringSSL. | |
| u16::from_ffi_ptr(algs, len) | |
| }; | |
| slice | |
| .iter() | |
| .copied() | |
| .map(SignatureAlgorithm::try_from) | |
| .collect() | |
| } | |
| /// 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) | |
| }; | |
| let slice = unsafe { | |
| // Safety: | |
| // - `types` is only live within this call. | |
| // - `types` is generated by BoringSSL. | |
| u8::from_ffi_ptr(types, len) | |
| }; | |
| slice | |
| .iter() | |
| .copied() | |
| .map(RequestedCertificateType::try_from) | |
| .collect() | |
| } | |
| /// 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()) | |
| } | |
| } |