Merge pull request #233 from jethrogb/topic/x509_extension

Allow setting of arbitrary X509 extensions
This commit is contained in:
Steven Fackler 2015-07-08 03:10:40 -04:00
commit 3229296105
5 changed files with 289 additions and 121 deletions

View File

@ -603,6 +603,7 @@ extern "C" {
pub fn X509_STORE_CTX_get_ex_data(ctx: *mut X509_STORE_CTX, idx: c_int) -> *mut c_void;
pub fn X509V3_EXT_conf_nid(conf: *mut c_void, ctx: *mut X509V3_CTX, ext_nid: c_int, value: *mut c_char) -> *mut X509_EXTENSION;
pub fn X509V3_EXT_conf(conf: *mut c_void, ctx: *mut X509V3_CTX, name: *mut c_char, value: *mut c_char) -> *mut X509_EXTENSION;
pub fn X509V3_set_ctx(ctx: *mut X509V3_CTX, issuer: *mut X509, subject: *mut X509, req: *mut X509_REQ, crl: *mut X509_CRL, flags: c_int);
pub fn i2d_RSA_PUBKEY(k: *mut RSA, buf: *const *mut u8) -> c_int;

View File

@ -1,5 +1,5 @@
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
#[repr(usize)]
pub enum Nid {
Undefined,

View File

@ -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",
})
}
}

View File

@ -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
@ -225,8 +146,8 @@ pub struct X509Generator {
bits: u32,
days: u32,
CN: String,
key_usage: Vec<KeyUsage>,
ext_key_usage: Vec<ExtKeyUsage>,
// RFC 3280 §4.2: A certificate MUST NOT include more than one instance of a particular extension.
extensions: HashMap<ExtensionType,Extension>,
hash_type: HashType,
}
@ -245,8 +166,7 @@ impl X509Generator {
bits: 1024,
days: 365,
CN: "rust-openssl".to_string(),
key_usage: Vec::new(),
ext_key_usage: Vec::new(),
extensions: HashMap::new(),
hash_type: HashType::SHA1
}
}
@ -270,15 +190,50 @@ impl X509Generator {
self
}
/// Sets what for certificate could be used
pub fn set_usage(mut self, purposes: &[KeyUsage]) -> X509Generator {
self.key_usage = purposes.to_vec();
/// (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 +242,22 @@ 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 => ffi::X509V3_EXT_conf(ptr::null_mut(),
mem::transmute(&ctx),
exttype.get_name().unwrap().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;
@ -376,14 +336,8 @@ impl X509Generator {
try!(X509Generator::add_name(name, "CN", &self.CN));
ffi::X509_set_issuer_name(x509.handle, name);
if self.key_usage.len() > 0 {
try!(X509Generator::add_extension(x509.handle, ffi::NID_key_usage,
&self.key_usage.to_str()));
}
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();

View File

@ -5,8 +5,10 @@ use std::fs::File;
use crypto::hash::Type::{SHA256};
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]
@ -16,16 +18,15 @@ fn test_cert_gen() {
.set_valid_period(365*2)
.set_CN("test_me")
.set_sign_hash(SHA256)
.set_usage(&[DigitalSignature, KeyEncipherment])
.set_ext_usage(&[ClientAuth, ServerAuth]);
.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()));
let res = gen.generate();
assert!(res.is_ok());
let (cert, pkey) = res.unwrap();
assert!(cert.write_pem(&mut io::sink()).is_ok());
assert!(pkey.write_pem(&mut io::sink()).is_ok());
let (cert, pkey) = gen.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