Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add issuer_name and reason_code to X509RevokedRef #1847

Merged
merged 4 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions openssl-sys/src/handwritten/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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 @@ -666,6 +666,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
Skepfyr marked this conversation as resolved.
Show resolved Hide resolved
/// `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 @@ -1481,6 +1491,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 @@ -1513,6 +1551,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 @@ -1532,6 +1577,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 @@ -1872,6 +1971,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-----