rust: Introduce X.509 bindings This crate will assist X.509 store construction, verification configuration and certificate verification. Signed-off-by: Xiangfei Ding <xfding@google.com> Change-Id: I8beb5f11ae04fb7c844b2d1970d65eff6a6a6964 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/90427 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/x509.h b/include/openssl/x509.h index 765de55..861bdad 100644 --- a/include/openssl/x509.h +++ b/include/openssl/x509.h
@@ -16,7 +16,7 @@ #ifndef OPENSSL_HEADER_X509_H #define OPENSSL_HEADER_X509_H -#include <openssl/base.h> // IWYU pragma: export +#include <openssl/base.h> // IWYU pragma: export #include <time.h> @@ -988,7 +988,7 @@ OPENSSL_EXPORT const STACK_OF(X509_EXTENSION) *X509_REVOKED_get0_extensions( const X509_REVOKED *r); - // X509_REVOKED_get_ext_count returns the number of extensions in |x|. +// X509_REVOKED_get_ext_count returns the number of extensions in |x|. OPENSSL_EXPORT int X509_REVOKED_get_ext_count(const X509_REVOKED *x); // X509_REVOKED_get_ext_by_NID behaves like |X509v3_get_ext_by_NID| but searches @@ -4133,7 +4133,8 @@ // This function outputs a legacy format that does not correctly handle string // encodings and other cases. Prefer |X509_NAME_print_ex| if printing a name for // debugging purposes. -OPENSSL_EXPORT char *X509_NAME_oneline(const X509_NAME *name, char *buf, int size); +OPENSSL_EXPORT char *X509_NAME_oneline(const X509_NAME *name, char *buf, + int size); // X509_NAME_print_ex_fp behaves like |X509_NAME_print_ex| but writes to |fp|. OPENSSL_EXPORT int X509_NAME_print_ex_fp(FILE *fp, const X509_NAME *nm,
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index a32ad1d..072266e 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock
@@ -3,6 +3,12 @@ version = 4 [[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] name = "bssl-crypto" version = "0.2.0" dependencies = [ @@ -10,6 +16,10 @@ ] [[package]] +name = "bssl-macros" +version = "0.1.0" + +[[package]] name = "bssl-rustls-adapters" version = "0.1.0" dependencies = [ @@ -23,6 +33,9 @@ [[package]] name = "bssl-sys" version = "0.1.0" +dependencies = [ + "bssl-macros", +] [[package]] name = "bssl-tls" @@ -35,6 +48,16 @@ ] [[package]] +name = "bssl-x509" +version = "0.1.0" +dependencies = [ + "bitflags", + "bssl-crypto", + "bssl-macros", + "bssl-sys", +] + +[[package]] name = "cc" version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml index f9873aa..ace391c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml
@@ -1,8 +1,10 @@ [workspace] members = [ "bssl-crypto", + "bssl-macros", "bssl-rustls-adapters", "bssl-sys", "bssl-tls", + "bssl-x509", ] resolver = "3"
diff --git a/rust/bssl-macros/Cargo.toml b/rust/bssl-macros/Cargo.toml new file mode 100644 index 0000000..5af1342 --- /dev/null +++ b/rust/bssl-macros/Cargo.toml
@@ -0,0 +1,6 @@ +[package] +name = "bssl-macros" +version = "0.1.0" +edition = "2024" + +[dependencies]
diff --git a/rust/bssl-macros/src/lib.rs b/rust/bssl-macros/src/lib.rs new file mode 100644 index 0000000..905f6a7 --- /dev/null +++ b/rust/bssl-macros/src/lib.rs
@@ -0,0 +1,55 @@ +// 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. + +#[doc(hidden)] +#[macro_export] +macro_rules! bssl_enum { + ( + $(#[$attr:meta])* + $vis:vis enum $name:ident : $repr:ty { + $( + $(#[$vattr:meta])* + $item:ident = $e:expr + ),* $(,)? + } + ) => { + $(#[$attr])* + #[repr($repr)] + $vis enum $name { + $( + $(#[$vattr])* + $item = $e, + )* + } + + impl ::core::convert::TryFrom<$repr> for $name { + type Error = $repr; + + fn try_from(value: $repr) -> Result<Self, Self::Error> { + $( + #[allow(non_upper_case_globals)] + const $item: $repr = $e; + )* + + #[allow(non_upper_case_globals)] + match value { + $( + $item => ::core::result::Result::Ok(Self::$item), + )* + _ => ::core::result::Result::Err(value), + } + } + } + }; +}
diff --git a/rust/bssl-sys/Cargo.toml b/rust/bssl-sys/Cargo.toml index 06cac9e..d94a090 100644 --- a/rust/bssl-sys/Cargo.toml +++ b/rust/bssl-sys/Cargo.toml
@@ -9,5 +9,8 @@ # https://github.com/rust-lang/cargo/issues/3544 links = "bssl" +[dependencies.bssl-macros] +path = "../bssl-macros" + [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(bindgen_rs_file)'] }
diff --git a/rust/bssl-sys/src/lib.rs b/rust/bssl-sys/src/lib.rs index b940ba6..2de77f1 100644 --- a/rust/bssl-sys/src/lib.rs +++ b/rust/bssl-sys/src/lib.rs
@@ -1,14 +1,11 @@ #![no_std] - // unnecessary_transmutes, needed to work around a Rust bug, is not available in // older Rusts. Stable lacks any way to condition code on Rust version, so the // workaround for a Rust bug below needs this additional Rust workaround. #![allow(unknown_lints)] - #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] - // Work around https://github.com/rust-lang/rust-bindgen/issues/2807 #![allow(unnecessary_transmutes)] @@ -60,3 +57,78 @@ // This function does nothing. // TODO(davidben): Remove rust-openssl's dependency on this and remove this. } + +bssl_macros::bssl_enum! { + /// Library code of the BoringSSL errors. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum LibCode: i32 { + /// No module + None = ERR_LIB_NONE as i32, + /// Module `SYS` + Sys = ERR_LIB_SYS as i32, + /// Module `BN` + Bn = ERR_LIB_BN as i32, + /// Module `RSA` + Rsa = ERR_LIB_RSA as i32, + /// Module `DH` + Dh = ERR_LIB_DH as i32, + /// Module `EVP` + Evp = ERR_LIB_EVP as i32, + /// Module `BUF` + Buf = ERR_LIB_BUF as i32, + /// Module `OBJ` + Obj = ERR_LIB_OBJ as i32, + /// Module `PEM` + Pem = ERR_LIB_PEM as i32, + /// Module `DSA` + Dsa = ERR_LIB_DSA as i32, + /// Module `X509` + X509 = ERR_LIB_X509 as i32, + /// Module `ASN1` + Asn1 = ERR_LIB_ASN1 as i32, + /// Module `CONF` + Conf = ERR_LIB_CONF as i32, + /// Module `CRYPTO` + Crypto = ERR_LIB_CRYPTO as i32, + /// Module `EC` + Ec = ERR_LIB_EC as i32, + /// Module `SSL` + Ssl = ERR_LIB_SSL as i32, + /// Module `BIO` + Bio = ERR_LIB_BIO as i32, + /// Module `PKCS7` + Pkcs7 = ERR_LIB_PKCS7 as i32, + /// Module `PKCS8` + Pkcs8 = ERR_LIB_PKCS8 as i32, + /// Module `X509V3` + X509v3 = ERR_LIB_X509V3 as i32, + /// Module `RAND` + Rand = ERR_LIB_RAND as i32, + /// Module `ENGINE` + Engine = ERR_LIB_ENGINE as i32, + /// Module `OCSP` + Ocsp = ERR_LIB_OCSP as i32, + /// Module `UI` + Ui = ERR_LIB_UI as i32, + /// Module `COMP` + Comp = ERR_LIB_COMP as i32, + /// Module `ECDSA` + Ecdsa = ERR_LIB_ECDSA as i32, + /// Module `ECDH` + Ecdh = ERR_LIB_ECDH as i32, + /// Module `HMAC` + Hmac = ERR_LIB_HMAC as i32, + /// Module `DIGEST` + Digest = ERR_LIB_DIGEST as i32, + /// Module `CIPHER` + Cipher = ERR_LIB_CIPHER as i32, + /// Module `HKDF` + Hkdf = ERR_LIB_HKDF as i32, + /// Module `TRUST_TOKEN` + TrustToken = ERR_LIB_TRUST_TOKEN as i32, + /// Module `CMS` + Cms = ERR_LIB_CMS as i32, + /// User sourced + User = ERR_LIB_USER as i32, + } +}
diff --git a/rust/bssl-x509/Cargo.toml b/rust/bssl-x509/Cargo.toml new file mode 100644 index 0000000..85a0166 --- /dev/null +++ b/rust/bssl-x509/Cargo.toml
@@ -0,0 +1,24 @@ +[package] +name = "bssl-x509" +version = "0.1.0" +edition = "2024" + +[dependencies] +bitflags = "2.0" + +[dependencies.bssl-crypto] +path = "../bssl-crypto" + +[dependencies.bssl-macros] +path = "../bssl-macros" + +[dependencies.bssl-sys] +path = "../bssl-sys" + +[features] +default = [] +# `std` depends on the Rust `std` crate, but adds some useful trait impls if +# available. +std = ["bssl-crypto/std"] +# `mlalgs` enables ML-KEM and ML-DSA support. This requires Rust 1.82. +mlalgs = ["bssl-crypto/mlalgs"]
diff --git a/rust/bssl-x509/src/certificates.rs b/rust/bssl-x509/src/certificates.rs new file mode 100644 index 0000000..827dc6a --- /dev/null +++ b/rust/bssl-x509/src/certificates.rs
@@ -0,0 +1,594 @@ +// 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 certificates +//! +//! This module houses the main type [`X509Certificate`] to support serialisation and inspection +//! of X.509 certificates. +//! +//! # Parsing and unparsing +//! +//! Certificates can be serialised or deserialised between [`X509Certificate`]s and PEM- or +//! DER-encoded data. +//! +//! ```rust +//! # use bssl_x509::certificates::X509Certificate; +//! # let concatenated_pem_bytes = include_bytes!("tests/consolidated.pem"); +//! # let pem = include_bytes!("tests/BoringSSLServerTest-RSA.crt"); +//! let certs: Vec<X509Certificate> = X509Certificate::parse_all_from_pem(concatenated_pem_bytes).unwrap(); +//! +//! let cert = X509Certificate::parse_one_from_pem(pem).unwrap(); +//! let der: Vec<u8> = cert.to_der().unwrap(); +//! ``` +//! +//! # Inspect a certificate +//! +//! One can check if a certificate has a public key that matches a [`PrivateKey`]. +//! ```rust +//! # use bssl_x509::certificates::X509Certificate; +//! # use bssl_x509::keys::PrivateKey; +//! # let cert_pem = include_bytes!("tests/BoringSSLTestCA.crt"); +//! # let password = b"BoringSSL is awesome!"; +//! # let key_pem = include_bytes!("tests/BoringSSLTestCA.key"); +//! # let cert = X509Certificate::parse_one_from_pem(cert_pem).unwrap(); +//! # let key = PrivateKey::from_pem(key_pem, || password).unwrap(); +//! assert!(cert.matches_private_key(&key)); +//! ``` +//! +//! Also it will allow inspection of common extensions of an X.509 certificate. + +use alloc::{vec, vec::Vec}; +use core::{ + marker::PhantomData, + mem::{forget, transmute}, + ptr::{NonNull, null_mut}, +}; + +use crate::{ + errors::{PemReason, PkiError, X509Error}, + ffi::{Bio, sanitize_slice, slice_into_ffi_raw_parts}, + keys::PrivateKey, +}; + +bssl_macros::bssl_enum! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + enum GeneralNameKind: u8 { + /// Other Name + OtherName = bssl_sys::GEN_OTHERNAME as u8, + /// Email Address + Email = bssl_sys::GEN_EMAIL as u8, + /// DNS + Dns = bssl_sys::GEN_DNS as u8, + /// X.400 + X400 = bssl_sys::GEN_X400 as u8, + /// Dir Name + DirName = bssl_sys::GEN_DIRNAME as u8, + /// EDI Party + EdiParty = bssl_sys::GEN_EDIPARTY as u8, + /// URI + Uri = bssl_sys::GEN_URI as u8, + /// IP address + IpAddr = bssl_sys::GEN_IPADD as u8, + /// RID + Rid = bssl_sys::GEN_RID as u8, + } +} + +/// Supported General Names in a Subject Alternative Name extension. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GeneralName<'a> { + /// Domain Name System name + Dns(&'a str), + /// E-mail address + Email(&'a str), + /// Uniform Resource Identifier + Uri(&'a str), + /// IP address + Ip(&'a [u8]), +} + +/// A collection of `GeneralName`s. +pub struct GeneralNames(NonNull<bssl_sys::GENERAL_NAMES>); + +impl GeneralNames { + /// Get an iterator into the names. + pub fn iter<'a>(&'a self) -> GeneralNamesIter<'a> { + let len = unsafe { + // Safety: `san` is still valid here. + bssl_sys::sk_GENERAL_NAME_num(self.0.as_ptr()) + }; + GeneralNamesIter { + data: self.0, + len, + cursor: 0, + _p: PhantomData, + } + } +} + +/// An enumeration of `GeneralName`s. +/// +/// This iterator skips invalid, unsupported or incomplete name entry. +#[derive(Clone, Copy)] +pub struct GeneralNamesIter<'a> { + data: NonNull<bssl_sys::GENERAL_NAMES>, + len: usize, + cursor: usize, + _p: PhantomData<&'a ()>, +} + +impl Drop for GeneralNames { + fn drop(&mut self) { + unsafe { + // Safety: `self` witnesses the validity of the `GENERAL_NAMES` handle. + bssl_sys::GENERAL_NAMES_free(self.0.as_ptr()); + } + } +} + +/// Safety: caller must ensure that `'a` outlives the input buffer. +unsafe fn extract_ia5str<'a>(ia5str: *const bssl_sys::ASN1_IA5STRING) -> Option<&'a str> { + let str_len = unsafe { + // Safety: `value` is still a valid `ASN1_STRING`, + // which is a super-type of `ASN1_IA5STRING. + bssl_sys::ASN1_STRING_length(ia5str) + }; + if str_len <= 0 { + return None; + } + let str_len = usize::try_from(str_len).ok()?; + let ptr = unsafe { + // Safety: `value` is still a valid `ASN1_STRING`, + // which is a super-type of `ASN1_IA5STRING. + bssl_sys::ASN1_STRING_get0_data(ia5str) + }; + if ptr.is_null() { + return None; + } + let bytes = unsafe { + // Safety: `'a` will still outlive the input buffer and this byte slice. + sanitize_slice(ptr, str_len)? + }; + core::str::from_utf8(bytes).ok() +} + +impl<'a> Iterator for GeneralNamesIter<'a> { + type Item = GeneralName<'a>; + + fn next(&mut self) -> Option<Self::Item> { + while self.cursor < self.len { + let name = unsafe { + // Safety: `data` is still valid. + bssl_sys::sk_GENERAL_NAME_value(self.data.as_ptr(), self.cursor) + }; + self.cursor += 1; + if name.is_null() { + continue; + } + let mut typ = -1; + let value = unsafe { bssl_sys::GENERAL_NAME_get0_value(name, &raw mut typ) }; + if value.is_null() || typ < 0 { + continue; + } + let Some(typ) = u8::try_from(typ) + .ok() + .and_then(|typ| GeneralNameKind::try_from(typ).ok()) + else { + continue; + }; + match typ { + GeneralNameKind::Dns => { + let Some(dns) = (unsafe { + // Safety: `value` is still a valid `ASN1_IA5STRING` and outlived by `self` + extract_ia5str(value as _) + }) else { + continue; + }; + return Some(GeneralName::Dns(dns)); + } + GeneralNameKind::Email => { + let Some(email) = (unsafe { + // Safety: `value` is still a valid `ASN1_IA5STRING` and outlived by `self` + extract_ia5str(value as _) + }) else { + continue; + }; + return Some(GeneralName::Email(email)); + } + GeneralNameKind::Uri => { + let Some(uri) = (unsafe { + // Safety: `value` is still a valid `ASN1_IA5STRING` and outlived by `self` + extract_ia5str(value as _) + }) else { + continue; + }; + return Some(GeneralName::Uri(uri)); + } + GeneralNameKind::IpAddr => { + let len = unsafe { + // Safety: `value` is still a valid `ASN1_STRING`, + // which is a super-type of `ASN1_IA5STRING. + bssl_sys::ASN1_STRING_length(value as _) + }; + match len { + 4 | 16 => { + let ptr = unsafe { + // Safety: `value` is still a valid `ASN1_OCTET_STRING` + bssl_sys::ASN1_STRING_get0_data(value as _) + }; + if ptr.is_null() { + continue; + } + let bytes = unsafe { + // Safety: `self` will outlive the input buffer and this byte slice. + sanitize_slice(ptr, len as usize)? + }; + return Some(GeneralName::Ip(bytes)); + } + _ => continue, + } + } + _ => continue, + } + } + None + } +} + +/// A X.509 certificate serial number +pub struct SerialNumber<'a>(NonNull<bssl_sys::ASN1_INTEGER>, PhantomData<&'a ()>); + +impl<'a> SerialNumber<'a> { + fn ptr(&self) -> *mut bssl_sys::ASN1_INTEGER { + self.0.as_ptr() + } + + /// Get a twos-complement big-endian representation of the serial number. + pub fn as_twos_complement_bytes(&self) -> Option<&[u8]> { + let serial = self.ptr(); + let len = unsafe { + // Safety: + // - self witnesses the validity of the X509 handle and, therefore, + // this handle to the integer; + // - ASN1_INTEGER is a subtype of ASN1_STRING. + bssl_sys::ASN1_STRING_length(serial) + }; + if len <= 0 { + return None; + } + let Ok(len) = usize::try_from(len) else { + return None; + }; + let ptr = unsafe { + // Safety: + // - self witnesses the validity of the X509 handle and, therefore, + // this handle to the integer; + // - ASN1_INTEGER is a subtype of ASN1_STRING. + bssl_sys::ASN1_STRING_get0_data(serial) + }; + unsafe { + // Safety: + // - `ptr` is non-null; + // - `len` is positive and less then isize::MAX; + // - the lifetime of `self` outlives that of `ptr`. + sanitize_slice(ptr, len) + } + } + + /// Get a `u64` value of the serial number. + pub fn as_u64(&self) -> Option<u64> { + let serial = self.ptr(); + let mut num = 0; + let rc = unsafe { + // Safety: + // - self witnesses the validity of the ASN1_INTEGER handle. + // - all pointers contain the initialised data. + bssl_sys::ASN1_INTEGER_get_uint64(&raw mut num, serial) + }; + (rc == 1).then_some(num) + } + + /// Get a `i64` value of the serial number. + pub fn as_i64(&self) -> Option<i64> { + let serial = self.ptr(); + let mut num = 0; + let rc = unsafe { + // Safety: + // - self witnesses the validity of the ASN1_INTEGER handle. + // - all pointers contain the initialised data. + bssl_sys::ASN1_INTEGER_get_int64(&raw mut num, serial) + }; + (rc == 1).then_some(num) + } +} + +/// An X.509 certificate +#[repr(transparent)] +pub struct X509Certificate(NonNull<bssl_sys::X509>); + +impl Clone for X509Certificate { + fn clone(&self) -> Self { + unsafe { + // Safety: self witnesses the validity of the X509 handle. + bssl_sys::X509_up_ref(self.0.as_ptr()); + } + Self(self.0) + } +} + +impl Drop for X509Certificate { + fn drop(&mut self) { + unsafe { + // Safety: this handle is taken from the builder, so it must be + // live and valid. + bssl_sys::X509_free(self.0.as_ptr()); + } + } +} + +impl X509Certificate { + /// Parse an X.509 certificate from DER and return the remaining bytes. + pub fn from_der(der: &[u8]) -> Result<(Self, &[u8]), PkiError> { + let (mut ptr, len) = slice_into_ffi_raw_parts(der); + let Ok(size) = len.try_into() else { + return Err(PkiError::X509(X509Error::InvalidParameters)); + }; + let start = ptr; + let res = unsafe { + // Safety: the buffer `ptr` is still valid. + bssl_sys::d2i_X509(null_mut(), &raw mut ptr, size) + }; + if ptr.is_null() { + return Err(PkiError::extract_lib_err()); + } + let cert = NonNull::new(res).ok_or_else(|| PkiError::extract_lib_err())?; + let consumed_bytes = unsafe { + // Safety: BoringSSL ensures that the pointer points at the one byte past the last DER + // byte. + ptr.offset_from(start) as usize + }; + Ok((Self(cert), &der[consumed_bytes..])) + } + + /// Serialise an X.509 certificate into DER. + pub fn to_der(&self) -> Result<Vec<u8>, PkiError> { + let len = unsafe { + // Safety: `self` holds a valid handle to `X509` object. + bssl_sys::i2d_X509(self.ptr(), null_mut()) + }; + if len < 0 { + return Err(PkiError::extract_lib_err()); + } + let Ok(buf_len) = usize::try_from(len) else { + return Err(PkiError::X509(X509Error::InvalidParameters)); + }; + let mut vec = vec![0; buf_len]; + let mut ptr = vec.as_mut_ptr(); + let len = unsafe { + // Safety: + // - `self` holds a valid handle to `X509` object. + // - `ptr` points to a valid buffer allocation with a sufficient length advised by + // the first oracle call. + bssl_sys::i2d_X509(self.ptr(), &raw mut ptr) + }; + if len < 0 { + Err(PkiError::extract_lib_err()) + } else { + Ok(vec) + } + } + + /// Identify all PEM structures in the input buffer and parse until the end + /// or the first error encountered. + pub fn parse_all_from_pem(pem: &[u8]) -> Result<Vec<Self>, PkiError> { + let mut bio = match Bio::from_bytes(pem) { + Ok(bio) => bio, + Err(_) => return Err(PkiError::X509(X509Error::PemTooLong)), + }; + let mut res = Vec::new(); + loop { + let (cert, eof) = match Self::parse_one(&mut bio) { + Ok(res) => res, + Err(PkiError::X509(X509Error::Pem(PemReason::NoStartLine))) if !res.is_empty() => { + return Ok(res); + } + Err(e) => return Err(e), + }; + if let Some(cert) = cert { + res.push(cert); + } + if eof { + break; + } + } + Ok(res) + } + + /// Parse one X.509 certificate from the PEM. + pub fn parse_one_from_pem(pem: &[u8]) -> Result<Self, PkiError> { + let mut bio = match Bio::from_bytes(pem) { + Ok(bio) => bio, + Err(_) => return Err(PkiError::X509(X509Error::PemTooLong)), + }; + let (cert, _) = Self::parse_one(&mut bio)?; + if let Some(cert) = cert { + Ok(cert) + } else { + Err(PkiError::X509(X509Error::NoPemBlocks)) + } + } + + pub(crate) fn parse_one(bio: &mut Bio<'_>) -> Result<(Option<Self>, bool), PkiError> { + let mut x509 = null_mut(); + unsafe { + // Safety: + // - the BIO is still valid; + // - the X509 pointer is not null, so the function will allocate a new structure; + // - the return value can be discarded since we provided a location to hold the handle, + // whose lifetime will be managed from this function. + bssl_sys::PEM_read_bio_X509(bio.ptr(), &raw mut x509, None, null_mut()); + } + let eof = unsafe { + // Safety: the BIO is still valid. + bssl_sys::BIO_eof(bio.ptr()) != 0 + }; + if let Some(x509) = NonNull::new(x509) { + // Safety: BoringSSL guarantees that we still have a valid handle here. + Ok((Some(Self(x509)), eof)) + } else { + Err(PkiError::extract_lib_err()) + } + } + + pub(crate) fn ptr(&self) -> *mut bssl_sys::X509 { + self.0.as_ptr() + } + + /// Take a **borrowed** raw certificate handle constructed by BoringSSL. + /// + /// **Use this only for cross-language interoperability.** + /// + /// # Safety + /// + /// The handle `x509` **must** be constructed by BoringSSL and valid. + /// This method increments the ref-count of the handle. + /// One should use this especially in case that the handle is returned from a + /// `*_get0_*` method from BoringSSL or [`bssl_sys`] in particular. + pub unsafe fn from_borrowed_raw(x509: NonNull<bssl_sys::X509>) -> Self { + unsafe { + // Safety: `cert` is valid and owned by the list. + bssl_sys::X509_up_ref(x509.as_ptr()); + } + Self(x509) + } + + /// Take a raw certificate handle constructed by BoringSSL. + /// + /// **Use this only for cross-language interoperability.** + /// + /// # Safety + /// + /// The handle `x509` **must** be constructed by BoringSSL and valid. + /// Unlike [`Self::from_borrowed_raw`], this method does **not** increment the ref-count + /// of the handle. + /// One should use this especially in case that the handle is returned from a + /// `*_get1_*` method from BoringSSL or [`bssl_sys`] in particular; or the ownership of the + /// handle has been transferred to the caller a priori. + pub unsafe fn from_raw(x509: NonNull<bssl_sys::X509>) -> Self { + Self(x509) + } + + /// Take a reference to a raw certificate handle constructed by BoringSSL. + /// + /// **Use this only for cross-language interoperability.** + /// + /// # Safety + /// + /// The handle `x509` **must** be constructed by BoringSSL and valid. + /// More over, `x509` must outlive the returned reference. + pub unsafe fn from_raw_ref(x509: &NonNull<bssl_sys::X509>) -> &Self { + unsafe { + // Safety: `Self` is a transparent wrapper around `NonNull<bssl_sys::X509>`. + transmute(x509) + } + } + + /// This method dischanges the ownership. + /// + /// Use this method only for cross-language interoperability. + pub fn into_raw(self) -> *mut bssl_sys::X509 { + let ptr = self.ptr(); + forget(self); + ptr + } + + /// Get notBefore time as POSIX timestamp. + /// + /// This method returns `None` if the certificate does not have a valid + /// time value in this field. + pub fn get_not_before(&self) -> Option<i64> { + let time = unsafe { + // Safety: self witnesses the validity of the X509 handle. + bssl_sys::X509_get0_notBefore(self.ptr()) + }; + if time.is_null() { + return None; + } + let mut time_val = 0; + let rc = unsafe { + // Safety: both `time` and `time_val` are valid pointers to live objects. + bssl_sys::ASN1_TIME_to_posix(time, &raw mut time_val) + }; + (rc == 1).then_some(time_val) + } + + /// Get notAfter time of the certificate as POSIX timestamp. + /// + /// This method returns `None` if the certificate does not have a valid + /// time value in this field. + pub fn get_not_after(&self) -> Option<i64> { + let time = unsafe { + // Safety: self witnesses the validity of the X509 handle. + bssl_sys::X509_get0_notAfter(self.ptr()) + }; + if time.is_null() { + return None; + } + let mut time_val = 0; + let rc = unsafe { + // Safety: both `time` and `time_val` are valid pointers to live objects. + bssl_sys::ASN1_TIME_to_posix(time, &raw mut time_val) + }; + (rc == 1).then_some(time_val) + } + + /// Get the serial number of the certificate. + /// + /// This method returns `None` if the certificate does not have a valid + /// serial number. + pub fn get_serial_number(&self) -> Option<SerialNumber<'_>> { + let serial = unsafe { + // Safety: self witnesses the validity of the X509 handle. + bssl_sys::X509_get0_serialNumber(self.ptr()) + }; + let serial = NonNull::new(serial as _)?; + Some(SerialNumber(serial, PhantomData)) + } + + /// Check if the private key matches the public key in the certificate. + pub fn matches_private_key(&self, key: &PrivateKey) -> bool { + unsafe { + // Safety: + // - `self.ptr()` is a valid pointer to an `X509` object. + // - `key.ptr()` is a valid pointer to an `EVP_PKEY` object. + bssl_sys::X509_check_private_key(self.ptr(), key.ptr()) == 1 + } + } + + /// Enumerate Subject Alternative Names. + pub fn subject_alt_names(&self) -> Option<GeneralNames> { + let san = unsafe { + // Safety: `self` witnesses the validity of the `X509` handle. + bssl_sys::X509_get_ext_d2i( + self.ptr(), + bssl_sys::NID_subject_alt_name, + null_mut(), + null_mut(), + ) + }; + let san = san as *mut bssl_sys::GENERAL_NAMES; + NonNull::new(san).map(GeneralNames) + } +}
diff --git a/rust/bssl-x509/src/errors.rs b/rust/bssl-x509/src/errors.rs new file mode 100644 index 0000000..54714f2 --- /dev/null +++ b/rust/bssl-x509/src/errors.rs
@@ -0,0 +1,419 @@ +// 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 errors. + +use core::ffi::{CStr, c_char}; +use core::fmt::{self, Debug, Display}; + +use bssl_macros::bssl_enum; +use bssl_sys::LibCode; + +bssl_enum! { + /// X.509 certificate verification result code. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum X509VerifyResult: i32 { + /// The operation was successful. + Ok = bssl_sys::X509_V_OK as i32, + /// Unspecified error. + Unspecified = bssl_sys::X509_V_ERR_UNSPECIFIED as i32, + /// Unable to get issuer certificate. + UnableToGetIssuerCert = bssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT as i32, + /// Unable to get CRL. + UnableToGetCrl = bssl_sys::X509_V_ERR_UNABLE_TO_GET_CRL as i32, + /// Unable to decrypt certificate signature. + UnableToDecryptCertSignature = bssl_sys::X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE as i32, + /// Unable to decrypt CRL signature. + UnableToDecryptCrlSignature = bssl_sys::X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE as i32, + /// Unable to decode issuer public key. + UnableToDecodeIssuerPublicKey = bssl_sys::X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY as i32, + /// Certificate signature failure. + CertSignatureFailure = bssl_sys::X509_V_ERR_CERT_SIGNATURE_FAILURE as i32, + /// CRL signature failure. + CrlSignatureFailure = bssl_sys::X509_V_ERR_CRL_SIGNATURE_FAILURE as i32, + /// Certificate is not yet valid. + CertNotYetValid = bssl_sys::X509_V_ERR_CERT_NOT_YET_VALID as i32, + /// Certificate has expired. + CertHasExpired = bssl_sys::X509_V_ERR_CERT_HAS_EXPIRED as i32, + /// CRL is not yet valid. + CrlNotYetValid = bssl_sys::X509_V_ERR_CRL_NOT_YET_VALID as i32, + /// CRL has expired. + CrlHasExpired = bssl_sys::X509_V_ERR_CRL_HAS_EXPIRED as i32, + /// Error in certificate NotBefore field. + ErrorInCertNotBeforeField = bssl_sys::X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD as i32, + /// Error in certificate NotAfter field. + ErrorInCertNotAfterField = bssl_sys::X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD as i32, + /// Error in CRL LastUpdate field. + ErrorInCrlLastUpdateField = bssl_sys::X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD as i32, + /// Error in CRL NextUpdate field. + ErrorInCrlNextUpdateField = bssl_sys::X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD as i32, + /// Out of memory. + OutOfMem = bssl_sys::X509_V_ERR_OUT_OF_MEM as i32, + /// Depth zero self-signed certificate. + DepthZeroSelfSignedCert = bssl_sys::X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT as i32, + /// Self-signed certificate in chain. + SelfSignedCertInChain = bssl_sys::X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN as i32, + /// Unable to get issuer certificate locally. + UnableToGetIssuerCertLocally = bssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY as i32, + /// Unable to verify leaf signature. + UnableToVerifyLeafSignature = bssl_sys::X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE as i32, + /// Certificate chain too long. + CertChainTooLong = bssl_sys::X509_V_ERR_CERT_CHAIN_TOO_LONG as i32, + /// Certificate revoked. + CertRevoked = bssl_sys::X509_V_ERR_CERT_REVOKED as i32, + /// Invalid CA. + InvalidCa = bssl_sys::X509_V_ERR_INVALID_CA as i32, + /// Path length exceeded. + PathLengthExceeded = bssl_sys::X509_V_ERR_PATH_LENGTH_EXCEEDED as i32, + /// Invalid purpose. + InvalidPurpose = bssl_sys::X509_V_ERR_INVALID_PURPOSE as i32, + /// Certificate untrusted. + CertUntrusted = bssl_sys::X509_V_ERR_CERT_UNTRUSTED as i32, + /// Certificate rejected. + CertRejected = bssl_sys::X509_V_ERR_CERT_REJECTED as i32, + /// Subject issuer mismatch. + SubjectIssuerMismatch = bssl_sys::X509_V_ERR_SUBJECT_ISSUER_MISMATCH as i32, + /// Authority Key Identifier and Subject Key Identifier mismatch. + AkidSkidMismatch = bssl_sys::X509_V_ERR_AKID_SKID_MISMATCH as i32, + /// Authority Key Identifier issuer serial mismatch. + AkidIssuerSerialMismatch = bssl_sys::X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH as i32, + /// Key usage does not include certificate signing. + KeyusageNoCertsign = bssl_sys::X509_V_ERR_KEYUSAGE_NO_CERTSIGN as i32, + /// Unable to get CRL issuer. + UnableToGetCrlIssuer = bssl_sys::X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER as i32, + /// Unhandled critical extension. + UnhandledCriticalExtension = bssl_sys::X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION as i32, + /// Key usage does not include CRL signing. + KeyusageNoCrlSign = bssl_sys::X509_V_ERR_KEYUSAGE_NO_CRL_SIGN as i32, + /// Unhandled critical CRL extension. + UnhandledCriticalCrlExtension = bssl_sys::X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION as i32, + /// Invalid non-CA. + InvalidNonCa = bssl_sys::X509_V_ERR_INVALID_NON_CA as i32, + /// Proxy path length exceeded. + ProxyPathLengthExceeded = bssl_sys::X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED as i32, + /// Key usage does not include digital signature. + KeyusageNoDigitalSignature = bssl_sys::X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE as i32, + /// Proxy certificates not allowed. + ProxyCertificatesNotAllowed = bssl_sys::X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED as i32, + /// Invalid extension. + InvalidExtension = bssl_sys::X509_V_ERR_INVALID_EXTENSION as i32, + /// Invalid policy extension. + InvalidPolicyExtension = bssl_sys::X509_V_ERR_INVALID_POLICY_EXTENSION as i32, + /// No explicit policy. + NoExplicitPolicy = bssl_sys::X509_V_ERR_NO_EXPLICIT_POLICY as i32, + /// Different CRL scope. + DifferentCrlScope = bssl_sys::X509_V_ERR_DIFFERENT_CRL_SCOPE as i32, + /// Unsupported extension feature. + UnsupportedExtensionFeature = bssl_sys::X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE as i32, + /// Unnested resource. + UnnestedResource = bssl_sys::X509_V_ERR_UNNESTED_RESOURCE as i32, + /// Permitted violation. + PermittedViolation = bssl_sys::X509_V_ERR_PERMITTED_VIOLATION as i32, + /// Excluded violation. + ExcludedViolation = bssl_sys::X509_V_ERR_EXCLUDED_VIOLATION as i32, + /// Subtree min/max. + SubtreeMinmax = bssl_sys::X509_V_ERR_SUBTREE_MINMAX as i32, + /// Application verification. + ApplicationVerification = bssl_sys::X509_V_ERR_APPLICATION_VERIFICATION as i32, + /// Unsupported constraint type. + UnsupportedConstraintType = bssl_sys::X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE as i32, + /// Unsupported constraint syntax. + UnsupportedConstraintSyntax = bssl_sys::X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX as i32, + /// Unsupported name syntax. + UnsupportedNameSyntax = bssl_sys::X509_V_ERR_UNSUPPORTED_NAME_SYNTAX as i32, + /// CRL path validation error. + CrlPathValidationError = bssl_sys::X509_V_ERR_CRL_PATH_VALIDATION_ERROR as i32, + /// Hostname mismatch. + HostnameMismatch = bssl_sys::X509_V_ERR_HOSTNAME_MISMATCH as i32, + /// Email mismatch. + EmailMismatch = bssl_sys::X509_V_ERR_EMAIL_MISMATCH as i32, + /// IP address mismatch. + IpAddressMismatch = bssl_sys::X509_V_ERR_IP_ADDRESS_MISMATCH as i32, + /// Invalid call. + InvalidCall = bssl_sys::X509_V_ERR_INVALID_CALL as i32, + /// Store lookup error. + StoreLookup = bssl_sys::X509_V_ERR_STORE_LOOKUP as i32, + /// Name constraints without SANs. + NameConstraintsWithoutSans = bssl_sys::X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS as i32, + } +} + +bssl_enum! { + /// X.509 reason codes. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum X509Reason: i32 { + /// `X509_R_AKID_MISMATCH` + AkidMismatch = bssl_sys::X509_R_AKID_MISMATCH as i32, + /// `X509_R_BAD_PKCS7_VERSION` + BadPkcs7Version = bssl_sys::X509_R_BAD_PKCS7_VERSION as i32, + /// `X509_R_BAD_X509_FILETYPE` + BadX509Filetype = bssl_sys::X509_R_BAD_X509_FILETYPE as i32, + /// `X509_R_BASE64_DECODE_ERROR` + Base64DecodeError = bssl_sys::X509_R_BASE64_DECODE_ERROR as i32, + /// `X509_R_CANT_CHECK_DH_KEY` + CantCheckDhKey = bssl_sys::X509_R_CANT_CHECK_DH_KEY as i32, + /// `X509_R_CERT_ALREADY_IN_HASH_TABLE` + CertAlreadyInHashTable = bssl_sys::X509_R_CERT_ALREADY_IN_HASH_TABLE as i32, + /// `X509_R_CRL_ALREADY_DELTA` + CrlAlreadyDelta = bssl_sys::X509_R_CRL_ALREADY_DELTA as i32, + /// `X509_R_CRL_VERIFY_FAILURE` + CrlVerifyFailure = bssl_sys::X509_R_CRL_VERIFY_FAILURE as i32, + /// `X509_R_IDP_MISMATCH` + IdpMismatch = bssl_sys::X509_R_IDP_MISMATCH as i32, + /// `X509_R_INVALID_BIT_STRING_BITS_LEFT` + InvalidBitStringBitsLeft = bssl_sys::X509_R_INVALID_BIT_STRING_BITS_LEFT as i32, + /// `X509_R_INVALID_DIRECTORY` + InvalidDirectory = bssl_sys::X509_R_INVALID_DIRECTORY as i32, + /// `X509_R_INVALID_FIELD_NAME` + InvalidFieldName = bssl_sys::X509_R_INVALID_FIELD_NAME as i32, + /// `X509_R_INVALID_PSS_PARAMETERS` + InvalidPssParameters = bssl_sys::X509_R_INVALID_PSS_PARAMETERS as i32, + /// `X509_R_INVALID_TRUST` + InvalidTrust = bssl_sys::X509_R_INVALID_TRUST as i32, + /// `X509_R_ISSUER_MISMATCH` + IssuerMismatch = bssl_sys::X509_R_ISSUER_MISMATCH as i32, + /// `X509_R_KEY_TYPE_MISMATCH` + KeyTypeMismatch = bssl_sys::X509_R_KEY_TYPE_MISMATCH as i32, + /// `X509_R_KEY_VALUES_MISMATCH` + KeyValuesMismatch = bssl_sys::X509_R_KEY_VALUES_MISMATCH as i32, + /// `X509_R_LOADING_CERT_DIR` + LoadingCertDir = bssl_sys::X509_R_LOADING_CERT_DIR as i32, + /// `X509_R_LOADING_DEFAULTS` + LoadingDefaults = bssl_sys::X509_R_LOADING_DEFAULTS as i32, + /// `X509_R_NEWER_CRL_NOT_NEWER` + NewerCrlNotNewer = bssl_sys::X509_R_NEWER_CRL_NOT_NEWER as i32, + /// `X509_R_NOT_PKCS7_SIGNED_DATA` + NotPkcs7SignedData = bssl_sys::X509_R_NOT_PKCS7_SIGNED_DATA as i32, + /// `X509_R_NO_CERTIFICATES_INCLUDED` + NoCertificatesIncluded = bssl_sys::X509_R_NO_CERTIFICATES_INCLUDED as i32, + /// `X509_R_NO_CERT_SET_FOR_US_TO_VERIFY` + NoCertSetForUsToVerify = bssl_sys::X509_R_NO_CERT_SET_FOR_US_TO_VERIFY as i32, + /// `X509_R_NO_CRLS_INCLUDED` + NoCrlsIncluded = bssl_sys::X509_R_NO_CRLS_INCLUDED as i32, + /// `X509_R_NO_CRL_NUMBER` + NoCrlNumber = bssl_sys::X509_R_NO_CRL_NUMBER as i32, + /// `X509_R_PUBLIC_KEY_DECODE_ERROR` + PublicKeyDecodeError = bssl_sys::X509_R_PUBLIC_KEY_DECODE_ERROR as i32, + /// `X509_R_PUBLIC_KEY_ENCODE_ERROR` + PublicKeyEncodeError = bssl_sys::X509_R_PUBLIC_KEY_ENCODE_ERROR as i32, + /// `X509_R_SHOULD_RETRY` + ShouldRetry = bssl_sys::X509_R_SHOULD_RETRY as i32, + /// `X509_R_UNKNOWN_KEY_TYPE` + UnknownKeyType = bssl_sys::X509_R_UNKNOWN_KEY_TYPE as i32, + /// `X509_R_UNKNOWN_NID` + UnknownNid = bssl_sys::X509_R_UNKNOWN_NID as i32, + /// `X509_R_UNKNOWN_PURPOSE_ID` + UnknownPurposeId = bssl_sys::X509_R_UNKNOWN_PURPOSE_ID as i32, + /// `X509_R_UNKNOWN_TRUST_ID` + UnknownTrustId = bssl_sys::X509_R_UNKNOWN_TRUST_ID as i32, + /// `X509_R_UNSUPPORTED_ALGORITHM` + UnsupportedAlgorithm = bssl_sys::X509_R_UNSUPPORTED_ALGORITHM as i32, + /// `X509_R_WRONG_LOOKUP_TYPE` + WrongLookupType = bssl_sys::X509_R_WRONG_LOOKUP_TYPE as i32, + /// `X509_R_WRONG_TYPE` + WrongType = bssl_sys::X509_R_WRONG_TYPE as i32, + /// `X509_R_NAME_TOO_LONG` + NameTooLong = bssl_sys::X509_R_NAME_TOO_LONG as i32, + /// `X509_R_INVALID_PARAMETER` + InvalidParameter = bssl_sys::X509_R_INVALID_PARAMETER as i32, + /// `X509_R_SIGNATURE_ALGORITHM_MISMATCH` + SignatureAlgorithmMismatch = bssl_sys::X509_R_SIGNATURE_ALGORITHM_MISMATCH as i32, + /// `X509_R_DELTA_CRL_WITHOUT_CRL_NUMBER` + DeltaCrlWithoutCrlNumber = bssl_sys::X509_R_DELTA_CRL_WITHOUT_CRL_NUMBER as i32, + /// `X509_R_INVALID_FIELD_FOR_VERSION` + InvalidFieldForVersion = bssl_sys::X509_R_INVALID_FIELD_FOR_VERSION as i32, + /// `X509_R_INVALID_VERSION` + InvalidVersion = bssl_sys::X509_R_INVALID_VERSION as i32, + /// `X509_R_NO_CERTIFICATE_FOUND` + NoCertificateFound = bssl_sys::X509_R_NO_CERTIFICATE_FOUND as i32, + /// `X509_R_NO_CERTIFICATE_OR_CRL_FOUND` + NoCertificateOrCrlFound = bssl_sys::X509_R_NO_CERTIFICATE_OR_CRL_FOUND as i32, + /// `X509_R_NO_CRL_FOUND` + NoCrlFound = bssl_sys::X509_R_NO_CRL_FOUND as i32, + /// `X509_R_INVALID_POLICY_EXTENSION` + InvalidPolicyExtension = bssl_sys::X509_R_INVALID_POLICY_EXTENSION as i32, + } +} + +bssl_enum! { + /// PEM parser failure reasons. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum PemReason: i32 { + /// `PEM_R_BAD_BASE64_DECODE` + BadBase64Decode = bssl_sys::PEM_R_BAD_BASE64_DECODE as i32, + /// `PEM_R_BAD_DECRYPT` + BadDecrypt = bssl_sys::PEM_R_BAD_DECRYPT as i32, + /// `PEM_R_BAD_END_LINE` + BadEndLine = bssl_sys::PEM_R_BAD_END_LINE as i32, + /// `PEM_R_BAD_IV_CHARS` + BadIvChars = bssl_sys::PEM_R_BAD_IV_CHARS as i32, + /// `PEM_R_BAD_PASSWORD_READ` + BadPasswordRead = bssl_sys::PEM_R_BAD_PASSWORD_READ as i32, + /// `PEM_R_CIPHER_IS_NULL` + CipherIsNull = bssl_sys::PEM_R_CIPHER_IS_NULL as i32, + /// `PEM_R_ERROR_CONVERTING_PRIVATE_KEY` + ErrorConvertingPrivateKey = bssl_sys::PEM_R_ERROR_CONVERTING_PRIVATE_KEY as i32, + /// `PEM_R_NOT_DEK_INFO` + NotDekInfo = bssl_sys::PEM_R_NOT_DEK_INFO as i32, + /// `PEM_R_NOT_ENCRYPTED` + NotEncrypted = bssl_sys::PEM_R_NOT_ENCRYPTED as i32, + /// `PEM_R_NOT_PROC_TYPE` + NotProcType = bssl_sys::PEM_R_NOT_PROC_TYPE as i32, + /// `PEM_R_NO_START_LINE` + NoStartLine = bssl_sys::PEM_R_NO_START_LINE as i32, + /// `PEM_R_READ_KEY` + ReadKey = bssl_sys::PEM_R_READ_KEY as i32, + /// `PEM_R_SHORT_HEADER` + ShortHeader = bssl_sys::PEM_R_SHORT_HEADER as i32, + /// `PEM_R_UNSUPPORTED_CIPHER` + UnsupportedCipher = bssl_sys::PEM_R_UNSUPPORTED_CIPHER as i32, + /// `PEM_R_UNSUPPORTED_ENCRYPTION` + UnsupportedEncryption = bssl_sys::PEM_R_UNSUPPORTED_ENCRYPTION as i32, + /// `PEM_R_UNSUPPORTED_PROC_TYPE_VERSION` + UnsupportedProcTypeVersion = bssl_sys::PEM_R_UNSUPPORTED_PROC_TYPE_VERSION as i32, + } +} + +/// X.509 related errors +#[derive(Debug)] +pub enum X509Error { + /// PEM string is too long. + PemTooLong, + /// PEM Password has interior null byte. + PemPasswordHasInteriorNull, + /// X.509 certificate verification error. + Verify(X509VerifyResult), + /// No PEM blocks found. + NoPemBlocks, + /// Invalid parameters. + InvalidParameters, + /// X.509 module failure with reasons. + Reason(X509Reason), + /// PEM module errors. + Pem(PemReason), +} + +impl Display for X509Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + X509Error::PemTooLong => f.write_str("PEM string too long"), + X509Error::PemPasswordHasInteriorNull => { + f.write_str("PEM password has interior null byte") + } + X509Error::Verify(err) => Debug::fmt(err, f), + X509Error::NoPemBlocks => f.write_str("no PEM blocks found"), + X509Error::InvalidParameters => f.write_str("invalid X.509 parameters"), + X509Error::Reason(err) => Debug::fmt(err, f), + X509Error::Pem(err) => Debug::fmt(err, f), + } + } +} + +/// Main PKI errors +#[derive(Debug)] +pub enum PkiError { + /// X.509 errors + X509(X509Error), + /// Byte string contains internal NUL bytes. + InternalNullBytes, + /// Invalid IP address. + InvalidIp, + /// Value out of range. + ValueOutOfRange, + /// Library sourced error. + Library(u32, Option<LibCode>, Option<i32>), +} + +impl core::error::Error for PkiError {} + +impl Display for PkiError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PkiError::X509(err) => Display::fmt(err, f), + PkiError::InternalNullBytes => f.write_str("byte string contains internal NUL bytes"), + PkiError::InvalidIp => f.write_str("invalid IP address"), + PkiError::ValueOutOfRange => f.write_str("value out of range"), + &PkiError::Library(code, _, _) => { + // Here the buffer is 120 bytes and BoringSSL uses this buffer size + // to hold the error string. + // Therefore, this ought to be sufficient for a human-readable message. + let mut err_str: [c_char; _] = [0; bssl_sys::ERR_ERROR_STRING_BUF_LEN as usize]; + unsafe { + // Safety: + // - err_str is non-null and valid, so we do not need FFI-specific pointer conversion. + bssl_sys::ERR_error_string_n(code, err_str.as_mut_ptr(), err_str.len()); + } + let err_str = unsafe { + // Safety: + // - err_str is still valid; + // - `ERR_error_string_n` guarantees that + CStr::from_ptr(err_str.as_ptr()) + }; + f.write_str(&err_str.to_string_lossy()) + } + } + } +} + +impl PkiError { + #[inline(always)] + pub(crate) fn extract_lib_err() -> Self { + let packed_error = unsafe { + // Safety: extracting error code does not have side-effect + bssl_sys::ERR_get_error() + }; + let error = Self::extract_err_from_code(packed_error); + unsafe { + // Safety: we only clear the error queue on the current thread. + bssl_sys::ERR_clear_error(); + } + error + } + + #[allow(irrefutable_let_patterns)] + fn extract_err_from_code(packed_error: u32) -> Self { + let lib = unsafe { + // Safety: extracing error source does not have a side-effect and only accesses static data. + bssl_sys::ERR_GET_LIB(packed_error) + }; + let Ok(lib) = i32::try_from(lib) else { + return Self::Library(packed_error, None, None); + }; + let Ok(lib) = LibCode::try_from(lib) else { + return Self::Library(packed_error, None, None); + }; + let reason = unsafe { + // Safety: extracing error reason does not have a side-effect and only accesses static data. + bssl_sys::ERR_GET_REASON(packed_error) + }; + let Ok(reason) = i32::try_from(reason) else { + return Self::Library(packed_error, Some(lib), None); + }; + match lib { + LibCode::X509 => { + let Ok(reason) = X509Reason::try_from(reason) else { + return Self::Library(packed_error, Some(lib), Some(reason)); + }; + Self::X509(X509Error::Reason(reason)) + } + LibCode::Pem => { + let Ok(reason) = PemReason::try_from(reason) else { + return Self::Library(packed_error, Some(lib), Some(reason)); + }; + Self::X509(X509Error::Pem(reason)) + } + _ => Self::Library(packed_error, Some(lib), Some(reason)), + } + } +}
diff --git a/rust/bssl-x509/src/ffi.rs b/rust/bssl-x509/src/ffi.rs new file mode 100644 index 0000000..71a3417 --- /dev/null +++ b/rust/bssl-x509/src/ffi.rs
@@ -0,0 +1,116 @@ +// 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. + +use core::{ + marker::PhantomData, + ptr::{NonNull, null}, + slice::from_raw_parts, +}; + +use bssl_crypto::FfiSlice; + +use crate::errors::{PkiError, X509Error}; + +/// This method catches panics before they cross language boundary. +#[must_use] +#[inline] +pub(crate) fn maybe_panic<T>(work: impl FnOnce() -> T) -> T { + let assert_unwind_safe = core::panic::AssertUnwindSafe(work); + let call = move || { + let core::panic::AssertUnwindSafe(work) = { assert_unwind_safe }; + work() + }; + #[cfg(feature = "std")] + let res = match std::panic::catch_unwind(call) { + Ok(res) => res, + Err(e) => { + eprintln!("panic about to cross FFI: {:?}", e); + std::process::abort() + } + }; + #[cfg(not(feature = "std"))] + let res = call(); + res +} + +pub(crate) fn slice_into_ffi_raw_parts<T>(slice: &[T]) -> (*const T, usize) { + if slice.is_empty() { + (null(), 0) + } else { + (slice.as_ptr(), slice.len()) + } +} + +/// Sanitize the data pointer and length and reconstitute the slice. +/// +/// This method returns an empty slice if the length is 0 or the pointer is NULL. +/// # Safety +/// Caller must ensure that `'a` outlives `input`. +#[inline] +pub(crate) unsafe fn sanitize_slice<'a, T>(input: *const T, len: usize) -> Option<&'a [T]> { + if len == 0 || input.is_null() { + return Some(&[]); + } + if !input.is_aligned() || len.checked_mul(size_of::<T>())? >= isize::MAX as usize { + return None; + } + unsafe { + // Safety: the pointer and the size has been sanitised. + Some(from_raw_parts(input, len)) + } +} + +/// BIO wrapper only for internal use. +pub(crate) struct Bio<'a>(NonNull<bssl_sys::BIO>, PhantomData<&'a ()>); + +impl<'a> Bio<'a> { + /// # Safety + /// Caller must ensure that the lifetime of this BIO outlives the backing object. + /// It is strongly recommended to call the builder functions. + pub(crate) unsafe fn new(bio: NonNull<bssl_sys::BIO>) -> Self { + Bio(bio, PhantomData) + } + + pub fn from_bytes(buf: &'a [u8]) -> Result<Self, PkiError> { + let len = if let Ok(len) = buf.len().try_into() { + len + } else { + return Err(PkiError::X509(X509Error::PemTooLong)); + }; + let mem_buf = unsafe { + // Safety: buf is still valid + bssl_sys::BIO_new_mem_buf(buf.as_ffi_void_ptr(), len) + }; + let Some(mem_buf) = NonNull::new(mem_buf) else { + panic!("bio: allocation failure"); + }; + Ok(unsafe { + // Safety: our returned object is outlived by the input buffer. + Self::new(mem_buf) + }) + } + + pub fn ptr(&mut self) -> *mut bssl_sys::BIO { + self.0.as_ptr() + } +} + +impl<'a> Drop for Bio<'a> { + fn drop(&mut self) { + unsafe { + // Safety: the BIO handle should still be valid + bssl_sys::BIO_free(self.0.as_ptr()); + } + } +}
diff --git a/rust/bssl-x509/src/keys.rs b/rust/bssl-x509/src/keys.rs new file mode 100644 index 0000000..d3244b9 --- /dev/null +++ b/rust/bssl-x509/src/keys.rs
@@ -0,0 +1,186 @@ +// 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. + +//! Private keys +//! +//! The private keys processed here can be paired with certificates to perform further +//! authentication. +//! +//! ```rust +//! # use bssl_x509::certificates::X509Certificate; +//! # use bssl_x509::keys::PrivateKey; +//! # let pem = include_bytes!("tests/BoringSSLTestCA.key"); +//! # let crt = include_bytes!("tests/BoringSSLTestCA.crt"); +//! # let crt = X509Certificate::parse_one_from_pem(crt).unwrap(); +//! +//! let key = PrivateKey::from_pem(pem, || b"BoringSSL is awesome!").unwrap(); +//! assert!(crt.matches_private_key(&key)); +//! ``` + +use core::ffi::{c_char, c_int, c_void}; +use core::panic::AssertUnwindSafe; +use core::ptr::NonNull; +use core::ptr::null_mut; + +use bssl_crypto::FfiSlice; +use bssl_macros::bssl_enum; + +use crate::ffi::maybe_panic; +use crate::{errors::PkiError, ffi::Bio}; + +bssl_enum! { + /// EVP public key algorithm types. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub enum PrivateKeyAlgorithm: i32 { + /// RSA + Rsa = bssl_sys::EVP_PKEY_RSA as i32, + /// RSA-PSS + RsaPss = bssl_sys::EVP_PKEY_RSA_PSS as i32, + /// EC + Ec = bssl_sys::EVP_PKEY_EC as i32, + /// Ed25519 + Ed25519 = bssl_sys::EVP_PKEY_ED25519 as i32, + /// X25519 + X25519 = bssl_sys::EVP_PKEY_X25519 as i32, + /// DSA + Dsa = bssl_sys::EVP_PKEY_DSA as i32, + /// DH + Dh = bssl_sys::EVP_PKEY_DH as i32, + } +} + +/// A private key. +pub struct PrivateKey(NonNull<bssl_sys::EVP_PKEY>); +// Safety: `PrivateKey` is locked as immutable at this type state. +unsafe impl Send for PrivateKey {} +unsafe impl Sync for PrivateKey {} + +impl PrivateKey { + /// Parse a `PrivateKey` from PEM encoding. + pub fn from_pem<'a, F: 'a + FnMut() -> &'a [u8]>( + pem: &[u8], + mut password_callback: F, + ) -> Result<Self, PkiError> { + let mut bio = Bio::from_bytes(pem)?; + + let mut priv_key = null_mut(); + + unsafe extern "C" fn write_password<'a, F: 'a + FnMut() -> &'a [u8]>( + out: *mut c_char, + size: c_int, + _rwflag: c_int, + ctxt: *mut c_void, + ) -> c_int { + if size < 0 { + return -1; + } + let password_callback = AssertUnwindSafe(unsafe { + // Safety: `ctxt` is a valid callback pointer and outlived by `'a`, so it must be + // valid when invoked. + &mut *(ctxt as *mut F) + }); + let get_password = move || { + let AssertUnwindSafe(pass_callback) = { password_callback }; + let password = pass_callback(); + let Ok(len) = password.len().try_into() else { + return -1; + }; + if len > size { + return -1; + } + unsafe { + // Safety: + // - `src` is valid and not larger than `out`. + // - `out` is valid per BoringSSL specification. + // - `src` and `out` are both 1-aligned. + core::ptr::copy(password.as_ffi_void_ptr(), out as _, password.len()); + } + len + }; + maybe_panic(get_password) + } + + let evp_pkey = unsafe { + // Safety: + // - the BIO is still valid; + // - the `&raw mut priv_key` pointer is not null, so the function will allocate + // a new structure; + // - the return value can be discarded since we provided a location to hold the handle, + // whose lifetime will be managed from this function. + bssl_sys::PEM_read_bio_PrivateKey( + bio.ptr(), + &raw mut priv_key, + Some(write_password::<'a, F>), + &raw mut password_callback as _, + ) + }; + NonNull::new(evp_pkey) + .map(Self) + .ok_or_else(PkiError::extract_lib_err) + } + + /// Get the algorithm ID of the private key. + /// + /// This method returns [`None`] if the key algorithm is unrecognised. + pub fn algorithm(&self) -> Option<PrivateKeyAlgorithm> { + let id = unsafe { + // Safety: self.0 is valid. + bssl_sys::EVP_PKEY_id(self.ptr()) + }; + let id = i32::try_from(id).ok()?; + PrivateKeyAlgorithm::try_from(id).ok() + } + + /// This method releases ownership of the internal key handle. + /// + /// This method should only be used for cross-language interoperability. + pub fn into_raw(self) -> *mut bssl_sys::EVP_PKEY { + let ptr = self.ptr(); + core::mem::forget(self); + ptr + } + + pub(crate) fn ptr(&self) -> *mut bssl_sys::EVP_PKEY { + self.0.as_ptr() + } + + /// Extract the handle + /// + /// # Safety + /// + /// `self` **must** outlive the uses of the returned handle. + /// Verify the callsite contract to honour the lifetime contracts. + pub unsafe fn as_raw(&self) -> *mut bssl_sys::EVP_PKEY { + self.ptr() + } +} + +impl Clone for PrivateKey { + fn clone(&self) -> Self { + unsafe { + // Safety: `self.0` is still valid at cloning. + bssl_sys::EVP_PKEY_up_ref(self.ptr()); + } + Self(self.0) + } +} + +impl Drop for PrivateKey { + fn drop(&mut self) { + unsafe { + // Safety: `self.0` is still valid at dropping. + bssl_sys::EVP_PKEY_free(self.ptr()); + } + } +}
diff --git a/rust/bssl-x509/src/lib.rs b/rust/bssl-x509/src/lib.rs new file mode 100644 index 0000000..cc90d90 --- /dev/null +++ b/rust/bssl-x509/src/lib.rs
@@ -0,0 +1,57 @@ +// 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. + +#![deny( + missing_docs, + unsafe_op_in_unsafe_fn, + clippy::missing_safety_doc, + clippy::indexing_slicing, + clippy::unwrap_used, + clippy::panic, + clippy::expect_used, + clippy::undocumented_unsafe_blocks +)] +#![allow(private_bounds)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] + +//! BoringSSL PKI and X.509 bindings +//! +//! *WARNING* this crate is still work in progress and unstable. + +extern crate alloc; +extern crate core; + +pub mod certificates; +pub mod errors; +pub(crate) mod ffi; +pub mod keys; +mod oids; +pub mod params; +pub mod store; +pub mod verify; + +#[cfg(test)] +mod tests; + +/// Extract library error per BoringSSL specification. +#[doc(hidden)] +#[macro_export] +macro_rules! check_lib_error { + ($e:expr) => { + match $e { + 1 => {} + _ => return Err($crate::errors::PkiError::extract_lib_err()), + } + }; +}
diff --git a/rust/bssl-x509/src/oids.rs b/rust/bssl-x509/src/oids.rs new file mode 100644 index 0000000..8342b29 --- /dev/null +++ b/rust/bssl-x509/src/oids.rs
@@ -0,0 +1,64 @@ +// 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. + +/// 2.5.4.3 - `commonName` +const COMMON_NAME: &[u8] = &[0x55, 0x04, 0x03]; + +/// 2.5.4.6 - `countryName` +const COUNTRY_NAME: &[u8] = &[0x55, 0x04, 0x06]; + +/// 2.5.4.7 - `localityName` +const LOCALITY_NAME: &[u8] = &[0x55, 0x04, 0x07]; + +/// 2.5.4.8 - `stateOrProvinceName` +const STATE_OR_PROVINCE_NAME: &[u8] = &[0x55, 0x04, 0x08]; + +/// 2.5.4.10 - `organizationName` +const ORGANIZATION_NAME: &[u8] = &[0x55, 0x04, 0x0a]; + +/// 2.5.4.11 - `organizationalUnitName` +const ORGANIZATIONAL_UNIT_NAME: &[u8] = &[0x55, 0x04, 0x0b]; + +/// 2.5.4.41 - `name` +const NAME: &[u8] = &[0x55, 0x04, 0x29]; + +/// 2.5.4.42 - `givenName` +const GIVEN_NAME: &[u8] = &[0x55, 0x04, 0x2a]; + +/// 2.5.4.4 - `surname` +const SURNAME: &[u8] = &[0x55, 0x04, 0x04]; + +/// 2.5.4.43 - `initials` +const INITIALS: &[u8] = &[0x55, 0x04, 0x2b]; + +/// 1.2.840.113549.1.9.20 - `friendlyName` +const FRIENDLY_NAME: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14]; + +/// 1.2.840.113549.1.9.1 - `emailAddress` +const EMAIL_ADDRESS: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01]; + +/// 1.2.840.113549.1.9.2 - `unstructuredName` +const UNSTRUCTURED_NAME: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x02]; + +/// 1.2.840.113549.1.9.8 - `unstructuredAddress` +const UNSTRUCTURED_ADDRESS: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x09]; + +/// 2.5.4.46 - `dnQualifier` +const DN_QUALIFIER: &[u8] = &[0x55, 0x04, 0x2e]; + +/// 0.9.2342.19200300.100.1.25 - `domainComponent` +const DOMAIN_COMPONENT: &[u8] = &[0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19]; + +/// 1.3.6.1.4.1.311.17.1 - `CSPName` +const CSP_NAME: &[u8] = &[0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x11, 0x01];
diff --git a/rust/bssl-x509/src/params.rs b/rust/bssl-x509/src/params.rs new file mode 100644 index 0000000..05901db --- /dev/null +++ b/rust/bssl-x509/src/params.rs
@@ -0,0 +1,294 @@ +// 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 parameters. + +use alloc::ffi::CString; +use core::{ + ffi::{c_int, c_uint, c_ulong}, + ptr::NonNull, +}; + +use bssl_macros::bssl_enum; + +use crate::{check_lib_error, errors::PkiError, ffi::slice_into_ffi_raw_parts}; + +bssl_enum! { + /// Trust settings for certificate verification. + #[derive(Clone, Copy, PartialEq, Eq)] + pub enum Trust: i8 { + /// Compatibility mode. + Compat = bssl_sys::X509_TRUST_COMPAT as i8, + /// Trust as a client SSL certificate. + SslClient = bssl_sys::X509_TRUST_SSL_CLIENT as i8, + /// Trust as a server SSL certificate. + SslServer = bssl_sys::X509_TRUST_SSL_SERVER as i8, + /// Trust as an email certificate. + Email = bssl_sys::X509_TRUST_EMAIL as i8, + /// Trust as an object signing certificate. + ObjectSign = bssl_sys::X509_TRUST_OBJECT_SIGN as i8, + /// Trust as a Time Stamping Authority. + Tsa = bssl_sys::X509_TRUST_TSA as i8, + } +} + +bssl_enum! { + /// Purpose settings for certificate verification. + #[derive(Clone, Copy, PartialEq, Eq)] + pub enum Purpose: i8 { + /// SSL client. + SslClient = bssl_sys::X509_PURPOSE_SSL_CLIENT as i8, + /// SSL server. + SslServer = bssl_sys::X509_PURPOSE_SSL_SERVER as i8, + /// Netscape SSL server. + NsSslServer = bssl_sys::X509_PURPOSE_NS_SSL_SERVER as i8, + /// S/MIME signing. + SmimeSign = bssl_sys::X509_PURPOSE_SMIME_SIGN as i8, + /// S/MIME encryption. + SmimeEncrypt = bssl_sys::X509_PURPOSE_SMIME_ENCRYPT as i8, + /// CRL signing. + CrlSign = bssl_sys::X509_PURPOSE_CRL_SIGN as i8, + /// Any purpose. + Any = bssl_sys::X509_PURPOSE_ANY as i8, + /// OCSP helper. + OcspHelper = bssl_sys::X509_PURPOSE_OCSP_HELPER as i8, + /// Timestamp signing. + TimestampSign = bssl_sys::X509_PURPOSE_TIMESTAMP_SIGN as i8, + } +} + +bitflags::bitflags! { + /// Flags for X.509 certificate verification. + #[repr(transparent)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct VerificationFlags: c_ulong { + /// Send issuer check callback. + const CB_ISSUER_CHECK = bssl_sys::X509_V_FLAG_CB_ISSUER_CHECK as c_ulong; + /// Use check time. + const USE_CHECK_TIME = bssl_sys::X509_V_FLAG_USE_CHECK_TIME as c_ulong; + /// Check CRL. + const CRL_CHECK = bssl_sys::X509_V_FLAG_CRL_CHECK as c_ulong; + /// Check CRL for all certificates in the chain. + const CRL_CHECK_ALL = bssl_sys::X509_V_FLAG_CRL_CHECK_ALL as c_ulong; + /// Ignore critical extensions. + const IGNORE_CRITICAL = bssl_sys::X509_V_FLAG_IGNORE_CRITICAL as c_ulong; + /// Strict X.509 checks. + const X509_STRICT = bssl_sys::X509_V_FLAG_X509_STRICT as c_ulong; + /// Allow proxy certificates. + const ALLOW_PROXY_CERTS = bssl_sys::X509_V_FLAG_ALLOW_PROXY_CERTS as c_ulong; + /// Check policies. + const POLICY_CHECK = bssl_sys::X509_V_FLAG_POLICY_CHECK as c_ulong; + /// Explicit policy. + const EXPLICIT_POLICY = bssl_sys::X509_V_FLAG_EXPLICIT_POLICY as c_ulong; + /// Inhibit any policy. + const INHIBIT_ANY = bssl_sys::X509_V_FLAG_INHIBIT_ANY as c_ulong; + /// Inhibit policy mapping. + const INHIBIT_MAP = bssl_sys::X509_V_FLAG_INHIBIT_MAP as c_ulong; + /// Notify policy. + const NOTIFY_POLICY = bssl_sys::X509_V_FLAG_NOTIFY_POLICY as c_ulong; + /// Extended CRL support. + const EXTENDED_CRL_SUPPORT = bssl_sys::X509_V_FLAG_EXTENDED_CRL_SUPPORT as c_ulong; + /// Use deltas. + const USE_DELTAS = bssl_sys::X509_V_FLAG_USE_DELTAS as c_ulong; + /// Check self-signed signature. + const CHECK_SS_SIGNATURE = bssl_sys::X509_V_FLAG_CHECK_SS_SIGNATURE as c_ulong; + /// Trusted first. + const TRUSTED_FIRST = bssl_sys::X509_V_FLAG_TRUSTED_FIRST as c_ulong; + /// Partial chain. + const PARTIAL_CHAIN = bssl_sys::X509_V_FLAG_PARTIAL_CHAIN as c_ulong; + /// No alternative chains. + const NO_ALT_CHAINS = bssl_sys::X509_V_FLAG_NO_ALT_CHAINS as c_ulong; + /// No check time. + const NO_CHECK_TIME = bssl_sys::X509_V_FLAG_NO_CHECK_TIME as c_ulong; + } +} + +bitflags::bitflags! { + /// Flags for X.509 host checking. + #[repr(transparent)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct HostFlags: c_uint { + /// Disable wildcard matching for DNS names. + const NO_WILDCARDS = bssl_sys::X509_CHECK_FLAG_NO_WILDCARDS as c_uint; + /// Disable the subject fallback, normally enabled when subjectAltNames is missing. + const NEVER_CHECK_SUBJECT = bssl_sys::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as c_uint; + } +} + +/// X.509 verification parameters. +pub struct CertificateVerificationParams(NonNull<bssl_sys::X509_VERIFY_PARAM>); + +// Safety: +// - `X509_VERIFY_PARAM` is a configuration struct and can be sent. +// - There are no methods that uses shared access, so no data race is possible. +unsafe impl Send for CertificateVerificationParams {} +unsafe impl Sync for CertificateVerificationParams {} + +impl Drop for CertificateVerificationParams { + fn drop(&mut self) { + unsafe { + bssl_sys::X509_VERIFY_PARAM_free(self.0.as_ptr()); + } + } +} + +impl CertificateVerificationParams { + /// Create a new `X509VerifyParam`. + pub fn new() -> Self { + let param = unsafe { + // Safety: this call returns a new X509_VERIFY_PARAM or null on failure. + bssl_sys::X509_VERIFY_PARAM_new() + }; + let Some(param) = NonNull::new(param) else { + panic!("allocation error") + }; + Self(param) + } + + pub(crate) fn ptr(&self) -> *mut bssl_sys::X509_VERIFY_PARAM { + self.0.as_ptr() + } + + /// Extract the handle for cross-language interoperability. + /// + /// # Safety + /// + /// The handle **must** outlive the use sites. + /// Verify the callsite contract to honour the lifetime contracts. + pub unsafe fn as_raw(&self) -> *mut bssl_sys::X509_VERIFY_PARAM { + self.ptr() + } + + /// Set the expected DNS hostname. + pub fn set_host(&mut self, host: &str) -> Result<&mut Self, PkiError> { + let c_host = CString::new(host).map_err(|_| PkiError::InternalNullBytes)?; + check_lib_error!(unsafe { + // Safety: the string has been NUL-terminated. + bssl_sys::X509_VERIFY_PARAM_set1_host(self.ptr(), c_host.as_ptr(), host.len()) + }); + Ok(self) + } + + /// Add an expected DNS hostname. + pub fn add_host(&mut self, host: &str) -> Result<&mut Self, PkiError> { + let c_host = CString::new(host).map_err(|_| PkiError::InternalNullBytes)?; + check_lib_error!(unsafe { + // Safety: the string has been NUL-terminated. + bssl_sys::X509_VERIFY_PARAM_add1_host(self.ptr(), c_host.as_ptr(), host.len()) + }); + Ok(self) + } + + /// Set the expected email address. + pub fn set_email(&mut self, email: &str) -> Result<&mut Self, PkiError> { + let c_email = CString::new(email).map_err(|_| PkiError::InternalNullBytes)?; + check_lib_error!(unsafe { + bssl_sys::X509_VERIFY_PARAM_set1_email(self.ptr(), c_email.as_ptr(), email.len()) + }); + + Ok(self) + } + + /// Set the expected IP address. + pub fn set_ip(&mut self, ip: &[u8]) -> Result<&mut Self, PkiError> { + if !matches!(ip.len(), 4 | 16) { + return Err(PkiError::InvalidIp); + } + let (ip, len) = slice_into_ffi_raw_parts(ip); + check_lib_error!(unsafe { + // Safety: the slice pointer is not null. + bssl_sys::X509_VERIFY_PARAM_set1_ip(self.ptr(), ip, len) + }); + + Ok(self) + } + + /// Set the expected IP address from an ASCII string, as defined by + pub fn set_ip_asc(&mut self, ip: &str) -> Result<&mut Self, PkiError> { + let c_ip = CString::new(ip).map_err(|_| PkiError::InvalidIp)?; + check_lib_error!(unsafe { + // Safety: the string has been NUL-terminated. + bssl_sys::X509_VERIFY_PARAM_set1_ip_asc(self.ptr(), c_ip.as_ptr()) + }); + + Ok(self) + } + + /// Set the verification depth. + pub fn set_depth(&mut self, depth: u16) -> &mut Self { + unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self`. + bssl_sys::X509_VERIFY_PARAM_set_depth(self.ptr(), depth.into()); + } + self + } + + /// Set the verification purpose. + pub fn set_purpose(&mut self, purpose: Purpose) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: + // - the validity of the handle `self.0` is witnessed by `self`. + // - the `purpose` value is valid per construction. + bssl_sys::X509_VERIFY_PARAM_set_purpose(self.ptr(), purpose as c_int) + }); + Ok(self) + } + + /// Set the verification trust. + pub fn set_trust(&mut self, trust: Trust) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self`. + bssl_sys::X509_VERIFY_PARAM_set_trust(self.ptr(), trust as c_int) + }); + Ok(self) + } + + /// Set time that verification will run against, specified as Unix timestamp. + pub fn set_time_posix(&mut self, time: i64) { + unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self`. + bssl_sys::X509_VERIFY_PARAM_set_time_posix(self.ptr(), time); + } + } + + /// Set verification flags. + pub fn set_flags(&mut self, flags: VerificationFlags) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: the flag bits are valid by construction. + bssl_sys::X509_VERIFY_PARAM_set_flags(self.ptr(), flags.bits()) + }); + Ok(self) + } + + /// Clear verification flags. + pub fn clear_flags(&mut self, flags: VerificationFlags) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: + // - the validity of the handle `self.0` is witnessed by `self`. + // - the flag bits are valid by construction. + bssl_sys::X509_VERIFY_PARAM_clear_flags(self.ptr(), flags.bits()) + }); + Ok(self) + } + + /// Set the host flags. + pub fn set_hostflags(&mut self, flags: HostFlags) { + unsafe { + // Safety: + // - the validity of the handle `self.0` is witnessed by `self`. + // - the flag bits are valid by construction. + bssl_sys::X509_VERIFY_PARAM_set_hostflags(self.ptr(), flags.bits()); + } + } +}
diff --git a/rust/bssl-x509/src/store.rs b/rust/bssl-x509/src/store.rs new file mode 100644 index 0000000..a3375fc --- /dev/null +++ b/rust/bssl-x509/src/store.rs
@@ -0,0 +1,196 @@ +// 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 store for verification + +use alloc::ffi::CString; +use core::{ + ffi::c_int, + panic, + ptr::{NonNull, null}, +}; + +use crate::{ + certificates::X509Certificate, + check_lib_error, + errors::{PkiError, X509Error}, + params::{CertificateVerificationParams, Purpose, Trust, VerificationFlags}, +}; + +/// A X.509 certificate store builder. +pub struct X509StoreBuilder(NonNull<bssl_sys::X509_STORE>); + +// Safety: `X509_STORE` is ref-counted and always requires exclusive accesses. +unsafe impl Send for X509StoreBuilder {} + +impl Drop for X509StoreBuilder { + fn drop(&mut self) { + unsafe { + bssl_sys::X509_STORE_free(self.0.as_ptr()); + } + } +} + +impl X509StoreBuilder { + /// Construct a new X.509 certificate store. + pub fn new() -> Self { + let store = unsafe { bssl_sys::X509_STORE_new() }; + let Some(store) = NonNull::new(store) else { + panic!("allocation error") + }; + Self(store) + } + + pub(crate) fn ptr(&self) -> *mut bssl_sys::X509_STORE { + self.0.as_ptr() + } + + /// Finalise the certificate store. + pub fn build(self) -> X509Store { + let ptr = self.0; + core::mem::forget(self); + X509Store(ptr) + } + + /// Add a certificate to the store. + pub fn add_cert(&mut self, cert: X509Certificate) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { bssl_sys::X509_STORE_add_cert(self.ptr(), cert.ptr()) }); + Ok(self) + } + + /// Load certificates from a file or directory. + /// + /// `file` and `dir` cannot be both [`None`]; + /// otherwise, this method returns [`ConfigurationError::InvalidParameters`]. + pub fn load_locations( + &mut self, + file: Option<&str>, + dir: Option<&str>, + ) -> Result<&mut Self, PkiError> { + if file.is_none() && dir.is_none() { + return Err(PkiError::X509(X509Error::InvalidParameters)); + } + let file_cstr; + let dir_cstr; + let file = if let Some(file) = file { + file_cstr = CString::new(file).map_err(|_| PkiError::InternalNullBytes)?; + file_cstr.as_ptr() + } else { + null() + }; + let dir = if let Some(dir) = dir { + dir_cstr = CString::new(dir).map_err(|_| PkiError::InternalNullBytes)?; + dir_cstr.as_ptr() + } else { + null() + }; + + check_lib_error!(unsafe { + // Safety: + // - the handle `self.0` is valid. + // - `file` and `dir` are either null or valid NUL-terminated C-strings. + bssl_sys::X509_STORE_load_locations(self.ptr(), file, dir) + }); + Ok(self) + } + + /// Set verification flags. + pub fn set_flags(&mut self, flags: VerificationFlags) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: + // - the validity of the handle `self.0` is witnessed by `self`. + // - the flag bits are valid by construction. + bssl_sys::X509_STORE_set_flags(self.ptr(), flags.bits()) + }); + Ok(self) + } + + /// Set verification depth. + pub fn set_depth(&mut self, depth: u16) -> Result<&mut Self, PkiError> { + let depth = c_int::try_from(depth).map_err(|_| PkiError::ValueOutOfRange)?; + check_lib_error!(unsafe { bssl_sys::X509_STORE_set_depth(self.ptr(), depth) }); + Ok(self) + } + + /// Set verification purpose. + pub fn set_purpose(&mut self, purpose: Purpose) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: + // - the validity of the handle `self.0` is witnessed by `self`. + // - the `purpose` value is valid per construction. + bssl_sys::X509_STORE_set_purpose(self.ptr(), purpose as c_int) + }); + Ok(self) + } + + /// Set verification trust. + pub fn set_trust(&mut self, trust: Trust) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: + // - the validity of the handle `self.0` is witnessed by `self`. + // - the `trust` value is valid per construction. + bssl_sys::X509_STORE_set_trust(self.ptr(), trust as c_int) + }); + Ok(self) + } + + /// Set verification parameters. + pub fn set_param( + &mut self, + param: &CertificateVerificationParams, + ) -> Result<&mut Self, PkiError> { + check_lib_error!(unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self`. + bssl_sys::X509_STORE_set1_param(self.ptr(), param.ptr()) + }); + Ok(self) + } +} + +/// A X.509 certificate store +pub struct X509Store(NonNull<bssl_sys::X509_STORE>); + +// Safety: `X509_STORE` is ref-counted and mutation behind a shared reference is protected +// behind a lock. +unsafe impl Send for X509Store {} +unsafe impl Sync for X509Store {} + +impl X509Store { + /// Extract the handle for cross-language interoperability. + /// + /// # Safety + /// + /// `self` **must** outlive the uses of the returned handle. + /// Verify the callsite contract to honour the lifetime contracts. + pub unsafe fn as_raw(&self) -> *mut bssl_sys::X509_STORE { + self.0.as_ptr() + } +} + +impl Clone for X509Store { + fn clone(&self) -> Self { + unsafe { + bssl_sys::X509_STORE_up_ref(self.0.as_ptr()); + } + Self(self.0) + } +} + +impl Drop for X509Store { + fn drop(&mut self) { + unsafe { + bssl_sys::X509_STORE_free(self.0.as_ptr()); + } + } +}
diff --git a/rust/bssl-x509/src/tests.rs b/rust/bssl-x509/src/tests.rs new file mode 100644 index 0000000..e24de83 --- /dev/null +++ b/rust/bssl-x509/src/tests.rs
@@ -0,0 +1,138 @@ +// 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. + +use super::*; + +const CA_CERT: &[u8] = include_bytes!("./tests/BoringSSLTestCA.crt"); +const PEM: &[u8] = include_bytes!("./tests/consolidated.pem"); +const TRUNCATED: &[u8] = include_bytes!("./tests/truncated.pem"); +const TRUNCATED_ONE: &[u8] = include_bytes!("./tests/truncated-one.pem"); +const PEM_WITH_PASS: &[u8] = include_bytes!("./tests/BoringSSLTestCA.key"); +const SERVER_CERT: &[u8] = include_bytes!("./tests/BoringSSLServerTest-RSA.crt"); +const SERIAL_NUMBERS: &[&[u8]] = &[ + &[ + 0x1e, 0x51, 0x13, 0x9d, 0x9c, 0x81, 0x90, 0x38, 0x25, 0x32, 0x72, 0x51, 0xf3, 0x33, 0x1b, + 0x70, 0xd0, 0x9b, 0x6a, 0x5c, + ], + &[ + 0x50, 0x2d, 0x32, 0x51, 0x7b, 0xb9, 0x51, 0xf0, 0xdf, 0x6e, 0x4e, 0xc3, 0x05, 0x1f, 0x2c, + 0x4b, 0xaf, 0xcd, 0x52, 0x74, + ], + &[ + 0x14, 0x05, 0x09, 0xad, 0x35, 0xee, 0xd1, 0xb6, 0x9a, 0xea, 0x34, 0xfb, 0x2a, 0x27, 0x19, + 0x82, 0x39, 0xfd, 0xf8, 0x84, + ], + &[ + 0x14, 0x05, 0x09, 0xad, 0x35, 0xee, 0xd1, 0xb6, 0x9a, 0xea, 0x34, 0xfb, 0x2a, 0x27, 0x19, + 0x82, 0x39, 0xfd, 0xf8, 0x85, + ], + &[ + 0x50, 0x2d, 0x32, 0x51, 0x7b, 0xb9, 0x51, 0xf0, 0xdf, 0x6e, 0x4e, 0xc3, 0x05, 0x1f, 0x2c, + 0x4b, 0xaf, 0xcd, 0x52, 0x73, + ], + &[ + 0x2a, 0x7d, 0x8e, 0xd1, 0xd8, 0x49, 0xdf, 0xfa, 0xb6, 0x31, 0x9a, 0xdf, 0x0b, 0x04, 0x9f, + 0xdc, 0xcb, 0xbe, 0x82, 0x03, + ], +]; +const NOT_BEFORE: &[i64] = &[ + 1769075621, // Jan 22 09:53:41 2026 GMT + 1769175652, // Jan 23 13:40:52 2026 GMT + 1770507561, // Feb 7 23:39:21 2026 GMT + 1770507569, // Feb 7 23:39:29 2026 GMT + 1769078409, // Jan 22 10:40:09 2026 GMT + 1770046795, // Feb 2 15:39:55 2026 GMT +]; +const NOT_AFTER: &[i64] = &[ + 1771667621, // Feb 21 09:53:41 2026 GMT + 64884375652, // Feb 7 13:40:52 4026 GMT + 64885707561, // Feb 22 23:39:21 4026 GMT + 64885707569, // Feb 22 23:39:29 4026 GMT + 64884278409, // Feb 6 10:40:09 4026 GMT + 64885246795, // Feb 17 15:39:55 4026 GMT +]; + +#[test] +fn parse_all_pems() { + let pems = certificates::X509Certificate::parse_all_from_pem(PEM).unwrap(); + assert_eq!(pems.len(), 6); + for (((pem, &serial_number), ¬_before), ¬_after) in pems + .iter() + .zip(SERIAL_NUMBERS) + .zip(NOT_BEFORE) + .zip(NOT_AFTER) + { + assert_eq!( + pem.get_serial_number() + .unwrap() + .as_twos_complement_bytes() + .unwrap(), + serial_number + ); + assert_eq!(pem.get_not_before().unwrap(), not_before); + assert_eq!(pem.get_not_after().unwrap(), not_after); + } + + assert!(matches!( + certificates::X509Certificate::parse_all_from_pem(TRUNCATED), + Err(errors::PkiError::X509(errors::X509Error::Pem( + errors::PemReason::BadEndLine + ))) + )); + + assert!(matches!( + certificates::X509Certificate::parse_one_from_pem(TRUNCATED_ONE), + Err(errors::PkiError::X509(errors::X509Error::Pem( + errors::PemReason::BadEndLine + ))) + )); + + assert!(matches!( + certificates::X509Certificate::parse_one_from_pem(b"Ahoy!\n"), + Err(errors::PkiError::X509(errors::X509Error::Pem( + errors::PemReason::NoStartLine + ))) + )); +} + +#[test] +fn inspect_san() { + let cert = certificates::X509Certificate::parse_one_from_pem(SERVER_CERT).unwrap(); + let san = cert.subject_alt_names().unwrap(); + let names: Vec<_> = san.iter().collect(); + assert_eq!( + &names, + &[ + certificates::GeneralName::Dns("www.google.com"), + certificates::GeneralName::Dns("localhost"), + ] + ) +} + +#[test] +fn parse_ca_cert() { + let ca_cert = certificates::X509Certificate::parse_all_from_pem(CA_CERT).unwrap(); + assert_eq!(ca_cert.len(), 1); +} + +#[test] +fn parse_pem_key_with_pass() { + assert!(keys::PrivateKey::from_pem(&PEM_WITH_PASS, || b"Try again!").is_err()); + let key = keys::PrivateKey::from_pem(&PEM_WITH_PASS, || b"BoringSSL is awesome!").unwrap(); + let ca_cert = certificates::X509Certificate::parse_all_from_pem(CA_CERT) + .unwrap() + .pop() + .unwrap(); + assert!(ca_cert.matches_private_key(&key)); +}
diff --git a/rust/bssl-x509/src/tests/BoringSSLServerTest-RSA.crt b/rust/bssl-x509/src/tests/BoringSSLServerTest-RSA.crt new file mode 100644 index 0000000..4b376c1 --- /dev/null +++ b/rust/bssl-x509/src/tests/BoringSSLServerTest-RSA.crt
@@ -0,0 +1,132 @@ +This is generated from + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-RSA.crt -extfile svcconfig -days 730500 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 50:2d:32:51:7b:b9:51:f0:df:6e:4e:c3:05:1f:2c:4b:af:cd:52:73 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Jan 22 10:40:09 2026 GMT + Not After : Feb 6 10:40:09 4026 GMT + Subject: CN=www.google.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (4096 bit) + Modulus: + 00:f7:a2:3f:8e:9e:a5:9a:f7:eb:c5:cf:d5:31:3b: + c0:ca:02:83:3a:81:90:9a:8d:47:d4:3c:c1:f2:61: + 3d:87:11:25:7f:bc:b9:a4:dc:7f:3e:7d:30:1e:32: + 8d:ec:d5:e0:b5:b2:47:b3:77:3d:2d:2e:61:dd:ad: + 03:62:2d:e0:19:c8:fb:1f:43:ef:ed:cd:a2:96:8c: + 61:27:fc:7d:9e:42:90:07:79:c8:87:f5:3a:ff:f9: + 0e:25:b3:b2:67:45:3e:6b:a6:bf:0c:8d:ac:78:00: + 8a:5d:6c:dd:97:8c:f6:1f:2f:73:c8:ed:af:39:73: + 81:a5:38:fc:ed:a6:0c:1c:46:45:49:98:19:cd:4c: + 3b:63:ac:67:9a:d8:95:36:46:b6:47:ec:cd:bd:84: + ca:8b:34:6e:5e:67:f0:1f:4f:c7:7e:57:97:3f:e6: + b9:7e:b1:f6:66:41:26:2b:3e:9f:1a:88:48:58:46: + 7e:6d:6a:74:f0:4a:6f:f4:51:2d:cb:22:45:33:c1: + 77:4d:c7:8a:0d:ad:0d:dc:6a:8e:5c:87:3a:a9:a7: + dc:df:45:0b:d8:36:5a:c9:3f:5c:2a:dc:9d:81:1d: + 30:bd:0d:03:5d:cb:8d:f8:5d:31:81:76:97:c1:1f: + 14:0c:54:3d:30:41:c4:7b:96:6f:8c:2a:2f:2a:fb: + fc:96:59:5f:7a:f6:48:4a:eb:8f:a6:c6:59:e1:e1: + 05:27:cc:7d:c0:60:77:dd:8f:a2:7c:86:c1:d7:3e: + 05:18:ad:54:c4:68:8f:10:d6:c7:f2:63:d8:17:bd: + 8f:c3:29:08:9b:15:af:19:eb:a2:16:23:67:b8:b8: + b1:ee:ee:32:d3:8f:31:4b:19:c2:4e:f9:3e:9a:bf: + 77:ec:b9:e9:d9:c7:9f:82:21:b8:2d:4d:ed:9c:30: + b1:b2:86:c1:38:22:3a:fe:08:21:fe:2e:94:d0:b9: + 5a:ef:8f:b0:15:74:b2:df:37:28:59:fd:48:92:eb: + a2:6a:17:4f:90:0f:78:b9:d3:59:22:f6:10:b9:35: + a5:ea:21:c9:0b:55:3e:56:00:f4:6a:37:17:d9:94: + 5a:d7:ec:76:6d:5d:22:9b:d0:bf:f1:03:b0:66:91: + 8d:ae:ae:ee:6c:00:e8:03:ad:1a:c5:18:80:15:b0: + cb:af:2d:3e:b4:25:fc:01:db:70:09:fe:44:d0:2d: + f3:be:25:55:82:c9:a0:36:f6:be:0d:d7:39:a7:51: + 9f:a7:ee:67:8a:a8:34:e6:f5:6d:82:54:bf:bc:07: + a6:26:da:0f:d6:58:f8:93:53:f9:f3:23:73:b5:69: + 22:cf:ef:af:0a:77:07:53:a2:59:59:96:36:8a:11: + d0:ac:2d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment + X509v3 Subject Alternative Name: + DNS:www.google.com, DNS:localhost + X509v3 Subject Key Identifier: + 3B:BF:82:CA:BD:B0:B7:0D:60:E4:DD:A5:3B:FC:AF:6A:F6:8A:A2:28 + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + c7:85:d7:41:f9:b1:67:7a:72:39:46:01:05:25:5f:9e:45:a1: + 06:3f:19:dd:31:30:ed:2f:9e:37:e6:fc:b7:55:e9:38:8d:77: + 5f:aa:ba:26:8f:4e:bf:d6:ff:d2:0e:46:41:d6:3c:5f:65:83: + 67:b6:46:a6:31:58:70:61:13:5e:6d:6c:2f:49:68:e8:eb:94: + 78:f5:a1:5a:0f:c6:d0:29:c2:86:bc:13:fc:9a:08:d5:d4:58: + 76:e6:85:2b:76:1b:d2:24:56:3d:0f:d0:f6:14:23:ed:48:3e: + 56:57:f9:c2:45:d0:ba:b7:01:df:50:97:bc:3d:74:dd:ac:c3: + 18:72:a7:66:9a:14:eb:22:33:89:bd:ad:f5:36:44:9a:71:6a: + 78:b9:bd:e4:9a:a8:bf:de:c7:18:d8:6b:09:81:d9:d6:3f:a4: + 11:df:18:68:58:e4:8a:8a:d3:31:28:eb:cc:59:a6:5f:89:62: + 23:b2:ee:09:86:fd:33:b6:ba:84:5a:fe:10:57:c7:ab:16:eb: + 69:44:84:67:bd:8a:89:22:d5:f7:91:d5:4c:99:00:45:dc:c7: + 90:e8:7e:60:5e:e8:f7:e4:90:22:8c:bd:a2:0b:58:67:6a:ca: + 7a:89:f9:fa:73:24:ac:39:29:0e:eb:cf:bc:82:72:b7:8d:52: + 59:a3:7c:bb:72:0d:05:79:c3:90:1f:31:05:fc:7b:54:f1:7e: + fd:78:99:6c:21:74:cc:83:95:3f:09:e6:77:7f:16:6c:0f:3e: + 71:24:31:ef:b8:95:3a:7a:2d:31:de:fa:33:7c:45:d7:ce:50: + 9f:9d:82:95:27:b1:78:5c:ba:8f:49:02:37:a4:3f:ac:bc:1d: + 51:c2:73:ac:75:9d:fe:8d:ee:94:81:60:95:45:3c:c4:ba:34: + 25:3e:01:04:da:a0:0f:ed:b9:96:3a:81:ae:2f:d2:aa:0c:73: + 05:f9:ec:79:90:6e:69:a7:aa:3d:ce:76:31:95:db:55:ef:64: + 69:d1:e2:e7:e8:71:04:3c:14:d5:97:ad:b9:6e:20:bd:33:3d: + f2:d5:22:f0:c3:be:63:35:e1:7d:f8:81:6d:ac:60:ff:52:f1: + 64:cc:b7:9c:bd:f4:bb:61:10:d7:a9:ba:6d:1a:e5:54:cc:4c: + 7d:1a:8d:8a:85:0d:3a:91:8a:f3:7b:fd:6a:a5:66:3e:3a:34: + f6:df:f2:6e:26:b6:e0:b2:1b:28:f3:b7:bc:0c:6f:c7:7f:0c: + a5:0a:51:07:be:be:bb:7b:48:50:ae:f8:5e:2c:01:7b:8a:7a: + 33:e2:30:39:53:a2:55:eb:4d:86:cf:eb:d4:21:63:9c:58:d6: + 6e:1d:42:79:d3:3c:40:f4 + +-----BEGIN CERTIFICATE----- +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 +1CFjnFjWbh1CedM8QPQ= +-----END CERTIFICATE-----
diff --git a/rust/bssl-x509/src/tests/BoringSSLTestCA.crt b/rust/bssl-x509/src/tests/BoringSSLTestCA.crt new file mode 100644 index 0000000..85d0d6f --- /dev/null +++ b/rust/bssl-x509/src/tests/BoringSSLTestCA.crt
@@ -0,0 +1,132 @@ +This is generated from + openssl req -new -x509 -key BoringSSLCATest.key -out BoringSSLCATest.crt \ + -config caconfig -days 30 + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 61:ae:e1:ed:10:88:bd:2e:88:e6:d9:c9:ad:1d:55:87:bf:85:99:b0 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Mar 2 13:53:27 2026 GMT + Not After : Mar 17 13:53:27 4026 GMT + Subject: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (4096 bit) + Modulus: + 00:d9:6c:65:59:51:32:fa:a5:6e:ab:93:a3:f7:45: + c9:75:97:54:0b:6e:5f:fb:6f:2f:d7:ed:5b:bc:57: + 1b:a8:8e:aa:be:03:5c:38:78:4c:92:2c:5f:1c:b2: + 39:a4:a4:53:9d:b9:d7:dd:7a:00:af:4d:4d:93:6a: + 5b:07:3c:10:bf:bc:02:3e:85:b9:a9:96:3f:52:ba: + c5:06:15:ac:ac:66:54:26:55:77:dc:86:11:8f:a6: + 9c:7b:49:f1:16:f7:3a:62:26:d4:fe:a9:69:6c:22: + 79:c2:92:62:2d:6c:1e:63:5e:07:41:8a:39:27:41: + 34:c0:99:57:22:cc:30:8c:95:e7:5c:3d:f2:26:e9: + 4d:29:9e:c9:69:37:3d:47:f5:5e:69:3c:43:4e:b6: + d3:52:e1:97:77:0e:1a:69:2e:ea:8a:fe:50:68:19: + f2:38:eb:05:58:56:8a:39:22:83:3c:ae:e3:87:48: + ad:fc:30:56:43:5d:6c:75:e4:e2:fd:cd:1f:d3:a7: + 81:52:1d:7f:dd:39:57:03:1f:d5:ed:be:e4:23:94: + 23:6d:70:b6:d7:84:ec:80:7d:ec:f4:ca:44:e2:a7: + 61:eb:16:5f:dd:a2:4e:6d:99:8f:30:9c:b7:75:74: + da:83:41:42:ae:56:44:21:7b:71:cf:7d:a8:b5:32: + 8f:91:b3:30:a1:f4:ea:10:cf:3e:0d:37:3c:fe:a3: + fa:f2:26:68:a5:16:40:be:b9:e7:ae:e0:e1:eb:6c: + 65:c2:08:bd:d7:32:7e:a7:5c:09:f3:d5:40:de:c8: + 9d:c4:fd:05:e0:6a:12:93:60:35:b6:a4:7a:6b:a7: + 35:a0:86:b2:80:f6:51:6c:49:a1:b4:26:1f:a2:7b: + 20:ff:14:fe:36:f1:1a:3e:2b:3c:cf:3e:9f:07:24: + d3:fa:ad:b6:88:a6:3f:98:a1:19:01:eb:e4:7b:e9: + ec:b3:58:56:39:bb:f3:70:58:d6:87:4e:83:1a:df: + 48:af:96:fb:70:3e:73:93:df:84:ab:6d:54:bb:0b: + 6d:dd:65:d2:9a:5f:a2:ed:b3:b4:7b:54:ee:49:fd: + fd:44:54:3a:2d:d6:b0:c9:62:89:e2:ee:71:e1:60: + 73:3a:dd:1c:05:fb:f5:97:9d:8f:82:9b:ac:42:32: + 77:42:d9:60:77:59:2c:0a:4e:cd:b6:cd:c5:e1:5d: + d2:00:31:c8:d2:a7:57:46:69:1a:6f:b6:ab:f3:92: + e2:42:38:2f:c7:5f:b3:03:d0:04:70:05:7f:5f:6d: + 4c:18:69:90:95:a8:9c:02:44:ac:96:88:f5:fc:4d: + 81:4a:de:e3:5e:1e:57:c1:2d:ba:8e:f3:2b:39:ae: + ab:8d:05 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 69:41:01:a4:6e:f3:22:e2:1d:6f:e4:1b:a5:84:cc:9e:4e:8f: + 4b:f2:83:a2:36:45:95:02:c2:e3:87:34:a4:e9:20:c9:3f:99: + df:8c:56:38:3e:5d:2f:a2:93:60:6e:e7:39:d4:46:4a:37:75: + 76:ba:6c:70:f8:0a:60:91:8b:46:19:df:2a:10:c9:9a:a5:8a: + 4b:bc:16:80:11:54:63:e9:60:ab:dd:0e:3b:0c:09:ce:25:08: + e7:d4:1a:b6:51:87:3a:bd:f7:b2:86:5f:d8:26:5f:35:19:89: + 64:b3:35:c8:58:b8:f2:ab:2e:b1:42:27:83:a0:38:b3:33:d3: + 0c:e4:92:8f:5b:0f:cb:a6:96:d7:b9:fd:30:21:18:0e:60:37: + cb:de:e0:e4:e8:0a:5c:b7:e4:b7:be:1a:ba:3b:ac:f7:d7:b8: + 02:5d:b1:c4:bc:b3:50:c7:e8:c7:33:06:6b:4e:26:47:58:f8: + 89:f6:c2:c6:91:6e:76:09:33:f4:48:44:2a:63:97:6e:1f:be: + 56:ac:f0:54:21:74:c7:95:1e:72:79:24:b4:27:08:fb:69:34: + 4e:85:49:40:e1:6d:dd:28:85:05:a2:f5:8b:53:45:ac:be:d9: + 63:40:40:80:78:72:7e:44:1d:9e:96:1f:b8:36:8c:cc:9e:a0: + 2a:7f:61:96:c5:4a:95:c6:4a:79:cd:86:27:7b:47:55:45:d4: + 23:23:8f:b8:8a:09:5d:66:c8:00:7a:6e:90:42:b1:45:f0:17: + b2:a7:07:57:08:45:3b:00:29:a9:26:ba:3f:5f:ba:27:e1:4e: + 40:46:dc:1b:28:e2:a0:48:da:1f:e2:55:ed:c3:b4:fa:01:21: + 06:43:41:01:7d:03:c0:73:af:a6:39:60:91:8f:d6:9f:4c:ae: + 3d:14:f1:48:20:34:ac:2c:10:d5:29:d8:c8:8b:ba:09:6b:b2: + 80:cb:83:ba:f0:dc:19:6b:1b:45:f5:91:e9:f4:5a:76:06:1f: + 43:f0:40:5d:60:52:48:50:03:ca:32:9d:cc:f8:89:82:65:5c: + d0:be:d4:91:1d:ae:8b:ac:23:64:f7:e5:64:ad:34:9e:60:71: + d1:63:7b:00:db:87:75:4c:25:c0:42:37:91:66:19:ac:e0:ca: + fd:40:23:b5:75:41:54:c7:9e:32:0d:db:bc:2d:03:22:e4:f5: + 89:b6:46:48:90:65:5e:f9:1f:0c:90:98:f2:08:07:4d:0b:93: + ef:ae:f5:c4:e4:4b:0f:6a:8b:6e:dc:d7:39:b2:9d:aa:b2:7e: + d1:cf:ee:d3:2e:0e:ae:8f:d8:20:bb:98:2a:de:29:15:bf:fd: + 7e:fe:1f:b7:f0:1e:23:d2 + +-----BEGIN CERTIFICATE----- +MIIGUzCCBDugAwIBAgIUYa7h7RCIvS6I5tnJrR1Vh7+FmbAwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMCAXDTI2MDMwMjEz +NTMyN1oYDzQwMjYwMzE3MTM1MzI3WjCBrzELMAkGA1UEBhMCREUxGTAXBgNVBAgM +EEZyZWlzdGFhdCBCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRwwGgYDVQQKDBNH +b29nbGUgR2VybWFueSBHbWJIMRMwEQYDVQQLDApJU0UgQ3J5cHRvMRowGAYDVQQD +DBFCb3JpbmdTU0wgQXV0aG9yczEjMCEGCSqGSIb3DQEJARYUYm9yaW5nc3NsQGdv +b2dsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDZbGVZUTL6 +pW6rk6P3Rcl1l1QLbl/7by/X7Vu8Vxuojqq+A1w4eEySLF8csjmkpFOdudfdegCv +TU2TalsHPBC/vAI+hbmplj9SusUGFaysZlQmVXfchhGPppx7SfEW9zpiJtT+qWls +InnCkmItbB5jXgdBijknQTTAmVcizDCMledcPfIm6U0pnslpNz1H9V5pPENOttNS +4Zd3DhppLuqK/lBoGfI46wVYVoo5IoM8ruOHSK38MFZDXWx15OL9zR/Tp4FSHX/d +OVcDH9XtvuQjlCNtcLbXhOyAfez0ykTip2HrFl/dok5tmY8wnLd1dNqDQUKuVkQh +e3HPfai1Mo+RszCh9OoQzz4NNzz+o/ryJmilFkC+ueeu4OHrbGXCCL3XMn6nXAnz +1UDeyJ3E/QXgahKTYDW2pHprpzWghrKA9lFsSaG0Jh+ieyD/FP428Ro+KzzPPp8H +JNP6rbaIpj+YoRkB6+R76eyzWFY5u/NwWNaHToMa30ivlvtwPnOT34SrbVS7C23d +ZdKaX6Lts7R7VO5J/f1EVDot1rDJYoni7nHhYHM63RwF+/WXnY+Cm6xCMndC2WB3 +WSwKTs22zcXhXdIAMcjSp1dGaRpvtqvzkuJCOC/HX7MD0ARwBX9fbUwYaZCVqJwC +RKyWiPX8TYFK3uNeHlfBLbqO8ys5rquNBQIDAQABo2MwYTAdBgNVHQ4EFgQU6htZ +Hl3jMylBf9dYiGPUKigGeBAwHwYDVR0jBBgwFoAU6htZHl3jMylBf9dYiGPUKigG +eBAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBAGlBAaRu8yLiHW/kG6WEzJ5Oj0vyg6I2RZUCwuOHNKTpIMk/md+MVjg+ +XS+ik2Bu5znURko3dXa6bHD4CmCRi0YZ3yoQyZqliku8FoARVGPpYKvdDjsMCc4l +COfUGrZRhzq997KGX9gmXzUZiWSzNchYuPKrLrFCJ4OgOLMz0wzkko9bD8umlte5 +/TAhGA5gN8ve4OToCly35Le+Gro7rPfXuAJdscS8s1DH6MczBmtOJkdY+In2wsaR +bnYJM/RIRCpjl24fvlas8FQhdMeVHnJ5JLQnCPtpNE6FSUDhbd0ohQWi9YtTRay+ +2WNAQIB4cn5EHZ6WH7g2jMyeoCp/YZbFSpXGSnnNhid7R1VF1CMjj7iKCV1myAB6 +bpBCsUXwF7KnB1cIRTsAKakmuj9fuifhTkBG3Bso4qBI2h/iVe3DtPoBIQZDQQF9 +A8Bzr6Y5YJGP1p9Mrj0U8UggNKwsENUp2MiLuglrsoDLg7rw3BlrG0X1ken0WnYG +H0PwQF1gUkhQA8oyncz4iYJlXNC+1JEdrousI2T35WStNJ5gcdFjewDbh3VMJcBC +N5FmGazgyv1AI7V1QVTHnjIN27wtAyLk9Ym2RkiQZV75HwyQmPIIB00Lk++u9cTk +Sw9qi27c1zmynaqyftHP7tMuDq6P2CC7mCreKRW//X7+H7fwHiPS +-----END CERTIFICATE-----
diff --git a/rust/bssl-x509/src/tests/BoringSSLTestCA.key b/rust/bssl-x509/src/tests/BoringSSLTestCA.key new file mode 100644 index 0000000..e96ca74 --- /dev/null +++ b/rust/bssl-x509/src/tests/BoringSSLTestCA.key
@@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY-----
diff --git a/rust/bssl-x509/src/tests/consolidated.pem b/rust/bssl-x509/src/tests/consolidated.pem new file mode 100644 index 0000000..2f49671 --- /dev/null +++ b/rust/bssl-x509/src/tests/consolidated.pem
@@ -0,0 +1,697 @@ +This is generated from + openssl req -new -x509 -key BoringSSLCATest.key -out BoringSSLCATest.crt \ + -config caconfig -days 30 + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 1e:51:13:9d:9c:81:90:38:25:32:72:51:f3:33:1b:70:d0:9b:6a:5c + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Jan 22 09:53:41 2026 GMT + Not After : Feb 21 09:53:41 2026 GMT + Subject: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (4096 bit) + Modulus: + 00:d9:6c:65:59:51:32:fa:a5:6e:ab:93:a3:f7:45: + c9:75:97:54:0b:6e:5f:fb:6f:2f:d7:ed:5b:bc:57: + 1b:a8:8e:aa:be:03:5c:38:78:4c:92:2c:5f:1c:b2: + 39:a4:a4:53:9d:b9:d7:dd:7a:00:af:4d:4d:93:6a: + 5b:07:3c:10:bf:bc:02:3e:85:b9:a9:96:3f:52:ba: + c5:06:15:ac:ac:66:54:26:55:77:dc:86:11:8f:a6: + 9c:7b:49:f1:16:f7:3a:62:26:d4:fe:a9:69:6c:22: + 79:c2:92:62:2d:6c:1e:63:5e:07:41:8a:39:27:41: + 34:c0:99:57:22:cc:30:8c:95:e7:5c:3d:f2:26:e9: + 4d:29:9e:c9:69:37:3d:47:f5:5e:69:3c:43:4e:b6: + d3:52:e1:97:77:0e:1a:69:2e:ea:8a:fe:50:68:19: + f2:38:eb:05:58:56:8a:39:22:83:3c:ae:e3:87:48: + ad:fc:30:56:43:5d:6c:75:e4:e2:fd:cd:1f:d3:a7: + 81:52:1d:7f:dd:39:57:03:1f:d5:ed:be:e4:23:94: + 23:6d:70:b6:d7:84:ec:80:7d:ec:f4:ca:44:e2:a7: + 61:eb:16:5f:dd:a2:4e:6d:99:8f:30:9c:b7:75:74: + da:83:41:42:ae:56:44:21:7b:71:cf:7d:a8:b5:32: + 8f:91:b3:30:a1:f4:ea:10:cf:3e:0d:37:3c:fe:a3: + fa:f2:26:68:a5:16:40:be:b9:e7:ae:e0:e1:eb:6c: + 65:c2:08:bd:d7:32:7e:a7:5c:09:f3:d5:40:de:c8: + 9d:c4:fd:05:e0:6a:12:93:60:35:b6:a4:7a:6b:a7: + 35:a0:86:b2:80:f6:51:6c:49:a1:b4:26:1f:a2:7b: + 20:ff:14:fe:36:f1:1a:3e:2b:3c:cf:3e:9f:07:24: + d3:fa:ad:b6:88:a6:3f:98:a1:19:01:eb:e4:7b:e9: + ec:b3:58:56:39:bb:f3:70:58:d6:87:4e:83:1a:df: + 48:af:96:fb:70:3e:73:93:df:84:ab:6d:54:bb:0b: + 6d:dd:65:d2:9a:5f:a2:ed:b3:b4:7b:54:ee:49:fd: + fd:44:54:3a:2d:d6:b0:c9:62:89:e2:ee:71:e1:60: + 73:3a:dd:1c:05:fb:f5:97:9d:8f:82:9b:ac:42:32: + 77:42:d9:60:77:59:2c:0a:4e:cd:b6:cd:c5:e1:5d: + d2:00:31:c8:d2:a7:57:46:69:1a:6f:b6:ab:f3:92: + e2:42:38:2f:c7:5f:b3:03:d0:04:70:05:7f:5f:6d: + 4c:18:69:90:95:a8:9c:02:44:ac:96:88:f5:fc:4d: + 81:4a:de:e3:5e:1e:57:c1:2d:ba:8e:f3:2b:39:ae: + ab:8d:05 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + d8:72:63:ba:c9:8d:26:18:12:d0:ab:51:e3:6a:ca:29:83:af: + d0:b1:42:ae:36:d6:23:92:d3:c7:e3:f4:1a:d9:16:14:da:3f: + 97:9a:ff:36:5f:83:ed:f8:d8:f2:8f:7a:2a:73:7a:8d:7a:5b: + 91:ce:b8:17:e8:42:29:f4:13:1e:e5:2e:3f:bf:0e:fb:de:20: + 69:c3:46:9e:67:66:26:dc:52:8e:c8:59:d8:f5:1d:ad:d4:04: + ac:e0:26:71:2e:a6:86:b2:97:37:44:cc:b0:fb:e6:72:20:83: + 12:7a:98:07:f3:a6:27:ba:a2:18:ff:40:ad:ca:33:be:a5:23: + d6:2f:0e:d1:75:d3:a1:6f:8b:bb:a9:7e:e0:fd:18:e1:44:35: + 84:56:55:72:86:72:35:bc:e5:50:77:69:e8:f2:b6:cc:3e:b6: + bf:e4:5b:ac:17:c4:84:ff:e8:d5:be:f4:7a:00:de:59:8e:2a: + 65:da:71:58:4e:d0:26:80:f0:4b:85:30:cd:c3:8c:02:2f:b4: + dc:82:c7:f7:f0:02:4b:67:5d:d8:a1:d7:4d:64:93:6b:a4:ec: + 5c:d9:52:bd:28:b8:6a:02:a6:bc:29:74:f5:bd:4d:a2:36:a6: + 57:95:42:2f:ee:1f:5d:63:6a:d2:0a:a2:f7:04:4a:6c:ca:0a: + 17:3a:9e:9a:fe:72:05:58:59:84:77:61:a0:73:8d:2b:5a:c7: + d5:98:8f:a7:de:bd:5f:25:cf:f4:01:7c:74:0e:04:49:1e:0a: + 2b:6f:2f:7f:e7:e3:c7:ce:77:43:73:10:3d:f0:a4:fc:0a:3b: + 21:1d:e0:0e:3e:4d:3a:e0:23:0a:59:ea:5b:15:07:d1:2c:e4: + 22:4d:da:01:5d:d2:94:89:0b:7a:3c:9f:35:bf:2c:36:b5:4d: + 8e:7c:88:60:0d:74:5d:b4:f0:cc:25:c7:c7:93:a4:fa:26:fa: + 6e:13:f6:03:58:6f:97:7e:cd:e3:c4:cd:aa:ae:64:5e:2a:cb: + 70:4b:df:13:3e:2b:07:3b:d8:e8:a8:91:eb:fa:a8:09:60:5c: + b3:da:d3:10:6e:23:8e:d7:95:34:01:16:3c:08:fc:f1:a3:41: + 1b:3a:45:a2:32:95:58:05:f5:b3:12:00:60:6e:49:a5:a3:ca: + 24:5d:eb:a8:e8:73:55:64:1c:13:f5:ba:e2:ba:6c:0c:15:66: + 55:40:6c:55:e6:4e:60:27:91:45:23:7c:e6:8e:ed:f5:ce:e4: + 09:9e:32:45:d8:63:21:e8:3a:9f:91:a5:52:c2:27:de:ff:35: + 31:3f:39:cc:d8:c6:0e:8c:be:58:2b:f4:77:06:ac:d7:fb:76: + 58:15:30:28:df:9e:94:e2 + +-----BEGIN CERTIFICATE----- +MIIGUTCCBDmgAwIBAgIUHlETnZyBkDglMnJR8zMbcNCbalwwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMB4XDTI2MDEyMjA5 +NTM0MVoXDTI2MDIyMTA5NTM0MVowga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBG +cmVpc3RhYXQgQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29v +Z2xlIEdlcm1hbnkgR21iSDETMBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwR +Qm9yaW5nU1NMIEF1dGhvcnMxIzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29n +bGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2WxlWVEy+qVu +q5Oj90XJdZdUC25f+28v1+1bvFcbqI6qvgNcOHhMkixfHLI5pKRTnbnX3XoAr01N +k2pbBzwQv7wCPoW5qZY/UrrFBhWsrGZUJlV33IYRj6ace0nxFvc6YibU/qlpbCJ5 +wpJiLWweY14HQYo5J0E0wJlXIswwjJXnXD3yJulNKZ7JaTc9R/VeaTxDTrbTUuGX +dw4aaS7qiv5QaBnyOOsFWFaKOSKDPK7jh0it/DBWQ11sdeTi/c0f06eBUh1/3TlX +Ax/V7b7kI5QjbXC214TsgH3s9MpE4qdh6xZf3aJObZmPMJy3dXTag0FCrlZEIXtx +z32otTKPkbMwofTqEM8+DTc8/qP68iZopRZAvrnnruDh62xlwgi91zJ+p1wJ89VA +3sidxP0F4GoSk2A1tqR6a6c1oIaygPZRbEmhtCYfonsg/xT+NvEaPis8zz6fByTT ++q22iKY/mKEZAevke+nss1hWObvzcFjWh06DGt9Ir5b7cD5zk9+Eq21Uuwtt3WXS +ml+i7bO0e1TuSf39RFQ6LdawyWKJ4u5x4WBzOt0cBfv1l52PgpusQjJ3Qtlgd1ks +Ck7Nts3F4V3SADHI0qdXRmkab7ar85LiQjgvx1+zA9AEcAV/X21MGGmQlaicAkSs +loj1/E2BSt7jXh5XwS26jvMrOa6rjQUCAwEAAaNjMGEwHQYDVR0OBBYEFOobWR5d +4zMpQX/XWIhj1CooBngQMB8GA1UdIwQYMBaAFOobWR5d4zMpQX/XWIhj1CooBngQ +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4ICAQDYcmO6yY0mGBLQq1Hjasopg6/QsUKuNtYjktPH4/Qa2RYU2j+Xmv82X4Pt ++Njyj3oqc3qNeluRzrgX6EIp9BMe5S4/vw773iBpw0aeZ2Ym3FKOyFnY9R2t1ASs +4CZxLqaGspc3RMyw++ZyIIMSepgH86YnuqIY/0CtyjO+pSPWLw7RddOhb4u7qX7g +/RjhRDWEVlVyhnI1vOVQd2no8rbMPra/5FusF8SE/+jVvvR6AN5Zjipl2nFYTtAm +gPBLhTDNw4wCL7Tcgsf38AJLZ13YoddNZJNrpOxc2VK9KLhqAqa8KXT1vU2iNqZX +lUIv7h9dY2rSCqL3BEpsygoXOp6a/nIFWFmEd2Ggc40rWsfVmI+n3r1fJc/0AXx0 +DgRJHgorby9/5+PHzndDcxA98KT8CjshHeAOPk064CMKWepbFQfRLOQiTdoBXdKU +iQt6PJ81vyw2tU2OfIhgDXRdtPDMJcfHk6T6JvpuE/YDWG+Xfs3jxM2qrmReKstw +S98TPisHO9joqJHr+qgJYFyz2tMQbiOO15U0ARY8CPzxo0EbOkWiMpVYBfWzEgBg +bkmlo8okXeuo6HNVZBwT9briumwMFWZVQGxV5k5gJ5FFI3zmju31zuQJnjJF2GMh +6DqfkaVSwife/zUxPznM2MYOjL5YK/R3BqzX+3ZYFTAo356U4g== +-----END CERTIFICATE----- +This is generated from + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-ECDSA-P256.crt -extfile svcconfig -days 730500 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 50:2d:32:51:7b:b9:51:f0:df:6e:4e:c3:05:1f:2c:4b:af:cd:52:74 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Jan 23 13:40:52 2026 GMT + Not After : Feb 7 13:40:52 4026 GMT + Subject: CN=www.google.com + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:eb:d1:ff:95:75:4a:0b:08:fd:00:ef:91:98:d4: + 6f:1e:f7:3e:38:03:ab:72:3b:a2:f3:04:71:9f:eb: + a5:8d:18:16:4f:72:cb:39:1c:29:98:0e:71:72:23: + 3e:3c:69:ad:e8:84:8f:70:e3:80:e7:b6:4d:f2:23: + b0:ec:5d:9f:7f + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment + X509v3 Subject Alternative Name: + DNS:www.google.com, DNS:localhost + X509v3 Subject Key Identifier: + F1:D8:5F:E8:36:6E:04:A3:E0:54:5D:8F:2C:44:EC:9E:B9:67:21:8C + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + ab:66:a7:3c:5d:8c:b8:70:09:38:ff:7a:6c:61:80:84:31:4c: + 06:8a:b6:51:10:f9:19:8e:86:94:6c:e2:28:9b:5b:5b:b1:9b: + ae:63:6b:03:ab:63:5f:32:ec:ff:6b:82:68:e9:f1:bb:43:02: + de:c5:af:02:67:84:a2:78:68:68:03:46:05:4e:a6:35:09:23: + 9b:34:b2:c6:2c:17:5a:d7:71:69:8f:df:41:84:17:3a:41:a6: + 34:fa:2e:9b:2a:27:ac:49:6a:8b:ae:dd:77:2e:ec:87:6a:d1: + b1:69:1f:b3:85:96:91:68:44:2d:e8:fc:5c:68:29:4e:c6:f2: + f8:fb:6f:c9:04:d1:b6:3b:da:4e:71:0e:a6:98:24:c9:22:cd: + db:46:fd:e9:7d:2c:98:4e:84:35:47:ca:aa:ed:64:e5:fe:96: + 42:62:1f:0b:74:cf:87:c7:56:94:06:e0:a5:a9:3b:74:6e:40: + 29:81:b1:19:00:32:2b:ee:d1:5a:80:5f:d5:ec:7b:22:4c:a7: + b8:f2:c8:0e:af:57:c4:c1:c1:16:2a:bc:b5:cc:74:7e:7f:46: + 19:72:72:0d:cc:54:80:06:2e:ef:98:92:39:4e:f5:7f:d8:19: + 81:8d:65:91:88:4b:62:f8:ee:49:11:28:17:ac:a1:6d:5c:bd: + 0c:f7:f3:5b:2d:f7:c6:a2:28:9e:a0:0e:57:6c:12:0d:51:69: + 00:d1:12:e5:8f:68:8c:a1:76:78:d0:0d:e4:c5:4e:69:b0:be: + 30:21:e8:bd:c4:a1:0f:7f:e5:ee:47:e8:0b:62:87:cc:98:d7: + b5:a1:20:a9:05:12:84:46:d1:b8:9f:e8:a0:fb:c1:4d:7d:df: + a6:3e:90:44:23:8b:d7:62:f2:90:89:4c:64:b9:56:d1:af:06: + 55:05:77:3f:98:ac:e6:31:78:e1:38:02:c3:e9:93:22:cf:00: + ec:1e:f0:6f:b2:b6:ee:76:34:5f:33:db:83:bc:ed:28:cf:eb: + 98:fa:25:32:e8:75:83:b3:9e:90:a0:63:7b:a9:e9:55:8d:66: + 67:a9:70:54:a5:92:a4:ec:0a:a7:79:4c:7b:62:84:c0:01:27: + cb:e6:5d:97:da:00:bc:52:c5:ef:6f:2a:56:4d:2e:34:3f:e1: + dc:b0:04:46:76:f6:cc:a1:1e:a1:b7:68:31:82:6e:ac:12:cc: + 58:da:ef:9f:2b:ef:0a:22:be:32:e0:40:9c:ca:30:65:2f:f0: + 9c:2d:13:e3:c7:94:24:5f:9e:eb:47:5d:07:70:35:48:5d:47: + 3f:56:d8:fd:50:1f:55:53:04:c1:65:c9:29:46:83:d0:77:86: + 70:8a:bc:04:8a:19:d7:ad + +-----BEGIN CERTIFICATE----- +MIIEDzCCAfegAwIBAgIUUC0yUXu5UfDfbk7DBR8sS6/NUnQwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMCAXDTI2MDEyMzEz +NDA1MloYDzQwMjYwMjA3MTM0MDUyWjAZMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNv +bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOvR/5V1SgsI/QDvkZjUbx73PjgD +q3I7ovMEcZ/rpY0YFk9yyzkcKZgOcXIjPjxpreiEj3DjgOe2TfIjsOxdn3+jgYAw +fjAfBgNVHSMEGDAWgBTqG1keXeMzKUF/11iIY9QqKAZ4EDAJBgNVHRMEAjAAMAsG +A1UdDwQEAwIE8DAkBgNVHREEHTAbgg53d3cuZ29vZ2xlLmNvbYIJbG9jYWxob3N0 +MB0GA1UdDgQWBBTx2F/oNm4Eo+BUXY8sROyeuWchjDANBgkqhkiG9w0BAQsFAAOC +AgEAq2anPF2MuHAJOP96bGGAhDFMBoq2URD5GY6GlGziKJtbW7GbrmNrA6tjXzLs +/2uCaOnxu0MC3sWvAmeEonhoaANGBU6mNQkjmzSyxiwXWtdxaY/fQYQXOkGmNPou +myonrElqi67ddy7sh2rRsWkfs4WWkWhELej8XGgpTsby+PtvyQTRtjvaTnEOppgk +ySLN20b96X0smE6ENUfKqu1k5f6WQmIfC3TPh8dWlAbgpak7dG5AKYGxGQAyK+7R +WoBf1ex7IkynuPLIDq9XxMHBFiq8tcx0fn9GGXJyDcxUgAYu75iSOU71f9gZgY1l +kYhLYvjuSREoF6yhbVy9DPfzWy33xqIonqAOV2wSDVFpANES5Y9ojKF2eNAN5MVO +abC+MCHovcShD3/l7kfoC2KHzJjXtaEgqQUShEbRuJ/ooPvBTX3fpj6QRCOL12Ly +kIlMZLlW0a8GVQV3P5is5jF44TgCw+mTIs8A7B7wb7K27nY0XzPbg7ztKM/rmPol +Muh1g7OekKBje6npVY1mZ6lwVKWSpOwKp3lMe2KEwAEny+Zdl9oAvFLF728qVk0u +ND/h3LAERnb2zKEeobdoMYJurBLMWNrvnyvvCiK+MuBAnMowZS/wnC0T48eUJF+e +60ddB3A1SF1HP1bY/VAfVVMEwWXJKUaD0HeGcIq8BIoZ160= +-----END CERTIFICATE----- +This is generated from + openssl ecparam -name secp384r1 -genkey -noout -out BoringSSLServerTest-ECDSA-P384.key + openssl req -new -key BoringSSLServerTest-ECDSA-P384.key -out server.csr \ + -subj '/CN=www.google.com' + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-ECDSA-P384.crt -extfile svcconfig -days 730500 + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 14:05:09:ad:35:ee:d1:b6:9a:ea:34:fb:2a:27:19:82:39:fd:f8:84 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Feb 7 23:39:21 2026 GMT + Not After : Feb 22 23:39:21 4026 GMT + Subject: CN=www.google.com + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (384 bit) + pub: + 04:e9:40:b9:49:ce:db:96:7f:94:e1:c0:fe:5a:27: + 34:f5:97:df:d6:db:47:18:2f:06:c5:64:4a:65:22: + e1:f9:7d:02:f7:6e:ab:20:52:ee:a2:c3:9f:0c:09: + 61:3f:28:72:32:b2:13:d1:88:9a:d5:ba:ee:a6:8f: + 61:50:fa:20:b4:49:6c:e3:50:97:3b:10:93:ed:04: + 85:6e:bd:15:81:1f:e3:db:60:ec:c2:51:43:ee:65: + 75:95:bf:4f:6d:cb:ff + ASN1 OID: secp384r1 + NIST CURVE: P-384 + X509v3 extensions: + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment + X509v3 Subject Alternative Name: + DNS:www.google.com, DNS:localhost + X509v3 Subject Key Identifier: + 05:AA:55:C8:84:9E:CD:8B:04:D8:0A:C4:E7:00:9A:7E:B0:A9:51:D9 + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 9b:2b:ad:e3:53:5a:7e:ef:68:0c:34:0b:62:97:a3:da:f9:39: + ca:39:d4:67:af:77:72:8f:a8:09:07:bd:29:40:29:1e:78:4c: + 8e:89:f1:d2:ab:1c:f1:07:1b:1c:8b:e7:25:0b:4f:44:40:b4: + 16:14:0f:52:15:26:f7:c1:66:87:d0:43:33:61:94:44:05:fb: + 2f:22:0a:aa:07:cd:cb:16:ca:a1:7f:c5:39:74:58:e2:98:30: + 71:8c:1a:9c:f2:61:55:58:82:b9:33:26:f9:77:72:25:9e:47: + 14:d3:4c:cb:c1:9e:b2:b3:cf:eb:60:54:25:f4:68:5e:93:19: + 79:a5:d8:dc:63:ed:f3:97:2d:98:b5:c2:b5:fc:ee:95:69:f5: + 05:e7:2f:41:e9:d5:3f:00:33:3c:8f:df:b2:72:fc:27:bd:e4: + b6:1d:32:3a:db:3d:80:0f:dc:e6:32:58:66:ac:b4:96:f7:85: + 11:9d:2d:bc:f6:61:85:a8:2a:a5:4a:74:e8:d8:1f:bd:29:63: + 34:d7:1c:62:15:7f:d7:4e:e8:36:c1:c3:c0:89:33:97:61:69: + 44:72:5a:95:68:6c:66:45:a8:47:0d:3e:68:26:42:13:81:0e: + d4:a4:fc:ae:ae:b5:b0:1e:13:76:6e:97:f2:1d:48:91:af:a4: + 68:74:9c:61:f5:9e:d6:97:8e:ff:34:b7:6c:df:06:d8:07:a3: + de:3c:43:a6:b0:b2:65:4e:9d:6e:12:68:77:b0:df:9c:46:19: + ed:19:18:49:23:ea:15:e2:e8:7f:be:20:ae:12:fa:78:21:1a: + 15:c0:ba:4d:91:48:b0:0d:af:9e:0f:17:e3:e6:31:9d:1e:dc: + e0:d8:74:d9:5c:e2:8f:a5:59:76:67:95:10:0f:a9:ab:1b:28: + 3d:1e:c3:c5:b3:80:8b:06:9c:20:51:42:12:7f:cd:89:76:54: + c1:47:c4:54:e7:54:d0:66:75:64:e6:5d:ff:32:53:39:55:9f: + f6:73:04:ea:a8:a4:57:a6:05:0a:47:dc:7a:da:17:55:4e:61: + 63:a8:9d:54:9e:78:39:a7:af:90:13:72:a5:ef:67:51:83:46: + 35:be:fc:d7:39:84:77:24:3f:b5:36:81:d7:83:6f:a6:a0:6a: + fc:77:0c:f4:d8:9a:8a:d4:98:03:f6:7f:04:30:fe:0f:ac:24: + 8e:44:b3:ab:ae:c0:bf:4d:8a:51:13:44:14:44:80:d4:ea:09: + 4a:a7:89:4a:43:64:74:00:99:eb:68:d7:e3:ee:f3:7e:3f:47: + d8:71:7e:40:ab:0a:44:8b:e9:66:4e:4c:09:69:17:3b:e8:22: + 3d:5c:b9:cd:01:f9:2c:ab + +-----BEGIN CERTIFICATE----- +MIIELDCCAhSgAwIBAgIUFAUJrTXu0baa6jT7KicZgjn9+IQwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMCAXDTI2MDIwNzIz +MzkyMVoYDzQwMjYwMjIyMjMzOTIxWjAZMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNv +bTB2MBAGByqGSM49AgEGBSuBBAAiA2IABOlAuUnO25Z/lOHA/lonNPWX39bbRxgv +BsVkSmUi4fl9AvduqyBS7qLDnwwJYT8ocjKyE9GImtW67qaPYVD6ILRJbONQlzsQ +k+0EhW69FYEf49tg7MJRQ+5ldZW/T23L/6OBgDB+MB8GA1UdIwQYMBaAFOobWR5d +4zMpQX/XWIhj1CooBngQMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMCQGA1UdEQQd +MBuCDnd3dy5nb29nbGUuY29tgglsb2NhbGhvc3QwHQYDVR0OBBYEFAWqVciEns2L +BNgKxOcAmn6wqVHZMA0GCSqGSIb3DQEBCwUAA4ICAQCbK63jU1p+72gMNAtil6Pa ++TnKOdRnr3dyj6gJB70pQCkeeEyOifHSqxzxBxsci+clC09EQLQWFA9SFSb3wWaH +0EMzYZREBfsvIgqqB83LFsqhf8U5dFjimDBxjBqc8mFVWIK5Myb5d3IlnkcU00zL +wZ6ys8/rYFQl9Ghekxl5pdjcY+3zly2YtcK1/O6VafUF5y9B6dU/ADM8j9+ycvwn +veS2HTI62z2AD9zmMlhmrLSW94URnS289mGFqCqlSnTo2B+9KWM01xxiFX/XTug2 +wcPAiTOXYWlEclqVaGxmRahHDT5oJkITgQ7UpPyurrWwHhN2bpfyHUiRr6RodJxh +9Z7Wl47/NLds3wbYB6PePEOmsLJlTp1uEmh3sN+cRhntGRhJI+oV4uh/viCuEvp4 +IRoVwLpNkUiwDa+eDxfj5jGdHtzg2HTZXOKPpVl2Z5UQD6mrGyg9HsPFs4CLBpwg +UUISf82JdlTBR8RU51TQZnVk5l3/MlM5VZ/2cwTqqKRXpgUKR9x62hdVTmFjqJ1U +nng5p6+QE3Kl72dRg0Y1vvzXOYR3JD+1NoHXg2+moGr8dwz02JqK1JgD9n8EMP4P +rCSORLOrrsC/TYpRE0QURIDU6glKp4lKQ2R0AJnraNfj7vN+P0fYcX5AqwpEi+lm +TkwJaRc76CI9XLnNAfksqw== +-----END CERTIFICATE----- +This is generated from + openssl genpkey -algorithm Ed25519 -out BoringSSLServerTest-Ed25519.key + openssl req -new -key BoringSSLServerTest-Ed25519.key -out server.csr \ + -subj '/CN=www.google.com' + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-Ed25519.crt -extfile svcconfig -days 730500 + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 14:05:09:ad:35:ee:d1:b6:9a:ea:34:fb:2a:27:19:82:39:fd:f8:85 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Feb 7 23:39:29 2026 GMT + Not After : Feb 22 23:39:29 4026 GMT + Subject: CN=www.google.com + Subject Public Key Info: + Public Key Algorithm: ED25519 + ED25519 Public-Key: + pub: + e4:b2:47:7f:79:c9:71:f7:2a:e5:77:f7:36:db:3a: + ee:76:fb:68:e3:05:ad:f5:31:ae:ad:50:21:2d:4d: + 13:70 + X509v3 extensions: + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment + X509v3 Subject Alternative Name: + DNS:www.google.com, DNS:localhost + X509v3 Subject Key Identifier: + 85:26:DD:50:0C:64:47:9D:76:F3:98:5A:95:1A:F0:43:25:78:98:68 + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + cb:cc:9a:b1:aa:ca:e8:d6:ae:20:8a:7b:55:29:85:4c:b9:83: + fe:54:05:6f:e2:80:da:8e:59:af:88:47:fb:b7:fc:dd:7d:79: + f8:5e:12:74:c1:17:06:cb:99:a3:72:90:c9:af:e0:e7:bc:c9: + 74:61:ae:ba:b8:94:a0:3b:6e:71:05:53:9c:89:1f:57:19:18: + 5a:ae:40:60:8b:d5:18:92:72:d3:f8:07:9a:cb:bc:53:d2:5e: + 0f:1b:4b:d6:4b:6a:ae:aa:9f:1d:ca:73:f2:e6:c3:4b:1b:73: + 35:de:ee:1b:d7:6d:73:37:9b:4d:ab:be:c2:80:a8:bc:5a:da: + cc:74:10:b8:7d:f1:4d:70:7d:e2:c3:85:a8:f5:59:df:6c:48: + de:9c:6a:ca:0b:cb:8a:a1:ac:61:41:c0:c6:6f:bf:16:ec:47: + db:cd:aa:04:38:79:9b:83:6c:69:80:f3:e2:c9:de:e8:2b:dd: + fc:1a:e9:8b:98:59:a6:38:cd:ba:c3:9e:3d:ef:1e:bf:f2:8a: + 5a:a3:04:da:84:7e:36:e4:be:f3:6c:60:7c:07:d6:27:de:d3: + 4f:df:29:2f:41:34:c4:27:1e:c6:54:3b:73:8e:f1:28:ef:87: + 24:99:0e:b8:1c:59:79:0b:5f:e9:3d:c7:6e:63:2c:5b:ce:c4: + 04:6a:19:45:c0:07:27:47:e2:6b:03:d1:e5:ce:e6:04:f8:2c: + d7:d4:db:4e:8e:49:b0:9f:f9:f3:e4:3c:29:e9:0b:1c:c0:f7: + c7:9f:ba:0a:54:63:09:88:b3:3a:84:28:96:36:4f:62:48:c5: + 4a:8e:8b:78:ad:e1:44:d0:45:13:b2:f0:b6:ba:5e:13:71:cf: + a9:b3:75:dc:1e:66:1b:c0:36:28:76:21:45:24:d8:03:e5:b0: + 6a:42:e3:c6:07:a5:8e:02:5e:8d:6b:af:0c:87:08:22:fc:e3: + 01:bb:28:a4:ae:20:6a:f5:c2:8a:53:b8:95:d0:26:e6:48:4b: + 47:2d:e7:09:1a:6f:f2:b3:68:5e:d7:5a:08:be:9d:c5:2d:ab: + 94:bf:55:95:db:a9:a8:36:f7:c0:40:d0:f8:8b:2d:9d:3d:d9: + 6a:d5:de:d7:0d:00:35:9f:17:da:d8:c9:d0:1f:02:36:fe:35: + eb:40:17:aa:57:e4:cf:17:3a:61:d7:7e:c0:32:45:86:c2:a1: + c6:68:5c:da:30:ad:b4:cf:56:06:02:51:d3:d2:6d:73:91:d5: + 5f:f6:68:9a:99:2c:2b:3b:79:b4:46:21:60:17:00:79:e4:f1: + 5d:04:a3:fa:ec:e5:7c:c9:48:bc:87:45:a1:11:5b:7d:44:0e: + 9b:ae:b9:48:69:a9:7a:48 + +-----BEGIN CERTIFICATE----- +MIID4DCCAcigAwIBAgIUFAUJrTXu0baa6jT7KicZgjn9+IUwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMCAXDTI2MDIwNzIz +MzkyOVoYDzQwMjYwMjIyMjMzOTI5WjAZMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNv +bTAqMAUGAytlcAMhAOSyR395yXH3KuV39zbbOu52+2jjBa31Ma6tUCEtTRNwo4GA +MH4wHwYDVR0jBBgwFoAU6htZHl3jMylBf9dYiGPUKigGeBAwCQYDVR0TBAIwADAL +BgNVHQ8EBAMCBPAwJAYDVR0RBB0wG4IOd3d3Lmdvb2dsZS5jb22CCWxvY2FsaG9z +dDAdBgNVHQ4EFgQUhSbdUAxkR51285halRrwQyV4mGgwDQYJKoZIhvcNAQELBQAD +ggIBAMvMmrGqyujWriCKe1UphUy5g/5UBW/igNqOWa+IR/u3/N19efheEnTBFwbL +maNykMmv4Oe8yXRhrrq4lKA7bnEFU5yJH1cZGFquQGCL1RiSctP4B5rLvFPSXg8b +S9ZLaq6qnx3Kc/Lmw0sbczXe7hvXbXM3m02rvsKAqLxa2sx0ELh98U1wfeLDhaj1 +Wd9sSN6casoLy4qhrGFBwMZvvxbsR9vNqgQ4eZuDbGmA8+LJ3ugr3fwa6YuYWaY4 +zbrDnj3vHr/yilqjBNqEfjbkvvNsYHwH1ife00/fKS9BNMQnHsZUO3OO8SjvhySZ +DrgcWXkLX+k9x25jLFvOxARqGUXABydH4msD0eXO5gT4LNfU206OSbCf+fPkPCnp +CxzA98efugpUYwmIszqEKJY2T2JIxUqOi3it4UTQRROy8La6XhNxz6mzddweZhvA +Nih2IUUk2APlsGpC48YHpY4CXo1rrwyHCCL84wG7KKSuIGr1wopTuJXQJuZIS0ct +5wkab/KzaF7XWgi+ncUtq5S/VZXbqag298BA0PiLLZ092WrV3tcNADWfF9rYydAf +Ajb+NetAF6pX5M8XOmHXfsAyRYbCocZoXNowrbTPVgYCUdPSbXOR1V/2aJqZLCs7 +ebRGIWAXAHnk8V0Eo/rs5XzJSLyHRaERW31EDpuuuUhpqXpI +-----END CERTIFICATE----- +This is generated from + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-RSA.crt -extfile svcconfig -days 730500 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 50:2d:32:51:7b:b9:51:f0:df:6e:4e:c3:05:1f:2c:4b:af:cd:52:73 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Jan 22 10:40:09 2026 GMT + Not After : Feb 6 10:40:09 4026 GMT + Subject: CN=www.google.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (4096 bit) + Modulus: + 00:f7:a2:3f:8e:9e:a5:9a:f7:eb:c5:cf:d5:31:3b: + c0:ca:02:83:3a:81:90:9a:8d:47:d4:3c:c1:f2:61: + 3d:87:11:25:7f:bc:b9:a4:dc:7f:3e:7d:30:1e:32: + 8d:ec:d5:e0:b5:b2:47:b3:77:3d:2d:2e:61:dd:ad: + 03:62:2d:e0:19:c8:fb:1f:43:ef:ed:cd:a2:96:8c: + 61:27:fc:7d:9e:42:90:07:79:c8:87:f5:3a:ff:f9: + 0e:25:b3:b2:67:45:3e:6b:a6:bf:0c:8d:ac:78:00: + 8a:5d:6c:dd:97:8c:f6:1f:2f:73:c8:ed:af:39:73: + 81:a5:38:fc:ed:a6:0c:1c:46:45:49:98:19:cd:4c: + 3b:63:ac:67:9a:d8:95:36:46:b6:47:ec:cd:bd:84: + ca:8b:34:6e:5e:67:f0:1f:4f:c7:7e:57:97:3f:e6: + b9:7e:b1:f6:66:41:26:2b:3e:9f:1a:88:48:58:46: + 7e:6d:6a:74:f0:4a:6f:f4:51:2d:cb:22:45:33:c1: + 77:4d:c7:8a:0d:ad:0d:dc:6a:8e:5c:87:3a:a9:a7: + dc:df:45:0b:d8:36:5a:c9:3f:5c:2a:dc:9d:81:1d: + 30:bd:0d:03:5d:cb:8d:f8:5d:31:81:76:97:c1:1f: + 14:0c:54:3d:30:41:c4:7b:96:6f:8c:2a:2f:2a:fb: + fc:96:59:5f:7a:f6:48:4a:eb:8f:a6:c6:59:e1:e1: + 05:27:cc:7d:c0:60:77:dd:8f:a2:7c:86:c1:d7:3e: + 05:18:ad:54:c4:68:8f:10:d6:c7:f2:63:d8:17:bd: + 8f:c3:29:08:9b:15:af:19:eb:a2:16:23:67:b8:b8: + b1:ee:ee:32:d3:8f:31:4b:19:c2:4e:f9:3e:9a:bf: + 77:ec:b9:e9:d9:c7:9f:82:21:b8:2d:4d:ed:9c:30: + b1:b2:86:c1:38:22:3a:fe:08:21:fe:2e:94:d0:b9: + 5a:ef:8f:b0:15:74:b2:df:37:28:59:fd:48:92:eb: + a2:6a:17:4f:90:0f:78:b9:d3:59:22:f6:10:b9:35: + a5:ea:21:c9:0b:55:3e:56:00:f4:6a:37:17:d9:94: + 5a:d7:ec:76:6d:5d:22:9b:d0:bf:f1:03:b0:66:91: + 8d:ae:ae:ee:6c:00:e8:03:ad:1a:c5:18:80:15:b0: + cb:af:2d:3e:b4:25:fc:01:db:70:09:fe:44:d0:2d: + f3:be:25:55:82:c9:a0:36:f6:be:0d:d7:39:a7:51: + 9f:a7:ee:67:8a:a8:34:e6:f5:6d:82:54:bf:bc:07: + a6:26:da:0f:d6:58:f8:93:53:f9:f3:23:73:b5:69: + 22:cf:ef:af:0a:77:07:53:a2:59:59:96:36:8a:11: + d0:ac:2d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment + X509v3 Subject Alternative Name: + DNS:www.google.com, DNS:localhost + X509v3 Subject Key Identifier: + 3B:BF:82:CA:BD:B0:B7:0D:60:E4:DD:A5:3B:FC:AF:6A:F6:8A:A2:28 + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + c7:85:d7:41:f9:b1:67:7a:72:39:46:01:05:25:5f:9e:45:a1: + 06:3f:19:dd:31:30:ed:2f:9e:37:e6:fc:b7:55:e9:38:8d:77: + 5f:aa:ba:26:8f:4e:bf:d6:ff:d2:0e:46:41:d6:3c:5f:65:83: + 67:b6:46:a6:31:58:70:61:13:5e:6d:6c:2f:49:68:e8:eb:94: + 78:f5:a1:5a:0f:c6:d0:29:c2:86:bc:13:fc:9a:08:d5:d4:58: + 76:e6:85:2b:76:1b:d2:24:56:3d:0f:d0:f6:14:23:ed:48:3e: + 56:57:f9:c2:45:d0:ba:b7:01:df:50:97:bc:3d:74:dd:ac:c3: + 18:72:a7:66:9a:14:eb:22:33:89:bd:ad:f5:36:44:9a:71:6a: + 78:b9:bd:e4:9a:a8:bf:de:c7:18:d8:6b:09:81:d9:d6:3f:a4: + 11:df:18:68:58:e4:8a:8a:d3:31:28:eb:cc:59:a6:5f:89:62: + 23:b2:ee:09:86:fd:33:b6:ba:84:5a:fe:10:57:c7:ab:16:eb: + 69:44:84:67:bd:8a:89:22:d5:f7:91:d5:4c:99:00:45:dc:c7: + 90:e8:7e:60:5e:e8:f7:e4:90:22:8c:bd:a2:0b:58:67:6a:ca: + 7a:89:f9:fa:73:24:ac:39:29:0e:eb:cf:bc:82:72:b7:8d:52: + 59:a3:7c:bb:72:0d:05:79:c3:90:1f:31:05:fc:7b:54:f1:7e: + fd:78:99:6c:21:74:cc:83:95:3f:09:e6:77:7f:16:6c:0f:3e: + 71:24:31:ef:b8:95:3a:7a:2d:31:de:fa:33:7c:45:d7:ce:50: + 9f:9d:82:95:27:b1:78:5c:ba:8f:49:02:37:a4:3f:ac:bc:1d: + 51:c2:73:ac:75:9d:fe:8d:ee:94:81:60:95:45:3c:c4:ba:34: + 25:3e:01:04:da:a0:0f:ed:b9:96:3a:81:ae:2f:d2:aa:0c:73: + 05:f9:ec:79:90:6e:69:a7:aa:3d:ce:76:31:95:db:55:ef:64: + 69:d1:e2:e7:e8:71:04:3c:14:d5:97:ad:b9:6e:20:bd:33:3d: + f2:d5:22:f0:c3:be:63:35:e1:7d:f8:81:6d:ac:60:ff:52:f1: + 64:cc:b7:9c:bd:f4:bb:61:10:d7:a9:ba:6d:1a:e5:54:cc:4c: + 7d:1a:8d:8a:85:0d:3a:91:8a:f3:7b:fd:6a:a5:66:3e:3a:34: + f6:df:f2:6e:26:b6:e0:b2:1b:28:f3:b7:bc:0c:6f:c7:7f:0c: + a5:0a:51:07:be:be:bb:7b:48:50:ae:f8:5e:2c:01:7b:8a:7a: + 33:e2:30:39:53:a2:55:eb:4d:86:cf:eb:d4:21:63:9c:58:d6: + 6e:1d:42:79:d3:3c:40:f4 + +-----BEGIN CERTIFICATE----- +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 +1CFjnFjWbh1CedM8QPQ= +-----END CERTIFICATE----- +This is generated from + openssl x509 -req -in server.csr -CA BoringSSLCATest.crt \ + -CAkey BoringSSLCATest.key -CAcreateserial \ + -out BoringSSLServerTest-RSA-PSS-SHA256.crt -extfile svcconfig \ + -days 730500 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 \ + -sigopt rsa_mgf1_md:sha256 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 2a:7d:8e:d1:d8:49:df:fa:b6:31:9a:df:0b:04:9f:dc:cb:be:82:03 + Signature Algorithm: rsassaPss + Hash Algorithm: sha256 + Mask Algorithm: mgf1 with sha256 + Salt Length: 0x20 + Trailer Field: 0x01 (default) + Issuer: C=DE, ST=Freistaat Bayern, L=Muenchen, O=Google Germany GmbH, OU=ISE Crypto, CN=BoringSSL Authors, emailAddress=boringssl@google.com + Validity + Not Before: Feb 2 15:39:55 2026 GMT + Not After : Feb 17 15:39:55 4026 GMT + Subject: CN=www.google.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (4096 bit) + Modulus: + 00:f7:a2:3f:8e:9e:a5:9a:f7:eb:c5:cf:d5:31:3b: + c0:ca:02:83:3a:81:90:9a:8d:47:d4:3c:c1:f2:61: + 3d:87:11:25:7f:bc:b9:a4:dc:7f:3e:7d:30:1e:32: + 8d:ec:d5:e0:b5:b2:47:b3:77:3d:2d:2e:61:dd:ad: + 03:62:2d:e0:19:c8:fb:1f:43:ef:ed:cd:a2:96:8c: + 61:27:fc:7d:9e:42:90:07:79:c8:87:f5:3a:ff:f9: + 0e:25:b3:b2:67:45:3e:6b:a6:bf:0c:8d:ac:78:00: + 8a:5d:6c:dd:97:8c:f6:1f:2f:73:c8:ed:af:39:73: + 81:a5:38:fc:ed:a6:0c:1c:46:45:49:98:19:cd:4c: + 3b:63:ac:67:9a:d8:95:36:46:b6:47:ec:cd:bd:84: + ca:8b:34:6e:5e:67:f0:1f:4f:c7:7e:57:97:3f:e6: + b9:7e:b1:f6:66:41:26:2b:3e:9f:1a:88:48:58:46: + 7e:6d:6a:74:f0:4a:6f:f4:51:2d:cb:22:45:33:c1: + 77:4d:c7:8a:0d:ad:0d:dc:6a:8e:5c:87:3a:a9:a7: + dc:df:45:0b:d8:36:5a:c9:3f:5c:2a:dc:9d:81:1d: + 30:bd:0d:03:5d:cb:8d:f8:5d:31:81:76:97:c1:1f: + 14:0c:54:3d:30:41:c4:7b:96:6f:8c:2a:2f:2a:fb: + fc:96:59:5f:7a:f6:48:4a:eb:8f:a6:c6:59:e1:e1: + 05:27:cc:7d:c0:60:77:dd:8f:a2:7c:86:c1:d7:3e: + 05:18:ad:54:c4:68:8f:10:d6:c7:f2:63:d8:17:bd: + 8f:c3:29:08:9b:15:af:19:eb:a2:16:23:67:b8:b8: + b1:ee:ee:32:d3:8f:31:4b:19:c2:4e:f9:3e:9a:bf: + 77:ec:b9:e9:d9:c7:9f:82:21:b8:2d:4d:ed:9c:30: + b1:b2:86:c1:38:22:3a:fe:08:21:fe:2e:94:d0:b9: + 5a:ef:8f:b0:15:74:b2:df:37:28:59:fd:48:92:eb: + a2:6a:17:4f:90:0f:78:b9:d3:59:22:f6:10:b9:35: + a5:ea:21:c9:0b:55:3e:56:00:f4:6a:37:17:d9:94: + 5a:d7:ec:76:6d:5d:22:9b:d0:bf:f1:03:b0:66:91: + 8d:ae:ae:ee:6c:00:e8:03:ad:1a:c5:18:80:15:b0: + cb:af:2d:3e:b4:25:fc:01:db:70:09:fe:44:d0:2d: + f3:be:25:55:82:c9:a0:36:f6:be:0d:d7:39:a7:51: + 9f:a7:ee:67:8a:a8:34:e6:f5:6d:82:54:bf:bc:07: + a6:26:da:0f:d6:58:f8:93:53:f9:f3:23:73:b5:69: + 22:cf:ef:af:0a:77:07:53:a2:59:59:96:36:8a:11: + d0:ac:2d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Authority Key Identifier: + EA:1B:59:1E:5D:E3:33:29:41:7F:D7:58:88:63:D4:2A:28:06:78:10 + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment + X509v3 Subject Alternative Name: + DNS:www.google.com, DNS:localhost + X509v3 Subject Key Identifier: + 3B:BF:82:CA:BD:B0:B7:0D:60:E4:DD:A5:3B:FC:AF:6A:F6:8A:A2:28 + Signature Algorithm: rsassaPss + Signature Value: + Hash Algorithm: sha256 + Mask Algorithm: mgf1 with sha256 + Salt Length: 0x20 + Trailer Field: 0x01 (default) + 96:84:18:0d:a9:40:54:39:26:80:58:7c:27:9a:28:06:b2:f2: + 59:f4:2c:3b:ab:a8:6e:77:33:27:6f:23:db:01:f3:11:22:ef: + 6a:ce:a4:fe:77:d1:18:fc:4c:85:13:e7:d9:ef:00:fc:b5:5d: + b7:a7:e3:74:3d:4a:fb:a4:66:7e:d6:91:94:10:1a:3d:d5:f0: + 5a:68:25:71:23:b9:31:8d:ba:ef:2a:e6:dd:3e:85:2d:6b:f6: + a6:7f:86:d9:a2:b5:8f:83:a6:56:b9:61:11:a5:e7:0c:19:20: + 9b:8c:e9:cc:b9:6b:69:fa:db:93:a8:bd:60:e2:0a:59:1d:58: + 51:3d:bd:44:df:f5:85:7c:19:04:47:8b:ca:18:09:3d:2a:ff: + c4:fe:48:09:5b:b2:d8:66:55:60:95:3b:34:72:71:cb:af:dd: + 17:1a:bd:68:17:5f:50:38:39:4e:c3:95:61:35:63:27:f2:d0: + da:20:7d:e2:d6:59:ec:c2:46:26:25:bc:a5:dd:7e:64:9a:44: + 33:31:84:75:05:3a:e2:a2:9a:8e:b2:cc:5e:95:1c:8d:2d:b4: + 9e:e0:f4:29:10:9e:16:0d:be:e7:1f:a9:ee:61:c0:48:4c:19: + fb:b0:d5:83:ef:eb:ee:50:a9:5c:94:41:fc:40:23:ff:63:38: + 18:3a:ab:50:f5:7b:b7:eb:8d:2d:f4:62:bb:20:33:57:22:97: + 7f:6d:32:9b:bf:7a:01:73:de:a4:04:96:47:19:0f:6d:99:a9: + 51:b0:63:2e:ff:56:36:b9:2f:d4:6c:a7:9c:f5:bc:2f:23:d1: + 4a:85:a8:63:ea:6a:62:3e:ef:10:14:f7:b6:97:42:a4:7c:6c: + ba:b1:53:ea:fc:4e:eb:db:20:60:41:32:26:dd:19:e0:a3:f4: + 06:f8:19:c3:48:9a:7e:7f:c7:c2:f4:88:55:5b:bc:41:69:be: + ab:7f:36:fe:ea:df:32:25:ab:cc:fe:9b:37:a6:92:6d:f0:9f: + 4c:45:3f:e7:1a:7f:06:86:4b:95:bc:78:03:4f:42:fc:4b:ac: + f6:3a:15:89:ed:40:25:34:b5:85:9d:16:1d:4c:1f:97:30:f1: + 68:58:09:fb:c2:83:76:b3:4a:cf:d1:fe:dc:33:a5:83:f8:0c: + 43:7b:f6:9c:36:17:ff:f7:a2:33:1e:db:83:9a:22:e3:76:2e: + ca:f2:e6:cc:2e:59:20:be:6c:06:57:8a:43:9b:17:e1:46:10: + c6:f7:bc:64:c7:18:ed:7a:88:9f:59:51:da:3d:2f:1c:a2:0a: + 92:1f:9a:e4:27:58:68:7f:71:d4:c2:b4:03:bb:e1:8b:92:2a: + 3d:75:1d:eb:54:3e:13:e7 + +-----BEGIN CERTIFICATE----- +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 +J1hof3HUwrQDu+GLkio9dR3rVD4T5w== +-----END CERTIFICATE----- + +So I hope that this trailing lines of text will entertain you for the rest of +the day. +See you next time!
diff --git a/rust/bssl-x509/src/tests/truncated-one.pem b/rust/bssl-x509/src/tests/truncated-one.pem new file mode 100644 index 0000000..5c4e892 --- /dev/null +++ b/rust/bssl-x509/src/tests/truncated-one.pem
@@ -0,0 +1,4 @@ +Ahoy! + +-----BEGIN CERTIFICATE----- +MIIGUTCCBDmgAwIBAgIUHlETnZyBkDglMnJR8zMbcNCbalwwDQYJKoZIhvcNAQEL \ No newline at end of file
diff --git a/rust/bssl-x509/src/tests/truncated.pem b/rust/bssl-x509/src/tests/truncated.pem new file mode 100644 index 0000000..77cf43e --- /dev/null +++ b/rust/bssl-x509/src/tests/truncated.pem
@@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIID4DCCAcigAwIBAgIUFAUJrTXu0baa6jT7KicZgjn9+IUwDQYJKoZIhvcNAQEL +BQAwga8xCzAJBgNVBAYTAkRFMRkwFwYDVQQIDBBGcmVpc3RhYXQgQmF5ZXJuMREw +DwYDVQQHDAhNdWVuY2hlbjEcMBoGA1UECgwTR29vZ2xlIEdlcm1hbnkgR21iSDET +MBEGA1UECwwKSVNFIENyeXB0bzEaMBgGA1UEAwwRQm9yaW5nU1NMIEF1dGhvcnMx +IzAhBgkqhkiG9w0BCQEWFGJvcmluZ3NzbEBnb29nbGUuY29tMCAXDTI2MDIwNzIz +MzkyOVoYDzQwMjYwMjIyMjMzOTI5WjAZMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNv +bTAqMAUGAytlcAMhAOSyR395yXH3KuV39zbbOu52+2jjBa31Ma6tUCEtTRNwo4GA +MH4wHwYDVR0jBBgwFoAU6htZHl3jMylBf9dYiGPUKigGeBAwCQYDVR0TBAIwADAL +BgNVHQ8EBAMCBPAwJAYDVR0RBB0wG4IOd3d3Lmdvb2dsZS5jb22CCWxvY2FsaG9z +dDAdBgNVHQ4EFgQUhSbdUAxkR51285halRrwQyV4mGgwDQYJKoZIhvcNAQELBQAD +ggIBAMvMmrGqyujWriCKe1UphUy5g/5UBW/igNqOWa+IR/u3/N19efheEnTBFwbL +maNykMmv4Oe8yXRhrrq4lKA7bnEFU5yJH1cZGFquQGCL1RiSctP4B5rLvFPSXg8b +S9ZLaq6qnx3Kc/Lmw0sbczXe7hvXbXM3m02rvsKAqLxa2sx0ELh98U1wfeLDhaj1 +Wd9sSN6casoLy4qhrGFBwMZvvxbsR9vNqgQ4eZuDbGmA8+LJ3ugr3fwa6YuYWaY4 +zbrDnj3vHr/yilqjBNqEfjbkvvNsYHwH1ife00/fKS9BNMQnHsZUO3OO8SjvhySZ +DrgcWXkLX+k9x25jLFvOxARqGUXABydH4msD0eXO5gT4LNfU206OSbCf+fPkPCnp +CxzA98efugpUYwmIszqEKJY2T2JIxUqOi3it4UTQRROy8La6XhNxz6mzddweZhvA +Nih2IUUk2APlsGpC48YHpY4CXo1rrwyHCCL84wG7KKSuIGr1wopTuJXQJuZIS0ct +5wkab/KzaF7XWgi+ncUtq5S/VZXbqag298BA0PiLLZ092WrV3tcNADWfF9rYydAf +Ajb+NetAF6pX5M8XOmHXfsAyRYbCocZoXNowrbTPVgYCUdPSbXOR1V/2aJqZLCs7 +ebRGIWAXAHnk8V0Eo/rs5XzJSLyHRaERW31EDpuuuUhpqXpI +-----END CERTIFICATE----- + +Ahoy! + +-----BEGIN CERTIFICATE----- +MIIGUTCCBDmgAwIBAgIUHlETnZyBkDglMnJR8zMbcNCbalwwDQYJKoZIhvcNAQEL \ No newline at end of file
diff --git a/rust/bssl-x509/src/verify.rs b/rust/bssl-x509/src/verify.rs new file mode 100644 index 0000000..41974d3 --- /dev/null +++ b/rust/bssl-x509/src/verify.rs
@@ -0,0 +1,225 @@ +// 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 process. +//! +//! To verify certificates, one may use [`X509Verifier`] which requires a fully constructed +//! [`X509Store`] certificate store, [`X509CertificateList`] a list of untrusted immediate +//! certificates and [`X509Certificate`] the final end-entity certificate. +//! +//! ```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 cert = X509Certificate::parse_one_from_pem(include_bytes!("tests/BoringSSLServerTest-RSA.crt")).unwrap(); +//! # let chain = X509CertificateList::new(); +//! # let mut store = X509StoreBuilder::new(); +//! # store.add_cert(ca).unwrap(); +//! # let store = store.build(); +//! let mut verifier = X509Verifier::new(&cert, &chain, &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 `X509StoreContext`. + 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_cert`] 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)); + } + } +}