| // 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)); |
| } |
| } |
| } |