Compare commits

...

1 Commits

Author SHA1 Message Date
Ravi Khadiwala
9d66031ab1 boring: Support validating certificates against CRLs
Adds CRL types and validation of certificates against CRLs with
a `X509Store/X509StoreContext`. Also enables customizing verification
flags on the `X509Store`, required to enable CRL checking.
2023-10-12 16:13:54 -07:00
10 changed files with 998 additions and 2 deletions

View File

@ -253,6 +253,16 @@ impl<T: Stackable> StackRef<T> {
}
}
impl<T> fmt::Debug for StackRef<T>
where
T: Stackable,
T::Ref: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_list().entries(self).finish()
}
}
impl<T: Stackable> Index<usize> for StackRef<T> {
type Output = T::Ref;

506
boring/src/x509/crl.rs Normal file
View File

@ -0,0 +1,506 @@
//! Certificate revocation lists describe certificates that have been revoked
//! by their issuer and should no longer be trusted.
//!
//! An `X509CRL` can be provided along with an issuing `X509` to verify that
//! issued certificates have not been revoked.
//!
//! # Example
//!
//! ```rust
//! use boring::hash::MessageDigest;
//! use boring::pkey::{PKey, Private};
//! use boring::x509::crl::{X509CRLBuilder, X509Revoked};
//! use boring::x509::extension::BasicConstraints;
//! use boring::x509::verify::X509VerifyFlags;
//! use boring::x509::X509Extension;
//! use boring::x509::X509;
//! use boring::x509::store::{X509Store, X509StoreBuilder};
//! use boring::asn1::Asn1Time;
//! use boring::bn::BigNum;
//! use boring::error::ErrorStack;
//!
//! fn crl_checking_store(issuer: X509, pkey: PKey<Private>) -> Result<X509Store, ErrorStack> {
//! let mut builder = X509CRLBuilder::new()?;
//! builder.set_issuer_name(issuer.subject_name())?;
//! builder.add_revoked(X509Revoked::from_parts(
//! &*BigNum::from_u32(1)?.to_asn1_integer()?,
//! &*Asn1Time::days_from_now(0)?
//! )?)?;
//! builder.set_last_update(&*Asn1Time::days_from_now(0)?)?;
//! builder.set_next_update(&*Asn1Time::days_from_now(30)?)?;
//! builder.sign(&pkey, MessageDigest::sha256())?;
//!
//! let mut store_builder = X509StoreBuilder::new()?;
//! store_builder.add_cert(issuer)?;
//! store_builder.add_crl(builder.build())?;
//! store_builder
//! .param_mut()
//! .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?;
//! Ok(store_builder.build())
//! }
//! ```
use crate::asn1::{Asn1BitStringRef, Asn1IntegerRef, Asn1TimeRef};
use crate::foreign_types::ForeignType;
use crate::foreign_types::ForeignTypeRef;
use crate::hash::{DigestBytes, MessageDigest};
use crate::pkey::{HasPrivate, HasPublic, PKeyRef};
use crate::stack::{StackRef, Stackable};
use crate::x509::X509ExtensionRef;
use crate::x509::{X509AlgorithmRef, X509Extension, X509NameRef};
use crate::{cvt, cvt_n, cvt_p, ErrorStack};
use std::convert::TryInto;
use std::fmt::Formatter;
use std::{fmt, mem, ptr};
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_REVOKED;
fn drop = ffi::X509_REVOKED_free;
/// An `X509_REVOKED` containing information about a revoked certificate
pub struct X509Revoked;
}
impl Stackable for X509Revoked {
type StackType = ffi::stack_st_X509_REVOKED;
}
impl X509Revoked {
/// Create an `X509Revoked`
///
/// This corresponds to [`X509_REVOKED_new`] followed by calls to
/// [`X509_REVOKED_set_serialNumber`] and [`X509_REVOKED_set_revocationDate`]
/// with the provided parameters
///
/// [`X509_REVOKED_new`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_new.html
/// [`X509_REVOKED_set_serialNumber`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_set_serialNumber.html
/// [`X509_REVOKED_set_revocationDate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_set_revocationDate.html
pub fn from_parts(
serial_number: &Asn1IntegerRef,
revocation_date: &Asn1TimeRef,
) -> Result<X509Revoked, ErrorStack> {
unsafe {
ffi::init();
let revoked = cvt_p(ffi::X509_REVOKED_new())?;
cvt(ffi::X509_REVOKED_set_serialNumber(
revoked,
serial_number.as_ptr(),
))?;
cvt(ffi::X509_REVOKED_set_revocationDate(
revoked,
revocation_date.as_ptr(),
))?;
Ok(X509Revoked::from_ptr(revoked))
}
}
}
impl X509RevokedRef {
/// Returns the serial number of the revoked certificate
///
/// This corresponds to [`X509_REVOKED_get0_serialNumber`].
///
/// [`X509_REVOKED_get0_serialNumber`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_get0_serialNumber.html
pub fn serial_number(&self) -> &Asn1IntegerRef {
unsafe {
let r = ffi::X509_REVOKED_get0_serialNumber(self.as_ptr());
assert!(!r.is_null());
Asn1IntegerRef::from_ptr(r as *mut _)
}
}
/// Returns certificate's revocation date
///
/// This corresponds to [`X509_REVOKED_get0_revocationDate`].
///
/// [`X509_REVOKED_get0_revocationDate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_get0_revocationDate
pub fn revocation_date(&self) -> &Asn1TimeRef {
unsafe {
let date = ffi::X509_REVOKED_get0_revocationDate(self.as_ptr());
assert!(!date.is_null());
Asn1TimeRef::from_ptr(date as *mut _)
}
}
}
impl fmt::Debug for X509RevokedRef {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
let sn = self.serial_number().to_bn().and_then(|bn| bn.to_hex_str());
let sn = sn.as_ref().map(|x| &***x).unwrap_or("");
fmt.debug_struct("X509Revoked")
.field("serial_number", &sn)
.field("revocation_date", self.revocation_date())
.finish()
}
}
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_CRL;
fn drop = ffi::X509_CRL_free;
/// An `X509CRL` certificate revocation list
pub struct X509CRL;
}
impl Stackable for X509CRL {
type StackType = ffi::stack_st_X509_CRL;
}
impl X509CRL {
from_pem! {
/// Deserializes a PEM-encoded X509CRL structure.
///
/// The input should have a header of `-----BEGIN X509 CRL-----`
///
/// This corresponds to [`PEM_read_bio_X509_CRL`].
///
/// [`PEM_read_bio_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/PEM_read_bio_X509_CRL
from_pem,
X509CRL,
ffi::PEM_read_bio_X509_CRL
}
from_der! {
/// Deserializes a DER-encoded X509 structure.
///
/// This corresponds to [`d2i_X509_CRL`].
///
/// [`d2i_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/d2i_X509_CRL.html
from_der,
X509CRL,
ffi::d2i_X509_CRL,
::libc::c_long
}
}
impl X509CRLRef {
/// Returns the CRL's last update time
///
/// This corresponds to [`X509_CRL_get0_lastUpdate`]
///
/// [`X509_CRL_get0_lastUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_lastUpdate
pub fn last_update(&self) -> Option<&Asn1TimeRef> {
unsafe {
let date = ffi::X509_CRL_get0_lastUpdate(self.as_ptr());
if date.is_null() {
None
} else {
Some(Asn1TimeRef::from_ptr(date as *mut _))
}
}
}
/// Returns the CRL's next update time
///
/// This corresponds to [`X509_CRL_get0_nextUpdate`]
///
/// [`X509_CRL_get0_nextUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_nextUpdate
pub fn next_update(&self) -> Option<&Asn1TimeRef> {
unsafe {
let date = ffi::X509_CRL_get0_nextUpdate(self.as_ptr());
if date.is_null() {
None
} else {
Some(Asn1TimeRef::from_ptr(date as *mut _))
}
}
}
/// Returns the CRL's issuer name
///
/// This corresponds to [`X509_CRL_get_issuer`]
///
/// [`X509_CRL_get_issuer`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get_issuer
pub fn issuer(&self) -> &X509NameRef {
unsafe {
let name = ffi::X509_CRL_get_issuer(self.as_ptr());
assert!(!name.is_null());
X509NameRef::from_ptr(name)
}
}
/// Returns the CRL's extensions
///
/// This corresponds to [`X509_CRL_get0_extensions`]
///
/// [`X509_CRL_get0_extensions`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_extensions
pub fn extensions(&self) -> Option<&StackRef<X509Extension>> {
unsafe {
let extensions = ffi::X509_CRL_get0_extensions(self.as_ptr());
if extensions.is_null() {
None
} else {
Some(StackRef::from_ptr(extensions as *mut _))
}
}
}
/// Returns the revoked certificates in this CRL
///
/// This corresponds to [`X509_CRL_get_REVOKED`]
///
/// [`X509_CRL_get_REVOKED`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get_REVOKED
pub fn revoked(&self) -> Option<&StackRef<X509Revoked>> {
unsafe {
let revoked = ffi::X509_CRL_get_REVOKED(self.as_ptr());
if revoked.is_null() {
None
} else {
Some(StackRef::from_ptr(revoked))
}
}
}
/// Returns the CRL's signature and signature algorithm
///
/// This corresponds to [`X509_CRL_get0_signature`]
///
/// [`X509_CRL_get0_signature`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_signature
pub fn signature(&self) -> (&Asn1BitStringRef, &X509AlgorithmRef) {
unsafe {
let mut signature = ptr::null();
let mut algor = ptr::null();
ffi::X509_CRL_get0_signature(self.as_ptr(), &mut signature, &mut algor);
assert!(!algor.is_null());
assert!(!signature.is_null());
(
Asn1BitStringRef::from_ptr(signature as *mut _),
X509AlgorithmRef::from_ptr(algor as *mut _),
)
}
}
/// Returns a digest of the DER representation of the CRL
///
/// This corresponds to [`X509_CRL_digest`]
///
/// [`X509_CRL_digest`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_digest
pub fn digest(&self, hash_type: MessageDigest) -> Result<DigestBytes, ErrorStack> {
unsafe {
let mut digest = DigestBytes {
buf: [0; ffi::EVP_MAX_MD_SIZE as usize],
len: ffi::EVP_MAX_MD_SIZE as usize,
};
let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap();
cvt(ffi::X509_CRL_digest(
self.as_ptr(),
hash_type.as_ptr(),
digest.buf.as_mut_ptr() as *mut _,
&mut len,
))?;
digest.len = len as usize;
Ok(digest)
}
}
/// Check if the CRL is signed using the given public key.
///
/// Only the signature is checked: no other checks (such as certificate chain validity)
/// are performed.
///
/// Returns `true` if verification succeeds.
///
/// This corresponds to [`X509_CRL_verify"].
///
/// [`X509_CRL_verify`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_verify
pub fn verify<T>(&self, key: &PKeyRef<T>) -> Result<bool, ErrorStack>
where
T: HasPublic,
{
unsafe { cvt_n(ffi::X509_CRL_verify(self.as_ptr(), key.as_ptr())).map(|n| n != 0) }
}
to_pem! {
/// Serializes the CRL into a PEM-encoded X509 CRL structure.
///
/// The output will have a header of `-----BEGIN X509 CRL-----`
///
/// This corresponds to [`PEM_write_bio_X509_CRL`]
///
/// [`PEM_write_bio_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/PEM_write_bio_X509_CRL
to_pem,
ffi::PEM_write_bio_X509_CRL
}
to_der! {
/// Serializes the CRL into a DER-encoded X509 CRL structure
///
/// This corresponds to `i2d_X509_CRL`
///
/// [`i2d_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/i2d_X509_CRL
to_der,
ffi::i2d_X509_CRL
}
}
impl ToOwned for X509CRLRef {
type Owned = X509CRL;
fn to_owned(&self) -> X509CRL {
unsafe {
ffi::X509_CRL_up_ref(self.as_ptr());
X509CRL::from_ptr(self.as_ptr())
}
}
}
impl Clone for X509CRL {
fn clone(&self) -> X509CRL {
X509CRLRef::to_owned(self)
}
}
impl fmt::Debug for X509CRLRef {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let mut debug_struct = formatter.debug_struct("X509CRL");
debug_struct.field("issuer", self.issuer());
debug_struct.field("signature_algorithm", &self.signature().1.object());
if let Some(next_update) = self.next_update() {
debug_struct.field("next_update", next_update);
}
if let Some(last_update) = self.last_update() {
debug_struct.field("last_update", last_update);
}
if let Some(revoked) = self.revoked() {
debug_struct.field("revoked", &revoked);
}
debug_struct.finish()
}
}
impl fmt::Debug for X509CRL {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let x: &X509CRLRef = self;
x.fmt(formatter)
}
}
/// A builder used to construct an `X509CRL`
pub struct X509CRLBuilder(X509CRL);
impl X509CRLBuilder {
/// Creates a new builder.
pub fn new() -> Result<X509CRLBuilder, ErrorStack> {
unsafe {
ffi::init();
cvt_p(ffi::X509_CRL_new()).map(|p| X509CRLBuilder(X509CRL::from_ptr(p)))
}
}
/// Append an `X509Extension` to the certificate revocation list
///
/// This corresponds to [`X509_CRL_add_ext`]
///
/// [`X509_CRL_add_ext`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_add_ext
pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> {
unsafe {
// -1 indicates append to end
cvt(ffi::X509_CRL_add_ext(
self.0.as_ptr(),
extension.as_ptr(),
-1,
))?;
Ok(())
}
}
/// Signs the certificate revocation list with a private key.
///
/// This corresponds to [`X509_CRL_sign`]
///
/// [`X509_CRL_sign`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_sign
pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack>
where
T: HasPrivate,
{
unsafe {
cvt(ffi::X509_CRL_sign(
self.0.as_ptr(),
key.as_ptr(),
hash.as_ptr(),
))
.map(|_| ())
}
}
/// Add a revoked certificate to the certificate revocation list
///
/// This corresponds to [`X509_CRL_add0_revoked`]
///
/// [`X509_CRL_add0_revoked`]: https://www.openssl.org/docs/man1.1.1/man3/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(())
}
}
/// Sets the issuer name of the certificate revocation list.
///
/// This corresponds to [`X509_CRL_set_issuer_name`]
///
/// [`X509_CRL_set_issuer_name`]: https://www.openssl.org/docs/man1.1.1/man3/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 the version of the certificate revocation list.
///
/// Note that the version is zero-indexed; that is, a certificate corresponding to version 3 of
/// the X.509 standard should pass `2` to this method.
///
/// This corresponds to [`X509_CRL_set_version`]
///
/// [`X509_CRL_set_version`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set_version
pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_CRL_set_version(self.0.as_ptr(), version.into())).map(|_| ()) }
}
/// Sets the last update time on the certificate revocation list.
///
/// This corresponds to [`X509_CRL_set1_lastUpdate`]
///
/// [`X509_CRL_set1_lastUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set1_lastUpdate
pub fn set_last_update(&mut self, last_update: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_CRL_set1_lastUpdate(
self.0.as_ptr(),
last_update.as_ptr(),
))
.map(|_| ())
}
}
/// Sets the next update time on the certificate revocation list.
///
/// This corresponds to [`X509_CRL_set1_nextUpdate`]
///
/// [`X509_CRL_set1_nextUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set1_nextUpdate
pub fn set_next_update(&mut self, next_update: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_CRL_set1_nextUpdate(
self.0.as_ptr(),
next_update.as_ptr(),
))
.map(|_| ())
}
}
/// Consumes the builder, returning the certificate revocation list
pub fn build(self) -> X509CRL {
self.0
}
}

