blob: b4fd436c2b62bb478954028f446df0bf32ae07ac [file] [log] [blame]
// 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.
//! X.509 certificate verification.
//!
//! To verify certificates, one may use [`X509Verifier`] which requires a fully constructed
//! [`X509Store`] certificate store, an [`X509CertificateList`] of untrusted intermediate
//! certificates, and the final end-entity certificate as an [`X509Certificate`].
//!
//! ```rust
//! # use bssl_x509::certificates::X509Certificate;
//! # use bssl_x509::store::{X509Store, X509StoreBuilder};
//! # use bssl_x509::verify::X509CertificateList;
//! # use bssl_x509::verify::X509Verifier;
//! let ca = X509Certificate::parse_one_from_pem(
//! include_bytes!("tests/BoringSSLTestCA.crt")).unwrap();
//! let mut store = X509StoreBuilder::new();
//! store.add_cert(ca).unwrap();
//! let store = store.build();
//!
//! let untrusted_intermediates = X509CertificateList::new();
//!
//! let leaf = X509Certificate::parse_one_from_pem(
//! include_bytes!("tests/BoringSSLServerTest-RSA.crt")).unwrap();
//!
//! let mut verifier = X509Verifier::new(&leaf, &untrusted_intermediates, &store).unwrap();
//! assert!(verifier.verify().is_ok());
//! ```
use alloc::vec::Vec;
use core::{marker::PhantomData, mem::transmute, ptr::NonNull};
use crate::{
certificates::X509Certificate,
check_lib_error,
errors::{PkiError, X509VerifyResult},
store::X509Store,
};
/// A context for X.509 certificate verification.
///
/// This corresponds to `X509_STORE_CTX` in BoringSSL.
pub struct X509Verifier<'a> {
ptr: NonNull<bssl_sys::X509_STORE_CTX>,
verified: bool,
_p: PhantomData<&'a ()>,
}
// Safety: X509_STORE_CTX is not thread-safe for concurrent access, but can be moved.
unsafe impl Send for X509Verifier<'_> {}
impl Drop for X509Verifier<'_> {
fn drop(&mut self) {
unsafe {
// Safety: The pointer is valid and owned by this struct.
bssl_sys::X509_STORE_CTX_free(self.ptr.as_ptr());
}
}
}
impl<'a> X509Verifier<'a> {
/// Creates a new `X509Verifier`.
pub fn new(
cert: &'a X509Certificate,
chain: &'a X509CertificateList,
store: &'a X509Store,
) -> Result<Self, PkiError> {
let this = Self::alloc();
check_lib_error!(unsafe {
// Safety:
// - `self.0` is valid.
// - `store.as_raw()` returns a valid X509_STORE pointer.
// - `cert.ptr()` returns a valid X509 pointer.
// - `chain_ptr` is either NULL or a valid stack pointer.
// - The input objects will outlive `'a`, so much so that the verifier is outlived by
// these objects.
bssl_sys::X509_STORE_CTX_init(this.ptr(), store.as_raw(), cert.ptr(), chain.ptr())
});
Ok(this)
}
fn alloc() -> Self {
let Some(ctx) = NonNull::new(unsafe {
// Safety: This function creates a new object and returns NULL on allocation failure.
bssl_sys::X509_STORE_CTX_new()
}) else {
panic!("allocation error");
};
Self {
ptr: ctx,
verified: false,
_p: PhantomData,
}
}
pub(crate) fn ptr(&self) -> *mut bssl_sys::X509_STORE_CTX {
self.ptr.as_ptr()
}
/// Performs the certificate verification.
///
/// Returns `Ok(())` if verification succeeds.
/// Returns `Err(X509VerifyResult)` if verification fails.
pub fn verify(&mut self) -> Result<(), X509VerifyResult> {
self.verified = true;
if unsafe {
// Safety: `self.0` is valid. The context must have been initialized.
bssl_sys::X509_verify_cert(self.ptr()) == 1
} {
Ok(())
} else {
Err(self.get_error())
}
}
fn get_error(&self) -> X509VerifyResult {
let error_code = unsafe {
// Safety: `self.0` is valid.
bssl_sys::X509_STORE_CTX_get_error(self.ptr())
};
X509VerifyResult::try_from(error_code as i32).unwrap_or(X509VerifyResult::Unspecified)
}
/// Returns the verified certificate chain.
///
/// The first certificate will be the leaf certificate and the last certificate will be one of
/// the trust anchor.
///
/// This method returns [`None`] if [`Self::verify`] has not been called.
pub fn chain(&self) -> Option<Vec<X509Certificate>> {
if !self.verified {
return None;
}
let chain = NonNull::new(unsafe {
// Safety: `self.0` is valid.
bssl_sys::X509_STORE_CTX_get0_chain(self.ptr())
})?;
let chain: &X509CertificateList = unsafe {
// Safety: `X509CertificateList` is a transparent wrapper around the handle
transmute(&chain)
};
let mut res = Vec::new();
for i in 0..chain.len() {
res.push(chain.get(i)?);
}
Some(res)
}
}
/// A list of certificates.
#[repr(transparent)]
pub struct X509CertificateList(NonNull<bssl_sys::stack_st_X509>);
// Safety: `X509CertificateList` is not clonable and contains no thread-local data.
unsafe impl Send for X509CertificateList {}
impl X509CertificateList {
/// Create an empty certificate list.
pub fn new() -> Self {
let Some(cert_list) = NonNull::new(unsafe {
// Safety: we only make allocation here.
bssl_sys::sk_X509_new_null()
}) else {
panic!("allocation error");
};
Self(cert_list)
}
pub(crate) fn ptr(&self) -> *mut bssl_sys::stack_st_X509 {
self.0.as_ptr()
}
/// Get the size of the list.
pub fn len(&self) -> usize {
unsafe {
// Safety: `self` is valid.
bssl_sys::sk_X509_num(self.ptr())
}
}
/// Get a certificate.
pub fn get(&self, index: usize) -> Option<X509Certificate> {
if index < self.len() {
let cert = unsafe {
// Safety: `self` is valid.
NonNull::new(bssl_sys::sk_X509_value(self.ptr(), index))?
};
unsafe {
// Safety: `cert` has the right ref-count now, so we have valid ownership.
Some(X509Certificate::from_borrowed_raw(cert))
}
} else {
None
}
}
/// Append a certificate into the list.
pub fn push(&mut self, cert: X509Certificate) -> Result<&mut Self, PkiError> {
if unsafe {
// Safety: `cert` is still valid and exclusively owned.
bssl_sys::sk_X509_push(self.ptr(), cert.ptr()) == 0
} {
panic!("allocation failure")
}
// We should transfer the ownership to the stack.
core::mem::forget(cert);
Ok(self)
}
}
impl Drop for X509CertificateList {
fn drop(&mut self) {
unsafe {
// Safety: `self` is valid.
bssl_sys::sk_X509_pop_free(self.ptr(), Some(bssl_sys::X509_free));
}
}
}