diff options
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | appveyor.yml | 8 | ||||
| -rw-r--r-- | openssl-sys-extras/Cargo.toml | 6 | ||||
| -rw-r--r-- | openssl-sys-extras/src/lib.rs | 2 | ||||
| -rw-r--r-- | openssl-sys-extras/src/openssl_shim.c | 4 | ||||
| -rw-r--r-- | openssl-sys/Cargo.toml | 4 | ||||
| -rw-r--r-- | openssl-sys/src/lib.rs | 59 | ||||
| -rw-r--r-- | openssl/Cargo.toml | 13 | ||||
| -rw-r--r-- | openssl/src/bn/mod.rs | 19 | ||||
| -rw-r--r-- | openssl/src/c_helpers.c | 8 | ||||
| -rw-r--r-- | openssl/src/crypto/mod.rs | 1 | ||||
| -rw-r--r-- | openssl/src/crypto/pkey.rs | 117 | ||||
| -rw-r--r-- | openssl/src/crypto/rsa.rs | 121 | ||||
| -rw-r--r-- | openssl/src/lib.rs | 3 | ||||
| -rw-r--r-- | openssl/src/nid.rs | 3 | ||||
| -rw-r--r-- | openssl/src/ssl/bio.rs | 98 | ||||
| -rw-r--r-- | openssl/src/ssl/mod.rs | 172 | ||||
| -rw-r--r-- | openssl/src/ssl/tests/mod.rs | 106 | ||||
| -rw-r--r-- | openssl/src/x509/mod.rs | 97 | ||||
| -rw-r--r-- | openssl/src/x509/tests.rs | 41 | ||||
| -rwxr-xr-x | openssl/test/build.sh | 2 | ||||
| -rw-r--r-- | openssl/test/nid_uid_test_cert.pem | 24 | ||||
| -rwxr-xr-x | openssl/test/run.sh | 6 |
23 files changed, 848 insertions, 70 deletions
@@ -2,7 +2,7 @@ [](https://travis-ci.org/sfackler/rust-openssl) -[Documentation](https://sfackler.github.io/rust-openssl/doc/v0.7.4/openssl). +[Documentation](https://sfackler.github.io/rust-openssl/doc/v0.7.6/openssl). ## Building @@ -19,6 +19,8 @@ something like `openssl-devel` or `libssl-dev`. sudo apt-get install libssl-dev # On Arch Linux sudo pacman -S openssl +# On Fedora +sudo dnf install openssl-devel ``` ### OSX diff --git a/appveyor.yml b/appveyor.yml index 9cb9ae95..fff3cff7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,11 +5,11 @@ environment: matrix: - TARGET: i686-pc-windows-gnu BITS: 32 - - TARGET: x86_64-pc-windows-msvc - BITS: 64 +# - TARGET: x86_64-pc-windows-msvc +# BITS: 64 install: - - ps: Start-FileDownload "http://slproweb.com/download/Win${env:BITS}OpenSSL-1_0_2e.exe" - - Win%BITS%OpenSSL-1_0_2e.exe /SILENT /VERYSILENT /SP- /DIR="C:\OpenSSL" + - ps: Start-FileDownload "http://slproweb.com/download/Win${env:BITS}OpenSSL-1_0_2g.exe" + - Win%BITS%OpenSSL-1_0_2g.exe /SILENT /VERYSILENT /SP- /DIR="C:\OpenSSL" - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.5.0-${env:TARGET}.exe" - rust-1.5.0-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin diff --git a/openssl-sys-extras/Cargo.toml b/openssl-sys-extras/Cargo.toml index 01a49e78..0607d3bb 100644 --- a/openssl-sys-extras/Cargo.toml +++ b/openssl-sys-extras/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "openssl-sys-extras" -version = "0.7.4" +version = "0.7.6" authors = ["Steven Fackler <[email protected]>"] license = "MIT" description = "Extra FFI bindings to OpenSSL that require a C shim" repository = "https://github.com/sfackler/rust-openssl" -documentation = "https://sfackler.github.io/rust-openssl/doc/v0.7.4/openssl_sys_extras" +documentation = "https://sfackler.github.io/rust-openssl/doc/v0.7.6/openssl_sys_extras" build = "build.rs" [features] @@ -13,7 +13,7 @@ ecdh_auto = [] [dependencies] libc = "0.2" -openssl-sys = { version = "0.7.4", path = "../openssl-sys" } +openssl-sys = { version = "0.7.6", path = "../openssl-sys" } [build-dependencies] gcc = "0.3" diff --git a/openssl-sys-extras/src/lib.rs b/openssl-sys-extras/src/lib.rs index 6097155a..9647929a 100644 --- a/openssl-sys-extras/src/lib.rs +++ b/openssl-sys-extras/src/lib.rs @@ -1,5 +1,5 @@ #![allow(non_upper_case_globals, non_snake_case)] -#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.7.4")] +#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.7.6")] extern crate openssl_sys; extern crate libc; diff --git a/openssl-sys-extras/src/openssl_shim.c b/openssl-sys-extras/src/openssl_shim.c index c3deeebc..a4b40280 100644 --- a/openssl-sys-extras/src/openssl_shim.c +++ b/openssl-sys-extras/src/openssl_shim.c @@ -5,8 +5,8 @@ #if defined(__APPLE__) || defined(__linux) -#include<pthread.h> -#include<openssl/crypto.h> +#include <pthread.h> +#include <openssl/crypto.h> unsigned long thread_id() { diff --git a/openssl-sys/Cargo.toml b/openssl-sys/Cargo.toml index 735c4d51..c8410104 100644 --- a/openssl-sys/Cargo.toml +++ b/openssl-sys/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "openssl-sys" -version = "0.7.4" +version = "0.7.6" authors = ["Alex Crichton <[email protected]>", "Steven Fackler <[email protected]>"] license = "MIT" description = "FFI bindings to OpenSSL" repository = "https://github.com/sfackler/rust-openssl" -documentation = "https://sfackler.github.io/rust-openssl/doc/v0.7.4/openssl_sys" +documentation = "https://sfackler.github.io/rust-openssl/doc/v0.7.6/openssl_sys" links = "openssl" build = "build.rs" diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index f780b6d9..d638b38a 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.7.4")] +#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.7.6")] extern crate libc; @@ -22,10 +22,9 @@ pub type ENGINE = c_void; pub type EVP_CIPHER = c_void; pub type EVP_CIPHER_CTX = c_void; pub type EVP_MD = c_void; -pub type EVP_PKEY = c_void; pub type EVP_PKEY_CTX = c_void; -pub type RSA = c_void; pub type SSL = c_void; +pub type SSL_CIPHER = c_void; pub type SSL_CTX = c_void; pub type SSL_METHOD = c_void; pub type X509 = c_void; @@ -48,7 +47,6 @@ pub type bio_info_cb = Option<unsafe extern "C" fn(*mut BIO, #[repr(C)] #[derive(Copy, Clone)] -#[allow(raw_pointer_derive)] pub struct BIO_METHOD { pub type_: c_int, pub name: *const c_char, @@ -66,6 +64,47 @@ pub struct BIO_METHOD { unsafe impl Sync for BIO_METHOD {} #[repr(C)] +pub struct RSA { + pub pad: c_int, + pub version: c_long, + pub meth: *const c_void, + + pub engine: *mut c_void, + pub n: *mut BIGNUM, + pub e: *mut BIGNUM, + pub d: *mut BIGNUM, + pub p: *mut BIGNUM, + pub q: *mut BIGNUM, + pub dmp1: *mut BIGNUM, + pub dmq1: *mut BIGNUM, + pub iqmp: *mut BIGNUM, + + pub ex_data: *mut c_void, + pub references: c_int, + pub flags: c_int, + + pub _method_mod_n: *mut c_void, + pub _method_mod_p: *mut c_void, + pub _method_mod_q: *mut c_void, + + pub bignum_data: *mut c_char, + pub blinding: *mut c_void, + pub mt_blinding: *mut c_void, +} + +#[repr(C)] +pub struct EVP_PKEY { + pub type_: c_int, + pub save_type: c_int, + pub references: c_int, + pub ameth: *const c_void, + pub engine: *mut ENGINE, + pub pkey: *mut c_void, + pub save_parameters: c_int, + pub attributes: *mut c_void, +} + +#[repr(C)] pub struct BIO { pub method: *mut BIO_METHOD, pub callback: Option<unsafe extern "C" fn(*mut BIO, @@ -527,6 +566,9 @@ extern "C" { pub fn PEM_read_bio_PUBKEY(bio: *mut BIO, out: *mut *mut EVP_PKEY, callback: Option<PasswordCallback>, user_data: *mut c_void) -> *mut X509; + pub fn PEM_read_bio_RSAPrivateKey(bio: *mut BIO, rsa: *mut *mut RSA, callback: Option<PasswordCallback>, user_data: *mut c_void) -> *mut RSA; + pub fn PEM_read_bio_RSA_PUBKEY(bio: *mut BIO, rsa: *mut *mut RSA, callback: Option<PasswordCallback>, user_data: *mut c_void) -> *mut RSA; + pub fn PEM_write_bio_PrivateKey(bio: *mut BIO, pkey: *mut EVP_PKEY, cipher: *const EVP_CIPHER, kstr: *mut c_char, klen: c_int, callback: Option<PasswordCallback>, @@ -547,6 +589,7 @@ extern "C" { pub fn RAND_bytes(buf: *mut u8, num: c_int) -> c_int; + pub fn RSA_new() -> *mut RSA; pub fn RSA_free(rsa: *mut RSA); pub fn RSA_generate_key(modsz: c_int, e: c_ulong, cb: *const c_void, cbarg: *const c_void) -> *mut RSA; pub fn RSA_generate_key_ex(rsa: *mut RSA, bits: c_int, e: *mut BIGNUM, cb: *const c_void) -> c_int; @@ -601,6 +644,7 @@ extern "C" { 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_get_version(ssl: *mut SSL) -> *const c_char; pub fn SSL_state_string(ssl: *mut SSL) -> *const c_char; pub fn SSL_state_string_long(ssl: *mut SSL) -> *const c_char; @@ -608,6 +652,13 @@ extern "C" { pub fn SSL_COMP_get_name(comp: *const COMP_METHOD) -> *const c_char; + pub fn SSL_get_current_cipher(ssl: *const SSL) -> *const SSL_CIPHER; + + pub fn SSL_CIPHER_get_name(cipher: *const SSL_CIPHER) -> *const c_char; + pub fn SSL_CIPHER_get_bits(cipher: *const SSL_CIPHER, alg_bits: *const c_int) -> c_int; + pub fn SSL_CIPHER_get_version(cipher: *const SSL_CIPHER) -> *const c_char; + pub fn SSL_CIPHER_description(cipher: *const SSL_CIPHER, buf: *mut c_char, size: c_int) -> *const c_char; + pub fn SSL_CTX_new(method: *const SSL_METHOD) -> *mut SSL_CTX; pub fn SSL_CTX_free(ctx: *mut SSL_CTX); pub fn SSL_CTX_set_verify(ctx: *mut SSL_CTX, mode: c_int, diff --git a/openssl/Cargo.toml b/openssl/Cargo.toml index 9c56623e..10d8ea4b 100644 --- a/openssl/Cargo.toml +++ b/openssl/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "openssl" -version = "0.7.4" +version = "0.7.6" authors = ["Steven Fackler <[email protected]>"] license = "Apache-2.0" description = "OpenSSL bindings" repository = "https://github.com/sfackler/rust-openssl" -documentation = "https://sfackler.github.io/rust-openssl/doc/v0.7.4/openssl" +documentation = "https://sfackler.github.io/rust-openssl/doc/v0.7.6/openssl" readme = "../README.md" keywords = ["crypto", "tls", "ssl", "dtls"] build = "build.rs" +exclude = ["test/*"] [features] tlsv1_2 = ["openssl-sys/tlsv1_2"] @@ -25,12 +26,14 @@ rfc5114 = ["openssl-sys/rfc5114"] ecdh_auto = ["openssl-sys-extras/ecdh_auto"] pkcs5_pbkdf2_hmac = ["openssl-sys/pkcs5_pbkdf2_hmac"] +nightly = [] + [dependencies] -bitflags = ">= 0.2, < 0.4" +bitflags = "0.4" lazy_static = "0.1" libc = "0.2" -openssl-sys = { version = "0.7.4", path = "../openssl-sys" } -openssl-sys-extras = { version = "0.7.4", path = "../openssl-sys-extras" } +openssl-sys = { version = "0.7.6", path = "../openssl-sys" } +openssl-sys-extras = { version = "0.7.6", path = "../openssl-sys-extras" } [build-dependencies] gcc = "0.3" diff --git a/openssl/src/bn/mod.rs b/openssl/src/bn/mod.rs index 51a49241..d548e9ef 100644 --- a/openssl/src/bn/mod.rs +++ b/openssl/src/bn/mod.rs @@ -1,7 +1,7 @@ use libc::{c_int, c_ulong, c_void}; use std::ffi::{CStr, CString}; use std::cmp::Ordering; -use std::{fmt, ptr}; +use std::{fmt, ptr, mem}; use ffi; use ssl::error::SslError; @@ -102,6 +102,18 @@ impl BigNum { }) } + pub unsafe fn new_from_ffi(orig: *mut ffi::BIGNUM) -> Result<BigNum, SslError> { + if orig.is_null() { + panic!("Null Pointer was supplied to BigNum::new_from_ffi"); + } + let r = ffi::BN_dup(orig); + if r.is_null() { + Err(SslError::get()) + } else { + Ok(BigNum(r)) + } + } + pub fn new_from_slice(n: &[u8]) -> Result<BigNum, SslError> { BigNum::new().and_then(|v| unsafe { try_ssl_null!(ffi::BN_bin2bn(n.as_ptr(), n.len() as c_int, v.raw())); @@ -461,6 +473,11 @@ impl BigNum { n } + pub fn into_raw(self) -> *mut ffi::BIGNUM { + let mut me = self; + mem::replace(&mut me.0, ptr::null_mut()) + } + pub fn to_vec(&self) -> Vec<u8> { let size = self.num_bytes() as usize; let mut v = Vec::with_capacity(size); diff --git a/openssl/src/c_helpers.c b/openssl/src/c_helpers.c index 402c36ec..1b48565e 100644 --- a/openssl/src/c_helpers.c +++ b/openssl/src/c_helpers.c @@ -7,3 +7,11 @@ void rust_SSL_clone(SSL *ssl) { void rust_SSL_CTX_clone(SSL_CTX *ctx) { CRYPTO_add(&ctx->references,1,CRYPTO_LOCK_SSL_CTX); } + +void rust_EVP_PKEY_clone(EVP_PKEY *pkey) { + CRYPTO_add(&pkey->references,1,CRYPTO_LOCK_EVP_PKEY); +} + +void rust_X509_clone(X509 *x509) { + CRYPTO_add(&x509->references,1,CRYPTO_LOCK_X509); +} diff --git a/openssl/src/crypto/mod.rs b/openssl/src/crypto/mod.rs index 0868ee95..bb77453f 100644 --- a/openssl/src/crypto/mod.rs +++ b/openssl/src/crypto/mod.rs @@ -21,5 +21,6 @@ pub mod pkey; pub mod rand; pub mod symm; pub mod memcmp; +pub mod rsa; mod symm_internal; diff --git a/openssl/src/crypto/pkey.rs b/openssl/src/crypto/pkey.rs index 10891224..cafd50ad 100644 --- a/openssl/src/crypto/pkey.rs +++ b/openssl/src/crypto/pkey.rs @@ -9,6 +9,7 @@ use crypto::hash; use crypto::hash::Type as HashType; use ffi; use ssl::error::{SslError, StreamError}; +use crypto::rsa::RSA; #[derive(Copy, Clone)] pub enum Parts { @@ -52,11 +53,18 @@ fn openssl_hash_nid(hash: HashType) -> c_int { } } +extern "C" { + fn rust_EVP_PKEY_clone(pkey: *mut ffi::EVP_PKEY); +} + pub struct PKey { evp: *mut ffi::EVP_PKEY, parts: Parts, } +unsafe impl Send for PKey {} +unsafe impl Sync for PKey {} + /// Represents a public key, optionally with a private key attached. impl PKey { pub fn new() -> PKey { @@ -93,7 +101,7 @@ impl PKey { None, ptr::null_mut())); Ok(PKey { - evp: evp, + evp: evp as *mut ffi::EVP_PKEY, parts: Parts::Both, }) } @@ -112,6 +120,38 @@ impl PKey { None, ptr::null_mut())); Ok(PKey { + evp: evp as *mut ffi::EVP_PKEY, + parts: Parts::Public, + }) + } + } + + /// Reads an RSA private key from PEM, takes ownership of handle + pub fn private_rsa_key_from_pem<R>(reader: &mut R) -> Result<PKey, SslError> + where R: Read + { + let rsa = try!(RSA::private_key_from_pem(reader)); + unsafe { + let evp = try_ssl_null!(ffi::EVP_PKEY_new()); + try_ssl!(ffi::EVP_PKEY_set1_RSA(evp, rsa.as_ptr())); + + Ok(PKey { + evp: evp, + parts: Parts::Public, + }) + } + } + + /// Reads an RSA public key from PEM, takes ownership of handle + pub fn public_rsa_key_from_pem<R>(reader: &mut R) -> Result<PKey, SslError> + where R: Read + { + let rsa = try!(RSA::public_key_from_pem(reader)); + unsafe { + let evp = try_ssl_null!(ffi::EVP_PKEY_new()); + try_ssl!(ffi::EVP_PKEY_set1_RSA(evp, rsa.as_ptr())); + + Ok(PKey { evp: evp, parts: Parts::Public, }) @@ -165,6 +205,28 @@ impl PKey { } } + /// assign RSA key to this pkey + pub fn set_rsa(&mut self, rsa: &RSA) { + unsafe { + // this needs to be a reference as the set1_RSA ups the reference count + let rsa_ptr = rsa.as_ptr(); + if ffi::EVP_PKEY_set1_RSA(self.evp, rsa_ptr) == 1 { + if rsa.has_e() && rsa.has_n() { + self.parts = Parts::Public; + } + } + } + } + + /// get a reference to the interal RSA key for direct access to the key components + pub fn get_rsa(&self) -> RSA { + unsafe { + let evp_pkey: *mut ffi::EVP_PKEY = self.evp; + // this is safe as the ffi increments a reference counter to the internal key + RSA::from_raw(ffi::EVP_PKEY_get1_RSA(evp_pkey)) + } + } + /** * Returns a DER serialized form of the public key, suitable for load_pub(). */ @@ -549,11 +611,22 @@ impl Drop for PKey { } } +impl Clone for PKey { + fn clone(&self) -> Self { + unsafe { + rust_EVP_PKEY_clone(self.evp); + } + + PKey::from_handle(self.evp, self.parts) + } +} + #[cfg(test)] mod tests { use std::path::Path; use std::fs::File; use crypto::hash::Type::{MD5, SHA1}; + use crypto::rsa::RSA; #[test] fn test_gen_pub() { @@ -614,6 +687,26 @@ mod tests { } #[test] + fn test_private_rsa_key_from_pem() { + let key_path = Path::new("test/key.pem"); + let mut file = File::open(&key_path) + .ok() + .expect("Failed to open `test/key.pem`"); + + super::PKey::private_rsa_key_from_pem(&mut file).unwrap(); + } + + #[test] + fn test_public_rsa_key_from_pem() { + let key_path = Path::new("test/key.pem.pub"); + let mut file = File::open(&key_path) + .ok() + .expect("Failed to open `test/key.pem.pub`"); + + super::PKey::public_rsa_key_from_pem(&mut file).unwrap(); + } + + #[test] fn test_private_encrypt() { let mut k0 = super::PKey::new(); let mut k1 = super::PKey::new(); @@ -721,6 +814,28 @@ mod tests { } #[test] + fn test_public_key_from_raw() { + let mut k0 = super::PKey::new(); + let mut k1 = super::PKey::new(); + let msg = vec![0xdeu8, 0xadu8, 0xd0u8, 0x0du8]; + + k0.gen(512); + let sig = k0.sign(&msg); + + let r0 = k0.get_rsa(); + let r1 = RSA::from_public_components(r0.n().expect("n"), r0.e().expect("e")).expect("r1"); + k1.set_rsa(&r1); + + assert!(k1.can(super::Role::Encrypt)); + assert!(!k1.can(super::Role::Decrypt)); + assert!(k1.can(super::Role::Verify)); + assert!(!k1.can(super::Role::Sign)); + + let rv = k1.verify(&msg, &sig); + assert!(rv == true); + } + + #[test] #[should_panic(expected = "Could not get RSA key for encryption")] fn test_nokey_encrypt() { let mut pkey = super::PKey::new(); diff --git a/openssl/src/crypto/rsa.rs b/openssl/src/crypto/rsa.rs new file mode 100644 index 00000000..6fcb5b07 --- /dev/null +++ b/openssl/src/crypto/rsa.rs @@ -0,0 +1,121 @@ +use ffi; +use std::fmt; +use ssl::error::{SslError, StreamError}; +use std::ptr; +use std::io::{self, Read}; + +use bn::BigNum; +use bio::MemBio; + +pub struct RSA(*mut ffi::RSA); + +impl Drop for RSA { + fn drop(&mut self) { + unsafe { + ffi::RSA_free(self.0); + } + } +} + +impl RSA { + /// only useful for associating the key material directly with the key, it's safer to use + /// the supplied load and save methods for DER formatted keys. + pub fn from_public_components(n: BigNum, e: BigNum) -> Result<RSA, SslError> { + unsafe { + let rsa = try_ssl_null!(ffi::RSA_new()); + (*rsa).n = n.into_raw(); + (*rsa).e = e.into_raw(); + Ok(RSA(rsa)) + } + } + + /// the caller should assert that the rsa pointer is valid. + pub unsafe fn from_raw(rsa: *mut ffi::RSA) -> RSA { + RSA(rsa) + } + + /// Reads an RSA private key from PEM formatted data. + pub fn private_key_from_pem<R>(reader: &mut R) -> Result<RSA, SslError> + where R: Read + { + let mut mem_bio = try!(MemBio::new()); + try!(io::copy(reader, &mut mem_bio).map_err(StreamError)); + + unsafe { + let rsa = try_ssl_null!(ffi::PEM_read_bio_RSAPrivateKey(mem_bio.get_handle(), + ptr::null_mut(), + None, + ptr::null_mut())); + Ok(RSA(rsa)) + } + } + + /// Reads an RSA public key from PEM formatted data. + pub fn public_key_from_pem<R>(reader: &mut R) -> Result<RSA, SslError> + where R: Read + { + let mut mem_bio = try!(MemBio::new()); + try!(io::copy(reader, &mut mem_bio).map_err(StreamError)); + + unsafe { + let rsa = try_ssl_null!(ffi::PEM_read_bio_RSA_PUBKEY(mem_bio.get_handle(), + ptr::null_mut(), + None, + ptr::null_mut())); + Ok(RSA(rsa)) + } + } + + pub fn as_ptr(&self) -> *mut ffi::RSA { + self.0 + } + + // The following getters are unsafe, since BigNum::new_from_ffi fails upon null pointers + pub fn n(&self) -> Result<BigNum, SslError> { + unsafe { + BigNum::new_from_ffi((*self.0).n) + } + } + + pub fn has_n(&self) -> bool { + unsafe { + !(*self.0).n.is_null() + } + } + + pub fn d(&self) -> Result<BigNum, SslError> { + unsafe { + BigNum::new_from_ffi((*self.0).d) + } + } + + pub fn e(&self) -> Result<BigNum, SslError> { + unsafe { + BigNum::new_from_ffi((*self.0).e) + } + } + + pub fn has_e(&self) -> bool { + unsafe { + !(*self.0).e.is_null() + } + } + + pub fn p(&self) -> Result<BigNum, SslError> { + unsafe { + BigNum::new_from_ffi((*self.0).p) + } + } + + pub fn q(&self) -> Result<BigNum, SslError> { + unsafe { + BigNum::new_from_ffi((*self.0).q) + } + } +} + +impl fmt::Debug for RSA { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RSA") + } +} diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 88b67d97..f1b6d13a 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -1,4 +1,5 @@ -#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.7.4")] +#![doc(html_root_url="https://sfackler.github.io/rust-openssl/doc/v0.7.6")] +#![cfg_attr(feature = "nightly", feature(const_fn, recover, panic_propagate))] #[macro_use] extern crate bitflags; diff --git a/openssl/src/nid.rs b/openssl/src/nid.rs index e04b004a..bfcae15a 100644 --- a/openssl/src/nid.rs +++ b/openssl/src/nid.rs @@ -104,6 +104,7 @@ pub enum Nid { G, S, I, + /// uniqueIdentifier UID, CrlDistributionPoints, RSA_NP_MD5, @@ -170,4 +171,6 @@ pub enum Nid { ID_QT_UNOTICE, RC2_64_CBC, SMIMECaps, + /// Shown as UID in cert subject + UserId = 458 } diff --git a/openssl/src/ssl/bio.rs b/openssl/src/ssl/bio.rs index a361ae81..aa445562 100644 --- a/openssl/src/ssl/bio.rs +++ b/openssl/src/ssl/bio.rs @@ -1,11 +1,12 @@ use libc::{c_char, c_int, c_long, c_void, strlen}; use ffi::{BIO, BIO_METHOD, BIO_CTRL_FLUSH, BIO_TYPE_NONE, BIO_new}; use ffi_extras::{BIO_clear_retry_flags, BIO_set_retry_read, BIO_set_retry_write}; +use std::any::Any; use std::io; use std::io::prelude::*; use std::mem; -use std::slice; use std::ptr; +use std::slice; use std::sync::Arc; use ssl::error::SslError; @@ -16,6 +17,7 @@ const NAME: [c_char; 5] = [114, 117, 115, 116, 0]; pub struct StreamState<S> { pub stream: S, pub error: Option<io::Error>, + pub panic: Option<Box<Any + Send>>, } pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, Arc<BIO_METHOD>), SslError> { @@ -35,6 +37,7 @@ pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, Arc<BIO_METHOD>), Ss let state = Box::new(StreamState { stream: stream, error: None, + panic: None, }); unsafe { @@ -51,6 +54,12 @@ pub unsafe fn take_error<S>(bio: *mut BIO) -> Option<io::Error> { state.error.take() } +#[cfg_attr(not(feature = "nightly"), allow(dead_code))] +pub unsafe fn take_panic<S>(bio: *mut BIO) -> Option<Box<Any + Send>> { + let state = state::<S>(bio); + state.panic.take() +} + pub unsafe fn get_ref<'a, S: 'a>(bio: *mut BIO) -> &'a S { let state: &'a StreamState<S> = mem::transmute((*bio).ptr); &state.stream @@ -64,20 +73,69 @@ unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> { mem::transmute((*bio).ptr) } +#[cfg(feature = "nightly")] +fn recover<F, T>(f: F) -> Result<T, Box<Any + Send>> where F: FnOnce() -> T + ::std::panic::RecoverSafe { + ::std::panic::recover(f) +} + +#[cfg(not(feature = "nightly"))] +fn recover<F, T>(f: F) -> Result<T, Box<Any + Send>> where F: FnOnce() -> T { + Ok(f()) +} + +#[cfg(feature = "nightly")] +use std::panic::AssertRecoverSafe; + +#[cfg(not(feature = "nightly"))] +struct AssertRecoverSafe<T>(T); + +#[cfg(not(feature = "nightly"))] +impl<T> AssertRecoverSafe<T> { + fn new(t: T) -> Self { + AssertRecoverSafe(t) + } +} + +#[cfg(not(feature = "nightly"))] +impl<T> ::std::ops::Deref for AssertRecoverSafe<T> { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +#[cfg(not(feature = "nightly"))] +impl<T> ::std::ops::DerefMut for AssertRecoverSafe<T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int { BIO_clear_retry_flags(bio); let state = state::<S>(bio); let buf = slice::from_raw_parts(buf as *const _, len as usize); - match state.stream.write(buf) { - Ok(len) => len as c_int, - Err(err) => { + + let result = { + let mut youre_not_my_supervisor = AssertRecoverSafe::new(&mut *state); + recover(move || youre_not_my_supervisor.stream.write(buf)) + }; + + match result { + Ok(Ok(len)) => len as c_int, + Ok(Err(err)) => { if retriable_error(&err) { BIO_set_retry_write(bio); } state.error = Some(err); -1 } + Err(err) => { + state.panic = Some(err); + -1 + } } } @@ -86,15 +144,26 @@ unsafe extern "C" fn bread<S: Read>(bio: *mut BIO, buf: *mut c_char, len: c_int) let state = state::<S>(bio); let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize); - match state.stream.read(buf) { - Ok(len) => len as c_int, - Err(err) => { + + let result = { + let mut youre_not_my_supervisor = AssertRecoverSafe::new(&mut *state); + let mut fuuuu = AssertRecoverSafe::new(buf); + recover(move || youre_not_my_supervisor.stream.read(&mut *fuuuu)) + }; + + match result { + Ok(Ok(len)) => len as c_int, + Ok(Err(err)) => { if retriable_error(&err) { BIO_set_retry_read(bio); } state.error = Some(err); -1 } + Err(err) => { + state.panic = Some(err); + -1 + } } } @@ -116,12 +185,21 @@ unsafe extern "C" fn ctrl<S: Write>(bio: *mut BIO, -> c_long { if cmd == BIO_CTRL_FLUSH { let state = state::<S>(bio); - match state.stream.flush() { - Ok(()) => 1, - Err(err) => { + let result = { + let mut youre_not_my_supervisor = AssertRecoverSafe::new(&mut *state); + recover(move || youre_not_my_supervisor.stream.flush()) + }; + + match result { + Ok(Ok(())) => 1, + Ok(Err(err)) => { state.error = Some(err); 0 } + Err(err) => { + state.panic = Some(err); + 0 + } } } else { 0 diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 955f10fd..b4c73479 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -26,8 +26,7 @@ use std::os::windows::io::{AsRawSocket, RawSocket}; use ffi; use ffi_extras; use dh::DH; -use ssl::error::{NonblockingSslError, SslError, StreamError, OpenSslErrors, OpenSslError, - OpensslError}; +use ssl::error::{NonblockingSslError, SslError, OpenSslError, OpensslError}; use x509::{X509StoreContext, X509FileType, X509}; use crypto::pkey::PKey; @@ -482,6 +481,8 @@ fn wrap_ssl_result(res: c_int) -> Result<(), SslError> { } /// An SSL context object +/// +/// Internally ref-counted, use `.clone()` in the same way as Rc and Arc. pub struct SslContext { ctx: *mut ffi::SSL_CTX, } @@ -489,6 +490,12 @@ pub struct SslContext { unsafe impl Send for SslContext {} unsafe impl Sync for SslContext {} +impl Clone for SslContext { + fn clone(&self) -> Self { + unsafe { SslContext::new_ref(self.ctx) } + } +} + // TODO: add useful info here impl fmt::Debug for SslContext { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { @@ -503,6 +510,12 @@ impl Drop for SslContext { } impl SslContext { + // Create a new SslContext given an existing ref, and incriment ref-count appropriately. + unsafe fn new_ref(ctx: *mut ffi::SSL_CTX) -> SslContext { + rust_SSL_CTX_clone(ctx); + SslContext { ctx: ctx } + } + /// Creates a new SSL context. pub fn new(method: SslMethod) -> Result<SslContext, SslError> { init(); @@ -756,6 +769,71 @@ impl SslContext { } } + +pub struct CipherBits { + /// The number of secret bits used for the cipher. + pub secret: i32, + /// The number of bits processed by the chosen algorithm, if not None. + pub algorithm: Option<i32>, +} + + +pub struct SslCipher<'a> { + cipher: *const ffi::SSL_CIPHER, + ph: PhantomData<&'a ()>, +} + +impl <'a> SslCipher<'a> { + /// Returns the name of cipher. + pub fn name(&self) -> &'static str { + let name = unsafe { + let ptr = ffi::SSL_CIPHER_get_name(self.cipher); + CStr::from_ptr(ptr as *const _) + }; + + str::from_utf8(name.to_bytes()).unwrap() + } + + /// Returns the SSL/TLS protocol version that first defined the cipher. + pub fn version(&self) -> &'static str { + let version = unsafe { + let ptr = ffi::SSL_CIPHER_get_version(self.cipher); + CStr::from_ptr(ptr as *const _) + }; + + str::from_utf8(version.to_bytes()).unwrap() + } + + /// Returns the number of bits used for the cipher. + pub fn bits(&self) -> CipherBits { + unsafe { + let algo_bits : *mut c_int = ptr::null_mut(); + let secret_bits = ffi::SSL_CIPHER_get_bits(self.cipher, algo_bits); + if !algo_bits.is_null() { + CipherBits { secret: secret_bits, algorithm: Some(*algo_bits) } + } else { + CipherBits { secret: secret_bits, algorithm: None } + } + } + } + + /// Returns a textual description of the cipher used + pub fn description(&self) -> Option<String> { + unsafe { + // SSL_CIPHER_description requires a buffer of at least 128 bytes. + let mut buf = [0i8; 128]; + let desc_ptr = ffi::SSL_CIPHER_description(self.cipher, &mut buf[0], 128); + + if !desc_ptr.is_null() { + String::from_utf8(CStr::from_ptr(desc_ptr).to_bytes().to_vec()).ok() + } else { + None + } + } + } +} + + pub struct Ssl { ssl: *mut ffi::SSL, } @@ -823,6 +901,18 @@ impl Ssl { } } + pub fn get_current_cipher<'a>(&'a self) -> Option<SslCipher<'a>> { + unsafe { + let ptr = ffi::SSL_get_current_cipher(self.ssl); + + if ptr.is_null() { + None + } else { + Some(SslCipher{ cipher: ptr, ph: PhantomData }) + } + } + } + pub fn state_string(&self) -> &'static str { let state = unsafe { let ptr = ffi::SSL_state_string(self.ssl); @@ -868,6 +958,16 @@ impl Ssl { } } + /// Returns the name of the protocol used for the connection, e.g. "TLSv1.2", "SSLv3", etc. + pub fn version(&self) -> &'static str { + let version = unsafe { + let ptr = ffi::SSL_get_version(self.ssl); + CStr::from_ptr(ptr as *const _) + }; + + str::from_utf8(version.to_bytes()).unwrap() + } + /// Returns the protocol selected by performing Next Protocol Negotiation, if any. /// /// The protocol's name is returned is an opaque sequence of bytes. It is up to the client @@ -956,16 +1056,27 @@ impl Ssl { } /// change the context corresponding to the current connection + /// + /// Returns a clone of the SslContext @ctx (ie: the new context). The old context is freed. pub fn set_ssl_context(&self, ctx: &SslContext) -> SslContext { - SslContext { ctx: unsafe { ffi::SSL_set_SSL_CTX(self.ssl, ctx.ctx) } } + // If duplication of @ctx's cert fails, this returns NULL. This _appears_ to only occur on + // allocation failures (meaning panicing is probably appropriate), but it might be nice to + // propogate the error. + assert!(unsafe { ffi::SSL_set_SSL_CTX(self.ssl, ctx.ctx) } != ptr::null_mut()); + + // FIXME: we return this reference here for compatibility, but it isn't actually required. + // This should be removed when a api-incompatabile version is to be released. + // + // ffi:SSL_set_SSL_CTX() returns copy of the ctx pointer passed to it, so it's easier for + // us to do the clone directly. + ctx.clone() } /// obtain the context corresponding to the current connection pub fn get_ssl_context(&self) -> SslContext { unsafe { let ssl_ctx = ffi::SSL_get_SSL_CTX(self.ssl); - rust_SSL_CTX_clone(ssl_ctx); - SslContext { ctx: ssl_ctx } + SslContext::new_ref(ssl_ctx) } } } @@ -1070,10 +1181,9 @@ impl<S: Read + Write> SslStream<S> { if ret > 0 { Ok(stream) } else { - match stream.make_error(ret) { - // This is fine - nonblocking sockets will finish the handshake in read/write - Error::WantRead(..) | Error::WantWrite(..) => Ok(stream), - _ => Err(stream.make_old_error(ret)), + match stream.make_old_error(ret) { + Some(err) => Err(err), + None => Ok(stream), } } } @@ -1086,10 +1196,9 @@ impl<S: Read + Write> SslStream<S> { if ret > 0 { Ok(stream) } else { - match stream.make_error(ret) { - // This is fine - nonblocking sockets will finish the handshake in read/write - Error::WantRead(..) | Error::WantWrite(..) => Ok(stream), - _ => Err(stream.make_old_error(ret)), + match stream.make_old_error(ret) { + Some(err) => Err(err), + None => Ok(stream), } } } @@ -1137,6 +1246,8 @@ impl<S: Read + Write> SslStream<S> { impl<S> SslStream<S> { fn make_error(&mut self, ret: c_int) -> Error { + self.check_panic(); + match self.ssl.get_error(ret) { LibSslError::ErrorSsl => Error::Ssl(OpenSslError::get_stack()), LibSslError::ErrorSyscall => { @@ -1162,9 +1273,11 @@ impl<S> SslStream<S> { } } - fn make_old_error(&mut self, ret: c_int) -> SslError { + fn make_old_error(&mut self, ret: c_int) -> Option<SslError> { + self.check_panic(); + match self.ssl.get_error(ret) { - LibSslError::ErrorSsl => SslError::get(), + LibSslError::ErrorSsl => Some(SslError::get()), LibSslError::ErrorSyscall => { let err = SslError::get(); let count = match err { @@ -1173,26 +1286,35 @@ impl<S> SslStream<S> { }; if count == 0 { if ret == 0 { - SslError::StreamError(io::Error::new(io::ErrorKind::ConnectionAborted, - "unexpected EOF observed")) + Some(SslError::StreamError(io::Error::new(io::ErrorKind::ConnectionAborted, + "unexpected EOF observed"))) } else { - SslError::StreamError(self.get_bio_error()) + Some(SslError::StreamError(self.get_bio_error())) } } else { - err + Some(err) } } - LibSslError::ErrorZeroReturn => SslError::SslSessionClosed, - LibSslError::ErrorWantWrite | LibSslError::ErrorWantRead => { - SslError::StreamError(self.get_bio_error()) - } + LibSslError::ErrorZeroReturn => Some(SslError::SslSessionClosed), + LibSslError::ErrorWantWrite | LibSslError::ErrorWantRead => None, err => { - SslError::StreamError(io::Error::new(io::ErrorKind::Other, - format!("unexpected error {:?}", err))) + Some(SslError::StreamError(io::Error::new(io::ErrorKind::Other, + format!("unexpected error {:?}", err)))) } } } + #[cfg(feature = "nightly")] + fn check_panic(&mut self) { + if let Some(err) = unsafe { bio::take_panic::<S>(self.ssl.get_raw_rbio()) } { + ::std::panic::propagate(err) + } + } + + #[cfg(not(feature = "nightly"))] + fn check_panic(&mut self) { + } + fn get_bio_error(&mut self) -> io::Error { let error = unsafe { bio::take_error::<S>(self.ssl.get_raw_rbio()) }; match error { diff --git a/openssl/src/ssl/tests/mod.rs b/openssl/src/ssl/tests/mod.rs index af3c005e..be35d7ef 100644 --- a/openssl/src/ssl/tests/mod.rs +++ b/openssl/src/ssl/tests/mod.rs @@ -9,6 +9,7 @@ use std::net::{TcpStream, TcpListener, SocketAddr}; use std::path::Path; use std::process::{Command, Child, Stdio, ChildStdin}; use std::thread; +use std::time::Duration; use net2::TcpStreamExt; @@ -79,7 +80,7 @@ impl Server { match TcpStream::connect(&addr) { Ok(s) => return (server, s), Err(ref e) if e.kind() == io::ErrorKind::ConnectionRefused => { - thread::sleep_ms(100); + thread::sleep(Duration::from_millis(100)); } Err(e) => panic!("wut: {}", e), } @@ -117,7 +118,7 @@ impl Server { // Need to wait for the UDP socket to get bound in our child process, // but don't currently have a great way to do that so just wait for a // bit. - thread::sleep_ms(100); + thread::sleep(Duration::from_millis(100)); let socket = UdpSocket::bind(next_addr()).unwrap(); socket.connect(&addr).unwrap(); (s, UdpConnected(socket)) @@ -957,3 +958,104 @@ fn broken_try_clone_doesnt_crash() { let stream1 = SslStream::connect(&context, inner).unwrap(); let _stream2 = stream1.try_clone().unwrap(); } + +#[test] +#[should_panic(expected = "blammo")] +#[cfg(feature = "nightly")] +fn write_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.0.read(buf) + } + } + + impl Write for ExplodingStream { + fn write(&mut self, _: &[u8]) -> io::Result<usize> { + panic!("blammo"); + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + } + + let (_s, stream) = Server::new(); + let stream = ExplodingStream(stream); + + let ctx = SslContext::new(SslMethod::Sslv23).unwrap(); + let _ = SslStream::connect(&ctx, stream); +} + +#[test] +#[should_panic(expected = "blammo")] +#[cfg(feature = "nightly")] +fn read_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, _: &mut [u8]) -> io::Result<usize> { + panic!("blammo"); + } + } + + impl Write for ExplodingStream { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + } + + let (_s, stream) = Server::new(); + let stream = ExplodingStream(stream); + + let ctx = SslContext::new(SslMethod::Sslv23).unwrap(); + let _ = SslStream::connect(&ctx, stream); +} + +#[test] +#[should_panic(expected = "blammo")] +#[cfg(feature = "nightly")] +fn flush_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.0.read(buf) + } + } + + impl Write for ExplodingStream { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + panic!("blammo"); + } + } + + let (_s, stream) = Server::new(); + let stream = ExplodingStream(stream); + + let ctx = SslContext::new(SslMethod::Sslv23).unwrap(); + let mut stream = SslStream::connect(&ctx, stream).unwrap(); + let _ = stream.flush(); +} + +#[test] +fn refcount_ssl_context() { + let ssl = { + let ctx = SslContext::new(SslMethod::Sslv23).unwrap(); + ssl::Ssl::new(&ctx).unwrap() + }; + + { + let new_ctx_a = SslContext::new(SslMethod::Sslv23).unwrap(); + let _new_ctx_b = ssl.set_ssl_context(&new_ctx_a); + } +} diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index ffd478ef..a69f61d5 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -146,8 +146,7 @@ pub struct X509Generator { bits: u32, days: u32, 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>, + extensions: Extensions, hash_type: HashType, } @@ -166,7 +165,7 @@ impl X509Generator { bits: 1024, days: 365, names: vec![], - extensions: HashMap::new(), + extensions: Extensions::new(), hash_type: HashType::SHA1, } } @@ -219,7 +218,7 @@ impl X509Generator { /// 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.extensions.add(ext); self } @@ -237,7 +236,10 @@ impl X509Generator { 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))); + for ext in exts { + self.extensions.add(ext); + } + self } @@ -372,7 +374,7 @@ impl X509Generator { ffi::X509_set_issuer_name(x509.handle, name); for (exttype, ext) in self.extensions.iter() { - try!(X509Generator::add_extension_internal(x509.handle, exttype, &ext.to_string())); + try!(X509Generator::add_extension_internal(x509.handle, &exttype, &ext.to_string())); } let hash_fn = self.hash_type.evp_md(); @@ -507,6 +509,20 @@ impl<'ctx> X509<'ctx> { } } +extern "C" { + fn rust_X509_clone(x509: *mut ffi::X509); +} + +impl<'ctx> Clone for X509<'ctx> { + fn clone(&self) -> X509<'ctx> { + unsafe { rust_X509_clone(self.handle) } + /* FIXME: given that we now have refcounting control, 'owned' should be uneeded, the 'ctx + * is probably also uneeded. We can remove both to condense the x509 api quite a bit + */ + X509::new(self.handle, true) + } +} + impl<'ctx> Drop for X509<'ctx> { fn drop(&mut self) { if self.owned { @@ -604,6 +620,75 @@ impl Drop for X509Req { } } +/// A collection of X.509 extensions. +/// +/// Upholds the invariant that a certificate MUST NOT include more than one +/// instance of a particular extension, according to RFC 3280 §4.2. Also +/// ensures that extensions are added to the certificate during signing +/// in the order they were inserted, which is required for certain +/// extensions like SubjectKeyIdentifier and AuthorityKeyIdentifier. +struct Extensions { + /// The extensions contained in the collection. + extensions: Vec<Extension>, + /// A map of used to keep track of added extensions and their indexes in `self.extensions`. + indexes: HashMap<ExtensionType, usize>, +} + +impl Extensions { + /// Creates a new `Extensions`. + pub fn new() -> Extensions { + Extensions { + extensions: vec![], + indexes: HashMap::new(), + } + } + + /// Adds a new `Extension`, replacing any existing one of the same + /// `ExtensionType`. + pub fn add(&mut self, ext: Extension) { + let ext_type = ext.get_type(); + + if let Some(index) = self.indexes.get(&ext_type) { + self.extensions[*index] = ext; + return; + } + + self.extensions.push(ext); + self.indexes.insert(ext_type, self.extensions.len() - 1); + } + + /// Returns an `ExtensionsIter` for the collection. + pub fn iter(&self) -> ExtensionsIter { + ExtensionsIter { + current: 0, + extensions: &self.extensions, + } + } +} + +/// An iterator that iterates over `(ExtensionType, Extension)` for each +/// extension in the collection. +struct ExtensionsIter<'a> { + current: usize, + extensions: &'a Vec<Extension> +} + +impl<'a> Iterator for ExtensionsIter<'a> { + type Item = (ExtensionType, &'a Extension); + + fn next(&mut self) -> Option<Self::Item> { + if self.current < self.extensions.len() { + let ext = &self.extensions[self.current]; + + self.current += 1; + + Some((ext.get_type(), ext)) + } else { + None + } + } +} + macro_rules! make_validation_error( ($ok_val:ident, $($name:ident = $val:ident,)+) => ( #[derive(Copy, Clone)] diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index 43ad0dec..69ad37f8 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -39,6 +39,30 @@ fn test_cert_gen() { assert_eq!(pkey.save_pub(), cert.public_key().save_pub()); } +/// SubjectKeyIdentifier must be added before AuthorityKeyIdentifier or OpenSSL +/// is "unable to get issuer keyid." This test ensures the order of insertion +/// for extensions is preserved when the cert is signed. +#[test] +fn test_cert_gen_extension_ordering() { + get_generator() + .add_extension(OtherNid(Nid::SubjectKeyIdentifier, "hash".to_owned())) + .add_extension(OtherNid(Nid::AuthorityKeyIdentifier, "keyid:always".to_owned())) + .generate() + .expect("Failed to generate cert with order-dependent extensions"); +} + +/// Proves that a passing result from `test_cert_gen_extension_ordering` is +/// deterministic by reversing the order of extensions and asserting failure. +#[test] +fn test_cert_gen_extension_bad_ordering() { + let result = get_generator() + .add_extension(OtherNid(Nid::AuthorityKeyIdentifier, "keyid:always".to_owned())) + .add_extension(OtherNid(Nid::SubjectKeyIdentifier, "hash".to_owned())) + .generate(); + + assert!(result.is_err()); +} + #[test] fn test_req_gen() { let mut pkey = PKey::new(); @@ -116,3 +140,20 @@ fn test_nid_values() { }; assert_eq!(&friendly as &str, "Example"); } + +#[test] +fn test_nid_uid_value() { + let cert_path = Path::new("test/nid_uid_test_cert.pem"); + let mut file = File::open(&cert_path) + .ok() + .expect("Failed to open `test/nid_uid_test_cert.pem`"); + + let cert = X509::from_pem(&mut file).ok().expect("Failed to load PEM"); + let subject = cert.subject_name(); + + let cn = match subject.text_by_nid(Nid::UserId) { + Some(x) => x, + None => panic!("Failed to read UID from cert"), + }; + assert_eq!(&cn as &str, "this is the userId"); +} diff --git a/openssl/test/build.sh b/openssl/test/build.sh index f52ded60..a4c19c05 100755 --- a/openssl/test/build.sh +++ b/openssl/test/build.sh @@ -15,7 +15,7 @@ fi mkdir /tmp/openssl cd /tmp/openssl -curl https://openssl.org/source/openssl-1.0.2e.tar.gz | tar --strip-components=1 -xzf - +curl https://openssl.org/source/openssl-1.0.2g.tar.gz | tar --strip-components=1 -xzf - ./Configure --prefix=$HOME/openssl shared --cross-compile-prefix=$CROSS $OS_COMPILER make make install diff --git a/openssl/test/nid_uid_test_cert.pem b/openssl/test/nid_uid_test_cert.pem new file mode 100644 index 00000000..de6722ab --- /dev/null +++ b/openssl/test/nid_uid_test_cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEGTCCAwGgAwIBAgIJAItKTzcGfL1lMA0GCSqGSIb3DQEBCwUAMIGiMSIwIAYK +CZImiZPyLGQBAQwSdGhpcyBpcyB0aGUgdXNlcklkMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJU3Vubnl2YWxlMRUwEwYDVQQKDAxS +dXN0IE9wZW5TU0wxDDAKBgNVBAsMA09TUzEhMB8GA1UEAwwYcnVzdC1vcGVuc3Ns +LmV4YW1wbGUuY29tMB4XDTE2MDIwMjE3MjIwMVoXDTE2MDMwMzE3MjIwMVowgaIx +IjAgBgoJkiaJk/IsZAEBDBJ0aGlzIGlzIHRoZSB1c2VySWQxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlTdW5ueXZhbGUxFTATBgNV +BAoMDFJ1c3QgT3BlblNTTDEMMAoGA1UECwwDT1NTMSEwHwYDVQQDDBhydXN0LW9w +ZW5zc2wuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDa3Gc+IE5DOhTv1m5DZW8qKiyNLd7v4DaAYLXSsDuLs+9wJ+Bs+wlBfrg+PT0t +EJlPaLL9IfD5eR3WpFu62TUexYhnJh+3vhCGsFHOXcTjtM+wy/dzZtOVh2wTzvqE +/FHBGw1eG3Ww+RkSFbwYmtm8JhIN8ffYxGn2O0yQpxypf5hNPYrC81zX+52X2w1h +jDYLpYt55w+e6q+iRRFk0tKiWHEqqh/r6UQQRpj2EeS+xTloZlO6h0nl2NPkVF3r +CXBoT8Ittxr7sqcYqf8TAA0I4qZRYXKYehFmv/VkSt85CcURJ/zXeoJ1TpxSvQie +2R9cRDkYROrIOAFbB/0mmHLBAgMBAAGjUDBOMB0GA1UdDgQWBBRKfPqtgrbdbTmH +XR6RC/p8t/65GjAfBgNVHSMEGDAWgBRKfPqtgrbdbTmHXR6RC/p8t/65GjAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCKfeGRduGsIwKNiGcDUNkNrc7Z +f8SWAmb/R6xiDfgjbhrtfBDowIZ5natEkTgf6kQPMJKyjg2NEM2uJWBc55rLOHIv +es1wQOlYjfEUmFD3lTIt2TM/IUgXn2j+zV1CRkJthQLVFChXsidd0Bqq2fBjd3ad +Yjzrxf3uOTBAs27koh2INNHfcUZCRsx8hP739zz2kw/r5NB/9iyENEyJKQvxo0jb +oN0JK2joGZrWetDukQrqf032TsdkboW5JresYybbAD3326Ljp+hlT/3WINc+3nZJ +Dn+pPMdpuZ5BUZ+u+XyNEPum3k3P3K19AF+zWYGooX0J1cmuCBrrqce20Lwy +-----END CERTIFICATE----- diff --git a/openssl/test/run.sh b/openssl/test/run.sh index 229d9a1d..829f11e9 100755 --- a/openssl/test/run.sh +++ b/openssl/test/run.sh @@ -4,7 +4,11 @@ set -e MAIN_TARGETS=https://static.rust-lang.org/dist if [ "$TEST_FEATURES" == "true" ]; then - FEATURES="tlsv1_2 tlsv1_1 dtlsv1 dtlsv1_2 sslv2 sslv3 aes_xts aes_ctr npn alpn rfc5114 ecdh_auto pkcs5_pbkdf2_hmac" + FEATURES="tlsv1_2 tlsv1_1 dtlsv1 dtlsv1_2 sslv3 aes_xts aes_ctr npn alpn rfc5114 ecdh_auto pkcs5_pbkdf2_hmac" +fi + +if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then + FEATURES="$FEATURES nightly" fi if [ "$TRAVIS_OS_NAME" != "osx" ]; then |