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

Small additions to X509 names/extensions and ASN1 time #2224

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
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 @@ -111,5 +111,9 @@ const_ptr_api! {
pub fn ASN1_STRING_type(x: #[const_ptr_if(any(ossl110, libressl280))] ASN1_STRING) -> c_int;
pub fn ASN1_generate_v3(str: #[const_ptr_if(any(ossl110, libressl280))] c_char, cnf: *mut X509V3_CTX) -> *mut ASN1_TYPE;
pub fn i2d_ASN1_TYPE(a: #[const_ptr_if(ossl300)] ASN1_TYPE, pp: *mut *mut c_uchar) -> c_int;
pub fn ASN1_TIME_to_generalizedtime(
t: #[const_ptr_if(any(ossl110, libressl280))] ASN1_TIME,
out: *mut *mut ASN1_GENERALIZEDTIME
) -> *mut ASN1_GENERALIZEDTIME;
}
}
3 changes: 3 additions & 0 deletions openssl-sys/src/handwritten/x509.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ extern "C" {

pub fn X509_NAME_ENTRY_free(x: *mut X509_NAME_ENTRY);

#[cfg(any(ossl110, libressl270))]
pub fn X509_NAME_ENTRY_set(x: *const X509_NAME_ENTRY) -> c_int;

pub fn X509_NAME_new() -> *mut X509_NAME;
pub fn X509_NAME_cmp(x: *const X509_NAME, y: *const X509_NAME) -> c_int;
pub fn X509_NAME_free(x: *mut X509_NAME);
Expand Down
59 changes: 59 additions & 0 deletions openssl/src/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,38 @@ impl fmt::Display for Asn1GeneralizedTimeRef {
}
}

impl Asn1GeneralizedTime {
/// Creates a new `Asn1GeneralizedTime` from an `Asn1Time`.
#[corresponds(ASN1_TIME_to_generalizedtime)]
pub fn from_time(time: &Asn1TimeRef) -> Result<Asn1GeneralizedTime, ErrorStack> {
ffi::init();

unsafe {
let handle = cvt_p(ffi::ASN1_TIME_to_generalizedtime(
time.as_ptr(),
ptr::null_mut(),
))?;
Ok(Asn1GeneralizedTime::from_ptr(handle))
}
}
}

impl Asn1GeneralizedTimeRef {
/// Returns the time as an ASN.1 time string.
#[corresponds(ASN1_STRING_get0_data)]
pub fn as_str(&self) -> Option<&str> {
unsafe {
let ptr = ASN1_STRING_get0_data(self.as_ptr().cast());
let len = ffi::ASN1_STRING_length(self.as_ptr().cast());

#[allow(clippy::unnecessary_cast)]
let slice = slice::from_raw_parts(ptr as *const u8, len as usize);
// GeneralizedTime strings must be ASCII (in fact an even smaller subset).
str::from_utf8(slice).ok()
}
}
}

/// The type of an ASN.1 value.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Asn1Type(c_int);
Expand Down Expand Up @@ -227,6 +259,17 @@ impl Asn1TimeRef {

Ok(Ordering::Equal)
}

/// Returns the time of this `Asn1TimeRef` if it is a `Asn1GeneralizedTimeRef`.
pub fn as_generalized_time(&self) -> Option<&Asn1GeneralizedTimeRef> {
let time_type = unsafe { ffi::ASN1_STRING_type(self.as_ptr().cast()) };
match time_type {
ffi::V_ASN1_GENERALIZEDTIME => {
Some(unsafe { Asn1GeneralizedTimeRef::from_ptr(self.as_ptr().cast()) })
}
_ => None,
}
}
}

#[cfg(any(ossl102, boringssl))]
Expand Down Expand Up @@ -857,6 +900,22 @@ mod tests {
assert!(c_ref < a_ref);
}

#[test]
fn generalize_time() {
let time = Asn1Time::from_str("991231235959Z").unwrap();
let gtime = Asn1GeneralizedTime::from_time(&time).unwrap();
assert_eq!(gtime.as_str(), Some("19991231235959Z"));
assert!(time.as_generalized_time().is_none());

let time = Asn1Time::from_str("99991231235959Z").unwrap();
let gtime = Asn1GeneralizedTime::from_time(&time).unwrap();
assert_eq!(gtime.as_str(), Some("99991231235959Z"));
assert_eq!(
time.as_generalized_time().unwrap().as_str(),
Some("99991231235959Z")
);
}

#[test]
fn integer_to_owned() {
let a = Asn1Integer::from_bn(&BigNum::from_dec_str("42").unwrap()).unwrap();
Expand Down
71 changes: 71 additions & 0 deletions openssl/src/x509/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,29 @@ impl X509Ref {
}
}

/// Return this certificate's list of extensions.
pub fn extensions(&self) -> Result<Vec<&X509ExtensionRef>, ErrorStack> {
let mut exts = Vec::new();
// SAFETY: the rust openssl binding guarantees that x509 is a valid object.
let ext_count = unsafe { ffi::X509_get_ext_count(self.as_ptr()) };

for index in 0..ext_count {
// SAFETY: the rust openssl binding guarantees that x509 is a valid object
// and `index` is a valid index.
// From the documentation of X509_get_ext:
// The returned extension is an internal pointer which must not be freed
// up by the application. Therefore this pointer is valid as long as the X509
// object lives.
let ext = unsafe {
X509ExtensionRef::from_ptr(cvt_p(ffi::X509_get_ext(self.as_ptr(), index))?)
};

exts.push(ext)
}

Ok(exts)
}

to_pem! {
/// Serializes the certificate into a PEM-encoded X509 structure.
///
Expand Down Expand Up @@ -1050,6 +1073,43 @@ impl X509Extension {
}

impl X509ExtensionRef {
/// Returns the criticality of this extension.
pub fn critical(&self) -> bool {
// SAFETY: `self` is a valid object.
let critical = unsafe { ffi::X509_EXTENSION_get_critical(self.as_ptr()) };
// In the ASN1, the critical marker is a boolean so it's actually impossible for
// openssl to return anything but 0 and 1, so throw in error in case we see anything else.
match critical {
0 => false,
1 => true,
_ => panic!("openssl returned non-boolean critical marker for extension"),
}
}

/// Returns this extension's type.
pub fn object(&self) -> Result<&Asn1ObjectRef, ErrorStack> {
// SAFETY: `self` is a valid object and the returned pointer is marked with the lifetime
// of the X509 object that owns the memory.
unsafe {
// From the documentation of X509_EXTENSION_get_object:
// The returned pointer is an internal value which must not be freed up.
let data = cvt_p(ffi::X509_EXTENSION_get_object(self.as_ptr()))?;
Ok(Asn1ObjectRef::from_ptr(data))
}
}

/// Returns this extension's data.
pub fn data(&self) -> Result<&Asn1OctetStringRef, ErrorStack> {
// SAFETY: `self` is a valid object and the returned pointer is marked with the lifetime
// of the X509 object that owns the memory.
unsafe {
// From the documentation of X509_EXTENSION_get_data:
// The returned pointer is an internal value which must not be freed up.
let data = cvt_p(ffi::X509_EXTENSION_get_data(self.as_ptr()))?;
Ok(Asn1OctetStringRef::from_ptr(data))
}
}

to_der! {
/// Serializes the Extension to its standard DER encoding.
#[corresponds(i2d_X509_EXTENSION)]
Expand Down Expand Up @@ -1360,6 +1420,17 @@ impl X509NameEntryRef {
Asn1ObjectRef::from_ptr(object)
}
}

/// Returns the index of the RDN that a of an `X509NameEntry` is part of.
/// This function retrieves X.501 RelativeDistinguishedName (RDN) that a `X509NameEntry` is part of in the `X509Name`
/// object using it. The first RDN has index 0. If an RDN consists of more than one X509_NAME_ENTRY object, they all
/// share the same index. This is useful when working with multi-valued RDNs.
///
/// This corresponds to `X509_NAME_ENTRY_set`.
#[cfg(any(ossl110, libressl270))]
pub fn set(&self) -> c_int {
unsafe { ffi::X509_NAME_ENTRY_set(self.as_ptr()) }
}
}

impl fmt::Debug for X509NameEntryRef {
Expand Down
17 changes: 17 additions & 0 deletions openssl/src/x509/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1192,3 +1192,20 @@ fn test_store_all_certificates() {

assert_eq!(store.all_certificates().len(), 1);
}

#[test]
fn test_get_extensions() {
let cert = include_bytes!("../../test/alt_name_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let exts = cert.extensions().unwrap();
const EXPECTED_EXTS: &[(Nid, usize)] = &[
(Nid::BASIC_CONSTRAINTS, 2),
(Nid::KEY_USAGE, 4),
(Nid::SUBJECT_ALT_NAME, 81),
];
assert_eq!(exts.len(), EXPECTED_EXTS.len());
for (i, (nid, len)) in EXPECTED_EXTS.iter().enumerate() {
assert_eq!(exts[i].object().unwrap().nid(), *nid);
assert_eq!(exts[i].data().unwrap().len(), *len);
}
}