aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--appveyor.yml8
-rw-r--r--openssl-sys-extras/Cargo.toml6
-rw-r--r--openssl-sys-extras/src/lib.rs2
-rw-r--r--openssl-sys-extras/src/openssl_shim.c4
-rw-r--r--openssl-sys/Cargo.toml4
-rw-r--r--openssl-sys/src/lib.rs59
-rw-r--r--openssl/Cargo.toml13
-rw-r--r--openssl/src/bn/mod.rs19
-rw-r--r--openssl/src/c_helpers.c8
-rw-r--r--openssl/src/crypto/mod.rs1
-rw-r--r--openssl/src/crypto/pkey.rs117
-rw-r--r--openssl/src/crypto/rsa.rs121
-rw-r--r--openssl/src/lib.rs3
-rw-r--r--openssl/src/nid.rs3
-rw-r--r--openssl/src/ssl/bio.rs98
-rw-r--r--openssl/src/ssl/mod.rs172
-rw-r--r--openssl/src/ssl/tests/mod.rs106
-rw-r--r--openssl/src/x509/mod.rs97
-rw-r--r--openssl/src/x509/tests.rs41
-rwxr-xr-xopenssl/test/build.sh2
-rw-r--r--openssl/test/nid_uid_test_cert.pem24
-rwxr-xr-xopenssl/test/run.sh6
23 files changed, 848 insertions, 70 deletions
diff --git a/README.md b/README.md
index fe07ddbe..5741e2c1 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.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