blob: 67c0e6c7298d27d8a4d72cd1b20812abcc017541 [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, a slice 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::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 leaf = X509Certificate::parse_one_from_pem(
//! include_bytes!("tests/BoringSSLServerTest-RSA.crt")).unwrap();
//!
//! let mut verifier = X509Verifier::new(&leaf, &[], &store).unwrap();
//! assert!(verifier.verify().is_ok());
//! ```
use alloc::vec::Vec;
use core::{marker::PhantomData, 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>,
chain: CertificateStack,
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,
untrusted: &[X509Certificate],
store: &'a X509Store,
) -> Result<Self, PkiError> {
let chain = CertificateStack::from_borrowed(untrusted);
let ctx = Self::alloc(chain);
check_lib_error!(unsafe {
// Safety:
// - `ctx.ptr()` is valid.
// - `store.as_raw()` returns a valid X509_STORE pointer.
// - `cert.ptr()` returns a valid X509 pointer.
// - `chain` is a valid stack pointer, kept alive by `ctx.chain`.
// - The input objects will outlive `'a`, so the verifier is outlived by
// these objects.
bssl_sys::X509_STORE_CTX_init(ctx.ptr(), store.as_raw(), cert.ptr(), ctx.chain.ptr())
});
Ok(ctx)
}
fn alloc(chain: CertificateStack) -> 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,
chain,
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 len = unsafe {
// Safety: `chain` is valid.
bssl_sys::sk_X509_num(chain.as_ptr())
};
let mut res = Vec::new();
for i in 0..len {
let cert = unsafe {
// Safety: `chain` is valid and `i` < `len`.
NonNull::new(bssl_sys::sk_X509_value(chain.as_ptr(), i))?
};
unsafe {
// Safety: `cert` is borrowed from the chain; bump the ref count.
res.push(X509Certificate::from_borrowed_raw(cert));
}
}
Some(res)
}
}
/// A transient STACK_OF(X509) that borrows certificates.
///
/// The certificates pushed into this stack have their reference counts
/// incremented, and are released when the stack is freed.
struct CertificateStack(NonNull<bssl_sys::stack_st_X509>);
// Safety: contains no thread-local data.
unsafe impl Send for CertificateStack {}
impl CertificateStack {
/// Build a stack from borrowed certificates. Each certificate's reference
/// count is incremented so the stack owns a reference.
fn from_borrowed(certs: &[X509Certificate]) -> Self {
let Some(stack) = NonNull::new(unsafe {
// Safety: only allocation.
bssl_sys::sk_X509_new_null()
}) else {
panic!("allocation error");
};
for cert in certs {
unsafe {
// Safety: `cert` is valid. Bump the ref count so the stack
// owns its own reference.
bssl_sys::X509_up_ref(cert.ptr());
}
if unsafe {
// Safety: `cert.ptr()` is valid.
bssl_sys::sk_X509_push(stack.as_ptr(), cert.ptr()) == 0
} {
panic!("allocation failure");
}
}
Self(stack)
}
fn ptr(&self) -> *mut bssl_sys::stack_st_X509 {
self.0.as_ptr()
}
}
impl Drop for CertificateStack {
fn drop(&mut self) {
unsafe {
// Safety: `self` is valid. Each element's ref count was incremented
// in `from_borrowed`, so `X509_free` will release that reference.
bssl_sys::sk_X509_pop_free(self.ptr(), Some(bssl_sys::X509_free));
}
}
}