diff --git a/openssl-sys/src/handwritten/x509_vfy.rs b/openssl-sys/src/handwritten/x509_vfy.rs index 31928f8979..b2f14a70b2 100644 --- a/openssl-sys/src/handwritten/x509_vfy.rs +++ b/openssl-sys/src/handwritten/x509_vfy.rs @@ -44,6 +44,7 @@ extern "C" { pub fn X509_STORE_CTX_cleanup(ctx: *mut X509_STORE_CTX); pub fn X509_STORE_add_cert(store: *mut X509_STORE, x: *mut X509) -> c_int; + pub fn X509_STORE_add_crl(xs: *mut X509_STORE, x: *mut X509_CRL) -> c_int; pub fn X509_STORE_set_default_paths(store: *mut X509_STORE) -> c_int; pub fn X509_STORE_set_flags(store: *mut X509_STORE, flags: c_ulong) -> c_int; @@ -68,6 +69,12 @@ const_ptr_api! { pub fn X509_STORE_CTX_get_error(ctx: #[const_ptr_if(ossl300)] X509_STORE_CTX) -> c_int; pub fn X509_STORE_CTX_get_error_depth(ctx: #[const_ptr_if(ossl300)] X509_STORE_CTX) -> c_int; pub fn X509_STORE_CTX_get_current_cert(ctx: #[const_ptr_if(ossl300)] X509_STORE_CTX) -> *mut X509; + #[cfg(ossl110)] + pub fn X509_STORE_CTX_get1_crls( + ctx: #[const_ptr_if(ossl300)] X509_STORE_CTX, + nm: #[const_ptr_if(ossl300)] X509_NAME, + ) -> *mut stack_st_X509_CRL; + } } extern "C" { diff --git a/openssl/src/util.rs b/openssl/src/util.rs index c903a32092..54ad450aa6 100644 --- a/openssl/src/util.rs +++ b/openssl/src/util.rs @@ -76,6 +76,14 @@ pub trait ForeignTypeExt: ForeignType { } } } + +pub fn opt_to_ptr(o: Option<&T>) -> *mut T::CType { + match o { + None => std::ptr::null_mut(), + Some(p) => p.as_ptr(), + } +} + impl ForeignTypeExt for FT {} pub trait ForeignTypeRefExt: ForeignTypeRef { diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index c4e0c5b4e2..a51ec32cd4 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -36,7 +36,7 @@ use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public}; use crate::ssl::SslRef; use crate::stack::{Stack, StackRef, Stackable}; use crate::string::OpensslString; -use crate::util::{self, ForeignTypeExt, ForeignTypeRefExt}; +use crate::util::{self, opt_to_ptr, ForeignTypeExt, ForeignTypeRefExt}; use crate::{cvt, cvt_n, cvt_p, cvt_p_const}; use openssl_macros::corresponds; @@ -156,6 +156,65 @@ impl X509StoreContextRef { } } + /// Initializes this context with the given certificate, certificates chain and certificate + /// store. After initializing the context, the `with_context` closure is called with the prepared + /// context. As long as the closure is running, the context stays initialized and can be used + /// to e.g. verify a certificate. The context will be cleaned up, after the closure finished. + /// + /// * `trust` - The certificate store with the trusted certificates. + /// * `cert` - The certificate that should be verified. + /// * `cert_chain` - The certificates chain. + /// * `with_context` - The closure that is called with the initialized context. + /// + /// This corresponds to [`X509_STORE_CTX_init`] before calling `with_context` and to + /// [`X509_STORE_CTX_cleanup`] after calling `with_context`. + /// + /// [`X509_STORE_CTX_init`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_init.html + /// [`X509_STORE_CTX_cleanup`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_cleanup.html + /// similar to [`X509StoreContextRef::init()`] but with optional cert and cert_chain. + /// Useful to call [`X509StoreContextRef::crls()`] on it. + pub fn init_opt( + &mut self, + trust: &store::X509StoreRef, + cert: Option<&X509Ref>, + cert_chain: Option<&StackRef>, + with_context: F, + ) -> Result + where + F: FnOnce(&mut X509StoreContextRef) -> std::result::Result, + { + struct Cleanup<'a>(&'a mut X509StoreContextRef); + + impl<'a> Drop for Cleanup<'a> { + fn drop(&mut self) { + unsafe { + ffi::X509_STORE_CTX_cleanup(self.0.as_ptr()); + } + } + } + + unsafe { + cvt(ffi::X509_STORE_CTX_init( + self.as_ptr(), + trust.as_ptr(), + opt_to_ptr(cert), + opt_to_ptr(cert_chain), + ))?; + } + let cleanup = Cleanup(self); + with_context(cleanup.0) + } + + /// Get all Certificate Revocation Lists with the subject currently stored + #[cfg(ossl110)] + #[corresponds(X509_STORE_CTX_get1_crls)] + pub fn crls(&mut self, subj: &X509NameRef) -> std::result::Result, ErrorStack> { + unsafe { + let crls = cvt_p(ffi::X509_STORE_CTX_get1_crls(self.as_ptr(), subj.as_ptr()))?; + Ok(Stack::from_ptr(crls)) + } + } + /// Verifies the stored certificate. /// /// Returns `true` if verification succeeds. The `error` method will return the specific @@ -1645,8 +1704,7 @@ impl X509RevokedRef { pub fn revocation_date(&self) -> &Asn1TimeRef { unsafe { let r = X509_REVOKED_get0_revocationDate(self.as_ptr() as *const _); - assert!(!r.is_null()); - Asn1TimeRef::from_ptr(r as *mut _) + Asn1TimeRef::from_const_ptr_opt(r).expect("revocation date must not be null") } } @@ -1655,8 +1713,7 @@ impl X509RevokedRef { pub fn serial_number(&self) -> &Asn1IntegerRef { unsafe { let r = X509_REVOKED_get0_serialNumber(self.as_ptr() as *const _); - assert!(!r.is_null()); - Asn1IntegerRef::from_ptr(r as *mut _) + Asn1IntegerRef::from_const_ptr_opt(r).expect("serial number must not be null") } } @@ -1736,6 +1793,10 @@ foreign_type_and_impl_send_sync! { pub struct X509CrlRef; } +impl Stackable for X509Crl { + type StackType = ffi::stack_st_X509_CRL; +} + /// The status of a certificate in a revoction list /// /// Corresponds to the return value from the [`X509_CRL_get0_by_*`] methods. @@ -1764,14 +1825,14 @@ impl<'a> CrlStatus<'a> { ) -> CrlStatus<'a> { match status { 0 => CrlStatus::NotRevoked, - 1 => { - assert!(!revoked_entry.is_null()); - CrlStatus::Revoked(X509RevokedRef::from_ptr(revoked_entry)) - } - 2 => { - assert!(!revoked_entry.is_null()); - CrlStatus::RemoveFromCrl(X509RevokedRef::from_ptr(revoked_entry)) - } + 1 => CrlStatus::Revoked( + X509RevokedRef::from_const_ptr_opt(revoked_entry) + .expect("revoked entry must no be null"), + ), + 2 => CrlStatus::RemoveFromCrl( + X509RevokedRef::from_const_ptr_opt(revoked_entry) + .expect("revoked entry must no be null"), + ), _ => unreachable!( "{}", "X509_CRL_get0_by_{{serial,cert}} should only return 0, 1, or 2." @@ -1798,6 +1859,39 @@ impl X509Crl { X509Crl, ffi::d2i_X509_CRL } + + #[corresponds(PEM_read_bio_X509_CRL)] + pub fn stack_from_pem(pem: &[u8]) -> Result, ErrorStack> { + unsafe { + ffi::init(); + let bio = MemBioSlice::new(pem)?; + + let mut crls = vec![]; + loop { + let r = ffi::PEM_read_bio_X509_CRL( + bio.as_ptr(), + ptr::null_mut(), + None, + ptr::null_mut(), + ); + if r.is_null() { + let err = ffi::ERR_peek_last_error(); + if ffi::ERR_GET_LIB(err) as X509LenTy == ffi::ERR_LIB_PEM + && ffi::ERR_GET_REASON(err) == ffi::PEM_R_NO_START_LINE + { + ffi::ERR_clear_error(); + break; + } + + return Err(ErrorStack::get()); + } else { + crls.push(X509Crl(r)); + } + } + + Ok(crls) + } + } } impl X509CrlRef { @@ -1834,8 +1928,7 @@ impl X509CrlRef { pub fn last_update(&self) -> &Asn1TimeRef { unsafe { let date = X509_CRL_get0_lastUpdate(self.as_ptr()); - assert!(!date.is_null()); - Asn1TimeRef::from_ptr(date as *mut _) + Asn1TimeRef::from_const_ptr_opt(date).expect("last update must not be null") } } @@ -1877,8 +1970,7 @@ impl X509CrlRef { pub fn issuer_name(&self) -> &X509NameRef { unsafe { let name = X509_CRL_get_issuer(self.as_ptr()); - assert!(!name.is_null()); - X509NameRef::from_ptr(name) + X509NameRef::from_const_ptr_opt(name).expect("issuer name must not be null") } } @@ -1927,6 +2019,40 @@ impl X509CrlRef { } } +impl Ord for X509CrlRef { + fn cmp(&self, other: &Self) -> cmp::Ordering { + // X509_cmp returns a number <0 for less than, 0 for equal and >0 for greater than. + // It can't fail if both pointers are valid, which we know is true. + let cmp = unsafe { ffi::X509_CRL_cmp(self.as_ptr(), other.as_ptr()) }; + cmp.cmp(&0) + } +} + +impl PartialOrd for X509CrlRef { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialOrd for X509CrlRef { + fn partial_cmp(&self, other: &X509Crl) -> Option { + >::partial_cmp(self, other) + } +} + +impl PartialEq for X509CrlRef { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == cmp::Ordering::Equal + } +} + +impl PartialEq for X509CrlRef { + fn eq(&self, other: &X509Crl) -> bool { + >::eq(self, other) + } +} +impl Eq for X509CrlRef {} + /// The result of peer certificate verification. #[derive(Copy, Clone, PartialEq, Eq)] pub struct X509VerifyResult(c_int); diff --git a/openssl/src/x509/store.rs b/openssl/src/x509/store.rs index ad62ac725d..3fa0e6f16f 100644 --- a/openssl/src/x509/store.rs +++ b/openssl/src/x509/store.rs @@ -62,6 +62,8 @@ use std::ffi::CString; #[cfg(not(any(boringssl, awslc)))] use std::path::Path; +use super::X509CrlRef; + foreign_type_and_impl_send_sync! { type CType = ffi::X509_STORE; fn drop = ffi::X509_STORE_free; @@ -141,6 +143,12 @@ impl X509StoreBuilderRef { pub fn set_param(&mut self, param: &X509VerifyParamRef) -> Result<(), ErrorStack> { unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())).map(|_| ()) } } + + /// Adds the CRL to the store + #[corresponds(X509_STORE_add_crl)] + pub fn add_crl(&mut self, crl: &X509CrlRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::X509_STORE_add_crl(self.as_ptr(), crl.as_ptr())).map(|_| ()) } + } } generic_foreign_type_and_impl_send_sync! { diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index e11f8bf223..580af4c274 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -1196,3 +1196,31 @@ fn test_store_all_certificates() { assert_eq!(store.all_certificates().len(), 1); } + +#[test] +#[cfg(ossl110)] +fn test_store_ctx_crls() { + let ca = include_bytes!("../../test/crl-ca.crt"); + let ca = X509::from_pem(ca).unwrap(); + let crl = include_bytes!("../../test/test.crl"); + let crl = X509Crl::from_der(crl).unwrap(); + let cert = include_bytes!("../../test/leaf.pem"); + let cert = X509::from_pem(cert).unwrap(); + assert!(crl.verify(&ca.public_key().unwrap()).unwrap()); + + let mut store_bldr = X509StoreBuilder::new().unwrap(); + store_bldr.add_crl(&crl).unwrap(); + let store = store_bldr.build(); + + let mut context = X509StoreContext::new().unwrap(); + assert_eq!( + context + .init_opt(&store, None, None, |c| c.crls(ca.subject_name())) + .unwrap() + .len(), + 1 + ); + assert!(context + .init_opt(&store, None, None, |c| c.crls(cert.subject_name())) + .is_err()); +}