From 890bca5109e7f15c18ccf8fb95967ef18f9115b6 Mon Sep 17 00:00:00 2001 From: Sven Herrmann Date: Tue, 9 May 2023 18:42:05 +0200 Subject: [PATCH 1/7] add X509_STORE_add_crl --- openssl-sys/src/handwritten/x509_vfy.rs | 1 + 1 file changed, 1 insertion(+) 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; From 76d490dba4ec5ead34574d945e3e4c0b16fa76dc Mon Sep 17 00:00:00 2001 From: Sven Herrmann Date: Tue, 9 May 2023 18:45:18 +0200 Subject: [PATCH 2/7] add add_crl to X509StoreBuilder --- openssl/src/x509/store.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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` From 39141cb602d20df62b5d9a9d957dd31848c46d36 Mon Sep 17 00:00:00 2001 From: Sven Herrmann Date: Tue, 9 May 2023 18:46:57 +0200 Subject: [PATCH 3/7] add X509RevokedBuilder --- openssl/src/x509/mod.rs | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index c4e0c5b4e2..9f36193179 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -1601,6 +1601,61 @@ impl CrlReason { } } +/// 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! { type CType = ffi::X509_REVOKED; fn drop = ffi::X509_REVOKED_free; @@ -1616,6 +1671,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)] From 4a1b65fbf0c63e33fb5e6cd6ddc63ca97da6235b Mon Sep 17 00:00:00 2001 From: Sven Herrmann Date: Tue, 9 May 2023 18:47:47 +0200 Subject: [PATCH 4/7] add X509CrlBuilder --- openssl/src/x509/mod.rs | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 9f36193179..95ca9013d9 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -1786,6 +1786,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; @@ -1841,6 +1965,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 /// @@ -2475,6 +2604,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, From 3c9226e399aa7216b8fe34bde63cdae2be5aef2c Mon Sep 17 00:00:00 2001 From: Sven Herrmann Date: Tue, 9 May 2023 18:48:56 +0200 Subject: [PATCH 5/7] add test for CRL building --- openssl/src/x509/tests.rs | 111 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index e11f8bf223..1427da140e 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -22,6 +22,8 @@ use crate::x509::verify::{X509VerifyFlags, X509VerifyParam}; use crate::x509::X509PurposeId; #[cfg(any(ossl102, boringssl, libressl261, awslc))] use crate::x509::X509PurposeRef; +#[cfg(any(ossl102, libressl261))] +use crate::x509::X509Revoked; #[cfg(ossl110)] use crate::x509::{CrlReason, X509Builder}; use crate::x509::{ @@ -696,6 +698,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"); From 08a3d7bb0acf6db3a027e93313ee17a8ece9e1cc Mon Sep 17 00:00:00 2001 From: Sven Herrmann Date: Thu, 11 May 2023 11:43:55 +0200 Subject: [PATCH 6/7] add CrlReason to asn1-integer conversion --- openssl/src/x509/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 95ca9013d9..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,10 @@ 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`. From f847ab30ebd9a1256111188ac884324241a229e3 Mon Sep 17 00:00:00 2001 From: Sven Herrmann Date: Thu, 22 Feb 2024 17:06:21 +0100 Subject: [PATCH 7/7] fixup! add test for CRL building --- openssl/src/x509/tests.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index 1427da140e..005be41749 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -18,14 +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(any(ossl102, libressl261))] -use crate::x509::X509Revoked; -#[cfg(ossl110)] -use crate::x509::{CrlReason, X509Builder}; +use crate::x509::{CrlReason, X509Revoked}; use crate::x509::{ CrlStatus, X509Crl, X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyResult, X509, };