blob: b7610efb869ad4960bc377ded48adfbbf8cd060e [file] [edit]
// 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 credentials
use alloc::{
boxed::Box,
vec,
vec::Vec, //
};
use core::{
ffi::{
CStr,
c_int, //
},
fmt::Debug,
future::Future,
iter::FusedIterator,
marker::PhantomData,
mem::forget,
pin::Pin,
ptr::{
NonNull,
null_mut, //
},
task::{
Context,
Poll, //
}, //
};
use bssl_x509::{
errors::PemReason,
keys::PrivateKey, //
};
use crate::{
VerifyCertificateMethods,
abort_on_panic,
alerts::AlertDescription,
call_slice_getter,
check_lib_error,
config::ConfigurationError,
connection::methods::{verify_cert_task_from_ssl, waker_data_from_ssl},
context::CertificateCache,
crypto_buffer_wrapper,
errors::{
Error,
IoError, //
},
ffi::{
Alloc,
Bio,
sanitize_slice,
slice_into_ffi_raw_parts, //
},
has_duplicates, //
};
pub(crate) mod methods;
/// TLS credentials builder
pub struct TlsCredentialBuilder<Mode>(NonNull<bssl_sys::SSL_CREDENTIAL>, PhantomData<fn() -> Mode>);
/// X.509 credential
pub enum X509Mode {}
pub(crate) trait NeedsPrivateKey {}
impl NeedsPrivateKey for X509Mode {}
// Safety: At this type state, the credential handle is exclusively owned.
unsafe impl<M> Send for TlsCredentialBuilder<M> {}
impl<M> Drop for TlsCredentialBuilder<M> {
fn drop(&mut self) {
unsafe {
// Safety: `self.0` is still valid at dropping.
bssl_sys::SSL_CREDENTIAL_free(self.0.as_ptr());
}
}
}
impl<M> TlsCredentialBuilder<M> {
fn ptr(&mut self) -> *mut bssl_sys::SSL_CREDENTIAL {
self.0.as_ptr()
}
fn set_ex_data(mut self) -> Self {
let rc = unsafe {
// Safety:
// - this method is called exactly once during construction.
// - the ex_data index will be generated correctly and exactly once.
// - the `SSL_CREDENTIAL*` handle is already valid, witnessed by `self`.
bssl_sys::SSL_CREDENTIAL_set_ex_data(
self.ptr(),
*methods::TLS_CREDENTIAL_METHOD,
Box::into_raw(Box::new(methods::RustCredentialMethods::default())) as _,
)
};
assert_eq!(rc, 1);
self
}
}
impl TlsCredentialBuilder<X509Mode> {
/// Construct X.509-powered credential instance.
pub fn new() -> Self {
let this = Self(
NonNull::new(unsafe {
// Safety: this call has no side-effect other than allocation.
bssl_sys::SSL_CREDENTIAL_new_x509()
})
.expect("allocation failure"),
PhantomData,
);
this.set_ex_data()
}
}
impl<M> TlsCredentialBuilder<M>
where
M: NeedsPrivateKey,
{
/// Set [`SignatureAlgorithm`] preferences.
///
/// This controls which signature algorithms will be used with this credential.
pub fn with_signing_algorithm_preferences(
&mut self,
algs: &[SignatureAlgorithm],
) -> Result<&mut Self, Error> {
let algs: &[u16] = unsafe {
// Safety: `SignatureAlgorithm` has a `repr(u16)`
core::mem::transmute(algs)
};
if has_duplicates(algs) {
return Err(Error::Configuration(
ConfigurationError::DuplicatedParameters,
));
}
let (ptr, len) = slice_into_ffi_raw_parts(algs);
check_lib_error!(unsafe {
// Safety
bssl_sys::SSL_CREDENTIAL_set1_signing_algorithm_prefs(self.ptr(), ptr, len)
});
Ok(self)
}
/// Set a private key.
///
/// **NOTE**: Call this method after setting the certificates with
/// [`Self::with_certificate_chain`].
///
/// This method errs when the private key does not match the public key in the leaf certificate
/// as configured through [`Self::with_certificate_chain`].
pub fn with_private_key(&mut self, mut key: PrivateKey) -> Result<&mut Self, Error> {
let rc = unsafe {
// Safety:
// - both `key.0` and `self.0` are still valid.
// - `bssl-tls` uses the same BoringSSL as that is linked to `bssl-sys` and `bssl-x509`.
// - the `EVP_PKEY` handle will outlive `key` because this call will claim ownership.
bssl_sys::SSL_CREDENTIAL_set1_private_key(self.ptr(), key.as_mut_ptr())
};
if rc != 1 {
Err(Error::Configuration(ConfigurationError::MismatchingKeyPair))
} else {
Ok(self)
}
}
}
impl TlsCredentialBuilder<X509Mode> {
/// Set certificate chain.
///
/// The leaf, also known as end-entity, certificate **must come first** in `certs`.
/// This method returns [`ConfigurationError::InvalidParameters`] if `certs` are empty.
pub fn with_certificate_chain(&mut self, certs: &[Certificate]) -> Result<&mut Self, Error> {
if certs.is_empty() {
return Err(Error::Configuration(ConfigurationError::InvalidParameters));
}
let certs: &[*mut bssl_sys::CRYPTO_BUFFER] = unsafe {
// Safety: `Certificate` is a `repr(transparent)` wrapper around
// `*mut bssl_sys::CRYPTO_BUFFER`.
core::mem::transmute(certs)
};
let (ptr, len) = slice_into_ffi_raw_parts(certs);
check_lib_error!(unsafe {
// Safety
bssl_sys::SSL_CREDENTIAL_set1_cert_chain(self.ptr(), ptr, len)
});
Ok(self)
}
/// Set Online Certificate Status Protocol Response.
pub fn with_ocsp_response(&mut self, ocsp: &OcspResponse) -> Result<&mut Self, Error> {
check_lib_error!(unsafe {
// Safety: both `self.0` and `ocsp.0` are still live witnessed by the wrapper types.
bssl_sys::SSL_CREDENTIAL_set1_ocsp_response(self.ptr(), ocsp.ptr())
});
Ok(self)
}
/// Set Signed Certificate Timestamp List.
pub fn with_scts(&mut self, scts: &SignedCertificateTimestampList) -> Result<&mut Self, Error> {
check_lib_error!(unsafe {
// Safety: both `self.0` and `scts.0` are still live.
bssl_sys::SSL_CREDENTIAL_set1_signed_cert_timestamp_list(self.ptr(), scts.ptr())
});
Ok(self)
}
/// Enforce a check if the peer supports the issuer of the configured certificate chain.
///
/// This setting can be used for certificate chains that may not be usable by all peers.
/// This scenario could happen with chains with fewer cross-signs or issued from a newer CA.
/// When in force, the credential list is tried in order, so more specific credentials that
/// enable issuer matching should generally be ordered before less specific credentials that
/// do not.
pub fn must_match_issuer(&mut self, match_: bool) -> Result<&mut Self, Error> {
unsafe {
// Safety: `self.0` is still valid.
bssl_sys::SSL_CREDENTIAL_set_must_match_issuer(self.ptr(), if match_ { 1 } else { 0 });
}
Ok(self)
}
}
impl<M> TlsCredentialBuilder<M> {
/// Finalise the credential.
pub fn build(mut self) -> Option<TlsCredential> {
if unsafe {
// Safety: `self.0` is still valid.
bssl_sys::SSL_CREDENTIAL_is_complete(self.ptr()) == 1
} {
let Self(cred, _) = self;
forget(self);
Some(TlsCredential(cred))
} else {
None
}
}
}
/// Supported hash algorithms for TLS 1.3 PSK
///
/// See [RFC 9258] § 5.1.
///
/// [RFC 9258]: <https://datatracker.ietf.org/doc/html/rfc9258#section-5.1>
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum PskHash {
/// SHA-256
Sha256,
/// SHA-384
Sha384,
}
impl PskHash {
pub(crate) fn as_evp_md(&self) -> *const bssl_sys::EVP_MD {
match self {
PskHash::Sha256 => unsafe {
// Safety: `EVP_sha256` returns a valid pointer to a static `EVP_MD`.
bssl_sys::EVP_sha256()
},
PskHash::Sha384 => unsafe {
// Safety: `EVP_sha384` returns a valid pointer to a static `EVP_MD`.
bssl_sys::EVP_sha384()
},
}
}
}
/// A completely constructed TLS credential.
pub struct TlsCredential(NonNull<bssl_sys::SSL_CREDENTIAL>);
// Safety: `TlsCredential` is locked as immutable at this type state.
unsafe impl Send for TlsCredential {}
unsafe impl Sync for TlsCredential {}
impl TlsCredential {
pub(crate) fn ptr(&self) -> *mut bssl_sys::SSL_CREDENTIAL {
self.0.as_ptr()
}
/// Create a new pre-shared key credential for TLS 1.3.
///
/// See [RFC 9258](https://datatracker.ietf.org/doc/html/rfc9258) for details.
pub fn new_pre_shared_key(
key: &[u8],
identity: &[u8],
hash: PskHash,
context: &[u8],
) -> Result<Self, Error> {
let (key_ptr, key_len) = slice_into_ffi_raw_parts(key);
let (id_ptr, id_len) = slice_into_ffi_raw_parts(identity);
let (ctx_ptr, ctx_len) = slice_into_ffi_raw_parts(context);
let cred = unsafe {
// Safety:
// - `key_ptr` and `key_len` are valid for the duration of the call.
// - `id_ptr` and `id_len` are valid for the duration of the call.
// - `hash.as_ptr()` returns a valid static `EVP_MD` pointer.
// - `ctx_ptr` and `ctx_len` are valid for the duration of the call.
// - The function returns a newly allocated `SSL_CREDENTIAL` or NULL.
bssl_sys::SSL_CREDENTIAL_new_pre_shared_key(
key_ptr,
key_len,
id_ptr,
id_len,
hash.as_evp_md(),
ctx_ptr,
ctx_len,
)
};
let cred = NonNull::new(cred).ok_or_else(|| Error::extract_lib_err())?;
Ok(TlsCredential(cred))
}
}
impl Clone for TlsCredential {
fn clone(&self) -> Self {
unsafe {
// Safety: this handle is already valid by the witness of self.
bssl_sys::SSL_CREDENTIAL_up_ref(self.ptr());
}
Self(self.0)
}
}
impl Drop for TlsCredential {
fn drop(&mut self) {
unsafe {
// Safety: `self.0` is still valid at dropping.
bssl_sys::SSL_CREDENTIAL_free(self.ptr());
}
}
}
crypto_buffer_wrapper! {
/// A dumb handle to a **possibly valid** TLS certificate.
pub struct Certificate
}
impl Certificate {
/// Parse certificates from PEM-encoded blocks.
///
/// The parsing will skip anything that is not a certificate or empty.
/// However, this method does not verify if the content is actually a valid, correctly signed
/// DER-encoded X.509 certificate.
/// The method returns at the first error encountered.
pub fn parse_all_from_pem(
cert: &[u8],
cache: Option<&CertificateCache>,
) -> Result<Vec<Self>, Error> {
let mut bio = Bio::from_bytes(cert).unwrap();
let mut res = vec![];
loop {
match Self::parse_one(&mut bio, cache) {
Ok((cert, eos)) => {
res.push(cert);
if eos {
return Ok(res);
}
}
Err(Error::PemReason(PemReason::NoStartLine)) => return Ok(res),
Err(err) => return Err(err),
}
}
}
/// Parse the first certificate from the PEM-encoded data.
pub fn parse_one_from_pem(
cert: &[u8],
cache: Option<&CertificateCache>,
) -> Result<Self, Error> {
let mut bio = Bio::from_bytes(cert)?;
let (cert, _) = Self::parse_one(&mut bio, cache)?;
Ok(cert)
}
fn parse_one(bio: &mut Bio, cache: Option<&CertificateCache>) -> Result<(Self, bool), Error> {
loop {
let mut name = null_mut();
let mut header = null_mut();
let mut data = null_mut();
let mut len = 0;
let ret = unsafe {
// Safety:
// - `name`, `header`, `data` and `len` are valid and aligned.
// - `bio` is still valid.
bssl_sys::PEM_read_bio(
bio.ptr(),
&raw mut name,
&raw mut header,
&raw mut data,
&raw mut len,
)
};
let eof = unsafe {
// Safety: `bio` is still valid.
bssl_sys::BIO_eof(bio.ptr()) != 0
};
let name = Alloc(name);
let header = Alloc(header);
let data = Alloc(data);
if name.0.is_null() || header.0.is_null() || data.0.is_null() || ret == 0 {
return Err(Error::extract_lib_err());
}
if len == 0 {
continue;
}
let Ok(len) = usize::try_from(len) else {
return Err(Error::Io(IoError::TooLong));
};
let buf = unsafe {
// Safety:
// - `name.0` is NUL-terminated by BoringSSL invariant.
// - the lifetime of `buf` is outlived by `name.0`.
CStr::from_ptr(name.0)
};
if buf.to_bytes() != b"CERTIFICATE" {
continue;
}
let Some(contents) = (unsafe {
// Safety: the slice is only used within the loop and we will copy the contents
// when constructing the certificate object.
sanitize_slice(data.0, len)
}) else {
return Err(Error::Io(IoError::TooLong));
};
let cert = Certificate::from_bytes(contents, cache)?;
return Ok((cert, eof));
}
}
/// Extract the DER encoded certificate.
pub fn as_der_bytes(&self) -> &[u8] {
let (data, len) = unsafe {
// Safety: `self.0` is still valid.
(
bssl_sys::CRYPTO_BUFFER_data(self.ptr()),
bssl_sys::CRYPTO_BUFFER_len(self.ptr()),
)
};
if data.is_null() || len == 0 || len >= isize::MAX as usize {
return &[];
}
unsafe {
// Safety: `data` will be outlived by `self`
core::slice::from_raw_parts(data, len)
}
}
}
crypto_buffer_wrapper! {
/// A dumb handle to a **possibly valid** OCSP Response.
pub struct OcspResponse
}
crypto_buffer_wrapper! {
/// A dumb handle to a **possibly valid** certificate property list.
pub struct CertificatePropertyList
}
crypto_buffer_wrapper! {
/// A dumb handle to a **possibly valid** certificate timestamp list.
///
/// This list must contain at least one Signed Certificate Timestamp, or SCT for short,
/// serialised as *[SignedCertificateTimestampList]*.
///
/// The list begins with a length encoded as a big-endian 16-byte unsigned integer.
/// Thereon follows a simple concatenation of SCTs.
/// Each SCT is prefixed with a length encoded as a big-endian 16-byte unsigned integer.
///
/// [SignedCertificateTimestampList]: <https://tools.ietf.org/html/rfc6962#section-3.3>
pub struct SignedCertificateTimestampList
}
crypto_buffer_wrapper! {
/// A dumb handle to a **possibly valid** delegated credential.
///
/// By construction we do not validate if the contained bytes actually conforms to
/// the structure of `DelegatedCredential` as stipulated by [RFC 9345].
///
/// [RFC 9345]: <https://tools.ietf.org/html/rfc9345>
pub struct DelegatedCredential
}
bssl_macros::bssl_enum! {
/// [IANA] designation of TLS signature algorithms.
///
/// [IANA]: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum SignatureAlgorithm: u16 {
/// IANA entry `rsa_pkcs1_sha256`
RsaPkcs1Sha256 = bssl_sys::SSL_SIGN_RSA_PKCS1_SHA256 as u16,
/// IANA entry `rsa_pkcs1_sha384`
RsaPkcs1Sha384 = bssl_sys::SSL_SIGN_RSA_PKCS1_SHA384 as u16,
/// IANA entry `rsa_pkcs1_sha512`
RsaPkcs1Sha512 = bssl_sys::SSL_SIGN_RSA_PKCS1_SHA512 as u16,
/// IANA entry `ecdsa_secp256r1_sha256`
EcdsaSecp256r1Sha256 = bssl_sys::SSL_SIGN_ECDSA_SECP256R1_SHA256 as u16,
/// IANA entry `ecdsa_secp384r1_sha384`
EcdsaSecp384r1Sha384 = bssl_sys::SSL_SIGN_ECDSA_SECP384R1_SHA384 as u16,
/// IANA entry `ecdsa_secp521r1_sha512`
EcdsaSecp521r1Sha512 = bssl_sys::SSL_SIGN_ECDSA_SECP521R1_SHA512 as u16,
/// IANA entry `rsa_pss_rsae_sha256`
RsaPssRsaeSha256 = bssl_sys::SSL_SIGN_RSA_PSS_RSAE_SHA256 as u16,
/// IANA entry `rsa_pss_rsae_sha384`
RsaPssRsaeSha384 = bssl_sys::SSL_SIGN_RSA_PSS_RSAE_SHA384 as u16,
/// IANA entry `rsa_pss_rsae_sha512`
RsaPssRsaeSha512 = bssl_sys::SSL_SIGN_RSA_PSS_RSAE_SHA512 as u16,
/// IANA entry `ed25519`
Ed25519 = bssl_sys::SSL_SIGN_ED25519 as u16,
/// IANA entry `ML-DSA-44`
Mldsa44 = bssl_sys::SSL_SIGN_ML_DSA_44 as u16,
/// IANA entry `ML-DSA-65`
Mldsa65 = bssl_sys::SSL_SIGN_ML_DSA_65 as u16,
/// IANA entry `ML-DSA-87`
Mldsa87 = bssl_sys::SSL_SIGN_ML_DSA_87 as u16,
}
}
// NOTE: this context does not own the connection.
/// A verification context handle.
#[repr(transparent)]
pub struct VerifyCertificateContext(NonNull<bssl_sys::SSL>);
impl VerifyCertificateContext {
fn ptr(&self) -> *mut bssl_sys::SSL {
self.0.as_ptr()
}
/// Get Encrypted `ClientHello` name override, specifically a DNS name per
/// [RFC 5280], which a character set stipulated by [RFC 1034] §3.5.
///
/// The returned name should be interpreted first as an opaque byte string.
///
/// # Interaction with custom certificate verification
///
/// If the return value is [`Some`], the end-entity certificate must be
/// verified against the name reported by this call.
///
/// [RFC 5280]: <https://datatracker.ietf.org/doc/html/rfc5280>
/// [RFC 1034]: <https://datatracker.ietf.org/doc/html/rfc1034#section-3.5>
pub fn get_ech_name_override(&self) -> Option<&str> {
let name: &[u8] = unsafe {
// Safety:
// - `self` outlives the slice.
// - transmuting i8 to u8 preserve the character value.
core::mem::transmute(call_slice_getter!(
bssl_sys::SSL_get0_ech_name_override,
self.ptr()
)?)
};
if name.is_empty() || !name.is_ascii() {
return None;
}
// A DNS name has to be an IA5String, specifically ASCII first.
str::from_utf8(name).ok()
}
/// Get the stapled OCSP response, if any.
///
/// The response may not be a valid OCSPResponse from the server as per
/// [RFC 2560].
///
/// [RFC 2560]: <https://datatracker.ietf.org/doc/html/rfc6960>
pub fn get_ocsp_response(&self) -> Option<&[u8]> {
// Safety: response, when it exists, is outlived by the connection.
let response = call_slice_getter!(bssl_sys::SSL_get0_ocsp_response, self.ptr())?;
(!response.is_empty()).then_some(response)
}
/// Get the Signed Certificate Timestamp list, if any, as per [RFC 6962] §3.2.
///
/// [RFC 6962]: <https://datatracker.ietf.org/doc/html/rfc6962#section-3.2>
pub fn get_signed_cert_timestamp_list(&self) -> Option<&[u8]> {
// Safety: list, when it exists, is outlived by the connection.
let list = call_slice_getter!(bssl_sys::SSL_get0_signed_cert_timestamp_list, self.ptr())?;
(!list.is_empty()).then_some(list)
}
}
/// Custom certificate verification callback.
///
/// It is recommended to avoid panicking in the trait implementation.
/// A panic in this callback will lead to abort.
pub trait VerifyCertificate: Send + Sync {
/// Decide whether a certificate chain is acceptable.
///
/// The peer certificate chain is supplied in `certs`, in which the first certificate is
/// the End Entity certificate, if any.
///
/// This method may be called more than once if the verification is asynchronous.
/// To signal suspension, this method should return [`VerifyResult::Pending`].
fn verify<'a>(
&self,
ctx: &'a VerifyCertificateContext,
certs: CertificateChainIterator<'a>,
) -> Box<dyn VerifyCertificateTask>;
}
/// An outstanding certificate verification task.
pub trait VerifyCertificateTask: Send {
/// Try to complete the verification task.
fn complete(&mut self, async_ctx: Option<&mut Context<'_>>) -> VerifyResult;
}
/// Custom certificate verification result.
pub enum VerifyResult {
/// The certificate chain is accepted.
Accept,
/// The certification chain is pending asynchronous result.
Pending,
/// The certificate chain is rejected possibly with an alert.
Reject(Option<AlertDescription>),
}
/// Asynchronous custom certificate verification.
///
/// This is the `async` analogue of [`VerifyCertificate`].
pub trait AsyncVerifyCertificate: Send + Sync + Unpin {
/// The future type of the verification process.
type VerifyFuture: 'static + Unpin + Send + Sync + Future<Output = bool>;
/// Decide whether a certificate chain is acceptable.
fn verify(
&self,
ctx: &VerifyCertificateContext,
certs: CertificateChainIterator<'_>,
) -> Self::VerifyFuture;
}
/// Adapter to run certificate verification asynchronously.
pub struct AsyncVerifyCertificateAdapter<T>(pub T);
impl<T, Fut> VerifyCertificate for AsyncVerifyCertificateAdapter<T>
where
T: AsyncVerifyCertificate<VerifyFuture = Fut>,
Fut: 'static + Unpin + Send + Sync + Future<Output = bool>,
{
fn verify<'a>(
&self,
ctx: &'a VerifyCertificateContext,
certs: CertificateChainIterator<'a>,
) -> Box<dyn VerifyCertificateTask> {
Box::new(AsyncVerifyCertificateTask(self.0.verify(ctx, certs)))
}
}
struct AsyncVerifyCertificateTask<Fut>(Fut);
impl<Fut> VerifyCertificateTask for AsyncVerifyCertificateTask<Fut>
where
Fut: 'static + Unpin + Send + Sync + Future<Output = bool>,
{
fn complete(&mut self, async_ctx: Option<&mut Context<'_>>) -> VerifyResult {
let Some(cx) = async_ctx else {
return VerifyResult::Reject(Some(AlertDescription::InternalError));
};
let outstanding_task = Pin::new(&mut self.0);
match outstanding_task.poll(cx) {
Poll::Ready(accept) => {
if accept {
VerifyResult::Accept
} else {
VerifyResult::Reject(Some(AlertDescription::BadCertificate))
}
}
Poll::Pending => VerifyResult::Pending,
}
}
}
/// Certificate chain iterator.
///
/// This iterator will supply the peer leaf certificate as the first element in the chain, if any.
#[derive(Clone, Copy)]
pub struct CertificateChainIterator<'a> {
certs: *const bssl_sys::stack_st_CRYPTO_BUFFER,
len: usize,
curr: usize,
_p: PhantomData<&'a ()>,
}
impl<'a> CertificateChainIterator<'a> {
/// Safety: caller must ensure that `certs` is outlived by,
/// or in other words stays alive as long as, `'a`.
pub(crate) unsafe fn new(certs: *const bssl_sys::stack_st_CRYPTO_BUFFER) -> Self {
let len = if certs.is_null() {
0
} else {
unsafe {
// Safety: `certs` is valid now.
bssl_sys::sk_CRYPTO_BUFFER_num(certs)
}
};
Self {
certs,
len,
curr: 0,
_p: PhantomData,
}
}
}
impl<'a> Iterator for CertificateChainIterator<'a> {
type Item = Certificate;
fn next(&mut self) -> Option<Self::Item> {
if self.curr >= self.len {
return None;
}
let cert = unsafe {
// Safety: `self.certs` is still valid now and `self.curr` is within the bound.
bssl_sys::sk_CRYPTO_BUFFER_value(self.certs, self.curr)
};
self.curr += 1;
let Some(cert) = NonNull::new(cert) else {
// Fuse the iterator.
self.curr = self.len;
return None;
};
unsafe {
// Safety: `cert` is valid here.
bssl_sys::CRYPTO_BUFFER_up_ref(cert.as_ptr());
}
Some(Certificate(cert))
}
}
impl ExactSizeIterator for CertificateChainIterator<'_> {
fn len(&self) -> usize {
self.len
}
}
impl FusedIterator for CertificateChainIterator<'_> {}
/// Safety: this callback stub must be installed with a context object allocated
/// as a `Box<dyn VerifyCertificate>`.
pub(crate) unsafe extern "C" fn cert_cb<M: VerifyCertificateMethods>(
ssl: *mut bssl_sys::SSL,
alert: *mut u8,
) -> bssl_sys::ssl_verify_result_t {
let Some(ssl) = NonNull::new(ssl) else {
return bssl_sys::ssl_verify_result_t_ssl_verify_invalid;
};
let Some(methods) = (unsafe {
// Safety: `ssl` outlives `methods`
M::from_ssl(ssl.as_ptr())
}) else {
return bssl_sys::ssl_verify_result_t_ssl_verify_invalid;
};
let waker = unsafe {
// Safety:
// - this callback must be installed by `TlsContextBuilder` or `TlsConnection`,
// so the associated data must have been set up correctly.
// - the caller of this callback must own the connection exclusively.
waker_data_from_ssl(ssl)
};
let mut context = waker.as_ref().map(Context::from_waker);
let Some(verify) = methods.verify_certificate_methods() else {
return bssl_sys::ssl_verify_result_t_ssl_verify_invalid;
};
let cert_chain = unsafe {
// Safety: `ssl` is still alive in handshake mode and will outlive `cert_chain`.
bssl_sys::SSL_get0_peer_certificates(ssl.as_ptr())
};
let certs = unsafe {
// Safety: `cert_chain` is outlived by `ssl` whose lifetime is annotated as `'a`.
CertificateChainIterator::new(cert_chain)
};
let ctx = &VerifyCertificateContext(ssl);
let async_ctx = context.as_mut();
let outstanding_task = unsafe {
// Safety: `ssl` outlives the in-flight task and exclusively owned by the caller of
// this callback.
verify_cert_task_from_ssl(ssl)
};
abort_on_panic(move || {
if let Some(task) = outstanding_task {
match task.complete(async_ctx) {
VerifyResult::Pending => bssl_sys::ssl_verify_result_t_ssl_verify_retry,
VerifyResult::Accept => {
let _ = outstanding_task.take();
bssl_sys::ssl_verify_result_t_ssl_verify_ok
}
VerifyResult::Reject(ad) => {
let _ = outstanding_task.take();
if let Some(ad) = ad {
unsafe {
// Safety: `alert` is valid per BoringSSL invariants.
alert.write(ad as _);
}
}
bssl_sys::ssl_verify_result_t_ssl_verify_invalid
}
}
} else {
let mut task = verify.verify(ctx, certs);
match task.complete(async_ctx) {
VerifyResult::Pending => {
*outstanding_task = Some(task);
bssl_sys::ssl_verify_result_t_ssl_verify_retry
}
VerifyResult::Accept => bssl_sys::ssl_verify_result_t_ssl_verify_ok,
VerifyResult::Reject(ad) => {
if let Some(ad) = ad {
unsafe {
// Safety: `alert` is valid per BoringSSL invariants.
alert.write(ad as _);
}
}
bssl_sys::ssl_verify_result_t_ssl_verify_invalid
}
}
}
})
}
bssl_macros::bssl_enum! {
/// Certificate verification mode
pub enum CertificateVerificationMode: i8 {
/// Verifies the server certificate on a client but does not make errors fatal.
None = bssl_sys::SSL_VERIFY_NONE as i8,
/// Verifies the server certificate on a client and makes errors fatal.
PeerCertRequested = bssl_sys::SSL_VERIFY_PEER as i8,
/// Configures a server to request a client certificate and **reject** connections if
/// the client declines to send a certificate.
PeerCertMandatory =
(bssl_sys::SSL_VERIFY_FAIL_IF_NO_PEER_CERT | bssl_sys::SSL_VERIFY_PEER) as i8,
}
}
impl TryFrom<c_int> for CertificateVerificationMode {
type Error = c_int;
fn try_from(mode: c_int) -> Result<Self, Self::Error> {
let Ok(value) = i8::try_from(mode) else {
return Err(mode);
};
if let Ok(mode) = Self::try_from(value) {
Ok(mode)
} else {
Err(mode)
}
}
}
#[cfg(test)]
mod tests;