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