diff --git a/openssl-sys/src/handwritten/asn1.rs b/openssl-sys/src/handwritten/asn1.rs index 16ffcccfe..b1b9e8000 100644 --- a/openssl-sys/src/handwritten/asn1.rs +++ b/openssl-sys/src/handwritten/asn1.rs @@ -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; } } diff --git a/openssl-sys/src/handwritten/x509.rs b/openssl-sys/src/handwritten/x509.rs index 7642dcd3b..b862db70b 100644 --- a/openssl-sys/src/handwritten/x509.rs +++ b/openssl-sys/src/handwritten/x509.rs @@ -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); diff --git a/openssl/src/asn1.rs b/openssl/src/asn1.rs index 8618be0e9..5fa137c5a 100644 --- a/openssl/src/asn1.rs +++ b/openssl/src/asn1.rs @@ -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 { + 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); @@ -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))] @@ -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(); diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index a64524cbe..d45cf1e9d 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -667,6 +667,29 @@ impl X509Ref { } } + /// Return this certificate's list of extensions. + pub fn extensions(&self) -> Result, 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. /// @@ -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)] @@ -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 { diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index ae61a2ad3..adc1e877d 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -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); + } +}