Skip to content

Commit

Permalink
Merge pull request #1847 from sfackler/x509-revoked-extra-methods
Browse files Browse the repository at this point in the history
Add issuer_name and reason_code to X509RevokedRef
  • Loading branch information
Skepfyr authored Apr 10, 2023
2 parents 48876d4 + 95680c8 commit 0854ffd
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 6 deletions.
4 changes: 4 additions & 0 deletions openssl-sys/src/handwritten/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ extern "C" {
pub fn ASN1_TIME_set_string(s: *mut ASN1_TIME, str: *const c_char) -> c_int;
#[cfg(ossl111)]
pub fn ASN1_TIME_set_string_X509(s: *mut ASN1_TIME, str: *const c_char) -> c_int;

pub fn ASN1_ENUMERATED_free(a: *mut ASN1_ENUMERATED);
#[cfg(ossl110)]
pub fn ASN1_ENUMERATED_get_int64(pr: *mut i64, a: *const ASN1_ENUMERATED) -> c_int;
}

const_ptr_api! {
Expand Down
1 change: 1 addition & 0 deletions openssl-sys/src/handwritten/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use libc::*;
use super::super::*;

pub enum ASN1_INTEGER {}
pub enum ASN1_ENUMERATED {}
pub enum ASN1_GENERALIZEDTIME {}
pub enum ASN1_STRING {}
pub enum ASN1_BIT_STRING {}
Expand Down
11 changes: 11 additions & 0 deletions openssl-sys/src/x509v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,14 @@ pub const X509_PURPOSE_OCSP_HELPER: c_int = 8;
pub const X509_PURPOSE_TIMESTAMP_SIGN: c_int = 9;
pub const X509_PURPOSE_MIN: c_int = 1;
pub const X509_PURPOSE_MAX: c_int = 9;

pub const CRL_REASON_UNSPECIFIED: c_int = 0;
pub const CRL_REASON_KEY_COMPROMISE: c_int = 1;
pub const CRL_REASON_CA_COMPROMISE: c_int = 2;
pub const CRL_REASON_AFFILIATION_CHANGED: c_int = 3;
pub const CRL_REASON_SUPERSEDED: c_int = 4;
pub const CRL_REASON_CESSATION_OF_OPERATION: c_int = 5;
pub const CRL_REASON_CERTIFICATE_HOLD: c_int = 6;
pub const CRL_REASON_REMOVE_FROM_CRL: c_int = 8;
pub const CRL_REASON_PRIVILEGE_WITHDRAWN: c_int = 9;
pub const CRL_REASON_AA_COMPROMISE: c_int = 10;
26 changes: 26 additions & 0 deletions openssl/src/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,32 @@ cfg_if! {
}
}

foreign_type_and_impl_send_sync! {
type CType = ffi::ASN1_ENUMERATED;
fn drop = ffi::ASN1_ENUMERATED_free;

/// An ASN.1 enumerated.
pub struct Asn1Enumerated;
/// A reference to an [`Asn1Enumerated`].
pub struct Asn1EnumeratedRef;
}