View File

@ -36,8 +36,10 @@ use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public};
use crate::ssl::SslRef;
use crate::stack::{Stack, StackRef, Stackable};
use crate::string::OpensslString;
use crate::x509::crl::X509CRL;
use crate::{cvt, cvt_n, cvt_p};
pub mod crl;
pub mod extension;
pub mod store;
pub mod verify;
@ -161,6 +163,32 @@ impl X509StoreContextRef {
unsafe { cvt_n(ffi::X509_verify_cert(self.as_ptr())).map(|n| n != 0) }
}
/// Verifies the stored certificate with additional untrusted CRLs
///
/// Returns `true` if verification succeeds. The `error` method will return the specific
/// validation error if the certificate was not valid.
///
/// This will only work inside of a call to `init`.
///
/// This corresponds to [`X509_STORE_CTX_set0_crls`] followed by [`X509_verify_cert`].
///
/// [`X509_STORE_CTX_set0_crls`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_set0_crls.html
/// [`X509_verify_cert`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_verify_cert.html
pub fn verify_cert_with_crls(
&mut self,
untrusted_crls: Stack<X509CRL>,
) -> Result<bool, ErrorStack> {
unsafe {
ffi::X509_STORE_CTX_set0_crls(self.as_ptr(), untrusted_crls.as_ptr());
let res = cvt_n(ffi::X509_verify_cert(self.as_ptr())).map(|n| n != 0);
// set0_crls does not take ownership of the stack, so we'll drop and free
// untrusted_crls after this method. null out the crls in ctx to make sure
// no one has a reference to it.
ffi::X509_STORE_CTX_set0_crls(self.as_ptr(), ptr::null_mut());
res
}
}
/// Set the verify result of the context.
///
/// This corresponds to [`X509_STORE_CTX_set_error`].

