Skip to content

Commit

Permalink
Add functions to extract X509 extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
pamaury committed Apr 24, 2024
1 parent 56697d5 commit 2470aa4
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 0 deletions.
60 changes: 60 additions & 0 deletions openssl/src/x509/mod.rs
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
17 changes: 17 additions & 0 deletions openssl/src/x509/tests.rs
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);
}
}

0 comments on commit 2470aa4

Please sign in to comment.