diff --git a/.travis.yml b/.travis.yml index 7df1431a4ce96d9928e610d52a51c294fd6f2195..2b056d108903140620355744be70826ab26148a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ os: - linux env: global: - - FEATURES="tlsv1_2 tlsv1_1 dtlsv1 dtlsv1_2 sslv2 aes_xts npn alpn" + - FEATURES="tlsv1_2 tlsv1_1 dtlsv1 dtlsv1_2 sslv2 aes_xts npn alpn aes_ctr" before_install: - (test $TRAVIS_OS_NAME == "osx" || ./openssl/test/build.sh) before_script: diff --git a/README.md b/README.md index aafabaee19c0806c4ec40e553f1ffcdc21c10d68..06b9b7f87e4a806bc12cb51ab003ce662f94c58b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/sfackler/rust-openssl.svg?branch=master)](https://travis-ci.org/sfackler/rust-openssl) -[Documentation](https://sfackler.github.io/rust-openssl/doc/v0.6.4/openssl). +[Documentation](https://sfackler.github.io/rust-openssl/doc/v0.6.5/openssl). ## Building diff --git a/openssl-sys/Cargo.toml b/openssl-sys/Cargo.toml index 5ed9d068ef365d4e14d63f845674383343a2de63..437f7fb624511816e9943c2cf49038558af59158 100644 --- a/openssl-sys/Cargo.toml +++ b/openssl-sys/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "openssl-sys" -version = "0.6.4" +version = "0.6.5" authors = ["Alex Crichton ", "Steven Fackler "] license = "MIT" description = "FFI bindings to OpenSSL" repository = "https://github.com/sfackler/rust-openssl" -documentation = "https://sfackler.github.io/rust-openssl/doc/v0.6.4/openssl_sys" +documentation = "https://sfackler.github.io/rust-openssl/doc/v0.6.5/openssl_sys" links = "openssl" build = "build.rs" @@ -18,6 +18,7 @@ dtlsv1 = [] dtlsv1_2 = [] sslv2 = [] aes_xts = [] +aes_ctr = [] npn = [] alpn = [] diff --git a/openssl-sys/build.rs b/openssl-sys/build.rs index c1f1203443ad4e01a458f7ddf38f07dace1cd3b1..b4a0056662a309a36979953f814638599b1a5597 100644 --- a/openssl-sys/build.rs +++ b/openssl-sys/build.rs @@ -2,7 +2,10 @@ extern crate pkg_config; extern crate gcc; use std::env; +use std::fmt::Write as FmtWrite; use std::path::PathBuf; +use std::fs::File; +use std::io::Write; fn main() { let target = env::var("TARGET").unwrap(); @@ -65,7 +68,63 @@ fn main() { build_openssl_shim(&include_dirs); } +macro_rules! import_options { + ( $( $name:ident $val:expr )* ) => { + &[ $( (stringify!($name),$val), )* ] + }; +} + +fn generate_options_shim() -> PathBuf { + let options: &[(&'static str,u64)]=include!("src/ssl_options.rs"); + let mut shim = String::new(); + writeln!(shim,"#include ").unwrap(); + writeln!(shim,"#include ").unwrap(); + + for &(name,value) in options { + writeln!(shim,"#define RUST_{} UINT64_C({})",name,value).unwrap(); + writeln!(shim,"#ifndef {}",name).unwrap(); + writeln!(shim,"# define {} 0",name).unwrap(); + writeln!(shim,"#endif").unwrap(); + } + + writeln!(shim,"#define COPY_MASK ( \\").unwrap(); + + let mut it=options.iter().peekable(); + while let Some(&(name,_))=it.next() { + let eol=match it.peek() { + Some(_) => " | \\", + None => " )" + }; + writeln!(shim," ((RUST_{0}==(uint64_t)(uint32_t){0})?RUST_{0}:UINT64_C(0)){1}",name,eol).unwrap(); + } + + writeln!(shim,"long rust_openssl_ssl_ctx_options_rust_to_c(uint64_t rustval) {{").unwrap(); + writeln!(shim," long cval=rustval©_MASK;").unwrap(); + for &(name,_) in options { + writeln!(shim," if (rustval&RUST_{0}) cval|={0};",name).unwrap(); + } + writeln!(shim," return cval;").unwrap(); + writeln!(shim,"}}").unwrap(); + + writeln!(shim,"uint64_t rust_openssl_ssl_ctx_options_c_to_rust(long cval) {{").unwrap(); + writeln!(shim," uint64_t rustval=cval©_MASK;").unwrap(); + for &(name,_) in options { + writeln!(shim," if (cval&{0}) rustval|=RUST_{0};",name).unwrap(); + } + writeln!(shim," return rustval;").unwrap(); + writeln!(shim,"}}").unwrap(); + + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_file = PathBuf::from(&out_dir).join("ssl_ctx_options_shim.c"); + let mut f = File::create(&dest_file).unwrap(); + + f.write_all(shim.as_bytes()).unwrap(); + + dest_file +} + fn build_openssl_shim(include_paths: &[PathBuf]) { + let options_shim_file = generate_options_shim(); let mut config = gcc::Config::new(); for path in include_paths { @@ -73,6 +132,7 @@ fn build_openssl_shim(include_paths: &[PathBuf]) { } config.file("src/openssl_shim.c") + .file(options_shim_file) .compile("libopenssl_shim.a"); } diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index eb7750f7a437b1d46b08a69a30dbe3a2d03362fc..0e0ef8a5b04235a895c19722a7e828f940c0f944 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -1,6 +1,6 @@ #![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] #![allow(dead_code)] -#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.6.4")] +#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.6.5")] extern crate libc; @@ -37,6 +37,7 @@ pub type X509_NAME = c_void; pub type X509_NAME_ENTRY = c_void; pub type X509_REQ = c_void; pub type X509_STORE_CTX = c_void; +pub type stack_st_X509_EXTENSION = c_void; #[repr(C)] pub struct EVP_MD_CTX { @@ -128,6 +129,8 @@ pub const MBSTRING_UTF8: c_int = MBSTRING_FLAG; pub const NID_ext_key_usage: c_int = 126; pub const NID_key_usage: c_int = 83; +pub const PKCS5_SALT_LEN: c_int = 8; + pub const SSL_CTRL_OPTIONS: c_int = 32; pub const SSL_CTRL_CLEAR_OPTIONS: c_int = 77; @@ -155,6 +158,14 @@ pub const SSL_TLSEXT_ERR_ALERT_WARNING: c_int = 1; pub const SSL_TLSEXT_ERR_ALERT_FATAL: c_int = 2; pub const SSL_TLSEXT_ERR_NOACK: c_int = 3; +macro_rules! import_options { + ( $( $name:ident $val:expr )* ) => { + $( pub const $name: u64 = $val; )* + }; +} + +include!("ssl_options.rs"); + #[cfg(feature = "npn")] pub const OPENSSL_NPN_UNSUPPORTED: c_int = 0; #[cfg(feature = "npn")] @@ -262,8 +273,23 @@ pub fn init() { } } +pub unsafe fn SSL_CTX_set_options(ssl: *mut SSL_CTX, op: u64) -> u64 { + rust_openssl_ssl_ctx_options_c_to_rust(SSL_CTX_set_options_shim(ssl, rust_openssl_ssl_ctx_options_rust_to_c(op))) +} + +pub unsafe fn SSL_CTX_get_options(ssl: *mut SSL_CTX) -> u64 { + rust_openssl_ssl_ctx_options_c_to_rust(SSL_CTX_get_options_shim(ssl)) +} + +pub unsafe fn SSL_CTX_clear_options(ssl: *mut SSL_CTX, op: u64) -> u64 { + rust_openssl_ssl_ctx_options_c_to_rust(SSL_CTX_clear_options_shim(ssl, rust_openssl_ssl_ctx_options_rust_to_c(op))) +} + // True functions extern "C" { + fn rust_openssl_ssl_ctx_options_rust_to_c(rustval: u64) -> c_long; + fn rust_openssl_ssl_ctx_options_c_to_rust(cval: c_long) -> u64; + pub fn ASN1_INTEGER_set(dest: *mut ASN1_INTEGER, value: c_long) -> c_int; pub fn ASN1_STRING_type_new(ty: c_int) -> *mut ASN1_STRING; pub fn ASN1_TIME_free(tm: *mut ASN1_TIME); @@ -374,16 +400,22 @@ extern "C" { pub fn EVP_aes_128_ecb() -> *const EVP_CIPHER; #[cfg(feature = "aes_xts")] pub fn EVP_aes_128_xts() -> *const EVP_CIPHER; - // fn EVP_aes_128_ctr() -> EVP_CIPHER; + #[cfg(feature = "aes_ctr")] + pub fn EVP_aes_128_ctr() -> *const EVP_CIPHER; // fn EVP_aes_128_gcm() -> EVP_CIPHER; pub fn EVP_aes_256_cbc() -> *const EVP_CIPHER; pub fn EVP_aes_256_ecb() -> *const EVP_CIPHER; #[cfg(feature = "aes_xts")] pub fn EVP_aes_256_xts() -> *const EVP_CIPHER; - // fn EVP_aes_256_ctr() -> EVP_CIPHER; + #[cfg(feature = "aes_ctr")] + pub fn EVP_aes_256_ctr() -> *const EVP_CIPHER; // fn EVP_aes_256_gcm() -> EVP_CIPHER; pub fn EVP_rc4() -> *const EVP_CIPHER; + pub fn EVP_BytesToKey(typ: *const EVP_CIPHER, md: *const EVP_MD, + salt: *const u8, data: *const u8, datalen: c_int, + count: c_int, key: *mut u8, iv: *mut u8) -> c_int; + pub fn EVP_CIPHER_CTX_new() -> *mut EVP_CIPHER_CTX; pub fn EVP_CIPHER_CTX_set_padding(ctx: *mut EVP_CIPHER_CTX, padding: c_int) -> c_int; pub fn EVP_CIPHER_CTX_free(ctx: *mut EVP_CIPHER_CTX); @@ -445,6 +477,7 @@ extern "C" { kstr: *mut c_char, klen: c_int, callback: Option, user_data: *mut c_void) -> c_int; + pub fn PEM_write_bio_PUBKEY(bp: *mut BIO, x: *mut EVP_PKEY) -> c_int; pub fn PEM_write_bio_X509(bio: *mut BIO, x509: *mut X509) -> c_int; pub fn PEM_write_bio_X509_REQ(bio: *mut BIO, x509: *mut X509_REQ) -> c_int; @@ -502,6 +535,9 @@ extern "C" { pub fn SSL_get_SSL_CTX(ssl: *mut SSL) -> *mut SSL_CTX; pub fn SSL_get_current_compression(ssl: *mut SSL) -> *const COMP_METHOD; pub fn SSL_get_peer_certificate(ssl: *mut SSL) -> *mut X509; + pub fn SSL_get_ssl_method(ssl: *mut SSL) -> *const SSL_METHOD; + pub fn SSL_state_string(ssl: *mut SSL) -> *const c_char; + pub fn SSL_state_string_long(ssl: *mut SSL) -> *const c_char; pub fn SSL_COMP_get_name(comp: *const COMP_METHOD) -> *const c_char; @@ -603,8 +639,12 @@ 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 X509_REQ_add_extensions(req: *mut X509_REQ, exts: *mut stack_st_X509_EXTENSION) -> c_int; + pub fn X509_REQ_sign(x: *mut X509_REQ, pkey: *mut EVP_PKEY, md: *const EVP_MD) -> c_int; + pub fn i2d_RSA_PUBKEY(k: *mut RSA, buf: *const *mut u8) -> c_int; pub fn d2i_RSA_PUBKEY(k: *const *mut RSA, buf: *const *const u8, len: c_uint) -> *mut RSA; pub fn i2d_RSAPrivateKey(k: *mut RSA, buf: *const *mut u8) -> c_int; @@ -615,18 +655,17 @@ extern "C" { pub fn BIO_eof(b: *mut BIO) -> c_int; #[link_name = "BIO_set_mem_eof_return_shim"] pub fn BIO_set_mem_eof_return(b: *mut BIO, v: c_int); - #[link_name = "SSL_CTX_set_options_shim"] - pub fn SSL_CTX_set_options(ctx: *mut SSL_CTX, options: c_long) -> c_long; - #[link_name = "SSL_CTX_get_options_shim"] - pub fn SSL_CTX_get_options(ctx: *mut SSL_CTX) -> c_long; - #[link_name = "SSL_CTX_clear_options_shim"] - pub fn SSL_CTX_clear_options(ctx: *mut SSL_CTX, options: c_long) -> c_long; + pub fn SSL_CTX_set_options_shim(ctx: *mut SSL_CTX, options: c_long) -> c_long; + pub fn SSL_CTX_get_options_shim(ctx: *mut SSL_CTX) -> c_long; + pub fn SSL_CTX_clear_options_shim(ctx: *mut SSL_CTX, options: c_long) -> c_long; #[link_name = "SSL_CTX_add_extra_chain_cert_shim"] pub fn SSL_CTX_add_extra_chain_cert(ctx: *mut SSL_CTX, x509: *mut X509) -> c_long; #[link_name = "SSL_CTX_set_read_ahead_shim"] pub fn SSL_CTX_set_read_ahead(ctx: *mut SSL_CTX, m: c_long) -> c_long; #[link_name = "SSL_set_tlsext_host_name_shim"] pub fn SSL_set_tlsext_host_name(s: *mut SSL, name: *const c_char) -> c_long; + #[link_name = "X509_get_extensions_shim"] + pub fn X509_get_extensions(x: *mut X509) -> *mut stack_st_X509_EXTENSION; } pub mod probe; diff --git a/openssl-sys/src/openssl_shim.c b/openssl-sys/src/openssl_shim.c index 7b4f9c74d963abeaba09fbd5d134f53666d09385..f0622d2db34d1410b9535d6479d8c368029ad485 100644 --- a/openssl-sys/src/openssl_shim.c +++ b/openssl-sys/src/openssl_shim.c @@ -82,3 +82,7 @@ long SSL_CTX_set_read_ahead_shim(SSL_CTX *ctx, long m) { long SSL_set_tlsext_host_name_shim(SSL *s, char *name) { return SSL_set_tlsext_host_name(s, name); } + +STACK_OF(X509_EXTENSION) *X509_get_extensions_shim(X509 *x) { + return x->cert_info ? x->cert_info->extensions : NULL; +} diff --git a/openssl-sys/src/ssl_options.rs b/openssl-sys/src/ssl_options.rs new file mode 100644 index 0000000000000000000000000000000000000000..a1c778acf8ec9a85361437d25f88938fb19a7882 --- /dev/null +++ b/openssl-sys/src/ssl_options.rs @@ -0,0 +1,46 @@ +import_options!{ +// The following values are directly from recent OpenSSL +SSL_OP_MICROSOFT_SESS_ID_BUG 0x00000001 +SSL_OP_NETSCAPE_CHALLENGE_BUG 0x00000002 +SSL_OP_LEGACY_SERVER_CONNECT 0x00000004 +SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG 0x00000008 +SSL_OP_TLSEXT_PADDING 0x00000010 +SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER 0x00000020 +SSL_OP_SAFARI_ECDHE_ECDSA_BUG 0x00000040 +SSL_OP_SSLEAY_080_CLIENT_DH_BUG 0x00000080 +SSL_OP_TLS_D5_BUG 0x00000100 +SSL_OP_TLS_BLOCK_PADDING_BUG 0x00000200 +// unused: 0x00000400 +SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS 0x00000800 +SSL_OP_NO_QUERY_MTU 0x00001000 +SSL_OP_COOKIE_EXCHANGE 0x00002000 +SSL_OP_NO_TICKET 0x00004000 +SSL_OP_CISCO_ANYCONNECT 0x00008000 +SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION 0x00010000 +SSL_OP_NO_COMPRESSION 0x00020000 +SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x00040000 +SSL_OP_SINGLE_ECDH_USE 0x00080000 +SSL_OP_SINGLE_DH_USE 0x00100000 +// unused: 0x00200000 +SSL_OP_CIPHER_SERVER_PREFERENCE 0x00400000 +SSL_OP_TLS_ROLLBACK_BUG 0x00800000 +SSL_OP_NO_SSLv2 0x01000000 +SSL_OP_NO_SSLv3 0x02000000 +SSL_OP_NO_DTLSv1 0x04000000 +SSL_OP_NO_TLSv1 0x04000000 +SSL_OP_NO_DTLSv1_2 0x08000000 +SSL_OP_NO_TLSv1_2 0x08000000 +SSL_OP_NO_TLSv1_1 0x10000000 +SSL_OP_NETSCAPE_CA_DN_BUG 0x20000000 +SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG 0x40000000 +SSL_OP_CRYPTOPRO_TLSEXT_BUG 0x80000000 + +// The following values were in 32-bit range in old OpenSSL +SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG 0x100000000 +SSL_OP_MSIE_SSLV2_RSA_PADDING 0x200000000 +SSL_OP_PKCS1_CHECK_1 0x400000000 +SSL_OP_PKCS1_CHECK_2 0x800000000 + +// The following values were redefined to 0 for security reasons +SSL_OP_EPHEMERAL_RSA 0x0 +} diff --git a/openssl/Cargo.toml b/openssl/Cargo.toml index 8a6b4ddca61c9a8e121ffaddafe35cbe232ee5d7..b8bc357db48fe882fdd28452bd880ec29e95c8f7 100644 --- a/openssl/Cargo.toml +++ b/openssl/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "openssl" -version = "0.6.4" +version = "0.6.5" authors = ["Steven Fackler "] license = "Apache-2.0" description = "OpenSSL bindings" repository = "https://github.com/sfackler/rust-openssl" -documentation = "https://sfackler.github.io/rust-openssl/doc/v0.6.4/openssl" +documentation = "https://sfackler.github.io/rust-openssl/doc/v0.6.5/openssl" readme = "../README.md" keywords = ["crypto", "tls", "ssl", "dtls"] @@ -16,6 +16,7 @@ dtlsv1 = ["openssl-sys/dtlsv1"] dtlsv1_2 = ["openssl-sys/dtlsv1_2"] sslv2 = ["openssl-sys/sslv2"] aes_xts = ["openssl-sys/aes_xts"] +aes_ctr = ["openssl-sys/aes_ctr"] npn = ["openssl-sys/npn"] alpn = ["openssl-sys/alpn"] diff --git a/openssl/src/crypto/mod.rs b/openssl/src/crypto/mod.rs index e695de33ca6ae2bdbcc15afa81e343dc3420bf4f..a33c5eb8e8c4fb33794e349d1064e63c8c348a35 100644 --- a/openssl/src/crypto/mod.rs +++ b/openssl/src/crypto/mod.rs @@ -22,3 +22,5 @@ pub mod pkey; pub mod rand; pub mod symm; pub mod memcmp; + +mod symm_internal; \ No newline at end of file diff --git a/openssl/src/crypto/pkcs5.rs b/openssl/src/crypto/pkcs5.rs index b101c3ed45c1ff283f90fff420731f8b744f8c24..b5f6973278806ee5956458bc41af7f37992ab11f 100644 --- a/openssl/src/crypto/pkcs5.rs +++ b/openssl/src/crypto/pkcs5.rs @@ -1,6 +1,69 @@ use libc::c_int; +use std::ptr::null; + +use crypto::symm_internal::evpc; +use crypto::hash; +use crypto::symm; use ffi; +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct KeyIvPair +{ + pub key: Vec, + pub iv: Vec +} + +/// Derives a key and an IV from various parameters. +/// +/// If specified `salt` must be 8 bytes in length. +/// +/// If the total key and IV length is less than 16 bytes and MD5 is used then +/// the algorithm is compatible with the key derivation algorithm from PKCS#5 +/// v1.5 or PBKDF1 from PKCS#5 v2.0. +/// +/// New applications should not use this and instead use `pbkdf2_hmac_sha1` or +/// another more modern key derivation algorithm. +pub fn evp_bytes_to_key_pbkdf1_compatible(typ: symm::Type, message_digest_type: hash::Type, + data: &[u8], salt: Option<&[u8]>, + count: u32) -> KeyIvPair { + + unsafe { + + let salt_ptr = match salt { + Some(salt) => { + assert_eq!(salt.len(), ffi::PKCS5_SALT_LEN as usize); + salt.as_ptr() + }, + None => null() + }; + + ffi::init(); + + let (evp, keylen, _) = evpc(typ); + + let message_digest = message_digest_type.evp_md(); + + let mut key = vec![0; keylen as usize]; + let mut iv = vec![0; keylen as usize]; + + + let ret: c_int = ffi::EVP_BytesToKey(evp, + message_digest, + salt_ptr, + data.as_ptr(), + data.len() as c_int, + count as c_int, + key.as_mut_ptr(), + iv.as_mut_ptr()); + assert!(ret == keylen as c_int); + + KeyIvPair { + key: key, + iv: iv + } + } +} + /// Derives a key from a password and salt using the PBKDF2-HMAC-SHA1 algorithm. pub fn pbkdf2_hmac_sha1(pass: &str, salt: &[u8], iter: usize, keylen: usize) -> Vec { unsafe { @@ -27,6 +90,9 @@ pub fn pbkdf2_hmac_sha1(pass: &str, salt: &[u8], iter: usize, keylen: usize) -> #[cfg(test)] mod tests { + use crypto::hash; + use crypto::symm; + // Test vectors from // http://tools.ietf.org/html/draft-josefsson-pbkdf2-test-vectors-06 #[test] @@ -116,4 +182,47 @@ mod tests { ) ); } + + #[test] + fn test_evp_bytes_to_key_pbkdf1_compatible() { + let salt = [ + 16_u8, 34_u8, 19_u8, 23_u8, 141_u8, 4_u8, 207_u8, 221_u8 + ]; + + let data = [ + 143_u8, 210_u8, 75_u8, 63_u8, 214_u8, 179_u8, 155_u8, + 241_u8, 242_u8, 31_u8, 154_u8, 56_u8, 198_u8, 145_u8, 192_u8, 64_u8, + 2_u8, 245_u8, 167_u8, 220_u8, 55_u8, 119_u8, 233_u8, 136_u8, 139_u8, + 27_u8, 71_u8, 242_u8, 119_u8, 175_u8, 65_u8, 207_u8 + ]; + + + + let expected_key = vec![ + 249_u8, 115_u8, 114_u8, 97_u8, 32_u8, 213_u8, 165_u8, 146_u8, 58_u8, + 87_u8, 234_u8, 3_u8, 43_u8, 250_u8, 97_u8, 114_u8, 26_u8, 98_u8, + 245_u8, 246_u8, 238_u8, 177_u8, 229_u8, 161_u8, 183_u8, 224_u8, + 174_u8, 3_u8, 6_u8, 244_u8, 236_u8, 255_u8 + ]; + let expected_iv = vec![ + 4_u8, 223_u8, 153_u8, 219_u8, 28_u8, 142_u8, 234_u8, 68_u8, 227_u8, + 69_u8, 98_u8, 107_u8, 208_u8, 14_u8, 236_u8, 60_u8, 0_u8, 0_u8, + 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, + 0_u8, 0_u8, 0_u8 + ]; + + assert_eq!( + super::evp_bytes_to_key_pbkdf1_compatible( + symm::Type::AES_256_CBC, + hash::Type::SHA1, + &data, + Some(&salt), + 1 + ), + super::KeyIvPair { + key: expected_key, + iv: expected_iv + } + ); + } } diff --git a/openssl/src/crypto/pkey.rs b/openssl/src/crypto/pkey.rs index 1474e53cdc24f79afb66daf78e3a0e3f49300263..4830838174235f462f9a268d2e2448072680634a 100644 --- a/openssl/src/crypto/pkey.rs +++ b/openssl/src/crypto/pkey.rs @@ -182,6 +182,17 @@ impl PKey { writer.write_all(&buf).map_err(StreamError) } + /// Stores public key as a PEM + pub fn write_pub_pem(&self, writer: &mut W/*, password: Option*/) -> Result<(), SslError> { + let mut mem_bio = try!(MemBio::new()); + unsafe { + try_ssl!(ffi::PEM_write_bio_PUBKEY(mem_bio.get_handle(), self.evp)) + } + let mut buf = vec![]; + try!(mem_bio.read_to_end(&mut buf).map_err(StreamError)); + writer.write_all(&buf).map_err(StreamError) + } + /** * Returns the size of the public key modulus. */ @@ -500,4 +511,25 @@ mod tests { assert!(!k0.public_eq(&p1)); assert!(!p0.public_eq(&k1)); } + + #[test] + fn test_pem() { + let key_path = Path::new("test/key.pem"); + let mut file = File::open(&key_path) + .ok() + .expect("Failed to open `test/key.pem`"); + + let key = super::PKey::private_key_from_pem(&mut file).unwrap(); + + let mut priv_key = Vec::new(); + let mut pub_key = Vec::new(); + + key.write_pem(&mut priv_key).unwrap(); + key.write_pub_pem(&mut pub_key).unwrap(); + + // As a super-simple verification, just check that the buffers contain + // the `PRIVATE KEY` or `PUBLIC KEY` strings. + assert!(priv_key.windows(11).any(|s| s == b"PRIVATE KEY")); + assert!(pub_key.windows(10).any(|s| s == b"PUBLIC KEY")); + } } diff --git a/openssl/src/crypto/symm.rs b/openssl/src/crypto/symm.rs index 82c668bad3f118ce9cf296dbdfac0ac100e2e14a..226b2cbfbd51d1e64b5598206802103e520cc11c 100644 --- a/openssl/src/crypto/symm.rs +++ b/openssl/src/crypto/symm.rs @@ -2,6 +2,7 @@ use std::iter::repeat; use std::convert::AsRef; use libc::{c_int}; +use crypto::symm_internal::evpc; use ffi; #[derive(Copy, Clone)] @@ -18,7 +19,8 @@ pub enum Type { /// Requires the `aes_xts` feature #[cfg(feature = "aes_xts")] AES_128_XTS, - // AES_128_CTR, + #[cfg(feature = "aes_ctr")] + AES_128_CTR, //AES_128_GCM, AES_256_ECB, @@ -26,33 +28,13 @@ pub enum Type { /// Requires the `aes_xts` feature #[cfg(feature = "aes_xts")] AES_256_XTS, - // AES_256_CTR, + #[cfg(feature = "aes_ctr")] + AES_256_CTR, //AES_256_GCM, RC4_128, } -fn evpc(t: Type) -> (*const ffi::EVP_CIPHER, u32, u32) { - unsafe { - match t { - Type::AES_128_ECB => (ffi::EVP_aes_128_ecb(), 16, 16), - Type::AES_128_CBC => (ffi::EVP_aes_128_cbc(), 16, 16), - #[cfg(feature = "aes_xts")] - Type::AES_128_XTS => (ffi::EVP_aes_128_xts(), 32, 16), - // AES_128_CTR => (EVP_aes_128_ctr(), 16, 0), - //AES_128_GCM => (EVP_aes_128_gcm(), 16, 16), - - Type::AES_256_ECB => (ffi::EVP_aes_256_ecb(), 32, 16), - Type::AES_256_CBC => (ffi::EVP_aes_256_cbc(), 32, 16), - #[cfg(feature = "aes_xts")] - Type::AES_256_XTS => (ffi::EVP_aes_256_xts(), 64, 16), - // AES_256_CTR => (EVP_aes_256_ctr(), 32, 0), - //AES_256_GCM => (EVP_aes_256_gcm(), 32, 16), - - Type::RC4_128 => (ffi::EVP_rc4(), 16, 0), - } - } -} /// Represents a symmetric cipher context. pub struct Crypter { @@ -288,16 +270,17 @@ mod tests { cipher_test(super::Type::AES_256_XTS, pt, ct, key, iv); } - /*#[test] + #[test] + #[cfg(feature = "aes_ctr")] fn test_aes128_ctr() { - let pt = ~"6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710"; - let ct = ~"874D6191B620E3261BEF6864990DB6CE9806F66B7970FDFF8617187BB9FFFDFF5AE4DF3EDBD5D35E5B4F09020DB03EAB1E031DDA2FBE03D1792170A0F3009CEE"; - let key = ~"2B7E151628AED2A6ABF7158809CF4F3C"; - let iv = ~"F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"; + let pt = "6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710"; + let ct = "874D6191B620E3261BEF6864990DB6CE9806F66B7970FDFF8617187BB9FFFDFF5AE4DF3EDBD5D35E5B4F09020DB03EAB1E031DDA2FBE03D1792170A0F3009CEE"; + let key = "2B7E151628AED2A6ABF7158809CF4F3C"; + let iv = "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"; - cipher_test(super::AES_128_CTR, pt, ct, key, iv); - }*/ + cipher_test(super::Type::AES_128_CTR, pt, ct, key, iv); + } /*#[test] fn test_aes128_gcm() { diff --git a/openssl/src/crypto/symm_internal.rs b/openssl/src/crypto/symm_internal.rs new file mode 100644 index 0000000000000000000000000000000000000000..c42efb79d12c70c40ba3dba07fa1eb11a6fb4fbb --- /dev/null +++ b/openssl/src/crypto/symm_internal.rs @@ -0,0 +1,26 @@ +use crypto::symm; +use ffi; + +pub fn evpc(t: symm::Type) -> (*const ffi::EVP_CIPHER, u32, u32) { + unsafe { + match t { + symm::Type::AES_128_ECB => (ffi::EVP_aes_128_ecb(), 16, 16), + symm::Type::AES_128_CBC => (ffi::EVP_aes_128_cbc(), 16, 16), + #[cfg(feature = "aes_xts")] + symm::Type::AES_128_XTS => (ffi::EVP_aes_128_xts(), 32, 16), + #[cfg(feature = "aes_ctr")] + symm::Type::AES_128_CTR => (ffi::EVP_aes_128_ctr(), 16, 0), + //AES_128_GCM => (EVP_aes_128_gcm(), 16, 16), + + symm::Type::AES_256_ECB => (ffi::EVP_aes_256_ecb(), 32, 16), + symm::Type::AES_256_CBC => (ffi::EVP_aes_256_cbc(), 32, 16), + #[cfg(feature = "aes_xts")] + symm::Type::AES_256_XTS => (ffi::EVP_aes_256_xts(), 64, 16), + #[cfg(feature = "aes_ctr")] + symm::Type::AES_256_CTR => (ffi::EVP_aes_256_ctr(), 32, 0), + //AES_256_GCM => (EVP_aes_256_gcm(), 32, 16), + + symm::Type::RC4_128 => (ffi::EVP_rc4(), 16, 0), + } + } +} \ No newline at end of file diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 62d18dcebc9dd3d028aad568ac958d28d34d2ff7..17e625b931574a59f5ce579917019a4d50e5586e 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -1,4 +1,4 @@ -#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.6.4")] +#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.6.5")] #[macro_use] extern crate bitflags; diff --git a/openssl/src/nid.rs b/openssl/src/nid.rs index c5b9e277446df584de229a7244b056145ce21f9d..81cc49759602dbc70b38526e9866891c6b0ebb71 100644 --- a/openssl/src/nid.rs +++ b/openssl/src/nid.rs @@ -1,5 +1,5 @@ #[allow(non_camel_case_types)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] #[repr(usize)] pub enum Nid { Undefined, diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 88ba9af440d60d862d73505ca8e56999642c4c29..35180d3a191456ed2a2f8df704d09a029d7bcc7f 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -6,6 +6,7 @@ use std::fmt; use std::io; use std::io::prelude::*; use std::mem; +use std::str; use std::net; use std::path::Path; use std::ptr; @@ -30,7 +31,9 @@ mod tests; static mut VERIFY_IDX: c_int = -1; -fn init() { +/// Manually initialize SSL. +/// It is optional to call this function and safe to do so more than once. +pub fn init() { static mut INIT: Once = ONCE_INIT; unsafe { @@ -46,35 +49,52 @@ fn init() { } bitflags! { - flags SslContextOptions: c_long { - const SSL_OP_LEGACY_SERVER_CONNECT = 0x00000004, - const SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000008, - const SSL_OP_TLSEXT_PADDING = 0x00000010, - const SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER = 0x00000020, - const SSL_OP_SAFARI_ECDHE_ECDSA_BUG = 0x00000040, - const SSL_OP_SSLEAY_080_CLIENT_DH_BUG = 0x00000080, - const SSL_OP_TLS_D5_BUG = 0x00000100, - const SSL_OP_TLS_BLOCK_PADDING_BUG = 0x00000200, - const SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = 0x00000800, - const SSL_OP_ALL = 0x80000BFF, - const SSL_OP_NO_QUERY_MTU = 0x00001000, - const SSL_OP_COOKIE_EXCHANGE = 0x00002000, - const SSL_OP_NO_TICKET = 0x00004000, - const SSL_OP_CISCO_ANYCONNECT = 0x00008000, - const SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = 0x00010000, - const SSL_OP_NO_COMPRESSION = 0x00020000, - const SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION = 0x00040000, - const SSL_OP_SINGLE_ECDH_USE = 0x00080000, - const SSL_OP_SINGLE_DH_USE = 0x00100000, - const SSL_OP_CIPHER_SERVER_PREFERENCE = 0x00400000, - const SSL_OP_TLS_ROLLBACK_BUG = 0x00800000, - const SSL_OP_NO_SSLV2 = 0x00000000, - const SSL_OP_NO_SSLV3 = 0x02000000, - const SSL_OP_NO_TLSV1 = 0x04000000, - const SSL_OP_NO_TLSV1_2 = 0x08000000, - const SSL_OP_NO_TLSV1_1 = 0x10000000, - const SSL_OP_NO_DTLSV1 = 0x04000000, - const SSL_OP_NO_DTLSV1_2 = 0x08000000 + flags SslContextOptions: u64 { + const SSL_OP_MICROSOFT_SESS_ID_BUG = ffi::SSL_OP_MICROSOFT_SESS_ID_BUG, + const SSL_OP_NETSCAPE_CHALLENGE_BUG = ffi::SSL_OP_NETSCAPE_CHALLENGE_BUG, + const SSL_OP_LEGACY_SERVER_CONNECT = ffi::SSL_OP_LEGACY_SERVER_CONNECT, + const SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = ffi::SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG, + const SSL_OP_TLSEXT_PADDING = ffi::SSL_OP_TLSEXT_PADDING, + const SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER = ffi::SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER, + const SSL_OP_SAFARI_ECDHE_ECDSA_BUG = ffi::SSL_OP_SAFARI_ECDHE_ECDSA_BUG, + const SSL_OP_SSLEAY_080_CLIENT_DH_BUG = ffi::SSL_OP_SSLEAY_080_CLIENT_DH_BUG, + const SSL_OP_TLS_D5_BUG = ffi::SSL_OP_TLS_D5_BUG, + const SSL_OP_TLS_BLOCK_PADDING_BUG = ffi::SSL_OP_TLS_BLOCK_PADDING_BUG, + const SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = ffi::SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, + const SSL_OP_NO_QUERY_MTU = ffi::SSL_OP_NO_QUERY_MTU, + const SSL_OP_COOKIE_EXCHANGE = ffi::SSL_OP_COOKIE_EXCHANGE, + const SSL_OP_NO_TICKET = ffi::SSL_OP_NO_TICKET, + const SSL_OP_CISCO_ANYCONNECT = ffi::SSL_OP_CISCO_ANYCONNECT, + const SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = ffi::SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION, + const SSL_OP_NO_COMPRESSION = ffi::SSL_OP_NO_COMPRESSION, + const SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION = ffi::SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION, + const SSL_OP_SINGLE_ECDH_USE = ffi::SSL_OP_SINGLE_ECDH_USE, + const SSL_OP_SINGLE_DH_USE = ffi::SSL_OP_SINGLE_DH_USE, + const SSL_OP_CIPHER_SERVER_PREFERENCE = ffi::SSL_OP_CIPHER_SERVER_PREFERENCE, + const SSL_OP_TLS_ROLLBACK_BUG = ffi::SSL_OP_TLS_ROLLBACK_BUG, + const SSL_OP_NO_SSLV2 = ffi::SSL_OP_NO_SSLv2, + const SSL_OP_NO_SSLV3 = ffi::SSL_OP_NO_SSLv3, + const SSL_OP_NO_DTLSV1 = ffi::SSL_OP_NO_DTLSv1, + const SSL_OP_NO_TLSV1 = ffi::SSL_OP_NO_TLSv1, + const SSL_OP_NO_DTLSV1_2 = ffi::SSL_OP_NO_DTLSv1_2, + const SSL_OP_NO_TLSV1_2 = ffi::SSL_OP_NO_TLSv1_2, + const SSL_OP_NO_TLSV1_1 = ffi::SSL_OP_NO_TLSv1_1, + const SSL_OP_NETSCAPE_CA_DN_BUG = ffi::SSL_OP_NETSCAPE_CA_DN_BUG, + const SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG = ffi::SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG, + const SSL_OP_CRYPTOPRO_TLSEXT_BUG = ffi::SSL_OP_CRYPTOPRO_TLSEXT_BUG, + const SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG = ffi::SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG, + const SSL_OP_MSIE_SSLV2_RSA_PADDING = ffi::SSL_OP_MSIE_SSLV2_RSA_PADDING, + const SSL_OP_PKCS1_CHECK_1 = ffi::SSL_OP_PKCS1_CHECK_1, + const SSL_OP_PKCS1_CHECK_2 = ffi::SSL_OP_PKCS1_CHECK_2, + const SSL_OP_EPHEMERAL_RSA = ffi::SSL_OP_EPHEMERAL_RSA, + const SSL_OP_ALL = SSL_OP_MICROSOFT_SESS_ID_BUG.bits|SSL_OP_NETSCAPE_CHALLENGE_BUG.bits + |SSL_OP_LEGACY_SERVER_CONNECT.bits|SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG.bits + |SSL_OP_TLSEXT_PADDING.bits|SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER.bits + |SSL_OP_SAFARI_ECDHE_ECDSA_BUG.bits|SSL_OP_SSLEAY_080_CLIENT_DH_BUG.bits + |SSL_OP_TLS_D5_BUG.bits|SSL_OP_TLS_BLOCK_PADDING_BUG.bits + |SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS.bits|SSL_OP_CRYPTOPRO_TLSEXT_BUG.bits, + const SSL_OP_NO_SSL_MASK = SSL_OP_NO_SSLV2.bits|SSL_OP_NO_SSLV3.bits|SSL_OP_NO_TLSV1.bits + |SSL_OP_NO_TLSV1_1.bits|SSL_OP_NO_TLSV1_2.bits, } } @@ -124,6 +144,25 @@ impl SslMethod { } } + unsafe fn from_raw(method: *const ffi::SSL_METHOD) -> Option { + match method { + #[cfg(feature = "sslv2")] + x if x == ffi::SSLv2_method() => Some(SslMethod::Sslv2), + x if x == ffi::SSLv3_method() => Some(SslMethod::Sslv3), + x if x == ffi::TLSv1_method() => Some(SslMethod::Tlsv1), + x if x == ffi::SSLv23_method() => Some(SslMethod::Sslv23), + #[cfg(feature = "tlsv1_1")] + x if x == ffi::TLSv1_1_method() => Some(SslMethod::Tlsv1_1), + #[cfg(feature = "tlsv1_2")] + x if x == ffi::TLSv1_2_method() => Some(SslMethod::Tlsv1_2), + #[cfg(feature = "dtlsv1")] + x if x == ffi::DTLSv1_method() => Some(SslMethod::Dtlsv1), + #[cfg(feature = "dtlsv1_2")] + x if x == ffi::DTLSv1_2_method() => Some(SslMethod::Dtlsv1_2), + _ => None, + } + } + #[cfg(feature = "dtlsv1")] pub fn is_dtlsv1(&self) -> bool { *self == SslMethod::Dtlsv1 @@ -652,6 +691,24 @@ impl Ssl { Ok(ssl) } + pub fn get_state_string(&self) -> &'static str { + let state = unsafe { + let ptr = ffi::SSL_state_string(self.ssl); + CStr::from_ptr(ptr) + }; + + str::from_utf8(state.to_bytes()).unwrap() + } + + pub fn get_state_string_long(&self) -> &'static str { + let state = unsafe { + let ptr = ffi::SSL_state_string_long(self.ssl); + CStr::from_ptr(ptr) + }; + + str::from_utf8(state.to_bytes()).unwrap() + } + fn get_rbio<'a>(&'a self) -> MemBioRef<'a> { unsafe { self.wrap_bio(ffi::SSL_get_rbio(self.ssl)) } } @@ -770,6 +827,13 @@ impl Ssl { ffi::SSL_pending(self.ssl) as usize } } + + pub fn get_ssl_method(&self) -> Option { + unsafe { + let method = ffi::SSL_get_ssl_method(self.ssl); + SslMethod::from_raw(method) + } + } } macro_rules! make_LibSslError { @@ -871,8 +935,16 @@ impl IndirectStream { LibSslError::ErrorWantRead => { try_ssl_stream!(self.flush()); let len = try_ssl_stream!(self.stream.read(&mut self.buf[..])); + + if len == 0 { - self.ssl.get_rbio().set_eof(true); + let method = self.ssl.get_ssl_method(); + + if method.map(|m| m.is_dtls()).unwrap_or(false) { + return Ok(0); + } else { + self.ssl.get_rbio().set_eof(true); + } } else { try_ssl_stream!(self.ssl.get_rbio().write_all(&self.buf[..len])); } @@ -993,6 +1065,9 @@ impl DirectStream { err } } + LibSslError::ErrorWantWrite | LibSslError::ErrorWantRead => { + SslError::StreamError(io::Error::last_os_error()) + } err => panic!("unexpected error {:?} with ret {}", err, ret), } } @@ -1260,6 +1335,14 @@ impl SslStream { pub fn pending(&self) -> usize { self.kind.ssl().pending() } + + pub fn get_state_string(&self) -> &'static str { + self.kind.ssl().get_state_string() + } + + pub fn get_state_string_long(&self) -> &'static str { + self.kind.ssl().get_state_string_long() + } } impl Read for SslStream { diff --git a/openssl/src/ssl/tests.rs b/openssl/src/ssl/tests.rs index 8401836d74e4c4116db27f7c0e077965dbf00f5d..9198a642a4003568c7acb0775268c644c46442ec 100644 --- a/openssl/src/ssl/tests.rs +++ b/openssl/src/ssl/tests.rs @@ -51,7 +51,7 @@ macro_rules! run_test( use std::net::TcpStream; use ssl; use ssl::SslMethod; - use ssl::{SslContext, SslStream, VerifyCallback}; + use ssl::{SslContext, Ssl, SslStream, VerifyCallback}; use ssl::SSL_VERIFY_PEER; use crypto::hash::Type::SHA256; use x509::X509StoreContext; @@ -86,6 +86,11 @@ run_test!(new_sslstream, |method, stream| { SslStream::connect_generic(&SslContext::new(method).unwrap(), stream).unwrap(); }); +run_test!(get_ssl_method, |method, _| { + let ssl = Ssl::new(&SslContext::new(method).unwrap()).unwrap(); + assert_eq!(ssl.get_ssl_method(), Some(method)); +}); + run_test!(verify_untrusted, |method, stream| { let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, None); @@ -390,6 +395,14 @@ fn test_pending() { assert_eq!(pending, len); } +#[test] +fn test_state() { + let tcp = TcpStream::connect("127.0.0.1:15418").unwrap(); + let stream = SslStream::connect_generic(&SslContext::new(Sslv23).unwrap(), tcp).unwrap(); + assert_eq!(stream.get_state_string(), "SSLOK "); + assert_eq!(stream.get_state_string_long(), "SSL negotiation finished successfully"); +} + /// Tests that connecting with the client using NPN, but the server not does not /// break the existing connection behavior. #[test] diff --git a/openssl/src/x509/extension.rs b/openssl/src/x509/extension.rs new file mode 100644 index 0000000000000000000000000000000000000000..3faa099665ca7678fe7303a4505fb18939bf8a32 --- /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), + /// The extended purposes of the key contained in the certificate + ExtKeyUsage(Vec), + /// 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 { + 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. This can +// eventually be replaced by the successor to std::slice::SliceConcatExt.connect +fn join,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 5446f125990fbba3f9716195cc45df2d2d2cdf2e..91daa66a2bab48cc84c0acc57e58931ff5870681 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 { - 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 { pub struct X509Generator { bits: u32, days: u32, - CN: String, - key_usage: Vec, - ext_key_usage: Vec, + names: Vec<(String,String)>, + // RFC 3280 ยง4.2: A certificate MUST NOT include more than one instance of a particular extension. + extensions: HashMap, 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(mut self, attrs: I) -> X509Generator + where I: IntoIterator { + 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(mut self, exts: I) -> X509Generator + where I: IntoIterator { + 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 = + 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 4e1c4f15728a9ef6427281ebd3d55fab87426db6..692539ba8ae7667d53e42f088f0230c6459e2d0f 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 @@ -33,6 +37,18 @@ fn test_cert_gen() { assert_eq!(pkey.save_pub(), cert.public_key().save_pub()); } +#[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"); diff --git a/openssl/test/build.sh b/openssl/test/build.sh index 27def60a6494b7bdee93aa89918d81363c557a82..db517ab3b4727ce9ab35f8b67b91a5d844cc7115 100755 --- a/openssl/test/build.sh +++ b/openssl/test/build.sh @@ -4,7 +4,7 @@ set -e mkdir /tmp/openssl cd /tmp/openssl sudo apt-get install gcc make -curl https://openssl.org/source/openssl-1.0.2-latest.tar.gz | tar --strip-components=1 -xzf - +curl https://openssl.org/source/openssl-1.0.2d.tar.gz | tar --strip-components=1 -xzf - ./config --prefix=/usr/ shared make sudo make install