Commit 32292961 authored by Steven Fackler's avatar Steven Fackler
Browse files

Merge pull request #233 from jethrogb/topic/x509_extension

Allow setting of arbitrary X509 extensions
parents 5f8d6a2a f9a836fa
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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;
+1 −1
Original line number Diff line number Diff line
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
#[repr(usize)]
pub enum Nid {
    Undefined,
+212 −0
Original line number Diff line number Diff line
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",
        })
    }
}
+63 −109
Original line number Diff line number Diff line
@@ -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();
+13 −12
Original line number Diff line number Diff line
@@ -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]);

    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());
        .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 (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