View File

@ -46,6 +46,8 @@ use std::mem;
use crate::error::ErrorStack;
use crate::stack::StackRef;
use crate::x509::crl::X509CRL;
use crate::x509::verify::X509VerifyParamRef;
use crate::x509::{X509Object, X509};
use crate::{cvt, cvt_p};
@ -84,6 +86,24 @@ impl X509StoreBuilderRef {
unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) }
}
/// Adds a CRL to the certificate store.
///
/// This corresponds to [`X509_STORE_add_crl`].
///
/// [`X509_STORE_add_crl`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_add_crl
pub fn add_crl(&mut self, crl: X509CRL) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_STORE_add_crl(self.as_ptr(), crl.as_ptr())).map(|_| ()) }
}
/// Returns a mutable reference to the X509 verification configuration.
///
/// This corresponds to [`X509_STORE_get0_param`].
///
/// [`X509_STORE_get0_param`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_get0_param
pub fn param_mut(&mut self) -> &mut X509VerifyParamRef {
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::X509_STORE_get0_param(self.as_ptr())) }
}
/// Load certificates from their default locations.
///
/// These locations are read from the `SSL_CERT_FILE` and `SSL_CERT_DIR`

View File

@ -6,12 +6,14 @@ use crate::hash::MessageDigest;
use crate::nid::Nid;
use crate::pkey::{PKey, Private};
use crate::rsa::Rsa;
use crate::stack::Stack;
use crate::stack::{Stack, Stackable};
use crate::x509::crl::{X509CRLBuilder, X509Revoked, X509CRL};
use crate::x509::extension::{
AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName,
SubjectKeyIdentifier,
};
use crate::x509::store::X509StoreBuilder;
use crate::x509::verify::X509VerifyFlags;
use crate::x509::{X509Extension, X509Name, X509Req, X509StoreContext, X509};
fn pkey() -> PKey<Private> {
@ -19,6 +21,15 @@ fn pkey() -> PKey<Private> {
PKey::from_rsa(rsa).unwrap()
}
fn stack_of<T>(item: T) -> Stack<T>
where
T: Stackable,
{
let mut stack = Stack::new().expect("unable to initialize stack");
stack.push(item).expect("failed to add to stack");
stack
}
#[test]
fn test_cert_loading() {
let cert = include_bytes!("../../test/cert.pem");
@ -250,6 +261,48 @@ fn x509_builder() {
assert_eq!(serial, x509.serial_number().to_bn().unwrap());
}
#[test]
fn x509_crl_builder() {
let mut builder = X509CRLBuilder::new().unwrap();
let mut name = X509Name::builder().unwrap();
name.append_entry_by_nid(Nid::COMMONNAME, "foobar.com")
.unwrap();
let name = name.build();
builder.set_issuer_name(&name).unwrap();
let mut serial = BigNum::new().unwrap();
serial.rand(128, MsbOption::MAYBE_ZERO, false).unwrap();
let serial_asn = serial.to_asn1_integer().unwrap();
let revoked =
X509Revoked::from_parts(&serial_asn, &Asn1Time::days_from_now(0).unwrap()).unwrap();
builder.add_revoked(revoked).unwrap();
builder
.set_last_update(&Asn1Time::days_from_now(0).unwrap())
.unwrap();
builder
.set_next_update(&Asn1Time::days_from_now(30).unwrap())
.unwrap();
let pkey = pkey();
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
let crl = builder.build();
let cn = crl.issuer().entries_by_nid(Nid::COMMONNAME).next().unwrap();
assert_eq!(cn.data().as_slice(), b"foobar.com");
let revoked_sn = crl
.revoked()
.unwrap()
.iter()
.next()
.unwrap()
.serial_number();
assert_eq!(serial, revoked_sn.to_bn().unwrap());
assert!(crl.verify(&pkey).unwrap());
}
#[test]
fn x509_extension_new() {
assert!(X509Extension::new(None, None, "crlDistributionPoints", "section").is_err());
@ -434,6 +487,257 @@ fn test_verify_fails() {
.unwrap());
}
#[test]
fn test_verify_revoked() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../test/crl.pem");
let crl = X509CRL::from_pem(crl).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
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)
.unwrap();
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_crl_signature() {
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../test/bad_sig.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert!(!crl.verify(&ca.public_key().unwrap()).unwrap());
let crl = include_bytes!("../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert!(crl.verify(&ca.public_key().unwrap()).unwrap());
}
#[test]
fn test_untrusted_valid_crl() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)
.unwrap();
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
// cert is not revoked
let crl = include_bytes!("../../test/empty_crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
// cert is revoked
let crl = include_bytes!("../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
assert_eq!(
context.verify_result().unwrap_err().as_raw(),
ffi::X509_V_ERR_CERT_REVOKED
);
}
#[test]
fn test_untrusted_invalid_crl() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)
.unwrap();
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
// this CRL was issued by a different CA (not in the trusted store)
let crl = include_bytes!("../../test/invalid_crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
assert_eq!(
context.verify_result().unwrap_err().as_raw(),
ffi::X509_V_ERR_UNABLE_TO_GET_CRL
);
// this CRL has an invalid signature
let crl = include_bytes!("../../test/bad_sig.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
assert_eq!(
context.verify_result().unwrap_err().as_raw(),
ffi::X509_V_ERR_CRL_SIGNATURE_FAILURE
);
}
#[test]
fn test_revoked_serial_numbers() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let cert_sn = cert.serial_number().to_bn().unwrap();
let crl = include_bytes!("../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert_eq!(
crl.revoked()
.unwrap()
.iter()
.filter(|revoked| revoked.serial_number().to_bn().unwrap() == cert_sn)
.count(),
1
);
}
#[test]
fn test_serialize_crl() {
let crl = include_bytes!("../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let digest = hex::encode(crl.digest(MessageDigest::sha1()).unwrap());
let serialized = crl.to_pem().unwrap();
let crl_deserialized = X509CRL::from_pem(&serialized).unwrap();
let new_digest = crl_deserialized.digest(MessageDigest::sha1()).unwrap();
assert_eq!(digest, hex::encode(new_digest));
}
#[test]
fn test_debug_crl() {
let crl = include_bytes!("../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let debugged = format!("{:#?}", crl);
assert!(debugged.contains(r#"countryName = "AU""#));
assert!(debugged.contains(r#"stateOrProvinceName = "Some-State""#));
assert!(debugged.contains(r#"organizationName = "Internet Widgits Pty Ltd""#));
assert!(debugged.contains(r#"last_update: Jun 21 20:22:02 2022 GMT"#));
assert!(debugged.contains(r#"next_update: Jun 18 20:22:02 2032 GMT"#));
assert!(debugged.contains(r#"revocation_date: Jun 21 20:21:55 2022 GMT"#));
assert!(debugged.contains(r#"serial_number: "8771f7bdee982fa5""#));
}
#[test]
fn test_custom_time_valid() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
assert!(context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_custom_time_expired() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.param_mut().set_time(1786838400); // 2026-08-16, after the root and leaf expiration
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_custom_time_too_soon() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.param_mut().set_time(1262304000); // 2010-01-01, before the root and leaf issuance
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
}
#[test]
fn test_custom_time_crl() {
let cert = include_bytes!("../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)
.unwrap();
store_bldr.param_mut().set_time(1640995200); // 2022-01-01, before the CRL's issue date
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
}
#[test]
fn test_save_subject_der() {
let cert = include_bytes!("../../test/cert.pem");

View File

@ -1,6 +1,6 @@
use crate::ffi;
use foreign_types::ForeignTypeRef;
use libc::c_uint;
use libc::{c_uint, time_t};
use std::net::IpAddr;
use crate::cvt;
@ -22,6 +22,30 @@ bitflags! {
}
}
bitflags! {
/// Flags used to configure verification of an `X509` certificate
pub struct X509VerifyFlags: c_uint {
const CB_ISSUER_CHECK = ffi::X509_V_FLAG_CB_ISSUER_CHECK as _;
const USE_CHECK_TIME = ffi::X509_V_FLAG_USE_CHECK_TIME as _;
const CRL_CHECK = ffi::X509_V_FLAG_CRL_CHECK as _;
const CRL_CHECK_ALL = ffi::X509_V_FLAG_CRL_CHECK_ALL as _;
const IGNORE_CRITICAL = ffi::X509_V_FLAG_IGNORE_CRITICAL as _;
const X509_STRICT = ffi::X509_V_FLAG_X509_STRICT as _;
const ALLOW_PROXY_CERTS = ffi::X509_V_FLAG_ALLOW_PROXY_CERTS as _;
const POLICY_CHECK = ffi::X509_V_FLAG_POLICY_CHECK as _;
const EXPLICIT_POLICY = ffi::X509_V_FLAG_EXPLICIT_POLICY as _;
const INHIBIT_ANY = ffi::X509_V_FLAG_INHIBIT_ANY as _;
const INHIBIT_MAP = ffi::X509_V_FLAG_INHIBIT_MAP as _;
const NOTIFY_POLICY = ffi::X509_V_FLAG_NOTIFY_POLICY as _;
const EXTENDED_CRL_SUPPORT = ffi::X509_V_FLAG_EXTENDED_CRL_SUPPORT as _;
const FLAG_USE_DELTAS = ffi::X509_V_FLAG_USE_DELTAS as _;
const CHECK_SS_SIGNATURE = ffi::X509_V_FLAG_CHECK_SS_SIGNATURE as _;
const TRUSTED_FIRST = ffi::X509_V_FLAG_TRUSTED_FIRST as _;
const PARTIAL_CHAIN = ffi::X509_V_FLAG_PARTIAL_CHAIN as _;
const NO_ALT_CHAINS = ffi::X509_V_FLAG_NO_ALT_CHAINS as _;
}
}
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_VERIFY_PARAM;
fn drop = ffi::X509_VERIFY_PARAM_free;
@ -84,4 +108,62 @@ impl X509VerifyParamRef {
.map(|_| ())
}
}
/// Sets the time to check certificates and CRLs against.
///
/// If unset, uses the current time.
///
/// This corresponds to [`X509_VERIFY_PARAM_set_time`]. Note that BoringSSL does not support
/// the OpenSSL `NO_CHECK_TIME` option.
///
/// [`X509_VERIFY_PARAM_set_time`]: https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_time.html
///
/// # Example
///
/// ```
/// use boring::x509::store::X509StoreBuilder;
/// use std::convert::TryInto;
/// use std::time::SystemTime;
///
/// let mut store_builder = X509StoreBuilder::new().expect("can create a new builder");
/// let duration_since_epoch = SystemTime::UNIX_EPOCH.elapsed().expect("it's after 1970");
/// let seconds_since_epoch: libc::time_t = duration_since_epoch
/// .as_secs()
/// .try_into()
/// .expect("time_t is large enough to represent this time");
/// store_builder.param_mut().set_time(seconds_since_epoch);
/// ```
pub fn set_time(&mut self, unix_time: time_t) {
unsafe { ffi::X509_VERIFY_PARAM_set_time(self.as_ptr(), unix_time) }
}
/// Set the verify flags by OR-ing them with `flags`
///
/// This corresponds to [`X509_VERIFY_PARAM_set_flags`].
///
/// [`X509_VERIFY_PARAM_set_flags`]: https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_flags.html
pub fn set_flags(&mut self, flags: X509VerifyFlags) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_VERIFY_PARAM_set_flags(
self.as_ptr(),
flags.bits().into(),
))
.map(|_| ())
}
}
/// Clears the verify flags in `flags`
///
/// This corresponds to [`X509_VERIFY_PARAM_clear_flags`]
///
/// [`X509_VERIFY_PARAM_clear_flags`]: https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_clear_flags.html
pub fn clear_flags(&mut self, flags: X509VerifyFlags) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_VERIFY_PARAM_clear_flags(
self.as_ptr(),
flags.bits().into(),
))
.map(|_| ())
}
}
}

11
boring/test/bad_sig.pem Normal file
View File

@ -0,0 +1,11 @@
-----BEGIN X509 CRL-----
MIIBqTCBkjANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwK
U29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkFw0y
MjA2MTYyMjQ0NDFaFw0yMzA2MTYyMjQ0NDFaMBwwGgIJAIdx973umC+lFw0yMjA2
MTYyMjIxMDVaMA0GCSqGSIb3DQEBBQUAA4IBAQApqFdwm46jxkJK8J5kprGm6cp8
b7XMKB1epvhJGIkXHjp7O+2rxYGIExcNlM7jPcwqnUE0E50qGrqSMEupmtaBH03a
fmmDKyhLema7KD64UaERLqWjaW2DPeX9VX6vL4ECc6zTVLxfmYzxVt6A9hhqCm3b
fu8klWczGTa79r/WhTbA7uVf5+OI98da5tlxw+DlAQfqd34L2qq5aFg2dcTGqIdz
3pxP6UlTyj0ZPK3tUtTpIURVO2/MX3j5V+QjWz81UeCv0gQcmOiIVSRUGwi9c6JY
jDqBIvDY6df0riz5is1SS+D94sp1iovBlluwpq4kB8xyDuwt7vblkzleS2YU
-----END X509 CRL-----

13
boring/test/crl.pem Normal file
View File

@ -0,0 +1,13 @@
-----BEGIN X509 CRL-----
MIIB3jCBxwIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE
CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
Fw0yMjA2MjEyMDIyMDJaFw0zMjA2MTgyMDIyMDJaMBwwGgIJAIdx973umC+lFw0y
MjA2MjEyMDIxNTVaoDAwLjAfBgNVHSMEGDAWgBRs06UDqw1fLMmNipyIp4h3uDf9
mjALBgNVHRQEBAICEAEwDQYJKoZIhvcNAQELBQADggEBABgsWr/sVZqXm1AzgGCJ
JBMJW1oUY18aqroxo4kAIoI4QveLmHxi1Wm2I4dqdc6pM09SJhU5v5CfqpJ2BDc0
JBfEk8KKi5O/OYyLcUKa4dEpAlYPgeDyLc6zF8rGLtJoDIYuk4JUeuuByoXt0Sh+
7vx6UzuI7EH+mr4ZjnyAkD3f9jZy+mDcTm/+0REuh4iZ1AotE2YuQWQgxc1Y8TlD
eK+ks1zBKI23s0hPBxJQunmz2k3Uu9Yf+Sg0KxCiDgJZWFiGSw/6DtnT0oYAFGaD
mCyQWtwmS6zGBg+p76wNXkwyJMVvSDgrXSZ55bmImNmA38yKqOLOpB5i+FAS3r4V
ApQ=
-----END X509 CRL-----

11
boring/test/empty_crl.pem Normal file
View File

@ -0,0 +1,11 @@
-----BEGIN X509 CRL-----
MIIBijB0MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApT
b21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQXDTIy
MDYxNjIyNDU1NVoXDTIzMDYxNjIyNDU1NVowDQYJKoZIhvcNAQEFBQADggEBAEtx
nBr3aI0qlegMVJsJn3GfkMzaPVTSTHuw76Dzdl9eGDj0hXzAzZW5k4WBHvaInzNT
NKkeISoQJHLH981R9sQU2zA8sTESLTJGyCFu05Y6XhdmqX4ywmzVRjL6p/aoHNdZ
H1mxgK16wG+Sv0pd+9qNJgC/cNFmNbWzbiEAi5kID4IUxSmId/FZsXsms1EjqDH4
DFIwLIQO/kR5zwE5fZ5EjqUBdAxoSVHfD+OPKl4x2t8CHMmao+ih2FOfd70+NLBD
2oxaJMjZL/SIf8vYxjpjimMR+7yJ5J5P1j/RBfG3LwwUDP0RtWLIvRQo/dZUyXTg
LuC1vNuUoObe12z/NQ4=
-----END X509 CRL-----

View File

@ -0,0 +1,11 @@
-----BEGIN X509 CRL-----
MIIBmTCBggIBATANBgkqhkiG9w0BAQsFADAhMQ0wCwYDVQQKDARGQUtFMRAwDgYD
VQQDDAdGQUtFIENBFw0yMjA2MjcyMTQ3NTBaFw0zMjA2MjQyMTQ3NTBaMBwwGgIJ
AIdx973umC+lFw0yMjA2MjcyMTQ2MjhaoA8wDTALBgNVHRQEBAICEAMwDQYJKoZI
hvcNAQELBQADggEBAMXZRqTG28rnJSUPnVaqkmePSH15iz5Q/e4MdrM0cipXGuzX
z5C8Oh0D2uT3ddawBxTosnbjuzlT7Tanbp3xCRBm9spRPxFbGaFWysBlG1aLTDka
e9t9YeErg7wpwU6Qar0dzkLL5IkW3NArgbe8gP9PkYQxz/B0ESdHIPYJP1YBMNG6
tLgEhg74Xs9UhOBInNsQB8qMGsEeOnzfiuvfspU/yvKHHzvjAcjeIrONLJaZcu2Y
Dsfm5gOXGkHEm5/qJZ/IILoY0GSsSBekCAZda5+v3nvyjfRaBPyhx3Zv+rXh7u5z
77bIzPZP60rslomacgEr4p/Y52E4GmKGV+X2+Hc=
-----END X509 CRL-----