diff options
Diffstat (limited to 'openssl/src/x509')
| -rw-r--r-- | openssl/src/x509/extension.rs | 212 | ||||
| -rw-r--r-- | openssl/src/x509/mod.rs | 242 | ||||
| -rw-r--r-- | openssl/src/x509/tests.rs | 46 |
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) |