impl Asn1EnumeratedRef {
/// Get the value, if it fits in the required bounds.
#[corresponds(ASN1_ENUMERATED_get_int64)]
#[cfg(ossl110)]
pub fn get_i64(&self) -> Result<i64, ErrorStack> {
let mut crl_reason = 0;
unsafe {
cvt(ffi::ASN1_ENUMERATED_get_int64(
&mut crl_reason,
self.as_ptr(),
))?;
}
Ok(crl_reason)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 2 additions & 2 deletions openssl/src/nid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ pub struct Nid(c_int);
#[allow(non_snake_case)]
impl Nid {
/// Create a `Nid` from an integer representation.
pub fn from_raw(raw: c_int) -> Nid {
pub const fn from_raw(raw: c_int) -> Nid {
Nid(raw)
}

/// Return the integer representation of a `Nid`.
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn as_raw(&self) -> c_int {
pub const fn as_raw(&self) -> c_int {
self.0
}

Expand Down
119 changes: 117 additions & 2 deletions openssl/src/x509/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use std::slice;
use std::str;

use crate::asn1::{
Asn1BitStringRef, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef,
Asn1Type,
Asn1BitStringRef, Asn1Enumerated, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1StringRef,
Asn1TimeRef, Asn1Type,
};
use crate::bio::MemBioSlice;
use crate::conf::ConfRef;
Expand All @@ -50,6 +50,16 @@ pub mod store;
#[cfg(test)]
mod tests;

/// A type of X509 extension.
///
/// # Safety
/// The value of NID and Output must match those in OpenSSL so that
/// `Output::from_ptr_opt(*_get_ext_d2i(*, NID, ...))` is valid.
pub unsafe trait ExtensionType {
const NID: Nid;
type Output: ForeignType;
}

foreign_type_and_impl_send_sync! {
type CType = ffi::X509_STORE_CTX;
fn drop = ffi::X509_STORE_CTX_free;
Expand Down Expand Up @@ -1495,6 +1505,34 @@ impl X509ReqRef {
}
}

/// The reason that a certificate was revoked.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CrlReason(c_int);

#[allow(missing_docs)] // no need to document the constants
impl CrlReason {
pub const UNSPECIFIED: CrlReason = CrlReason(ffi::CRL_REASON_UNSPECIFIED);
pub const KEY_COMPROMISE: CrlReason = CrlReason(ffi::CRL_REASON_KEY_COMPROMISE);
pub const CA_COMPROMISE: CrlReason = CrlReason(ffi::CRL_REASON_CA_COMPROMISE);
pub const AFFILIATION_CHANGED: CrlReason = CrlReason(ffi::CRL_REASON_AFFILIATION_CHANGED);
pub const SUPERSEDED: CrlReason = CrlReason(ffi::CRL_REASON_SUPERSEDED);
pub const CESSATION_OF_OPERATION: CrlReason = CrlReason(ffi::CRL_REASON_CESSATION_OF_OPERATION);
pub const CERTIFICATE_HOLD: CrlReason = CrlReason(ffi::CRL_REASON_CERTIFICATE_HOLD);
pub const REMOVE_FROM_CRL: CrlReason = CrlReason(ffi::CRL_REASON_REMOVE_FROM_CRL);
pub const PRIVILEGE_WITHDRAWN: CrlReason = CrlReason(ffi::CRL_REASON_PRIVILEGE_WITHDRAWN);
pub const AA_COMPROMISE: CrlReason = CrlReason(ffi::CRL_REASON_AA_COMPROMISE);

/// Constructs an `CrlReason` from a raw OpenSSL value.
pub const fn from_raw(value: c_int) -> Self {
CrlReason(value)
}

/// Returns the raw OpenSSL value represented by this type.
pub const fn as_raw(&self) -> c_int {
self.0
}
}

foreign_type_and_impl_send_sync! {
type CType = ffi::X509_REVOKED;
fn drop = ffi::X509_REVOKED_free;
Expand Down Expand Up @@ -1527,6 +1565,13 @@ impl X509RevokedRef {
ffi::i2d_X509_REVOKED
}

/// Copies the entry to a new `X509Revoked`.
#[corresponds(X509_NAME_dup)]
#[cfg(any(boringssl, ossl110, libressl270))]
pub fn to_owned(&self) -> Result<X509Revoked, ErrorStack> {
unsafe { cvt_p(ffi::X509_REVOKED_dup(self.as_ptr())).map(|n| X509Revoked::from_ptr(n)) }
}

/// Get the date that the certificate was revoked
#[corresponds(X509_REVOKED_get0_revocationDate)]
pub fn revocation_date(&self) -> &Asn1TimeRef {
Expand All @@ -1546,6 +1591,60 @@ impl X509RevokedRef {
Asn1IntegerRef::from_ptr(r as *mut _)
}
}

/// Get the criticality and value of an extension.
///
/// This returns None if the extension is not present or occurs multiple times.
#[corresponds(X509_REVOKED_get_ext_d2i)]
pub fn extension<T: ExtensionType>(&self) -> Result<Option<(bool, T::Output)>, ErrorStack> {
let mut critical = -1;
let out = unsafe {
// SAFETY: self.as_ptr() is a valid pointer to an X509_REVOKED.
let ext = ffi::X509_REVOKED_get_ext_d2i(
self.as_ptr(),
T::NID.as_raw(),
&mut critical as *mut _,
ptr::null_mut(),
);
// SAFETY: Extensions's contract promises that the type returned by
// OpenSSL here is T::Output.
T::Output::from_ptr_opt(ext as *mut _)
};
match (critical, out) {
(0, Some(out)) => Ok(Some((false, out))),
(1, Some(out)) => Ok(Some((true, out))),
// -1 means the extension wasn't found, -2 means multiple were found.
(-1 | -2, _) => Ok(None),
// A critical value of 0 or 1 suggests success, but a null pointer
// was returned so something went wrong.
(0 | 1, None) => Err(ErrorStack::get()),
(c_int::MIN..=-2 | 2.., _) => panic!("OpenSSL should only return -2, -1, 0, or 1 for an extension's criticality but it returned {}", critical),
}
}
}

/// The CRL entry extension identifying the reason for revocation see [`CrlReason`],
/// this is as defined in RFC 5280 Section 5.3.1.
pub enum ReasonCode {}

// SAFETY: CertificateIssuer is defined to be a stack of GeneralName in the RFC
// and in OpenSSL.
unsafe impl ExtensionType for ReasonCode {
const NID: Nid = Nid::from_raw(ffi::NID_crl_reason);

type Output = Asn1Enumerated;
}

/// The CRL entry extension identifying the issuer of a certificate used in
/// indirect CRLs, as defined in RFC 5280 Section 5.3.3.
pub enum CertificateIssuer {}

// SAFETY: CertificateIssuer is defined to be a stack of GeneralName in the RFC
// and in OpenSSL.
unsafe impl ExtensionType for CertificateIssuer {
const NID: Nid = Nid::from_raw(ffi::NID_certificate_issuer);

type Output = Stack<GeneralName>;
}

foreign_type_and_impl_send_sync! {
Expand Down Expand Up @@ -1886,6 +1985,22 @@ impl GeneralNameRef {
self.ia5_string(ffi::GEN_EMAIL)
}

/// Returns the contents of this `GeneralName` if it is a `directoryName`.
pub fn directory_name(&self) -> Option<&X509NameRef> {
unsafe {
if (*self.as_ptr()).type_ != ffi::GEN_DIRNAME {
return None;
}

#[cfg(boringssl)]
let d = (*self.as_ptr()).d.ptr;
#[cfg(not(boringssl))]
let d = (*self.as_ptr()).d;

Some(X509NameRef::from_const_ptr(d as *const _))
}
}

/// Returns the contents of this `GeneralName` if it is a `dNSName`.
pub fn dnsname(&self) -> Option<&str> {
self.ia5_string(ffi::GEN_DNS)
Expand Down
42 changes: 40 additions & 2 deletions openssl/src/x509/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@ use crate::x509::store::X509Lookup;
use crate::x509::store::X509StoreBuilder;
#[cfg(any(ossl102, libressl261))]
use crate::x509::verify::{X509VerifyFlags, X509VerifyParam};
#[cfg(ossl110)]
use crate::x509::X509Builder;
#[cfg(ossl102)]
use crate::x509::X509PurposeId;
#[cfg(any(ossl102, libressl261))]
use crate::x509::X509PurposeRef;
#[cfg(ossl110)]
use crate::x509::{CrlReason, X509Builder};
use crate::x509::{
CrlStatus, X509Crl, X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyResult, X509,
};
use hex::{self, FromHex};
#[cfg(any(ossl102, libressl261))]
use libc::time_t;

use super::{CertificateIssuer, ReasonCode};

fn pkey() -> PKey<Private> {
let rsa = Rsa::generate(2048).unwrap();
PKey::from_rsa(rsa).unwrap()
Expand Down Expand Up @@ -611,6 +613,42 @@ fn test_load_crl() {
);
}

#[test]
fn test_crl_entry_extensions() {
let crl = include_bytes!("../../test/entry_extensions.crl");
let crl = X509Crl::from_pem(crl).unwrap();

let revoked_certs = crl.get_revoked().unwrap();
let entry = &revoked_certs[0];

let (critical, issuer) = entry
.extension::<CertificateIssuer>()
.unwrap()
.expect("Certificate issuer extension should be present");
assert!(critical, "Certificate issuer extension is critical");
assert_eq!(issuer.len(), 1, "Certificate issuer should have one entry");
let issuer = issuer[0]
.directory_name()
.expect("Issuer should be a directory name");
assert_eq!(
format!("{:?}", issuer),
r#"[countryName = "GB", commonName = "Test CA"]"#
);

// reason_code can't be inspected without ossl110
#[allow(unused_variables)]
let (critical, reason_code) = entry
.extension::<ReasonCode>()
.unwrap()
.expect("Reason code extension should be present");
assert!(!critical, "Reason code extension is not critical");
#[cfg(ossl110)]
assert_eq!(
CrlReason::KEY_COMPROMISE,
CrlReason::from_raw(reason_code.get_i64().unwrap() as ffi::c_int)
);
}

#[test]
fn test_save_subject_der() {
let cert = include_bytes!("../../test/cert.pem");
Expand Down
10 changes: 10 additions & 0 deletions openssl/test/entry_extensions.crl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBXDCCAQICAQEwCgYIKoZIzj0EAwIwETEPMA0GA1UEAwwGQ1JMIENBFw0yMzAz
MjgwOTQ5MThaFw0yMzA0MDQwOTUwMDdaMIGAMH4CFE+Y95/1pOqa6c9fUEJ8c04k
xu2PFw0yMzAzMjgwOTQ3MzNaMFcwLwYDVR0dAQH/BCUwI6QhMB8xCzAJBgNVBAYT
AkdCMRAwDgYDVQQDDAdUZXN0IENBMAoGA1UdFQQDCgEBMBgGA1UdGAQRGA8yMDIz
MDMyODA5NDQ0MFqgPTA7MB8GA1UdIwQYMBaAFNX1GZ0RWuC+4gz1wuy5H32T2W+R
MAoGA1UdFAQDAgEUMAwGA1UdHAQFMAOEAf8wCgYIKoZIzj0EAwIDSAAwRQIgbl7x
W+WVAb+zlvKcJLmHVuC+gbqR4jqwGIHHgQl2J8kCIQCo/sAF5sDqy/cL+fbzBeUe
YoY2h6lIkj9ENwU8ZCt03w==
-----END X509 CRL-----

0 comments on commit 0854ffd

Please sign in to comment.