blob: 058392ea6f4f7555a29e7216cec673f664d98938 [file] [log] [blame]
// Copyright 2026 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! X.509 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();
//! assert_eq!(certs.len(), 6);
//!
//! let cert = X509Certificate::parse_one_from_pem(pem).unwrap();
//!
//! assert_eq!(cert.not_before().unwrap(), 1769078409);
//! assert_eq!(cert.not_after().unwrap(), 64884278409);
//! let serial = cert.serial_number();
//! assert_eq!(format!("{serial}"),
//! "457727178541466628947827494934005101068454548083");
//!
//! # use bssl_x509::certificates::GeneralName;
//! let names = cert.subject_alt_names().unwrap();
//! let sans: Vec<_> = names.iter().collect();
//! assert_eq!(sans, vec![GeneralName::Dns("www.google.com"),
//! GeneralName::Dns("localhost")]);
//!
//! let der: Vec<u8> = cert.to_der().unwrap();
//! ```
//!
//! # Inspecting 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::{
ffi::CStr,
fmt,
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_OCTET_STRING`.
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 two's-complement, big-endian representation of the serial number.
///
/// This is a big-endian integer. If the most-significant bit is set then
/// it is negative. Positive values that would otherwise have the
/// most-significant bit set are left padded with a zero byte to avoid this.
pub fn as_twos_complement_bytes(&self) -> &'a [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)
};
let Ok(len) = usize::try_from(len) else {
panic!("invalid ASN1_INTEGER")
};
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).expect("invalid ASN1_INTEGER")
}
}
}
impl<'a> SerialNumber<'a> {
/// Formats the serial number as a string using the given BoringSSL
/// conversion function (`BN_bn2dec` or `BN_bn2hex`).
fn fmt_with(
&self,
f: &mut fmt::Formatter<'_>,
conv: unsafe extern "C" fn(*const bssl_sys::BIGNUM) -> *mut core::ffi::c_char,
alt_prefix: &str,
) -> fmt::Result {
let bn = unsafe {
// Safety: self witnesses the validity of the ASN1_INTEGER handle.
bssl_sys::ASN1_INTEGER_to_BN(self.ptr(), null_mut())
};
if bn.is_null() {
return Err(fmt::Error);
}
struct BnGuard(*mut bssl_sys::BIGNUM);
impl Drop for BnGuard {
fn drop(&mut self) {
unsafe { bssl_sys::BN_free(self.0) };
}
}
let _bn_guard = BnGuard(bn);
let s = unsafe {
// Safety: `bn` is a valid BIGNUM.
conv(bn)
};
if s.is_null() {
return Err(fmt::Error);
}
struct StringGuard(*mut core::ffi::c_char);
impl Drop for StringGuard {
fn drop(&mut self) {
unsafe { bssl_sys::OPENSSL_free(self.0 as *mut _) };
}
}
let _s_guard = StringGuard(s);
let s_str = unsafe {
// Safety: `s` is a valid, NUL-terminated C string allocated by BoringSSL.
CStr::from_ptr(s).to_str()
}
.map_err(|_| fmt::Error)?;
let (is_nonnegative, abs_str) = if let Some(stripped) = s_str.strip_prefix('-') {
(false, stripped)
} else {
(true, s_str)
};
let prefix = if f.alternate() { alt_prefix } else { "" };
f.pad_integral(is_nonnegative, prefix, abs_str)
}
}
impl<'a> fmt::Display for SerialNumber<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_with(f, bssl_sys::BN_bn2dec, "")
}
}
impl<'a> fmt::LowerHex for SerialNumber<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_with(f, bssl_sys::BN_bn2hex, "0x")
}
}
/// An X.509 certificate
#[repr(transparent)]
pub struct X509Certificate(NonNull<bssl_sys::X509>);
// Safety: X509 objects are reference counted and thread-safe.
unsafe impl Send for X509Certificate {}
unsafe impl Sync for X509Certificate {}
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 discharges 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 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 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 serial_number(&self) -> 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 _).expect("non-null serial number");
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)
}
}