diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 0c1a2e81d..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)] 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); + } +}