use crate::bio::{MemBio, MemBioBuf};
use crate::error::ErrorStack;
use native_ossl_sys as sys;
use std::ffi::CStr;
use std::marker::PhantomData;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BrokenDownTime {
pub year: i32,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignatureInfo {
pub md_nid: i32,
pub pk_nid: i32,
pub security_bits: i32,
}
pub struct X509 {
ptr: *mut sys::X509,
}
unsafe impl Send for X509 {}
unsafe impl Sync for X509 {}
impl Clone for X509 {
fn clone(&self) -> Self {
unsafe { sys::X509_up_ref(self.ptr) };
X509 { ptr: self.ptr }
}
}
impl Drop for X509 {
fn drop(&mut self) {
unsafe { sys::X509_free(self.ptr) };
}
}
impl X509 {
pub(crate) unsafe fn from_ptr(ptr: *mut sys::X509) -> Self {
X509 { ptr }
}
pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
let bio = MemBioBuf::new(pem)?;
let ptr = unsafe {
sys::PEM_read_bio_X509(
bio.as_ptr(),
std::ptr::null_mut(),
None,
std::ptr::null_mut(),
)
};
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { Self::from_ptr(ptr) })
}
pub fn from_pem_in(_ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
Self::from_pem(pem)
}
pub fn new_in(ctx: &Arc<crate::lib_ctx::LibCtx>) -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::X509_new_ex(ctx.as_ptr(), std::ptr::null()) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { Self::from_ptr(ptr) })
}
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
let mut der_ptr = der.as_ptr();
let len = i64::try_from(der.len()).map_err(|_| ErrorStack::drain())?;
let ptr =
unsafe { sys::d2i_X509(std::ptr::null_mut(), std::ptr::addr_of_mut!(der_ptr), len) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { Self::from_ptr(ptr) })
}
pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
let mut bio = MemBio::new()?;
crate::ossl_call!(sys::PEM_write_bio_X509(bio.as_ptr(), self.ptr))?;
Ok(bio.into_vec())
}
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
let len = unsafe { sys::i2d_X509(self.ptr, std::ptr::null_mut()) };
if len < 0 {
return Err(ErrorStack::drain());
}
let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
let mut out_ptr = buf.as_mut_ptr();
let written = unsafe { sys::i2d_X509(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
if written < 0 {
return Err(ErrorStack::drain());
}
buf.truncate(usize::try_from(written).unwrap_or(0));
Ok(buf)
}
#[must_use]
pub fn subject_name(&self) -> X509Name<'_> {
let ptr = unsafe { sys::X509_get_subject_name(self.ptr) }.cast();
X509Name {
ptr,
_owner: PhantomData,
}
}
#[must_use]
pub fn issuer_name(&self) -> X509Name<'_> {
let ptr = unsafe { sys::X509_get_issuer_name(self.ptr) }.cast();
X509Name {
ptr,
_owner: PhantomData,
}
}
#[must_use]
pub fn serial_number(&self) -> Option<i64> {
let ai = unsafe { sys::X509_get0_serialNumber(self.ptr) };
if ai.is_null() {
return None;
}
let mut n: i64 = 0;
let rc = unsafe { sys::ASN1_INTEGER_get_int64(std::ptr::addr_of_mut!(n), ai) };
if rc == 1 {
Some(n)
} else {
None
}
}
#[must_use]
pub fn serial_number_bytes(&self) -> Option<Vec<u8>> {
let ai = unsafe { sys::X509_get_serialNumber(self.ptr) };
if ai.is_null() {
return None;
}
Some(unsafe { asn1_string_data(ai.cast()) }.to_vec())
}
#[must_use]
pub fn not_before_str(&self) -> Option<String> {
let t = unsafe { sys::X509_get0_notBefore(self.ptr) };
asn1_time_to_str(t)
}
#[must_use]
pub fn not_after_str(&self) -> Option<String> {
let t = unsafe { sys::X509_get0_notAfter(self.ptr) };
asn1_time_to_str(t)
}
#[must_use]
pub fn not_before_tm(&self) -> Option<BrokenDownTime> {
let t = unsafe { sys::X509_get0_notBefore(self.ptr) };
asn1_time_to_broken_down(t)
}
#[must_use]
pub fn not_after_tm(&self) -> Option<BrokenDownTime> {
let t = unsafe { sys::X509_get0_notAfter(self.ptr) };
asn1_time_to_broken_down(t)
}
#[must_use]
pub fn is_valid_now(&self) -> bool {
let nb = unsafe { sys::X509_get0_notBefore(self.ptr) };
let na = unsafe { sys::X509_get0_notAfter(self.ptr) };
unsafe {
sys::X509_cmp_time(nb, std::ptr::null_mut()) <= 0
&& sys::X509_cmp_time(na, std::ptr::null_mut()) >= 0
}
}
pub fn public_key(&self) -> Result<crate::pkey::Pkey<crate::pkey::Public>, ErrorStack> {
let ptr = unsafe { sys::X509_get_pubkey(self.ptr) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { crate::pkey::Pkey::from_ptr(ptr) })
}
#[must_use]
pub fn public_key_is_a(&self, alg: &CStr) -> bool {
let pkey = unsafe { sys::X509_get0_pubkey(self.ptr) };
if pkey.is_null() {
return false;
}
unsafe { sys::EVP_PKEY_is_a(pkey, alg.as_ptr()) == 1 }
}
#[must_use]
pub fn public_key_bits(&self) -> Option<u32> {
let pkey = unsafe { sys::X509_get0_pubkey(self.ptr) };
if pkey.is_null() {
return None;
}
u32::try_from(unsafe { sys::EVP_PKEY_get_bits(pkey) }).ok()
}
pub fn signature_info(&self) -> Result<SignatureInfo, ErrorStack> {
let mut md_nid: std::ffi::c_int = 0;
let mut pk_nid: std::ffi::c_int = 0;
let mut security_bits: std::ffi::c_int = 0;
let rc = unsafe {
sys::X509_get_signature_info(
self.ptr,
&raw mut md_nid,
&raw mut pk_nid,
&raw mut security_bits,
std::ptr::null_mut(),
)
};
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(SignatureInfo {
md_nid,
pk_nid,
security_bits,
})
}
pub fn verify(&self, key: &crate::pkey::Pkey<crate::pkey::Public>) -> Result<bool, ErrorStack> {
match unsafe { sys::X509_verify(self.ptr, key.as_ptr()) } {
1 => Ok(true),
0 => Ok(false),
_ => Err(ErrorStack::drain()),
}
}
#[must_use]
pub fn is_self_signed(&self) -> bool {
unsafe { sys::X509_self_signed(self.ptr, 0) == 1 }
}
#[must_use]
pub fn extension_count(&self) -> usize {
let n = unsafe { sys::X509_get_ext_count(self.ptr) };
usize::try_from(n).unwrap_or(0)
}
#[must_use]
pub fn extension(&self, idx: usize) -> Option<X509Extension<'_>> {
let idx_i32 = i32::try_from(idx).ok()?;
let ptr = unsafe { sys::X509_get_ext(self.ptr, idx_i32) }.cast::<sys::X509_EXTENSION>();
if ptr.is_null() {
None
} else {
Some(X509Extension {
ptr,
_owner: PhantomData,
})
}
}
#[must_use]
pub fn extension_by_nid(&self, nid: i32) -> Option<X509Extension<'_>> {
let idx = unsafe { sys::X509_get_ext_by_NID(self.ptr, nid, -1) };
if idx < 0 {
return None;
}
let ptr = unsafe { sys::X509_get_ext(self.ptr, idx) }.cast::<sys::X509_EXTENSION>();
if ptr.is_null() {
None
} else {
Some(X509Extension {
ptr,
_owner: PhantomData,
})
}
}
#[must_use]
pub fn extension_der(&self, nid: i32) -> Option<&[u8]> {
let idx = unsafe { sys::X509_get_ext_by_NID(self.ptr, nid, -1) };
if idx < 0 {
return None;
}
let ext = unsafe { sys::X509_get_ext(self.ptr, idx) };
if ext.is_null() {
return None;
}
let data = unsafe { sys::X509_EXTENSION_get_data(ext) };
if data.is_null() {
return Some(&[]);
}
Some(unsafe { asn1_string_data(data.cast()) })
}
#[must_use]
#[allow(dead_code)] pub(crate) fn as_ptr(&self) -> *mut sys::X509 {
self.ptr
}
}
pub struct X509Name<'cert> {
ptr: *mut sys::X509_NAME,
_owner: PhantomData<&'cert X509>,
}
impl X509Name<'_> {
#[must_use]
pub fn entry_count(&self) -> usize {
usize::try_from(unsafe { sys::X509_NAME_entry_count(self.ptr) }).unwrap_or(0)
}
#[must_use]
pub fn entry(&self, idx: usize) -> Option<X509NameEntry<'_>> {
let idx_i32 = i32::try_from(idx).ok()?;
let ptr =
unsafe { sys::X509_NAME_get_entry(self.ptr, idx_i32) }.cast::<sys::X509_NAME_ENTRY>();
if ptr.is_null() {
None
} else {
Some(X509NameEntry {
ptr,
_owner: PhantomData,
})
}
}
#[must_use]
pub fn to_oneline(&self) -> Option<String> {
let ptr = unsafe { sys::X509_NAME_oneline(self.ptr, std::ptr::null_mut(), 0) };
if ptr.is_null() {
return None;
}
let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
unsafe { sys::CRYPTO_free(ptr.cast(), c"x509.rs".as_ptr(), 0) };
Some(s)
}
#[must_use]
pub fn to_string(&self) -> Option<String> {
let mut bio = MemBio::new().ok()?;
let n = unsafe { sys::X509_NAME_print_ex(bio.as_ptr(), self.ptr, 0, 0) };
if n < 0 {
return None;
}
String::from_utf8(bio.into_vec()).ok()
}
}
pub struct X509NameEntry<'name> {
ptr: *mut sys::X509_NAME_ENTRY,
_owner: PhantomData<&'name ()>,
}
impl X509NameEntry<'_> {
#[must_use]
pub fn nid(&self) -> i32 {
let obj = unsafe { sys::X509_NAME_ENTRY_get_object(self.ptr) };
unsafe { sys::OBJ_obj2nid(obj) }
}
#[must_use]
pub fn data(&self) -> &[u8] {
let asn1 = unsafe { sys::X509_NAME_ENTRY_get_data(self.ptr) };
if asn1.is_null() {
return &[];
}
unsafe { asn1_string_data(asn1) }
}
}
pub struct X509Extension<'cert> {
ptr: *mut sys::X509_EXTENSION,
_owner: PhantomData<&'cert X509>,
}
impl X509Extension<'_> {
#[must_use]
pub fn nid(&self) -> i32 {
let obj = unsafe { sys::X509_EXTENSION_get_object(self.ptr) };
unsafe { sys::OBJ_obj2nid(obj) }
}
#[must_use]
pub fn is_critical(&self) -> bool {
unsafe { sys::X509_EXTENSION_get_critical(self.ptr) == 1 }
}
#[must_use]
pub fn data(&self) -> &[u8] {
let asn1 = unsafe { sys::X509_EXTENSION_get_data(self.ptr) };
if asn1.is_null() {
return &[];
}
unsafe { asn1_string_data(asn1.cast()) }
}
}
pub struct X509NameOwned {
ptr: *mut sys::X509_NAME,
}
impl X509NameOwned {
pub fn new() -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::X509_NAME_new() };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(X509NameOwned { ptr })
}
pub fn add_entry_by_txt(&mut self, field: &CStr, value: &[u8]) -> Result<(), ErrorStack> {
let len = i32::try_from(value.len()).map_err(|_| ErrorStack::drain())?;
let rc = unsafe {
sys::X509_NAME_add_entry_by_txt(
self.ptr,
field.as_ptr(),
0x1000, value.as_ptr(),
len,
-1, 0,
)
};
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(())
}
}
impl Drop for X509NameOwned {
fn drop(&mut self) {
unsafe { sys::X509_NAME_free(self.ptr) };
}
}
pub struct X509Builder {
ptr: *mut sys::X509,
}
impl X509Builder {
pub fn new() -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::X509_new() };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(X509Builder { ptr })
}
pub fn set_version(self, version: i64) -> Result<Self, ErrorStack> {
crate::ossl_call!(sys::X509_set_version(self.ptr, version))?;
Ok(self)
}
pub fn set_serial_number(self, n: i64) -> Result<Self, ErrorStack> {
let ai = unsafe { sys::ASN1_INTEGER_new() };
if ai.is_null() {
return Err(ErrorStack::drain());
}
crate::ossl_call!(sys::ASN1_INTEGER_set_int64(ai, n)).map_err(|e| {
unsafe { sys::ASN1_INTEGER_free(ai) };
e
})?;
let rc = unsafe { sys::X509_set_serialNumber(self.ptr, ai) };
unsafe { sys::ASN1_INTEGER_free(ai) };
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(self)
}
pub fn set_not_before_offset(self, offset_secs: i64) -> Result<Self, ErrorStack> {
let t = unsafe { sys::X509_getm_notBefore(self.ptr) };
if unsafe { sys::X509_gmtime_adj(t, offset_secs) }.is_null() {
return Err(ErrorStack::drain());
}
Ok(self)
}
pub fn set_not_after_offset(self, offset_secs: i64) -> Result<Self, ErrorStack> {
let t = unsafe { sys::X509_getm_notAfter(self.ptr) };
if unsafe { sys::X509_gmtime_adj(t, offset_secs) }.is_null() {
return Err(ErrorStack::drain());
}
Ok(self)
}
pub fn set_subject_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack> {
crate::ossl_call!(sys::X509_set_subject_name(self.ptr, name.ptr))?;
Ok(self)
}
pub fn set_issuer_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack> {
crate::ossl_call!(sys::X509_set_issuer_name(self.ptr, name.ptr))?;
Ok(self)
}
pub fn set_public_key<T: crate::pkey::HasPublic>(
self,
key: &crate::pkey::Pkey<T>,
) -> Result<Self, ErrorStack> {
crate::ossl_call!(sys::X509_set_pubkey(self.ptr, key.as_ptr()))?;
Ok(self)
}
pub fn sign(
self,
key: &crate::pkey::Pkey<crate::pkey::Private>,
digest: Option<&crate::digest::DigestAlg>,
) -> Result<Self, ErrorStack> {
let md_ptr = digest.map_or(std::ptr::null(), crate::digest::DigestAlg::as_ptr);
let rc = unsafe { sys::X509_sign(self.ptr, key.as_ptr(), md_ptr) };
if rc <= 0 {
return Err(ErrorStack::drain());
}
Ok(self)
}
#[must_use]
pub fn build(self) -> X509 {
let ptr = self.ptr;
std::mem::forget(self);
X509 { ptr }
}
}
impl Drop for X509Builder {
fn drop(&mut self) {
unsafe { sys::X509_free(self.ptr) };
}
}
pub struct X509Store {
ptr: *mut sys::X509_STORE,
}
unsafe impl Send for X509Store {}
unsafe impl Sync for X509Store {}
impl Clone for X509Store {
fn clone(&self) -> Self {
unsafe { sys::X509_STORE_up_ref(self.ptr) };
X509Store { ptr: self.ptr }
}
}
impl Drop for X509Store {
fn drop(&mut self) {
unsafe { sys::X509_STORE_free(self.ptr) };
}
}
impl X509Store {
pub fn new() -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::X509_STORE_new() };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(X509Store { ptr })
}
pub fn add_cert(&mut self, cert: &X509) -> Result<(), ErrorStack> {
let rc = unsafe { sys::X509_STORE_add_cert(self.ptr, cert.ptr) };
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(())
}
pub fn add_crl(&mut self, crl: &X509Crl) -> Result<(), ErrorStack> {
let rc = unsafe { sys::X509_STORE_add_crl(self.ptr, crl.ptr) };
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(())
}
pub fn set_flags(&mut self, flags: u64) -> Result<(), ErrorStack> {
let rc = unsafe { sys::X509_STORE_set_flags(self.ptr, flags) };
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(())
}
#[must_use]
pub(crate) fn as_ptr(&self) -> *mut sys::X509_STORE {
self.ptr
}
}
pub struct X509StoreCtx {
ptr: *mut sys::X509_STORE_CTX,
}
impl Drop for X509StoreCtx {
fn drop(&mut self) {
unsafe { sys::X509_STORE_CTX_free(self.ptr) };
}
}
unsafe impl Send for X509StoreCtx {}
impl X509StoreCtx {
pub fn new() -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::X509_STORE_CTX_new() };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(X509StoreCtx { ptr })
}
pub fn init(&mut self, store: &X509Store, cert: &X509) -> Result<(), ErrorStack> {
let rc = unsafe {
sys::X509_STORE_CTX_init(self.ptr, store.ptr, cert.ptr, std::ptr::null_mut())
};
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(())
}
pub fn verify(&mut self) -> Result<bool, ErrorStack> {
match unsafe { sys::X509_verify_cert(self.ptr) } {
1 => Ok(true),
0 => Ok(false),
_ => Err(ErrorStack::drain()),
}
}
#[must_use]
pub fn error(&self) -> i32 {
unsafe { sys::X509_STORE_CTX_get_error(self.ptr) }
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn chain(&self) -> Vec<X509> {
let stack = unsafe { sys::X509_STORE_CTX_get0_chain(self.ptr) };
if stack.is_null() {
return Vec::new();
}
let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
let n = usize::try_from(n).unwrap_or(0);
let mut out = Vec::with_capacity(n);
for i in 0..n {
let raw =
unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i as i32) };
if raw.is_null() {
continue;
}
let cert_ptr = raw.cast::<sys::X509>();
unsafe { sys::X509_up_ref(cert_ptr) };
out.push(X509 { ptr: cert_ptr });
}
out
}
}
pub struct X509Crl {
ptr: *mut sys::X509_CRL,
}
unsafe impl Send for X509Crl {}
unsafe impl Sync for X509Crl {}
impl Clone for X509Crl {
fn clone(&self) -> Self {
unsafe { sys::X509_CRL_up_ref(self.ptr) };
X509Crl { ptr: self.ptr }
}
}
impl Drop for X509Crl {
fn drop(&mut self) {
unsafe { sys::X509_CRL_free(self.ptr) };
}
}
impl X509Crl {
pub(crate) unsafe fn from_ptr(ptr: *mut sys::X509_CRL) -> Self {
X509Crl { ptr }
}
pub fn new() -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::X509_CRL_new() };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { Self::from_ptr(ptr) })
}
pub fn new_in(ctx: &std::sync::Arc<crate::lib_ctx::LibCtx>) -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::X509_CRL_new_ex(ctx.as_ptr(), std::ptr::null()) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { Self::from_ptr(ptr) })
}
pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
let bio = MemBioBuf::new(pem)?;
let ptr = unsafe {
sys::PEM_read_bio_X509_CRL(
bio.as_ptr(),
std::ptr::null_mut(),
None,
std::ptr::null_mut(),
)
};
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { Self::from_ptr(ptr) })
}
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
let bio = MemBioBuf::new(der)?;
let ptr = unsafe { sys::d2i_X509_CRL_bio(bio.as_ptr(), std::ptr::null_mut()) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(unsafe { Self::from_ptr(ptr) })
}
pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
let mut bio = MemBio::new()?;
let rc = unsafe { sys::PEM_write_bio_X509_CRL(bio.as_ptr(), self.ptr) };
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(bio.into_vec())
}
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
let mut bio = MemBio::new()?;
let rc = unsafe { sys::i2d_X509_CRL_bio(bio.as_ptr(), self.ptr) };
if rc != 1 {
return Err(ErrorStack::drain());
}
Ok(bio.into_vec())
}
#[must_use]
pub fn issuer_name(&self) -> X509Name<'_> {
let ptr = unsafe { sys::X509_CRL_get_issuer(self.ptr) };
X509Name {
ptr: ptr.cast(),
_owner: PhantomData,
}
}
#[must_use]
pub fn last_update_str(&self) -> Option<String> {
let t = unsafe { sys::X509_CRL_get0_lastUpdate(self.ptr) };
asn1_time_to_str(t)
}
#[must_use]
pub fn next_update_str(&self) -> Option<String> {
let t = unsafe { sys::X509_CRL_get0_nextUpdate(self.ptr) };
asn1_time_to_str(t)
}
pub fn verify(&self, key: &crate::pkey::Pkey<crate::pkey::Public>) -> Result<bool, ErrorStack> {
match unsafe { sys::X509_CRL_verify(self.ptr, key.as_ptr()) } {
1 => Ok(true),
0 => Ok(false),
_ => Err(ErrorStack::drain()),
}
}
#[must_use]
#[allow(dead_code)]
pub(crate) fn as_ptr(&self) -> *mut sys::X509_CRL {
self.ptr
}
}
#[must_use]
pub fn nid_from_short_name(sn: &CStr) -> Option<i32> {
let nid = unsafe { sys::OBJ_sn2nid(sn.as_ptr()) };
if nid == 0 {
None
} else {
Some(nid)
}
}
#[must_use]
pub fn nid_from_text(s: &CStr) -> Option<i32> {
let nid = unsafe { sys::OBJ_txt2nid(s.as_ptr()) };
if nid == 0 {
None
} else {
Some(nid)
}
}
#[must_use]
pub fn nid_to_short_name(nid: i32) -> Option<&'static CStr> {
let ptr = unsafe { sys::OBJ_nid2sn(nid) };
if ptr.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(ptr) })
}
#[must_use]
pub fn nid_to_long_name(nid: i32) -> Option<&'static CStr> {
let ptr = unsafe { sys::OBJ_nid2ln(nid) };
if ptr.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(ptr) })
}
fn asn1_time_to_broken_down(t: *const sys::ASN1_TIME) -> Option<BrokenDownTime> {
if t.is_null() {
return None;
}
let mut tm = unsafe { std::mem::zeroed::<sys::tm>() };
let rc = unsafe { sys::ASN1_TIME_to_tm(t, &raw mut tm) };
if rc != 1 {
return None;
}
Some(BrokenDownTime {
year: tm.tm_year + 1900,
month: u8::try_from(tm.tm_mon + 1).unwrap_or(0),
day: u8::try_from(tm.tm_mday).unwrap_or(0),
hour: u8::try_from(tm.tm_hour).unwrap_or(0),
minute: u8::try_from(tm.tm_min).unwrap_or(0),
second: u8::try_from(tm.tm_sec).unwrap_or(0),
})
}
fn asn1_time_to_str(t: *const sys::ASN1_TIME) -> Option<String> {
if t.is_null() {
return None;
}
let mut bio = MemBio::new().ok()?;
let rc = unsafe { sys::ASN1_TIME_print(bio.as_ptr(), t) };
if rc != 1 {
return None;
}
String::from_utf8(bio.into_vec()).ok()
}
unsafe fn asn1_string_data<'a>(asn1: *const sys::ASN1_STRING) -> &'a [u8] {
let len = usize::try_from(sys::ASN1_STRING_length(asn1)).unwrap_or(0);
let ptr = sys::ASN1_STRING_get0_data(asn1);
if ptr.is_null() || len == 0 {
return &[];
}
std::slice::from_raw_parts(ptr, len)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pkey::{KeygenCtx, Pkey, Private, Public};
fn make_self_signed() -> (X509, Pkey<Private>, Pkey<Public>) {
let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
let priv_key = kgen.generate().unwrap();
let pub_key = Pkey::<Public>::from(priv_key.clone());
let mut name = X509NameOwned::new().unwrap();
name.add_entry_by_txt(c"CN", b"Test Cert").unwrap();
name.add_entry_by_txt(c"O", b"Example Org").unwrap();
let cert = X509Builder::new()
.unwrap()
.set_version(2)
.unwrap()
.set_serial_number(1)
.unwrap()
.set_not_before_offset(0)
.unwrap()
.set_not_after_offset(365 * 86400)
.unwrap()
.set_subject_name(&name)
.unwrap()
.set_issuer_name(&name)
.unwrap()
.set_public_key(&pub_key)
.unwrap()
.sign(&priv_key, None)
.unwrap()
.build();
(cert, priv_key, pub_key)
}
#[test]
fn build_and_verify_self_signed() {
let (cert, _, pub_key) = make_self_signed();
assert!(cert.verify(&pub_key).unwrap());
assert!(cert.is_self_signed());
}
#[test]
fn pem_round_trip() {
let (cert, _, _) = make_self_signed();
let pem = cert.to_pem().unwrap();
assert!(pem.starts_with(b"-----BEGIN CERTIFICATE-----"));
let cert2 = X509::from_pem(&pem).unwrap();
assert_eq!(cert.to_der().unwrap(), cert2.to_der().unwrap());
}
#[test]
fn der_round_trip() {
let (cert, _, _) = make_self_signed();
let der = cert.to_der().unwrap();
assert!(!der.is_empty());
let cert2 = X509::from_der(&der).unwrap();
assert_eq!(cert2.to_der().unwrap(), der);
}
#[test]
fn subject_name_entries() {
let (cert, _, _) = make_self_signed();
let name = cert.subject_name();
assert_eq!(name.entry_count(), 2);
let e0 = name.entry(0).unwrap();
assert_eq!(e0.nid(), 13); assert!(!e0.data().is_empty());
let s = name.to_string().unwrap();
assert!(s.contains("Test Cert") || s.contains("CN=Test Cert"));
}
#[test]
fn serial_number() {
let (cert, _, _) = make_self_signed();
assert_eq!(cert.serial_number(), Some(1));
}
#[test]
fn validity_strings_present() {
let (cert, _, _) = make_self_signed();
let nb = cert.not_before_str().unwrap();
let na = cert.not_after_str().unwrap();
assert!(nb.contains("GMT"), "not_before_str = {nb:?}");
assert!(na.contains("GMT"), "not_after_str = {na:?}");
}
#[test]
fn is_valid_now() {
let (cert, _, _) = make_self_signed();
assert!(cert.is_valid_now());
}
#[test]
fn public_key_extraction() {
let (cert, _, pub_key) = make_self_signed();
let extracted = cert.public_key().unwrap();
assert!(extracted.is_a(c"ED25519"));
assert_eq!(pub_key.bits(), extracted.bits());
}
#[test]
fn clone_cert() {
let (cert, _, pub_key) = make_self_signed();
let cert2 = cert.clone();
assert_eq!(cert.to_der().unwrap(), cert2.to_der().unwrap());
assert!(cert2.verify(&pub_key).unwrap());
}
#[test]
fn verify_fails_with_wrong_key() {
let (cert, _, _) = make_self_signed();
let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
let other_priv = kgen.generate().unwrap();
let other_pub = Pkey::<Public>::from(other_priv);
assert!(!cert.verify(&other_pub).unwrap());
}
#[test]
fn x509_store_add_cert_and_verify() {
let (cert, _, _) = make_self_signed();
let mut store = X509Store::new().unwrap();
store.add_cert(&cert).unwrap();
let mut ctx = X509StoreCtx::new().unwrap();
ctx.init(&store, &cert).unwrap();
assert!(ctx.verify().unwrap());
}
#[test]
fn x509_store_verify_untrusted_fails() {
let (cert, _, _) = make_self_signed();
let store = X509Store::new().unwrap();
let mut ctx = X509StoreCtx::new().unwrap();
ctx.init(&store, &cert).unwrap();
assert!(!ctx.verify().unwrap());
assert_ne!(ctx.error(), 0);
}
#[test]
fn x509_store_ctx_chain_populated_after_verify() {
let (cert, _, _) = make_self_signed();
let mut store = X509Store::new().unwrap();
store.add_cert(&cert).unwrap();
let mut ctx = X509StoreCtx::new().unwrap();
ctx.init(&store, &cert).unwrap();
assert!(ctx.verify().unwrap());
let chain = ctx.chain();
assert!(
!chain.is_empty(),
"verified chain should contain at least the leaf"
);
}
#[test]
fn x509crl_new_roundtrip() {
let crl = X509Crl::new().expect("X509_CRL_new should succeed");
let _result = crl.to_der();
drop(crl);
}
#[test]
fn x509crl_new_in_roundtrip() {
use std::sync::Arc;
let ctx = Arc::new(crate::lib_ctx::LibCtx::new().expect("LibCtx::new should succeed"));
let crl = X509Crl::new_in(&ctx).expect("X509_CRL_new_ex should succeed");
let _result = crl.to_der();
drop(crl);
}
const TEST_CRL_PEM: &[u8] = b"\
-----BEGIN X509 CRL-----\n\
MIIBVjBAMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBlJTQSBDQRcNMjYwNDE1\n\
MTUwNDEzWhcNMjYwNTE1MTUwNDEzWjANBgkqhkiG9w0BAQsFAAOCAQEAi209u0hh\n\
Vz42YaqLplQwBoYCjtjETenl4xXRNcFOYU6Y+FmR66XNGkl9HbPClrz3hRMnbBYr\n\
OQJfWQOKS9lS0zpEI4qtlH/H1JBNGwiY32HMqf5HULn0w0ARvmoXR4NzsCecK22G\n\
gN61k5FCCpPY8HztsuoHMHAQ65W1WfBiTWu8ZH0nCCU0CA4MSaPZUiNt8/mJZzTG\n\
UwTGcZ/hcHQMpocBX40nE7ta5opcIpjG+q2uiCWhXwoqmYsLvdJ+Obw20bLirMHt\n\
UsmESTw5G+vcRCudoiSw89Z/jzsYq8yuFhRzF9kA/RtqCoQ+ylQSSH5hxzW2+bPd\n\
QPHivSGDiUhH6Q==\n\
-----END X509 CRL-----\n";
#[test]
fn crl_pem_round_trip() {
let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
let issuer = crl.issuer_name();
assert!(issuer.entry_count() > 0);
assert!(crl.last_update_str().is_some());
assert!(crl.next_update_str().is_some());
let pem = crl.to_pem().unwrap();
assert!(pem.starts_with(b"-----BEGIN X509 CRL-----"));
}
#[test]
fn crl_der_round_trip() {
let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
let der = crl.to_der().unwrap();
assert!(!der.is_empty());
let crl2 = X509Crl::from_der(&der).unwrap();
assert_eq!(crl2.to_der().unwrap(), der);
}
#[test]
fn crl_clone() {
let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
let crl2 = crl.clone();
assert_eq!(crl.to_der().unwrap(), crl2.to_der().unwrap());
}
#[test]
fn x509_nid_from_short_name_known() {
let nid = nid_from_short_name(c"SHA256");
assert!(nid.is_some(), "SHA256 should be a known short name");
assert_eq!(nid_from_short_name(c"CN"), Some(13));
}
#[test]
fn x509_nid_from_short_name_unknown() {
assert_eq!(nid_from_short_name(c"not-a-real-algorithm-xyz"), None);
}
#[test]
fn x509_nid_from_text_dotted_oid() {
assert_eq!(nid_from_text(c"2.5.4.3"), Some(13));
}
#[test]
fn x509_nid_from_text_short_name() {
let via_sn = nid_from_short_name(c"SHA256");
let via_txt = nid_from_text(c"SHA256");
assert_eq!(
via_sn, via_txt,
"short-name lookup must agree between OBJ_sn2nid and OBJ_txt2nid"
);
}
#[test]
fn x509_nid_from_text_unknown() {
assert_eq!(nid_from_text(c"9.9.9.9.9.9.9.9"), None);
}
#[test]
fn x509_serial_number_bytes_small_serial() {
let (cert, _, _) = make_self_signed();
let bytes = cert.serial_number_bytes().unwrap();
assert!(!bytes.is_empty());
assert_eq!(*bytes.last().unwrap(), 1u8);
}
#[test]
fn x509_serial_number_bytes_consistent_with_serial_number() {
let (cert, _, _) = make_self_signed();
let bytes = cert.serial_number_bytes().unwrap();
let n = cert.serial_number().unwrap();
let be = n.to_be_bytes();
let start = be.iter().position(|&b| b != 0).unwrap_or(7);
assert_eq!(bytes, &be[start..]);
}
#[test]
fn x509_not_before_tm_is_some() {
let (cert, _, _) = make_self_signed();
let tm = cert.not_before_tm().expect("notBefore must be parseable");
assert!(tm.year >= 2026, "year must be 2026 or later");
assert!((1..=12).contains(&tm.month));
assert!((1..=31).contains(&tm.day));
}
#[test]
fn x509_not_after_tm_one_year_after_not_before() {
let (cert, _, _) = make_self_signed();
let nb = cert.not_before_tm().unwrap();
let na = cert.not_after_tm().unwrap();
let year_diff = na.year - nb.year;
assert!(year_diff == 0 || year_diff == 1, "year diff must be 0 or 1");
}
#[test]
fn x509_not_before_tm_consistent_with_not_before_str() {
let (cert, _, _) = make_self_signed();
assert!(cert.not_before_tm().is_some());
assert!(cert.not_before_str().is_some());
}
#[test]
fn x509_public_key_is_a_ed25519() {
let (cert, _, _) = make_self_signed();
assert!(cert.public_key_is_a(c"ED25519"));
assert!(!cert.public_key_is_a(c"RSA"));
}
#[test]
fn x509_public_key_bits_ed25519() {
let (cert, _, _) = make_self_signed();
let bits = cert.public_key_bits().unwrap();
assert!(bits > 0, "Ed25519 key must report non-zero bit size");
}
#[test]
fn x509_public_key_bits_agrees_with_public_key_method() {
let (cert, _, _) = make_self_signed();
let owned_bits = cert.public_key().unwrap().bits();
let borrow_bits = cert.public_key_bits().unwrap();
assert_eq!(owned_bits, borrow_bits);
}
#[test]
fn nid_to_short_name_known_nid() {
let sn = nid_to_short_name(13).expect("NID 13 must be known");
assert_eq!(sn.to_bytes(), b"CN");
}
#[test]
fn nid_to_long_name_known_nid() {
let ln = nid_to_long_name(13).expect("NID 13 must be known");
assert_eq!(ln.to_bytes(), b"commonName");
}
#[test]
fn nid_to_short_name_unknown_nid() {
assert!(nid_to_short_name(i32::MAX).is_none());
}
#[test]
fn nid_to_long_name_unknown_nid() {
assert!(nid_to_long_name(i32::MAX).is_none());
}
#[test]
fn nid_to_short_name_sha256() {
let sn = nid_to_short_name(672).expect("NID 672 (SHA256) must be known");
assert_eq!(sn.to_bytes(), b"SHA256");
}
#[test]
fn x509_name_oneline_returns_string() {
let (cert, _, _) = make_self_signed();
let name = cert.subject_name();
let s = name
.to_oneline()
.expect("to_oneline must return Some for a non-empty name");
assert!(
s.contains("CN="),
"to_oneline output should contain CN=: {s:?}"
);
}
#[test]
fn x509_new_in_lib_ctx() {
use crate::lib_ctx::LibCtx;
let ctx = Arc::new(LibCtx::new().expect("LibCtx::new must succeed"));
let cert = X509::new_in(&ctx);
assert!(
cert.is_ok(),
"X509::new_in must succeed with a valid LibCtx"
);
}
#[test]
fn x509_extension_der_absent_nid_returns_none() {
let (cert, _, _) = make_self_signed();
let result = cert.extension_der(85);
assert!(
result.is_none(),
"extension_der must return None for absent extension"
);
}
#[test]
fn x509_extension_der_present_returns_some() {
let (cert, _, _) = make_self_signed();
let count = cert.extension_count();
if count > 0 {
let ext = cert
.extension(0)
.expect("extension(0) must be Some when count > 0");
let nid = ext.nid();
let der = cert
.extension_der(nid)
.expect("extension_der must return Some for a NID that exists in the cert");
assert_eq!(
der,
ext.data(),
"extension_der bytes must match X509Extension::data"
);
}
}
}
#[cfg(test)]
mod signature_info_tests {
use super::*;
use crate::pkey::{KeygenCtx, Pkey, Private, Public};
fn make_ed25519_cert() -> (X509, Pkey<Private>, Pkey<Public>) {
let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
let priv_key = kgen.generate().unwrap();
let pub_key = Pkey::<Public>::from(priv_key.clone());
let mut name = X509NameOwned::new().unwrap();
name.add_entry_by_txt(c"CN", b"Ed25519 Sig Info Test")
.unwrap();
let cert = X509Builder::new()
.unwrap()
.set_version(2)
.unwrap()
.set_serial_number(42)
.unwrap()
.set_not_before_offset(0)
.unwrap()
.set_not_after_offset(365 * 86400)
.unwrap()
.set_subject_name(&name)
.unwrap()
.set_issuer_name(&name)
.unwrap()
.set_public_key(&pub_key)
.unwrap()
.sign(&priv_key, None)
.unwrap()
.build();
(cert, priv_key, pub_key)
}
#[test]
fn x509_signature_info_ed25519() {
let (cert, _, _) = make_ed25519_cert();
let info = cert
.signature_info()
.expect("signature_info must succeed for a signed cert");
assert_eq!(
info.md_nid, 0,
"Ed25519 md_nid must be 0 (NID_undef); got {}",
info.md_nid
);
assert_ne!(info.pk_nid, 0, "Ed25519 pk_nid must not be NID_undef");
let sn = nid_to_short_name(info.pk_nid)
.expect("Ed25519 pk_nid must have a short name in OpenSSL's OBJ table");
assert_eq!(
sn.to_bytes(),
b"ED25519",
"pk_nid short name must be ED25519; got {sn:?}"
);
assert_eq!(
info.security_bits, 128,
"Ed25519 security bits must be 128; got {}",
info.security_bits
);
}
}