aboutsummaryrefslogtreecommitdiff
path: root/openssl/src/x509
diff options
context:
space:
mode:
Diffstat (limited to 'openssl/src/x509')
-rw-r--r--openssl/src/x509/extension.rs212
-rw-r--r--openssl/src/x509/mod.rs242
-rw-r--r--openssl/src/x509/tests.rs46
3 files changed, 367 insertions, 133 deletions
diff --git a/openssl/src/x509/extension.rs b/openssl/src/x509/extension.rs
new file mode 100644
index 00000000..3faa0996
--- /dev/null
+++ b/openssl/src/x509/extension.rs
@@ -0,0 +1,212 @@
+use std::fmt;
+use nid::Nid;
+
+/// Type-only version of the `Extension` enum.
+///
+/// See the `Extension` documentation for more information on the different
+/// variants.
+#[derive(Clone,Hash,PartialEq,Eq)]
+pub enum ExtensionType {
+ KeyUsage,
+ ExtKeyUsage,
+ SubjectAltName,
+ IssuerAltName,
+ OtherNid(Nid),
+ OtherStr(String),
+}
+
+/// A X.509 v3 certificate extension.
+///
+/// Only one extension of each type is allow in a certificate.
+/// See RFC 3280 for more information about extensions.
+#[derive(Clone)]
+pub enum Extension {
+ /// The purposes of the key contained in the certificate
+ KeyUsage(Vec<KeyUsageOption>),
+ /// The extended purposes of the key contained in the certificate
+ ExtKeyUsage(Vec<ExtKeyUsageOption>),
+ /// Subject Alternative Names
+ SubjectAltName(Vec<(AltNameOption,String)>),
+ /// Issuer Alternative Names
+ IssuerAltName(Vec<(AltNameOption,String)>),
+ /// Arbitrary extensions by NID. See `man x509v3_config` for value syntax.
+ ///
+ /// You must not use this to add extensions which this enum can express directly.
+ ///
+ /// ```
+ /// use openssl::x509::extension::Extension::*;
+ /// use openssl::nid::Nid;
+ ///
+ /// # let generator = openssl::x509::X509Generator::new();
+ /// generator.add_extension(OtherNid(Nid::BasicConstraints,"critical,CA:TRUE".to_owned()));
+ /// ```
+ OtherNid(Nid,String),
+ /// Arbitrary extensions by OID string. See `man ASN1_generate_nconf` for value syntax.
+ ///
+ /// You must not use this to add extensions which this enum can express directly.
+ ///
+ /// ```
+ /// use openssl::x509::extension::Extension::*;
+ ///
+ /// # let generator = openssl::x509::X509Generator::new();
+ /// generator.add_extension(OtherStr("2.999.2".to_owned(),"ASN1:UTF8:example value".to_owned()));
+ /// ```
+ OtherStr(String,String),
+}
+
+impl Extension {
+ pub fn get_type(&self) -> ExtensionType {
+ match self {
+ &Extension::KeyUsage(_) => ExtensionType::KeyUsage,
+ &Extension::ExtKeyUsage(_) => ExtensionType::ExtKeyUsage,
+ &Extension::SubjectAltName(_) => ExtensionType::SubjectAltName,
+ &Extension::IssuerAltName(_) => ExtensionType::IssuerAltName,
+ &Extension::OtherNid(nid,_) => ExtensionType::OtherNid(nid),
+ &Extension::OtherStr(ref s,_) => ExtensionType::OtherStr(s.clone()),
+ }
+ }
+}
+
+impl ExtensionType {
+ pub fn get_nid(&self) -> Option<Nid> {
+ match self {
+ &ExtensionType::KeyUsage => Some(Nid::KeyUsage),
+ &ExtensionType::ExtKeyUsage => Some(Nid::ExtendedKeyUsage),
+ &ExtensionType::SubjectAltName => Some(Nid::SubjectAltName),
+ &ExtensionType::IssuerAltName => Some(Nid::IssuerAltName),
+ &ExtensionType::OtherNid(nid) => Some(nid),
+ &ExtensionType::OtherStr(_) => None,
+ }
+ }
+
+ pub fn get_name<'a>(&'a self) -> Option<&'a str> {
+ match self {
+ &ExtensionType::OtherStr(ref s) => Some(s),
+ _ => None,
+ }
+ }
+}
+
+// FIXME: This would be nicer as a method on Iterator<Item=ToString>. This can
+// eventually be replaced by the successor to std::slice::SliceConcatExt.connect
+fn join<I: Iterator<Item=T>,T: ToString>(iter: I, sep: &str) -> String {
+ iter.enumerate().fold(String::new(), |mut acc, (idx, v)| {
+ if idx > 0 { acc.push_str(sep) };
+ acc.push_str(&v.to_string());
+ acc
+ })
+}
+
+impl ToString for Extension {
+ fn to_string(&self) -> String {
+ match self {
+ &Extension::KeyUsage(ref purposes) => join(purposes.iter(),","),
+ &Extension::ExtKeyUsage(ref purposes) => join(purposes.iter(),","),
+ &Extension::SubjectAltName(ref names) => join(names.iter().map(|&(ref opt,ref val)|opt.to_string()+":"+&val),","),
+ &Extension::IssuerAltName(ref names) => join(names.iter().map(|&(ref opt,ref val)|opt.to_string()+":"+&val),","),
+ &Extension::OtherNid(_,ref value) => value.clone(),
+ &Extension::OtherStr(_,ref value) => value.clone(),
+ }
+ }
+}
+
+#[derive(Clone,Copy)]
+pub enum KeyUsageOption {
+ DigitalSignature,
+ NonRepudiation,
+ KeyEncipherment,
+ DataEncipherment,
+ KeyAgreement,
+ KeyCertSign,
+ CRLSign,
+ EncipherOnly,
+ DecipherOnly,
+}
+
+impl fmt::Display for KeyUsageOption {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ f.pad(match self {
+ &KeyUsageOption::DigitalSignature => "digitalSignature",
+ &KeyUsageOption::NonRepudiation => "nonRepudiation",
+ &KeyUsageOption::KeyEncipherment => "keyEncipherment",
+ &KeyUsageOption::DataEncipherment => "dataEncipherment",
+ &KeyUsageOption::KeyAgreement => "keyAgreement",
+ &KeyUsageOption::KeyCertSign => "keyCertSign",
+ &KeyUsageOption::CRLSign => "cRLSign",
+ &KeyUsageOption::EncipherOnly => "encipherOnly",
+ &KeyUsageOption::DecipherOnly => "decipherOnly",
+ })
+ }
+}
+
+#[derive(Clone)]
+pub enum ExtKeyUsageOption {
+ ServerAuth,
+ ClientAuth,
+ CodeSigning,
+ EmailProtection,
+ TimeStamping,
+ MsCodeInd,
+ MsCodeCom,
+ MsCtlSign,
+ MsSgc,
+ MsEfs,
+ NsSgc,
+ /// An arbitrary key usage by OID.
+ Other(String),
+}
+
+impl fmt::Display for ExtKeyUsageOption {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ f.pad(match self {
+ &ExtKeyUsageOption::ServerAuth => "serverAuth",
+ &ExtKeyUsageOption::ClientAuth => "clientAuth",
+ &ExtKeyUsageOption::CodeSigning => "codeSigning",
+ &ExtKeyUsageOption::EmailProtection => "emailProtection",
+ &ExtKeyUsageOption::TimeStamping => "timeStamping",
+ &ExtKeyUsageOption::MsCodeInd => "msCodeInd",
+ &ExtKeyUsageOption::MsCodeCom => "msCodeCom",
+ &ExtKeyUsageOption::MsCtlSign => "msCTLSign",
+ &ExtKeyUsageOption::MsSgc => "msSGC",
+ &ExtKeyUsageOption::MsEfs => "msEFS",
+ &ExtKeyUsageOption::NsSgc =>"nsSGC",
+ &ExtKeyUsageOption::Other(ref s) => &s[..],
+ })
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum AltNameOption {
+ /// The value is specified as OID;content. See `man ASN1_generate_nconf` for more information on the content syntax.
+ ///
+ /// ```
+ /// use openssl::x509::extension::Extension::*;
+ /// use openssl::x509::extension::AltNameOption::Other as OtherName;
+ ///
+ /// # let generator = openssl::x509::X509Generator::new();
+ /// generator.add_extension(SubjectAltName(vec![(OtherName,"2.999.3;ASN1:UTF8:some other name".to_owned())]));
+ /// ```
+ Other,
+ Email,
+ DNS,
+ //X400, // Not supported by OpenSSL
+ Directory,
+ //EDIParty, // Not supported by OpenSSL
+ URI,
+ IPAddress,
+ RegisteredID,
+}
+
+impl fmt::Display for AltNameOption {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ f.pad(match self {
+ &AltNameOption::Other => "otherName",
+ &AltNameOption::Email => "email",
+ &AltNameOption::DNS => "DNS",
+ &AltNameOption::Directory => "dirName",
+ &AltNameOption::URI => "URI",
+ &AltNameOption::IPAddress => "IP",
+ &AltNameOption::RegisteredID => "RID",
+ })
+ }
+}
diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs
index 5446f125..91daa66a 100644
--- a/openssl/src/x509/mod.rs
+++ b/openssl/src/x509/mod.rs
@@ -9,6 +9,7 @@ use std::ptr;
use std::ops::Deref;
use std::fmt;
use std::str;
+use std::collections::HashMap;
use asn1::{Asn1Time};
use bio::{MemBio};
@@ -20,6 +21,9 @@ use ffi;
use ssl::error::{SslError, StreamError};
use nid;
+pub mod extension;
+
+use self::extension::{ExtensionType,Extension};
#[cfg(test)]
mod tests;
@@ -98,92 +102,9 @@ impl X509StoreContext {
}
}
-#[doc(hidden)]
-trait AsStr<'a> {
- fn as_str(&self) -> &'a str;
-}
-
-#[derive(Clone, Copy)]
-pub enum KeyUsage {
- DigitalSignature,
- NonRepudiation,
- KeyEncipherment,
- DataEncipherment,
- KeyAgreement,
- KeyCertSign,
- CRLSign,
- EncipherOnly,
- DecipherOnly
-}
-
-impl AsStr<'static> for KeyUsage {
- fn as_str(&self) -> &'static str {
- match self {
- &KeyUsage::DigitalSignature => "digitalSignature",
- &KeyUsage::NonRepudiation => "nonRepudiation",
- &KeyUsage::KeyEncipherment => "keyEncipherment",
- &KeyUsage::DataEncipherment => "dataEncipherment",
- &KeyUsage::KeyAgreement => "keyAgreement",
- &KeyUsage::KeyCertSign => "keyCertSign",
- &KeyUsage::CRLSign => "cRLSign",
- &KeyUsage::EncipherOnly => "encipherOnly",
- &KeyUsage::DecipherOnly => "decipherOnly"
- }
- }
-}
-
-
-#[derive(Clone, Copy)]
-pub enum ExtKeyUsage {
- ServerAuth,
- ClientAuth,
- CodeSigning,
- EmailProtection,
- TimeStamping,
- MsCodeInd,
- MsCodeCom,
- MsCtlSign,
- MsSgc,
- MsEfs,
- NsSgc
-}
-
-impl AsStr<'static> for ExtKeyUsage {
- fn as_str(&self) -> &'static str {
- match self {
- &ExtKeyUsage::ServerAuth => "serverAuth",
- &ExtKeyUsage::ClientAuth => "clientAuth",
- &ExtKeyUsage::CodeSigning => "codeSigning",
- &ExtKeyUsage::EmailProtection => "emailProtection",
- &ExtKeyUsage::TimeStamping => "timeStamping",
- &ExtKeyUsage::MsCodeInd => "msCodeInd",
- &ExtKeyUsage::MsCodeCom => "msCodeCom",
- &ExtKeyUsage::MsCtlSign => "msCTLSign",
- &ExtKeyUsage::MsSgc => "msSGC",
- &ExtKeyUsage::MsEfs => "msEFS",
- &ExtKeyUsage::NsSgc =>"nsSGC"
- }
- }
-}
-
-
-// FIXME: a dirty hack as there is no way to
-// implement ToString for Vec as both are defined
-// in another crate
-#[doc(hidden)]
-trait ToStr {
- fn to_str(&self) -> String;
-}
-
-impl<'a, T: AsStr<'a>> ToStr for Vec<T> {
- fn to_str(&self) -> String {
- self.iter().enumerate().fold(String::new(), |mut acc, (idx, v)| {
- if idx > 0 { acc.push(',') };
- acc.push_str(v.as_str());
- acc
- })
- }
-}
+// Backwards-compatibility
+pub use self::extension::KeyUsageOption as KeyUsage;
+pub use self::extension::ExtKeyUsageOption as ExtKeyUsage;
#[allow(non_snake_case)]
/// Generator of private key/certificate pairs
@@ -224,9 +145,9 @@ impl<'a, T: AsStr<'a>> ToStr for Vec<T> {
pub struct X509Generator {
bits: u32,
days: u32,
- CN: String,
- key_usage: Vec<KeyUsage>,
- ext_key_usage: Vec<ExtKeyUsage>,
+ names: Vec<(String,String)>,
+ // RFC 3280 ยง4.2: A certificate MUST NOT include more than one instance of a particular extension.
+ extensions: HashMap<ExtensionType,Extension>,
hash_type: HashType,
}
@@ -244,9 +165,8 @@ impl X509Generator {
X509Generator {
bits: 1024,
days: 365,
- CN: "rust-openssl".to_string(),
- key_usage: Vec::new(),
- ext_key_usage: Vec::new(),
+ names: vec![],
+ extensions: HashMap::new(),
hash_type: HashType::SHA1
}
}
@@ -264,21 +184,88 @@ impl X509Generator {
}
#[allow(non_snake_case)]
- /// Sets Common Name of certificate
+ /// (deprecated) Sets Common Name of certificate
+ ///
+ /// This function is deprecated, use `X509Generator.add_name` instead.
+ /// Don't use this function AND the `add_name` method
pub fn set_CN(mut self, CN: &str) -> X509Generator {
- self.CN = CN.to_string();
+ match self.names.get_mut(0) {
+ Some(&mut(_,ref mut val)) => *val=CN.to_string(),
+ _ => {} /* would move push here, but borrow checker won't let me */
+ }
+ if self.names.len()==0 {
+ self.names.push(("CN".to_string(),CN.to_string()));
+ }
+ self
+ }
+
+ /// Add attribute to the name of the certificate
+ ///
+ /// ```
+ /// # let generator = openssl::x509::X509Generator::new();
+ /// generator.add_name("CN".to_string(),"example.com".to_string());
+ /// ```
+ pub fn add_name(mut self, attr_type: String, attr_value: String) -> X509Generator {
+ self.names.push((attr_type,attr_value));
self
}
- /// Sets what for certificate could be used
- pub fn set_usage(mut self, purposes: &[KeyUsage]) -> X509Generator {
- self.key_usage = purposes.to_vec();
+ /// Add multiple attributes to the name of the certificate
+ ///
+ /// ```
+ /// # let generator = openssl::x509::X509Generator::new();
+ /// generator.add_names(vec![("CN".to_string(),"example.com".to_string())]);
+ /// ```
+ pub fn add_names<I>(mut self, attrs: I) -> X509Generator
+ where I: IntoIterator<Item=(String,String)> {
+ self.names.extend(attrs);
+ self
+ }
+
+ /// (deprecated) Sets what for certificate could be used
+ ///
+ /// This function is deprecated, use `X509Generator.add_extension` instead.
+ pub fn set_usage(self, purposes: &[KeyUsage]) -> X509Generator {
+ self.add_extension(Extension::KeyUsage(purposes.to_owned()))
+ }
+
+ /// (deprecated) Sets allowed extended usage of certificate
+ ///
+ /// This function is deprecated, use `X509Generator.add_extension` instead.
+ pub fn set_ext_usage(self, purposes: &[ExtKeyUsage]) -> X509Generator {
+ self.add_extension(Extension::ExtKeyUsage(purposes.to_owned()))
+ }
+
+ /// Add an extension to a certificate
+ ///
+ /// If the extension already exists, it will be replaced.
+ ///
+ /// ```
+ /// use openssl::x509::extension::Extension::*;
+ /// use openssl::x509::extension::KeyUsageOption::*;
+ ///
+ /// # let generator = openssl::x509::X509Generator::new();
+ /// generator.add_extension(KeyUsage(vec![DigitalSignature, KeyEncipherment]));
+ /// ```
+ pub fn add_extension(mut self, ext: extension::Extension) -> X509Generator {
+ self.extensions.insert(ext.get_type(),ext);
self
}
- /// Sets allowed extended usage of certificate
- pub fn set_ext_usage(mut self, purposes: &[ExtKeyUsage]) -> X509Generator {
- self.ext_key_usage = purposes.to_vec();
+ /// Add multiple extensions to a certificate
+ ///
+ /// If any of the extensions already exist, they will be replaced.
+ ///
+ /// ```
+ /// use openssl::x509::extension::Extension::*;
+ /// use openssl::x509::extension::KeyUsageOption::*;
+ ///
+ /// # let generator = openssl::x509::X509Generator::new();
+ /// generator.add_extensions(vec![KeyUsage(vec![DigitalSignature, KeyEncipherment])]);
+ /// ```
+ pub fn add_extensions<I>(mut self, exts: I) -> X509Generator
+ where I: IntoIterator<Item=extension::Extension> {
+ self.extensions.extend(exts.into_iter().map(|ext|(ext.get_type(),ext)));
self
}
@@ -287,17 +274,25 @@ impl X509Generator {
self
}
- fn add_extension(x509: *mut ffi::X509, extension: c_int, value: &str) -> Result<(), SslError> {
+ fn add_extension_internal(x509: *mut ffi::X509, exttype: &extension::ExtensionType, value: &str) -> Result<(), SslError> {
unsafe {
let mut ctx: ffi::X509V3_CTX = mem::zeroed();
ffi::X509V3_set_ctx(&mut ctx, x509, x509,
ptr::null_mut(), ptr::null_mut(), 0);
let value = CString::new(value.as_bytes()).unwrap();
- let ext = ffi::X509V3_EXT_conf_nid(ptr::null_mut(),
+ let ext=match exttype.get_nid() {
+ Some(nid) => ffi::X509V3_EXT_conf_nid(ptr::null_mut(),
mem::transmute(&ctx),
- extension,
- value.as_ptr() as *mut c_char);
-
+ nid as c_int,
+ value.as_ptr() as *mut c_char),
+ None => {
+ let name=CString::new(exttype.get_name().unwrap().as_bytes()).unwrap();
+ ffi::X509V3_EXT_conf(ptr::null_mut(),
+ mem::transmute(&ctx),
+ name.as_ptr() as *mut c_char,
+ value.as_ptr() as *mut c_char)
+ }
+ };
let mut success = false;
if ext != ptr::null_mut() {
success = ffi::X509_add_ext(x509, ext, -1) != 0;
@@ -307,7 +302,7 @@ impl X509Generator {
}
}
- fn add_name(name: *mut ffi::X509_NAME, key: &str, value: &str) -> Result<(), SslError> {
+ fn add_name_internal(name: *mut ffi::X509_NAME, key: &str, value: &str) -> Result<(), SslError> {
let value_len = value.len() as c_int;
lift_ssl!(unsafe {
let key = CString::new(key.as_bytes()).unwrap();
@@ -373,17 +368,19 @@ impl X509Generator {
let name = ffi::X509_get_subject_name(x509.handle);
try_ssl_null!(name);
- try!(X509Generator::add_name(name, "CN", &self.CN));
- ffi::X509_set_issuer_name(x509.handle, name);
+ let default=[("CN","rust-openssl")];
+ let default_iter=&mut default.iter().map(|&(k,v)|(k,v));
+ let arg_iter=&mut self.names.iter().map(|&(ref k,ref v)|(&k[..],&v[..]));
+ let iter: &mut Iterator<Item=(&str,&str)> =
+ if self.names.len()==0 { default_iter } else { arg_iter };
- if self.key_usage.len() > 0 {
- try!(X509Generator::add_extension(x509.handle, ffi::NID_key_usage,
- &self.key_usage.to_str()));
+ for (key,val) in iter {
+ try!(X509Generator::add_name_internal(name, &key, &val));
}
+ ffi::X509_set_issuer_name(x509.handle, name);
- if self.ext_key_usage.len() > 0 {
- try!(X509Generator::add_extension(x509.handle, ffi::NID_ext_key_usage,
- &self.ext_key_usage.to_str()));
+ for (exttype,ext) in self.extensions.iter() {
+ try!(X509Generator::add_extension_internal(x509.handle, exttype, &ext.to_string()));
}
let hash_fn = self.hash_type.evp_md();
@@ -399,11 +396,20 @@ impl X509Generator {
Err(x) => return Err(x)
};
- let hash_fn = self.hash_type.evp_md();
- let req = unsafe { ffi::X509_to_X509_REQ(cert.handle, p_key.get_handle(), hash_fn) };
- try_ssl_null!(req);
+ unsafe {
+ let req = ffi::X509_to_X509_REQ(cert.handle, ptr::null_mut(), ptr::null());
+ try_ssl_null!(req);
+
+ let exts = ffi::X509_get_extensions(cert.handle);
+ if exts != ptr::null_mut() {
+ try_ssl!(ffi::X509_REQ_add_extensions(req,exts));
+ }
- Ok(X509Req::new(req))
+ let hash_fn = self.hash_type.evp_md();
+ try_ssl!(ffi::X509_REQ_sign(req, p_key.get_handle(), hash_fn));
+
+ Ok(X509Req::new(req))
+ }
}
}
diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs
index 4e1c4f15..692539ba 100644
--- a/openssl/src/x509/tests.rs
+++ b/openssl/src/x509/tests.rs
@@ -4,28 +4,32 @@ use std::path::Path;
use std::fs::File;
use crypto::hash::Type::{SHA256};
+use crypto::pkey::PKey;
use x509::{X509, X509Generator};
-use x509::KeyUsage::{DigitalSignature, KeyEncipherment};
-use x509::ExtKeyUsage::{ClientAuth, ServerAuth};
+use x509::extension::Extension::{KeyUsage,ExtKeyUsage,SubjectAltName,OtherNid,OtherStr};
+use x509::extension::AltNameOption as SAN;
+use x509::extension::KeyUsageOption::{DigitalSignature, KeyEncipherment};
+use x509::extension::ExtKeyUsageOption::{self, ClientAuth, ServerAuth};
use nid::Nid;
-#[test]
-fn test_cert_gen() {
- let gen = X509Generator::new()
+fn get_generator() -> X509Generator {
+ X509Generator::new()
.set_bitlength(2048)
.set_valid_period(365*2)
- .set_CN("test_me")
+ .add_name("CN".to_string(),"test_me".to_string())
.set_sign_hash(SHA256)
- .set_usage(&[DigitalSignature, KeyEncipherment])
- .set_ext_usage(&[ClientAuth, ServerAuth]);
-
- let res = gen.generate();
- assert!(res.is_ok());
-
- let (cert, pkey) = res.unwrap();
+ .add_extension(KeyUsage(vec![DigitalSignature, KeyEncipherment]))
+ .add_extension(ExtKeyUsage(vec![ClientAuth, ServerAuth, ExtKeyUsageOption::Other("2.999.1".to_owned())]))
+ .add_extension(SubjectAltName(vec![(SAN::DNS,"example.com".to_owned())]))
+ .add_extension(OtherNid(Nid::BasicConstraints,"critical,CA:TRUE".to_owned()))
+ .add_extension(OtherStr("2.999.2".to_owned(),"ASN1:UTF8:example value".to_owned()))
+}
- assert!(cert.write_pem(&mut io::sink()).is_ok());
- assert!(pkey.write_pem(&mut io::sink()).is_ok());
+#[test]
+fn test_cert_gen() {
+ let (cert, pkey) = get_generator().generate().unwrap();
+ cert.write_pem(&mut io::sink()).unwrap();
+ pkey.write_pem(&mut io::sink()).unwrap();
// FIXME: check data in result to be correct, needs implementation
// of X509 getters
@@ -34,6 +38,18 @@ fn test_cert_gen() {
}
#[test]
+fn test_req_gen() {
+ let mut pkey = PKey::new();
+ pkey.gen(512);
+
+ let req = get_generator().request(&pkey).unwrap();
+ req.write_pem(&mut io::sink()).unwrap();
+
+ // FIXME: check data in result to be correct, needs implementation
+ // of X509_REQ getters
+}
+
+#[test]
fn test_cert_loading() {
let cert_path = Path::new("test/cert.pem");
let mut file = File::open(&cert_path)