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;
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 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 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 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) })
}
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]
#[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_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 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
}
}
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"
);
}
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());
}
}