diff --git a/openssl-sys/src/handwritten/x509_vfy.rs b/openssl-sys/src/handwritten/x509_vfy.rs index 31928f8979..22744e9c21 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(store: *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; diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index c4e0c5b4e2..fa02f975c7 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -23,10 +23,11 @@ use std::ptr; use std::str; use crate::asn1::{ - Asn1BitStringRef, Asn1Enumerated, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, + Asn1BitStringRef, Asn1Enumerated, Asn1Integer, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1OctetStringRef, Asn1StringRef, Asn1TimeRef, Asn1Type, }; use crate::bio::MemBioSlice; +use crate::bn::BigNum; use crate::conf::ConfRef; use crate::error::ErrorStack; use crate::ex_data::Index; @@ -1599,6 +1600,65 @@ impl CrlReason { pub const fn as_raw(&self) -> c_int { self.0 } + + pub fn to_asn1_integer(&self) -> Result { + Asn1Integer::from_bn(BigNum::from_u32(self.0 as u32)?.as_ref()) + } +} + +/// A builder used to construct `X509Revoked`. +pub struct X509RevokedBuilder(X509Revoked); + +impl X509RevokedBuilder { + /// Creates a new X509Revoked builder. + pub fn new() -> Result { + unsafe { + ffi::init(); + cvt_p(ffi::X509_REVOKED_new()).map(|p| X509RevokedBuilder(X509Revoked(p))) + } + } + + /// Set revocation reason. + pub fn set_crl_reason(&mut self, crl_reason: &CrlReason) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::X509_REVOKED_add1_ext_i2d( + self.0.as_ptr(), + ffi::NID_crl_reason, + crl_reason.to_asn1_integer()?.as_ptr() as *mut c_void, + 0, + 0, + )) + .map(|_| ()) + } + } + + /// Set revocation date. + #[corresponds(X509_REVOKED_set_revocationDate)] + pub fn set_revocation_date(&mut self, revocation_date: &Asn1TimeRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::X509_REVOKED_set_revocationDate( + self.0.as_ptr(), + revocation_date.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Set serial number. + #[corresponds(X509_REVOKED_set_serialNumber)] + pub fn set_serial_number(&mut self, serial_number: &Asn1IntegerRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::X509_REVOKED_set_serialNumber( + self.0.as_ptr(), + serial_number.as_ptr(), + )) + .map(|_| ()) + } + } + + pub fn build(self) -> X509Revoked { + self.0 + } } foreign_type_and_impl_send_sync! { @@ -1616,6 +1676,11 @@ impl Stackable for X509Revoked { } impl X509Revoked { + /// Returns a new builder. + pub fn builder() -> Result { + X509RevokedBuilder::new() + } + from_der! { /// Deserializes a DER-encoded certificate revocation status #[corresponds(d2i_X509_REVOKED)] @@ -1726,6 +1791,130 @@ unsafe impl ExtensionType for AuthorityInformationAccess { type Output = Stack; } +/// A builder used to construct `X509Crl`. +pub struct X509CrlBuilder(X509Crl); + +impl X509CrlBuilder { + /// Creates a new CRL builder. + #[corresponds(X509_CRL_new)] + pub fn new() -> Result { + unsafe { + ffi::init(); + cvt_p(ffi::X509_CRL_new()).map(|p| X509CrlBuilder(X509Crl(p))) + } + } + + /// Sets the issuer name of the CRL. + #[corresponds(X509_CRL_set_issuer_name)] + pub fn set_issuer_name(&mut self, issuer_name: &X509NameRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::X509_CRL_set_issuer_name( + self.0.as_ptr(), + issuer_name.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Sets last update to CRL. + #[corresponds(X509_CRL_set1_lastUpdate)] + pub fn set_last_update(&mut self, last_update: &Asn1TimeRef) -> Result<(), ErrorStack> { + unsafe { + cvt(X509_CRL_set1_lastUpdate( + self.0.as_ptr(), + last_update.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Sets next update to CRL. + #[corresponds(X509_CRL_set1_nextUpdate)] + pub fn set_next_update(&mut self, next_update: &Asn1TimeRef) -> Result<(), ErrorStack> { + unsafe { + cvt(X509_CRL_set1_nextUpdate( + self.0.as_ptr(), + next_update.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Adds an X509 extension value to the CRL. + #[corresponds(X509_CRL_add_ext)] + pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::X509_CRL_add_ext( + self.0.as_ptr(), + extension.as_ptr(), + -1, + )) + .map(|_| ()) + } + } + + /// Return an `X509v3Context`. This context object can be used to construct + /// certain `X509Crl` extensions. + pub fn x509v3_context<'a>( + &'a self, + issuer: &X509Ref, + conf: Option<&'a ConfRef>, + ) -> X509v3Context<'a> { + unsafe { + let mut ctx = mem::zeroed(); + + ffi::X509V3_set_ctx( + &mut ctx, + issuer.as_ptr(), + ptr::null_mut(), + ptr::null_mut(), + self.0.as_ptr(), + 0, + ); + + if let Some(conf) = conf { + ffi::X509V3_set_nconf(&mut ctx, conf.as_ptr()); + } + + X509v3Context(ctx, PhantomData) + } + } + + /// Add a certificate the CRL. + #[corresponds(X509_CRL_add0_revoked)] + pub fn add_revoked(&mut self, revoked: X509Revoked) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::X509_CRL_add0_revoked( + self.0.as_ptr(), + revoked.as_ptr(), + ))?; + mem::forget(revoked); + Ok(()) + } + } + + /// Signs the CRL with a private key. + #[corresponds(X509_CRL_sign)] + pub fn sign(&mut self, key: &PKeyRef, hash: MessageDigest) -> Result<(), ErrorStack> + where + T: HasPrivate, + { + unsafe { + cvt(ffi::X509_CRL_sign( + self.0.as_ptr(), + key.as_ptr(), + hash.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Consumes the builder, returning the CRL. + pub fn build(self) -> X509Crl { + self.0 + } +} + foreign_type_and_impl_send_sync! { type CType = ffi::X509_CRL; fn drop = ffi::X509_CRL_free; @@ -1781,6 +1970,11 @@ impl<'a> CrlStatus<'a> { } impl X509Crl { + /// Returns a new builder. + pub fn builder() -> Result { + X509CrlBuilder::new() + } + from_pem! { /// Deserializes a PEM-encoded Certificate Revocation List /// @@ -2415,6 +2609,18 @@ cfg_if! { cfg_if! { if #[cfg(any(ossl110, libressl350, boringssl, awslc))] { + use ffi::{X509_CRL_set1_nextUpdate, X509_CRL_set1_lastUpdate}; + } else { + use ffi::{ + X509_CRL_set_nextUpdate as X509_CRL_set1_nextUpdate, + X509_CRL_set_lastUpdate as X509_CRL_set1_lastUpdate, + + }; + } +} + +cfg_if! { + if #[cfg(any(ossl110, libressl350, boringssl))] { use ffi::{ X509_CRL_get_issuer, X509_CRL_get0_nextUpdate, X509_CRL_get0_lastUpdate, X509_CRL_get_REVOKED, diff --git a/openssl/src/x509/store.rs b/openssl/src/x509/store.rs index ad62ac725d..f707a1a07f 100644 --- a/openssl/src/x509/store.rs +++ b/openssl/src/x509/store.rs @@ -54,7 +54,7 @@ use crate::stack::StackRef; use crate::util::ForeignTypeRefExt; #[cfg(any(ossl102, boringssl, libressl261, awslc))] use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef}; -use crate::x509::{X509Object, X509PurposeId, X509}; +use crate::x509::{X509CrlRef, X509Object, X509PurposeId, X509}; use crate::{cvt, cvt_p}; use openssl_macros::corresponds; #[cfg(not(any(boringssl, awslc)))] @@ -101,6 +101,12 @@ impl X509StoreBuilderRef { unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) } } + /// Add a CRL to the certificate 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(|_| ()) } + } + /// Load certificates from their default locations. /// /// These locations are read from the `SSL_CERT_FILE` and `SSL_CERT_DIR` diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index e11f8bf223..005be41749 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -18,12 +18,15 @@ use crate::x509::store::X509Lookup; use crate::x509::store::X509StoreBuilder; #[cfg(any(ossl102, boringssl, libressl261, awslc))] use crate::x509::verify::{X509VerifyFlags, X509VerifyParam}; + #[cfg(any(ossl102, boringssl, awslc))] +use crate::x509::X509Builder; +#[cfg(any(ossl102, boringssl))] use crate::x509::X509PurposeId; #[cfg(any(ossl102, boringssl, libressl261, awslc))] use crate::x509::X509PurposeRef; -#[cfg(ossl110)] -use crate::x509::{CrlReason, X509Builder}; +#[cfg(any(ossl102, libressl261))] +use crate::x509::{CrlReason, X509Revoked}; use crate::x509::{ CrlStatus, X509Crl, X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyResult, X509, }; @@ -696,6 +699,115 @@ fn test_load_crl() { ); } +#[test] +#[cfg(any(ossl102, libressl261))] +fn test_verify_crl() { + 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(); + assert!(crl.verify(&ca.public_key().unwrap()).unwrap()); + + let cert = include_bytes!("../../test/subca.crt"); + let cert = X509::from_pem(cert).unwrap(); + + let revoked = match crl.get_by_cert(&cert) { + CrlStatus::Revoked(revoked) => revoked, + _ => panic!("cert should be revoked"), + }; + + assert_eq!( + revoked.serial_number().to_bn().unwrap(), + cert.serial_number().to_bn().unwrap(), + "revoked and cert serial numbers should match" + ); + + let chain = Stack::new().unwrap(); + let mut store_bldr = X509StoreBuilder::new().unwrap(); + store_bldr.add_cert(ca).unwrap(); + store_bldr.add_crl(&crl).unwrap(); + store_bldr.set_flags(X509VerifyFlags::CRL_CHECK).unwrap(); + let store = store_bldr.build(); + let mut context = X509StoreContext::new().unwrap(); + assert_eq!( + context + .init(&store, &cert, &chain, |c| { + c.verify_cert()?; + Ok(c.error()) + }) + .unwrap() + .error_string(), + "CRL has expired" + ) +} + +#[test] +#[cfg(any(ossl102, libressl261))] +fn test_crl_builder() { + let ca = include_bytes!("../../test/root-ca.pem"); + let ca = X509::from_pem(ca).unwrap(); + let pkey = include_bytes!("../../test/root-ca.key"); + let pkey = PKey::::private_key_from_pem(pkey).unwrap(); + let cert = include_bytes!("../../test/intermediate-ca.pem"); + let cert = X509::from_pem(cert).unwrap(); + + let mut crl_bldr = X509Crl::builder().unwrap(); + crl_bldr.set_issuer_name(ca.subject_name()).unwrap(); + let authority_key_identifier = AuthorityKeyIdentifier::new() + .keyid(true) + .issuer(true) + .build(&crl_bldr.x509v3_context(&cert, None)) + .unwrap(); + crl_bldr + .append_extension(&authority_key_identifier) + .unwrap(); + + // revoke certificate with unspecified reason something + let mut revoked_bldr = X509Revoked::builder().unwrap(); + revoked_bldr + .set_crl_reason(&CrlReason::KEY_COMPROMISE) + .unwrap(); + revoked_bldr + .set_revocation_date(&Asn1Time::days_from_now(0).unwrap()) + .unwrap(); + revoked_bldr + .set_serial_number(cert.serial_number()) + .unwrap(); + let revoked = revoked_bldr.build(); + + crl_bldr.add_revoked(revoked).unwrap(); + + crl_bldr + .set_last_update(&Asn1Time::days_from_now(0).unwrap()) + .unwrap(); + crl_bldr + .set_next_update(&Asn1Time::days_from_now(1).unwrap()) + .unwrap(); + + crl_bldr.sign(&pkey, MessageDigest::sha256()).unwrap(); + let crl = crl_bldr.build(); + assert!(crl.verify(&ca.public_key().unwrap()).unwrap()); + + let chain = Stack::new().unwrap(); + let mut store_bldr = X509StoreBuilder::new().unwrap(); + store_bldr.add_cert(ca).unwrap(); + store_bldr.add_crl(&crl).unwrap(); + store_bldr.set_flags(X509VerifyFlags::CRL_CHECK).unwrap(); + let store = store_bldr.build(); + let mut context = X509StoreContext::new().unwrap(); + assert_eq!( + context + .init(&store, &cert, &chain, |c| { + c.verify_cert()?; + Ok(c.error()) + }) + .unwrap() + .error_string(), + "certificate revoked" + ); +} + #[test] fn test_crl_entry_extensions() { let crl = include_bytes!("../../test/entry_extensions.crl");