diff options
| author | Steven Fackler <[email protected]> | 2016-11-05 20:06:50 -0700 |
|---|---|---|
| committer | Steven Fackler <[email protected]> | 2016-11-05 20:06:50 -0700 |
| commit | a0b56c437803a08413755928040a0970a93a7b83 (patch) | |
| tree | 0f21848301b62d6078eafaee10e513df4163087b /openssl/src/ssl | |
| parent | Merge branch 'release-v0.8.3' into release (diff) | |
| parent | Release v0.9.0 (diff) | |
| download | rust-openssl-0.9.0.tar.xz rust-openssl-0.9.0.zip | |
Merge branch 'release-v0.9.0' into releasev0.9.0
Diffstat (limited to 'openssl/src/ssl')
| -rw-r--r-- | openssl/src/ssl/bio.rs | 169 | ||||
| -rw-r--r-- | openssl/src/ssl/connector.rs | 452 | ||||
| -rw-r--r-- | openssl/src/ssl/error.rs | 55 | ||||
| -rw-r--r-- | openssl/src/ssl/mod.rs | 1159 | ||||
| -rw-r--r-- | openssl/src/ssl/tests/mod.rs | 690 |
5 files changed, 1654 insertions, 871 deletions
diff --git a/openssl/src/ssl/bio.rs b/openssl/src/ssl/bio.rs index c5663eb1..486b4dba 100644 --- a/openssl/src/ssl/bio.rs +++ b/openssl/src/ssl/bio.rs @@ -1,14 +1,14 @@ use libc::{c_char, c_int, c_long, c_void, strlen}; -use ffi::{self, BIO, BIO_CTRL_FLUSH, BIO_TYPE_NONE, BIO_new, BIO_clear_retry_flags, - BIO_set_retry_read, BIO_set_retry_write}; +use ffi::{BIO, BIO_CTRL_FLUSH, BIO_new, 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::ptr; use std::slice; -use std::sync::Arc; +use cvt_p; use error::ErrorStack; pub struct StreamState<S> { @@ -18,29 +18,19 @@ pub struct StreamState<S> { } /// Safe wrapper for BIO_METHOD -pub struct BioMethod(ffi::BIO_METHOD); +pub struct BioMethod(compat::BIO_METHOD); impl BioMethod { - pub fn new<S: Read + Write>() -> BioMethod { - BioMethod(ffi::BIO_METHOD { - type_: BIO_TYPE_NONE, - name: b"rust\0".as_ptr() as *const _, - bwrite: Some(bwrite::<S>), - bread: Some(bread::<S>), - bputs: Some(bputs::<S>), - bgets: None, - ctrl: Some(ctrl::<S>), - create: Some(create), - destroy: Some(destroy::<S>), - callback_ctrl: None, - }) + fn new<S: Read + Write>() -> BioMethod { + BioMethod(compat::BIO_METHOD::new::<S>()) } } +unsafe impl Sync for BioMethod {} unsafe impl Send for BioMethod {} -pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, Arc<BioMethod>), ErrorStack> { - let method = Arc::new(BioMethod::new::<S>()); +pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, BioMethod), ErrorStack> { + let method = BioMethod::new::<S>(); let state = Box::new(StreamState { stream: stream, @@ -49,9 +39,9 @@ pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, Arc<BioMethod>), Err }); unsafe { - let bio = try_ssl_null!(BIO_new(&method.0)); - (*bio).ptr = Box::into_raw(state) as *mut _; - (*bio).init = 1; + let bio = try!(cvt_p(BIO_new(method.0.get()))); + compat::BIO_set_data(bio, Box::into_raw(state) as *mut _); + compat::BIO_set_init(bio, 1); return Ok((bio, method)); } @@ -62,14 +52,13 @@ 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); + let state: &'a StreamState<S> = mem::transmute(compat::BIO_get_data(bio)); &state.stream } @@ -78,23 +67,15 @@ pub unsafe fn get_mut<'a, S: 'a>(bio: *mut BIO) -> &'a mut S { } unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> { - mem::transmute((*bio).ptr) + mem::transmute(compat::BIO_get_data(bio)) } -#[cfg(feature = "nightly")] fn catch_unwind<F, T>(f: F) -> Result<T, Box<Any + Send>> where F: FnOnce() -> T { ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(f)) } -#[cfg(not(feature = "nightly"))] -fn catch_unwind<F, T>(f: F) -> Result<T, Box<Any + Send>> - where F: FnOnce() -> T -{ - Ok(f()) -} - unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int { BIO_clear_retry_flags(bio); @@ -176,10 +157,10 @@ unsafe extern "C" fn ctrl<S: Write>(bio: *mut BIO, } unsafe extern "C" fn create(bio: *mut BIO) -> c_int { - (*bio).init = 0; - (*bio).num = 0; - (*bio).ptr = ptr::null_mut(); - (*bio).flags = 0; + compat::BIO_set_init(bio, 0); + compat::BIO_set_num(bio, 0); + compat::BIO_set_data(bio, ptr::null_mut()); + compat::BIO_set_flags(bio, 0); 1 } @@ -188,9 +169,115 @@ unsafe extern "C" fn destroy<S>(bio: *mut BIO) -> c_int { return 0; } - assert!(!(*bio).ptr.is_null()); - Box::<StreamState<S>>::from_raw((*bio).ptr as *mut _); - (*bio).ptr = ptr::null_mut(); - (*bio).init = 0; + let data = compat::BIO_get_data(bio); + assert!(!data.is_null()); + Box::<StreamState<S>>::from_raw(data as *mut _); + compat::BIO_set_data(bio, ptr::null_mut()); + compat::BIO_set_init(bio, 0); 1 } + +#[cfg(ossl110)] +#[allow(bad_style)] +mod compat { + use std::io::{Read, Write}; + + use libc::c_int; + use ffi; + pub use ffi::{BIO_set_init, BIO_set_flags, BIO_set_data, BIO_get_data}; + + pub unsafe fn BIO_set_num(_bio: *mut ffi::BIO, _num: c_int) {} + + pub struct BIO_METHOD(*mut ffi::BIO_METHOD); + + impl BIO_METHOD { + pub fn new<S: Read + Write>() -> BIO_METHOD { + unsafe { + let ptr = ffi::BIO_meth_new(ffi::BIO_TYPE_NONE, b"rust\0".as_ptr() as *const _); + assert!(!ptr.is_null()); + let ret = BIO_METHOD(ptr); + assert!(ffi::BIO_meth_set_write(ptr, super::bwrite::<S>) != 0); + assert!(ffi::BIO_meth_set_read(ptr, super::bread::<S>) != 0); + assert!(ffi::BIO_meth_set_puts(ptr, super::bputs::<S>) != 0); + assert!(ffi::BIO_meth_set_ctrl(ptr, super::ctrl::<S>) != 0); + assert!(ffi::BIO_meth_set_create(ptr, super::create) != 0); + assert!(ffi::BIO_meth_set_destroy(ptr, super::destroy::<S>) != 0); + return ret; + } + } + + pub fn get(&self) -> *mut ffi::BIO_METHOD { + self.0 + } + } + + impl Drop for BIO_METHOD { + fn drop(&mut self) { + unsafe { + ffi::BIO_meth_free(self.0); + } + } + } +} + +#[cfg(ossl10x)] +#[allow(bad_style)] +mod compat { + use std::io::{Read, Write}; + + use ffi; + use libc::{c_int, c_void}; + + pub struct BIO_METHOD(*mut ffi::BIO_METHOD); + + impl BIO_METHOD { + pub fn new<S: Read + Write>() -> BIO_METHOD { + let ptr = Box::new(ffi::BIO_METHOD { + type_: ffi::BIO_TYPE_NONE, + name: b"rust\0".as_ptr() as *const _, + bwrite: Some(super::bwrite::<S>), + bread: Some(super::bread::<S>), + bputs: Some(super::bputs::<S>), + bgets: None, + ctrl: Some(super::ctrl::<S>), + create: Some(super::create), + destroy: Some(super::destroy::<S>), + callback_ctrl: None, + }); + + BIO_METHOD(Box::into_raw(ptr)) + } + + pub fn get(&self) -> *mut ffi::BIO_METHOD { + self.0 + } + } + + impl Drop for BIO_METHOD { + fn drop(&mut self) { + unsafe { + Box::<ffi::BIO_METHOD>::from_raw(self.0); + } + } + } + + pub unsafe fn BIO_set_init(bio: *mut ffi::BIO, init: c_int) { + (*bio).init = init; + } + + pub unsafe fn BIO_set_flags(bio: *mut ffi::BIO, flags: c_int) { + (*bio).flags = flags; + } + + pub unsafe fn BIO_get_data(bio: *mut ffi::BIO) -> *mut c_void { + (*bio).ptr + } + + pub unsafe fn BIO_set_data(bio: *mut ffi::BIO, data: *mut c_void) { + (*bio).ptr = data; + } + + pub unsafe fn BIO_set_num(bio: *mut ffi::BIO, num: c_int) { + (*bio).num = num; + } +} diff --git a/openssl/src/ssl/connector.rs b/openssl/src/ssl/connector.rs new file mode 100644 index 00000000..0d92529d --- /dev/null +++ b/openssl/src/ssl/connector.rs @@ -0,0 +1,452 @@ +use std::io::{Read, Write}; + +use dh::Dh; +use error::ErrorStack; +use ssl::{self, SslMethod, SslContextBuilder, SslContext, Ssl, SSL_VERIFY_PEER, SslStream, + HandshakeError}; +use pkey::PKeyRef; +use x509::X509Ref; + +// Serialized form of DH_get_2048_256 +#[cfg(any(ossl101, all(test, any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))))] +const DHPARAM_PEM: &'static str = r#" +-----BEGIN DH PARAMETERS----- +MIICCQKCAQEAh6jmHbS2Zjz/u9GcZRlZmYzu9ghmDdDyXSzu1ENeOwDgDfjx1hlX +1Pr330VhsqowFsPZETQJb6o79Cltgw6afCCeDGSXUXq9WoqdMGvPZ+2R+eZyW0dY +wCLgse9Cdb97bFv8EdRfkIi5QfVOseWbuLw5oL8SMH9cT9twxYGyP3a2Osrhyqa3 +kC1SUmc1SIoO8TxtmlG/pKs62DR3llJNjvahZ7WkGCXZZ+FE5RQFZCUcysuD5rSG +9rPKP3lxUGAmwLhX9omWKFbe1AEKvQvmIcOjlgpU5xDDdfJjddcBQQOktUMwwZiv +EmEW0iduEXFfaTh3+tfvCcrbCUrpHhoVlwKCAQA/syybcxNNCy53UGZg7b1ITKex +jyHvIFQH9Hk6GguhJRDbwVB3vkY//0/tSqwLtVW+OmwbDGtHsbw3c79+jG9ikBIo ++MKMuxilWuMTQQAKZQGW+THHelfy3fRj5ensFEt3feYqqrioYorDdtKC1u04ZOZ5 +gkKOvIMdFDSPby+Rk7UEWvJ2cWTh38lnwfs/LlWkvRv/6DucgNBSuYXRguoK2yo7 +cxPT/hTISEseBSWIubfSu9LfAWGZ7NBuFVfNCRWzNTu7ZODsN3/QKDcN+StSx4kU +KM3GfrYYS1I9HbJGwy9jB4SQ8A741kfRSNR5VFFeIyfP75jFgmZLTA9sxBZZ +-----END DH PARAMETERS----- +"#; + +fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> { + let mut ctx = try!(SslContextBuilder::new(method)); + + let mut opts = ssl::SSL_OP_ALL; + opts |= ssl::SSL_OP_NO_TICKET; + opts |= ssl::SSL_OP_NO_COMPRESSION; + opts &= !ssl::SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; + opts &= !ssl::SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + opts |= ssl::SSL_OP_NO_SSLV2; + opts |= ssl::SSL_OP_NO_SSLV3; + opts |= ssl::SSL_OP_SINGLE_DH_USE; + opts |= ssl::SSL_OP_SINGLE_ECDH_USE; + opts |= ssl::SSL_OP_CIPHER_SERVER_PREFERENCE; + ctx.set_options(opts); + + let mode = ssl::SSL_MODE_AUTO_RETRY | ssl::SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | + ssl::SSL_MODE_ENABLE_PARTIAL_WRITE; + ctx.set_mode(mode); + + Ok(ctx) +} + +/// A builder for `SslConnector`s. +pub struct SslConnectorBuilder(SslContextBuilder); + +impl SslConnectorBuilder { + /// Creates a new builder for TLS connections. + /// + /// The default configuration is subject to change, and is currently derived from Python. + pub fn new(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> { + let mut ctx = try!(ctx(method)); + try!(ctx.set_default_verify_paths()); + // From https://github.com/python/cpython/blob/c30098c8c6014f3340a369a31df9c74bdbacc269/Lib/ssl.py#L191 + try!(ctx.set_cipher_list("ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:\ + DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:\ + RSA+AES:RSA+HIGH:!aNULL:!eNULL:!MD5:!3DES")); + + Ok(SslConnectorBuilder(ctx)) + } + + /// Returns a shared reference to the inner `SslContextBuilder`. + pub fn builder(&self) -> &SslContextBuilder { + &self.0 + } + + /// Returns a mutable reference to the inner `SslContextBuilder`. + pub fn builder_mut(&mut self) -> &mut SslContextBuilder { + &mut self.0 + } + + /// Consumes the builder, returning a `SslConnector`. + pub fn build(self) -> SslConnector { + SslConnector(self.0.build()) + } +} + +/// A type which wraps client-side streams in a TLS session. +/// +/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL +/// structures, configuring cipher suites, session options, hostname verification, and more. +/// +/// OpenSSL's built in hostname verification is used when linking against OpenSSL 1.0.2 or 1.1.0, +/// and a custom implementation is used when linking against OpenSSL 1.0.1. +pub struct SslConnector(SslContext); + +impl SslConnector { + /// Initiates a client-side TLS session on a stream. + /// + /// The domain is used for SNI and hostname verification. + pub fn connect<S>(&self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>> + where S: Read + Write + { + let mut ssl = try!(Ssl::new(&self.0)); + try!(ssl.set_hostname(domain)); + try!(setup_verify(&mut ssl, domain)); + + ssl.connect(stream) + } +} + +/// A builder for `SslAcceptor`s. +pub struct SslAcceptorBuilder(SslContextBuilder); + +impl SslAcceptorBuilder { + /// Creates a new builder configured to connect to non-legacy clients. This should generally be + /// considered a reasonable default choice. + /// + /// This corresponds to the intermediate configuration of Mozilla's server side TLS + /// recommendations. See its [documentation][docs] for more details on specifics. + /// + /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS + pub fn mozilla_intermediate<I>(method: SslMethod, + private_key: &PKeyRef, + certificate: &X509Ref, + chain: I) + -> Result<SslAcceptorBuilder, ErrorStack> + where I: IntoIterator, + I::Item: AsRef<X509Ref> + { + let mut ctx = try!(ctx(method)); + let dh = try!(get_dh()); + try!(ctx.set_tmp_dh(&dh)); + try!(setup_curves(&mut ctx)); + try!(ctx.set_cipher_list("ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ + ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\ + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\ + DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:\ + ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:\ + ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:\ + ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:\ + ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:\ + DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:\ + DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:\ + ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:\ + EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:\ + AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:\ + DES-CBC3-SHA:!DSS")); + SslAcceptorBuilder::finish_setup(ctx, private_key, certificate, chain) + } + + /// Creates a new builder configured to connect to modern clients. + /// + /// This corresponds to the modern configuration of Mozilla's server side TLS recommendations. + /// See its [documentation][docs] for more details on specifics. + /// + /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS + pub fn mozilla_modern<I>(method: SslMethod, + private_key: &PKeyRef, + certificate: &X509Ref, + chain: I) + -> Result<SslAcceptorBuilder, ErrorStack> + where I: IntoIterator, + I::Item: AsRef<X509Ref> + { + let mut ctx = try!(ctx(method)); + try!(setup_curves(&mut ctx)); + try!(ctx.set_cipher_list("ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\ + ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ + ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\ + ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:\ + ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256")); + SslAcceptorBuilder::finish_setup(ctx, private_key, certificate, chain) + } + + fn finish_setup<I>(mut ctx: SslContextBuilder, + private_key: &PKeyRef, + certificate: &X509Ref, + chain: I) + -> Result<SslAcceptorBuilder, ErrorStack> + where I: IntoIterator, + I::Item: AsRef<X509Ref> + { + try!(ctx.set_private_key(private_key)); + try!(ctx.set_certificate(certificate)); + try!(ctx.check_private_key()); + for cert in chain { + try!(ctx.add_extra_chain_cert(cert.as_ref().to_owned())); + } + Ok(SslAcceptorBuilder(ctx)) + } + + /// Returns a shared reference to the inner `SslContextBuilder`. + pub fn builder(&self) -> &SslContextBuilder { + &self.0 + } + + /// Returns a mutable reference to the inner `SslContextBuilder`. + pub fn builder_mut(&mut self) -> &mut SslContextBuilder { + &mut self.0 + } + + /// Consumes the builder, returning a `SslAcceptor`. + pub fn build(self) -> SslAcceptor { + SslAcceptor(self.0.build()) + } +} + +#[cfg(ossl101)] +fn get_dh() -> Result<Dh, ErrorStack> { + Dh::from_pem(DHPARAM_PEM.as_bytes()) +} + +#[cfg(not(ossl101))] +fn get_dh() -> Result<Dh, ErrorStack> { + use ffi; + + use cvt_p; + use types::OpenSslType; + + // manually call into ffi to avoid forcing the features + unsafe { cvt_p(ffi::DH_get_2048_256()).map(|p| Dh::from_ptr(p)) } +} + +#[cfg(ossl101)] +fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> { + use ec_key::EcKey; + use nid; + + let curve = try!(EcKey::new_by_curve_name(nid::X9_62_PRIME256V1)); + ctx.set_tmp_ecdh(&curve) +} + +#[cfg(ossl102)] +fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> { + ctx._set_ecdh_auto(true) +} + +#[cfg(ossl110)] +fn setup_curves(_: &mut SslContextBuilder) -> Result<(), ErrorStack> { + Ok(()) +} + +/// A type which wraps server-side streams in a TLS session. +/// +/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL +/// structures, configuring cipher suites, session options, and more. +pub struct SslAcceptor(SslContext); + +impl SslAcceptor { + /// Initiates a server-side TLS session on a stream. + pub fn accept<S>(&self, stream: S) -> Result<SslStream<S>, HandshakeError<S>> + where S: Read + Write + { + let ssl = try!(Ssl::new(&self.0)); + ssl.accept(stream) + } +} + +#[cfg(any(ossl102, ossl110))] +fn setup_verify(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> { + ssl.set_verify(SSL_VERIFY_PEER); + let param = ssl._param_mut(); + param.set_hostflags(::verify::X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + param.set_host(domain) +} + +#[cfg(ossl101)] +fn setup_verify(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> { + let domain = domain.to_owned(); + ssl.set_verify_callback(SSL_VERIFY_PEER, + move |p, x| verify::verify_callback(&domain, p, x)); + Ok(()) +} + +#[cfg(ossl101)] +mod verify { + use std::net::IpAddr; + use std::str; + + use nid; + use x509::{X509StoreContextRef, X509Ref, X509NameRef, GeneralName}; + use stack::Stack; + use types::OpenSslTypeRef; + + pub fn verify_callback(domain: &str, + preverify_ok: bool, + x509_ctx: &X509StoreContextRef) + -> bool { + if !preverify_ok || x509_ctx.error_depth() != 0 { + return preverify_ok; + } + + match x509_ctx.current_cert() { + Some(x509) => verify_hostname(domain, &x509), + None => true, + } + } + + fn verify_hostname(domain: &str, cert: &X509Ref) -> bool { + match cert.subject_alt_names() { + Some(names) => verify_subject_alt_names(domain, names), + None => verify_subject_name(domain, &cert.subject_name()), + } + } + + fn verify_subject_alt_names(domain: &str, names: Stack<GeneralName>) -> bool { + let ip = domain.parse(); + + for name in &names { + match ip { + Ok(ip) => { + if let Some(actual) = name.ipaddress() { + if matches_ip(&ip, actual) { + return true; + } + } + } + Err(_) => { + if let Some(pattern) = name.dnsname() { + if matches_dns(pattern, domain, false) { + return true; + } + } + } + } + } + + false + } + + fn verify_subject_name(domain: &str, subject_name: &X509NameRef) -> bool { + if let Some(pattern) = subject_name.entries_by_nid(nid::COMMONNAME).next() { + let pattern = match str::from_utf8(pattern.data().as_slice()) { + Ok(pattern) => pattern, + Err(_) => return false, + }; + + // Unlike with SANs, IP addresses in the subject name don't have a + // different encoding. We need to pass this down to matches_dns to + // disallow wildcard matches with bogus patterns like *.0.0.1 + let is_ip = domain.parse::<IpAddr>().is_ok(); + + if matches_dns(&pattern, domain, is_ip) { + return true; + } + } + + false + } + + fn matches_dns(mut pattern: &str, mut hostname: &str, is_ip: bool) -> bool { + // first strip trailing . off of pattern and hostname to normalize + if pattern.ends_with('.') { + pattern = &pattern[..pattern.len() - 1]; + } + if hostname.ends_with('.') { + hostname = &hostname[..hostname.len() - 1]; + } + + matches_wildcard(pattern, hostname, is_ip).unwrap_or_else(|| pattern == hostname) + } + + fn matches_wildcard(pattern: &str, hostname: &str, is_ip: bool) -> Option<bool> { + // IP addresses and internationalized domains can't involved in wildcards + if is_ip || pattern.starts_with("xn--") { + return None; + } + + let wildcard_location = match pattern.find('*') { + Some(l) => l, + None => return None, + }; + + let mut dot_idxs = pattern.match_indices('.').map(|(l, _)| l); + let wildcard_end = match dot_idxs.next() { + Some(l) => l, + None => return None, + }; + + // Never match wildcards if the pattern has less than 2 '.'s (no *.com) + // + // This is a bit dubious, as it doesn't disallow other TLDs like *.co.uk. + // Chrome has a black- and white-list for this, but Firefox (via NSS) does + // the same thing we do here. + // + // The Public Suffix (https://www.publicsuffix.org/) list could + // potentically be used here, but it's both huge and updated frequently + // enough that management would be a PITA. + if dot_idxs.next().is_none() { + return None; + } + + // Wildcards can only be in the first component + if wildcard_location > wildcard_end { + return None; + } + + let hostname_label_end = match hostname.find('.') { + Some(l) => l, + None => return None, + }; + + // check that the non-wildcard parts are identical + if pattern[wildcard_end..] != hostname[hostname_label_end..] { + return Some(false); + } + + let wildcard_prefix = &pattern[..wildcard_location]; + let wildcard_suffix = &pattern[wildcard_location + 1..wildcard_end]; + + let hostname_label = &hostname[..hostname_label_end]; + + // check the prefix of the first label + if !hostname_label.starts_with(wildcard_prefix) { + return Some(false); + } + + // and the suffix + if !hostname_label[wildcard_prefix.len()..].ends_with(wildcard_suffix) { + return Some(false); + } + + Some(true) + } + + fn matches_ip(expected: &IpAddr, actual: &[u8]) -> bool { + match (expected, actual.len()) { + (&IpAddr::V4(ref addr), 4) => actual == addr.octets(), + (&IpAddr::V6(ref addr), 16) => { + let segments = [((actual[0] as u16) << 8) | actual[1] as u16, + ((actual[2] as u16) << 8) | actual[3] as u16, + ((actual[4] as u16) << 8) | actual[5] as u16, + ((actual[6] as u16) << 8) | actual[7] as u16, + ((actual[8] as u16) << 8) | actual[9] as u16, + ((actual[10] as u16) << 8) | actual[11] as u16, + ((actual[12] as u16) << 8) | actual[13] as u16, + ((actual[14] as u16) << 8) | actual[15] as u16]; + segments == addr.segments() + } + _ => false, + } + } +} + +#[cfg(test)] +mod test { + #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] + #[test] + fn check_dhparam() { + use dh::Dh; + + let expected = String::from_utf8(Dh::get_2048_256().unwrap().to_pem().unwrap()).unwrap(); + assert_eq!(expected.trim(), super::DHPARAM_PEM.trim()); + } +} diff --git a/openssl/src/ssl/error.rs b/openssl/src/ssl/error.rs index 95213361..518ae90f 100644 --- a/openssl/src/ssl/error.rs +++ b/openssl/src/ssl/error.rs @@ -1,8 +1,11 @@ +use std::any::Any; use std::error; use std::error::Error as StdError; use std::fmt; use std::io; + use error::ErrorStack; +use ssl::MidHandshakeSslStream; /// An SSL error. #[derive(Debug)] @@ -62,3 +65,55 @@ impl From<ErrorStack> for Error { Error::Ssl(e) } } + +/// An error or intermediate state after a TLS handshake attempt. +#[derive(Debug)] +pub enum HandshakeError<S> { + /// Setup failed. + SetupFailure(ErrorStack), + /// The handshake failed. + Failure(MidHandshakeSslStream<S>), + /// The handshake was interrupted midway through. + Interrupted(MidHandshakeSslStream<S>), +} + +impl<S: Any + fmt::Debug> StdError for HandshakeError<S> { + fn description(&self) -> &str { + match *self { + HandshakeError::SetupFailure(_) => "stream setup failed", + HandshakeError::Failure(_) => "the handshake failed", + HandshakeError::Interrupted(_) => "the handshake was interrupted", + } + } + + fn cause(&self) -> Option<&StdError> { + match *self { + HandshakeError::SetupFailure(ref e) => Some(e), + HandshakeError::Failure(ref s) | + HandshakeError::Interrupted(ref s) => Some(s.error()), + } + } +} + +impl<S: Any + fmt::Debug> fmt::Display for HandshakeError<S> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(f.write_str(StdError::description(self))); + match *self { + HandshakeError::SetupFailure(ref e) => try!(write!(f, ": {}", e)), + HandshakeError::Failure(ref s) | + HandshakeError::Interrupted(ref s) => { + try!(write!(f, ": {}", s.error())); + if let Some(err) = s.ssl().verify_result() { + try!(write!(f, ": {}", err)); + } + } + } + Ok(()) + } +} + +impl<S> From<ErrorStack> for HandshakeError<S> { + fn from(e: ErrorStack) -> HandshakeError<S> { + HandshakeError::SetupFailure(e) + } +} diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 6e365af6..9a477993 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -1,49 +1,123 @@ -use libc::{c_int, c_void, c_long}; +//! SSL/TLS support. +//! +//! `SslConnector` and `SslAcceptor` should be used in most cases - they handle +//! configuration of the OpenSSL primitives for you. +//! +//! # Examples +//! +//! To connect as a client to a remote server: +//! +//! ``` +//! use openssl::ssl::{SslMethod, SslConnectorBuilder}; +//! use std::io::{Read, Write}; +//! use std::net::TcpStream; +//! +//! let connector = SslConnectorBuilder::new(SslMethod::tls()).unwrap().build(); +//! +//! let stream = TcpStream::connect("google.com:443").unwrap(); +//! let mut stream = connector.connect("google.com", stream).unwrap(); +//! +//! stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); +//! let mut res = vec![]; +//! stream.read_to_end(&mut res).unwrap(); +//! println!("{}", String::from_utf8_lossy(&res)); +//! ``` +//! +//! To accept connections as a server from remote clients: +//! +//! ```no_run +//! use openssl::pkcs12::Pkcs12; +//! use openssl::ssl::{SslMethod, SslAcceptorBuilder, SslStream}; +//! use std::fs::File; +//! use std::io::{Read, Write}; +//! use std::net::{TcpListener, TcpStream}; +//! use std::sync::Arc; +//! use std::thread; +//! +//! // In this example we retrieve our keypair and certificate chain from a PKCS #12 archive, +//! // but but they can also be retrieved from, for example, individual PEM- or DER-formatted +//! // files. See the documentation for the `PKey` and `X509` types for more details. +//! let mut file = File::open("identity.pfx").unwrap(); +//! let mut pkcs12 = vec![]; +//! file.read_to_end(&mut pkcs12).unwrap(); +//! let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap(); +//! let identity = pkcs12.parse("password123").unwrap(); +//! +//! let acceptor = SslAcceptorBuilder::mozilla_intermediate(SslMethod::tls(), +//! &identity.pkey, +//! &identity.cert, +//! &identity.chain) +//! .unwrap() +//! .build(); +//! let acceptor = Arc::new(acceptor); +//! +//! let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); +//! +//! fn handle_client(stream: SslStream<TcpStream>) { +//! // ... +//! } +//! +//! for stream in listener.incoming() { +//! match stream { +//! Ok(stream) => { +//! let acceptor = acceptor.clone(); +//! thread::spawn(move || { +//! let stream = acceptor.accept(stream).unwrap(); +//! handle_client(stream); +//! }); +//! } +//! Err(e) => { /* connection failed */ } +//! } +//! } +//! ``` +use ffi; +use libc::{c_int, c_void, c_long, c_ulong}; +use libc::{c_uchar, c_uint}; use std::any::Any; use std::any::TypeId; use std::cmp; use std::collections::HashMap; -use std::error as stderror; use std::ffi::{CStr, CString}; use std::fmt; use std::io; use std::io::prelude::*; +use std::marker::PhantomData; use std::mem; use std::ops::{Deref, DerefMut}; use std::path::Path; use std::ptr; -use std::str; -use std::sync::{Mutex, Arc}; -#[cfg(any(feature = "npn", feature = "alpn"))] -use libc::{c_uchar, c_uint}; -#[cfg(any(feature = "npn", feature = "alpn"))] use std::slice; -use std::marker::PhantomData; -use ffi; - -use init; -use dh::DH; -use x509::{X509StoreContext, X509FileType, X509, X509Ref}; -use crypto::pkey::PKey; +use std::str; +use std::sync::Mutex; + +use {init, cvt, cvt_p}; +use dh::DhRef; +use ec_key::EcKeyRef; +use x509::{X509StoreContextRef, X509FileType, X509, X509Ref, X509VerifyError}; +#[cfg(any(ossl102, ossl110))] +use verify::X509VerifyParamRef; +use pkey::PKeyRef; use error::ErrorStack; +use types::{OpenSslType, OpenSslTypeRef}; +use util::Opaque; -pub mod error; +mod error; +mod connector; mod bio; #[cfg(test)] mod tests; use self::bio::BioMethod; -#[doc(inline)] -pub use ssl::error::Error; +pub use ssl::connector::{SslConnectorBuilder, SslConnector, SslAcceptorBuilder, SslAcceptor}; +pub use ssl::error::{Error, HandshakeError}; bitflags! { - pub flags SslContextOptions: c_long { + pub flags SslOption: c_ulong { 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_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_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, @@ -66,80 +140,59 @@ bitflags! { const SSL_OP_NO_SSLV2 = ffi::SSL_OP_NO_SSLv2, const SSL_OP_NO_SSLV3 = ffi::SSL_OP_NO_SSLv3, const SSL_OP_NO_TLSV1 = ffi::SSL_OP_NO_TLSv1, + const SSL_OP_NO_TLSV1_2 = ffi::SSL_OP_NO_TLSv1_2, + const SSL_OP_NO_TLSV1_1 = ffi::SSL_OP_NO_TLSv1_1, + /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0. + #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] + const SSL_OP_NO_DTLSV1 = ffi::SSL_OP_NO_DTLSv1, + /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0. + #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] + const SSL_OP_NO_DTLSV1_2 = ffi::SSL_OP_NO_DTLSv1_2, + /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0. + #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] + const SSL_OP_NO_SSL_MASK = ffi::SSL_OP_NO_SSL_MASK, } } -/// Determines the SSL method supported -#[allow(non_camel_case_types)] -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub enum SslMethod { - #[cfg(feature = "sslv2")] - /// Only support the SSLv2 protocol, requires the `sslv2` feature. - Sslv2, - /// Support the SSLv2, SSLv3, TLSv1, TLSv1.1, and TLSv1.2 protocols depending on what the - /// linked OpenSSL library supports. - Sslv23, - #[cfg(feature = "sslv3")] - /// Only support the SSLv3 protocol. - Sslv3, - /// Only support the TLSv1 protocol. - Tlsv1, - #[cfg(feature = "tlsv1_1")] - /// Support TLSv1.1 protocol, requires the `tlsv1_1` feature. - Tlsv1_1, - #[cfg(feature = "tlsv1_2")] - /// Support TLSv1.2 protocol, requires the `tlsv1_2` feature. - Tlsv1_2, - #[cfg(feature = "dtlsv1")] - /// Support DTLSv1 protocol, requires the `dtlsv1` feature. - Dtlsv1, - #[cfg(feature = "dtlsv1_2")] - /// Support DTLSv1.2 protocol, requires the `dtlsv1_2` feature. - Dtlsv1_2, +bitflags! { + pub flags SslMode: c_long { + const SSL_MODE_ENABLE_PARTIAL_WRITE = ffi::SSL_MODE_ENABLE_PARTIAL_WRITE, + const SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = ffi::SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER, + const SSL_MODE_AUTO_RETRY = ffi::SSL_MODE_AUTO_RETRY, + const SSL_MODE_NO_AUTO_CHAIN = ffi::SSL_MODE_NO_AUTO_CHAIN, + const SSL_MODE_RELEASE_BUFFERS = ffi::SSL_MODE_RELEASE_BUFFERS, + const SSL_MODE_SEND_CLIENTHELLO_TIME = ffi::SSL_MODE_SEND_CLIENTHELLO_TIME, + const SSL_MODE_SEND_SERVERHELLO_TIME = ffi::SSL_MODE_SEND_SERVERHELLO_TIME, + const SSL_MODE_SEND_FALLBACK_SCSV = ffi::SSL_MODE_SEND_FALLBACK_SCSV, + } } +#[derive(Copy, Clone)] +pub struct SslMethod(*const ffi::SSL_METHOD); + impl SslMethod { - fn to_raw(&self) -> *const ffi::SSL_METHOD { - unsafe { - match *self { - #[cfg(feature = "sslv2")] - SslMethod::Sslv2 => ffi::SSLv2_method(), - #[cfg(feature = "sslv3")] - SslMethod::Sslv3 => ffi::SSLv3_method(), - SslMethod::Tlsv1 => ffi::TLSv1_method(), - SslMethod::Sslv23 => ffi::SSLv23_method(), - #[cfg(feature = "tlsv1_1")] - SslMethod::Tlsv1_1 => ffi::TLSv1_1_method(), - #[cfg(feature = "tlsv1_2")] - SslMethod::Tlsv1_2 => ffi::TLSv1_2_method(), - #[cfg(feature = "dtlsv1")] - SslMethod::Dtlsv1 => ffi::DTLSv1_method(), - #[cfg(feature = "dtlsv1_2")] - SslMethod::Dtlsv1_2 => ffi::DTLSv1_2_method(), - } - } + /// Support all versions of the TLS protocol. + /// + /// This corresponds to `TLS_method` on OpenSSL 1.1.0 and `SSLv23_method` + /// on OpenSSL 1.0.x. + pub fn tls() -> SslMethod { + SslMethod(compat::tls_method()) } - fn from_raw(method: *const ffi::SSL_METHOD) -> Option<SslMethod> { - unsafe { - match method { - #[cfg(feature = "sslv2")] - x if x == ffi::SSLv2_method() => Some(SslMethod::Sslv2), - #[cfg(feature = "sslv3")] - 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, - } - } + /// Support all versions of the DTLS protocol. + /// + /// This corresponds to `DTLS_method` on OpenSSL 1.1.0 and `DTLSv1_method` + /// on OpenSSL 1.0.x. + pub fn dtls() -> SslMethod { + SslMethod(compat::dtls_method()) + } + + pub unsafe fn from_ptr(ptr: *const ffi::SSL_METHOD) -> SslMethod { + SslMethod(ptr) + } + + pub fn as_ptr(&self) -> *const ffi::SSL_METHOD { + self.0 } } @@ -172,85 +225,72 @@ fn get_ssl_verify_data_idx<T: Any + 'static>() -> c_int { *SSL_INDEXES.lock().unwrap().entry(TypeId::of::<T>()).or_insert_with(|| get_new_ssl_idx::<T>()) } -#[cfg(feature = "npn")] lazy_static! { static ref NPN_PROTOS_IDX: c_int = get_new_idx::<Vec<u8>>(); } -#[cfg(feature = "alpn")] + +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] lazy_static! { static ref ALPN_PROTOS_IDX: c_int = get_new_idx::<Vec<u8>>(); } +unsafe extern "C" fn free_data_box<T>(_parent: *mut c_void, + ptr: *mut c_void, + _ad: *mut ffi::CRYPTO_EX_DATA, + _idx: c_int, + _argl: c_long, + _argp: *mut c_void) { + if !ptr.is_null() { + Box::<T>::from_raw(ptr as *mut T); + } +} + /// Determine a new index to use for SSL CTX ex data. /// Registers a destruct for the data which will be called by openssl when the context is freed. fn get_new_idx<T>() -> c_int { - extern "C" fn free_data_box<T>(_parent: *mut c_void, - ptr: *mut c_void, - _ad: *mut ffi::CRYPTO_EX_DATA, - _idx: c_int, - _argl: c_long, - _argp: *mut c_void) { - if !ptr.is_null() { - let _: Box<T> = unsafe { mem::transmute(ptr) }; - } - } - unsafe { - let f: ffi::CRYPTO_EX_free = free_data_box::<T>; - let idx = ffi::SSL_CTX_get_ex_new_index(0, ptr::null(), None, None, Some(f)); + let idx = compat::get_new_idx(free_data_box::<T>); assert!(idx >= 0); idx } } fn get_new_ssl_idx<T>() -> c_int { - extern "C" fn free_data_box<T>(_parent: *mut c_void, - ptr: *mut c_void, - _ad: *mut ffi::CRYPTO_EX_DATA, - _idx: c_int, - _argl: c_long, - _argp: *mut c_void) { - if !ptr.is_null() { - let _: Box<T> = unsafe { mem::transmute(ptr) }; - } - } - unsafe { - let f: ffi::CRYPTO_EX_free = free_data_box::<T>; - let idx = ffi::SSL_get_ex_new_index(0, ptr::null(), None, None, Some(f)); + let idx = compat::get_new_ssl_idx(free_data_box::<T>); assert!(idx >= 0); idx } } extern "C" fn raw_verify<F>(preverify_ok: c_int, x509_ctx: *mut ffi::X509_STORE_CTX) -> c_int - where F: Fn(bool, &X509StoreContext) -> bool + Any + 'static + Sync + Send + where F: Fn(bool, &X509StoreContextRef) -> bool + Any + 'static + Sync + Send { unsafe { let idx = ffi::SSL_get_ex_data_X509_STORE_CTX_idx(); let ssl = ffi::X509_STORE_CTX_get_ex_data(x509_ctx, idx); - let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl); + let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl as *const _); let verify = ffi::SSL_CTX_get_ex_data(ssl_ctx, get_verify_data_idx::<F>()); - let verify: &F = mem::transmute(verify); + let verify: &F = &*(verify as *mut F); - let ctx = X509StoreContext::new(x509_ctx); + let ctx = X509StoreContextRef::from_ptr(x509_ctx); - verify(preverify_ok != 0, &ctx) as c_int + verify(preverify_ok != 0, ctx) as c_int } } extern "C" fn ssl_raw_verify<F>(preverify_ok: c_int, x509_ctx: *mut ffi::X509_STORE_CTX) -> c_int - where F: Fn(bool, &X509StoreContext) -> bool + Any + 'static + Sync + Send + where F: Fn(bool, &X509StoreContextRef) -> bool + Any + 'static + Sync + Send { unsafe { let idx = ffi::SSL_get_ex_data_X509_STORE_CTX_idx(); let ssl = ffi::X509_STORE_CTX_get_ex_data(x509_ctx, idx); - let verify = ffi::SSL_get_ex_data(ssl, get_ssl_verify_data_idx::<F>()); - let verify: &F = mem::transmute(verify); + let verify = ffi::SSL_get_ex_data(ssl as *const _, get_ssl_verify_data_idx::<F>()); + let verify: &F = &*(verify as *mut F); - let ctx = X509StoreContext::new(x509_ctx); + let ctx = X509StoreContextRef::from_ptr(x509_ctx); - verify(preverify_ok != 0, &ctx) as c_int + verify(preverify_ok != 0, ctx) as c_int } } @@ -260,10 +300,10 @@ extern "C" fn raw_sni<F>(ssl: *mut ffi::SSL, al: *mut c_int, _arg: *mut c_void) unsafe { let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl); let callback = ffi::SSL_CTX_get_ex_data(ssl_ctx, get_verify_data_idx::<F>()); - let callback: &F = mem::transmute(callback); - let mut ssl = SslRef::from_ptr(ssl); + let callback: &F = &*(callback as *mut F); + let ssl = SslRef::from_ptr_mut(ssl); - match callback(&mut ssl) { + match callback(ssl) { Ok(()) => ffi::SSL_TLSEXT_ERR_OK, Err(SniError::Fatal(e)) => { *al = e; @@ -278,7 +318,6 @@ extern "C" fn raw_sni<F>(ssl: *mut ffi::SSL, al: *mut c_int, _arg: *mut c_void) } } -#[cfg(any(feature = "npn", feature = "alpn"))] unsafe fn select_proto_using(ssl: *mut ffi::SSL, out: *mut *mut c_uchar, outlen: *mut c_uchar, @@ -291,7 +330,7 @@ unsafe fn select_proto_using(ssl: *mut ffi::SSL, // extra data. let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl); let protocols = ffi::SSL_CTX_get_ex_data(ssl_ctx, ex_data); - let protocols: &Vec<u8> = mem::transmute(protocols); + let protocols: &Vec<u8> = &*(protocols as *mut Vec<u8>); // Prepare the client list parameters to be passed to the OpenSSL function... let client = protocols.as_ptr(); let client_len = protocols.len() as c_uint; @@ -311,7 +350,6 @@ unsafe fn select_proto_using(ssl: *mut ffi::SSL, /// supported by the server. It achieves this by delegating to the `SSL_select_next_proto` /// function. The list of protocols supported by the client is found in the extra data of the /// OpenSSL context. -#[cfg(feature = "npn")] extern "C" fn raw_next_proto_select_cb(ssl: *mut ffi::SSL, out: *mut *mut c_uchar, outlen: *mut c_uchar, @@ -322,15 +360,15 @@ extern "C" fn raw_next_proto_select_cb(ssl: *mut ffi::SSL, unsafe { select_proto_using(ssl, out, outlen, inbuf, inlen, *NPN_PROTOS_IDX) } } -#[cfg(feature = "alpn")] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] extern "C" fn raw_alpn_select_cb(ssl: *mut ffi::SSL, - out: *mut *mut c_uchar, + out: *mut *const c_uchar, outlen: *mut c_uchar, inbuf: *const c_uchar, inlen: c_uint, _arg: *mut c_void) -> c_int { - unsafe { select_proto_using(ssl, out, outlen, inbuf, inlen, *ALPN_PROTOS_IDX) } + unsafe { select_proto_using(ssl, out as *mut _, outlen, inbuf, inlen, *ALPN_PROTOS_IDX) } } /// The function is given as the callback to `SSL_CTX_set_next_protos_advertised_cb`. @@ -340,7 +378,6 @@ extern "C" fn raw_alpn_select_cb(ssl: *mut ffi::SSL, /// that it supports. /// The list of supported protocols is found in the extra data of the OpenSSL /// context. -#[cfg(feature = "npn")] extern "C" fn raw_next_protos_advertise_cb(ssl: *mut ffi::SSL, out: *mut *const c_uchar, outlen: *mut c_uint, @@ -356,7 +393,7 @@ extern "C" fn raw_next_protos_advertise_cb(ssl: *mut ffi::SSL, } else { // If the pointer is valid, put the pointer to the actual byte array into the // output parameter `out`, as well as its length into `outlen`. - let protocols: &Vec<u8> = mem::transmute(protocols); + let protocols: &Vec<u8> = &*(protocols as *mut Vec<u8>); *out = protocols.as_ptr(); *outlen = protocols.len() as c_uint; } @@ -367,7 +404,6 @@ extern "C" fn raw_next_protos_advertise_cb(ssl: *mut ffi::SSL, /// Convert a set of byte slices into a series of byte strings encoded for SSL. Encoding is a byte /// containing the length followed by the string. -#[cfg(any(feature = "npn", feature = "alpn"))] fn ssl_encode_byte_strings(strings: &[&[u8]]) -> Vec<u8> { let mut enc = Vec::new(); for string in strings { @@ -389,22 +425,30 @@ pub enum SniError { NoAck, } -// FIXME: macro may be instead of inlining? -#[inline] -fn wrap_ssl_result(res: c_int) -> Result<(), ErrorStack> { - if res == 0 { - Err(ErrorStack::get()) - } else { - Ok(()) +/// A builder for `SslContext`s. +pub struct SslContextBuilder(*mut ffi::SSL_CTX); + +unsafe impl Sync for SslContextBuilder {} +unsafe impl Send for SslContextBuilder {} + +impl Drop for SslContextBuilder { + fn drop(&mut self) { + unsafe { ffi::SSL_CTX_free(self.as_ptr()) } } } -/// A borrowed SSL context object. -pub struct SslContextRef<'a>(*mut ffi::SSL_CTX, PhantomData<&'a ()>); +impl SslContextBuilder { + pub fn new(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> { + unsafe { + init(); + let ctx = try!(cvt_p(ffi::SSL_CTX_new(method.as_ptr()))); -impl<'a> SslContextRef<'a> { - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextRef<'a> { - SslContextRef(ctx, PhantomData) + Ok(SslContextBuilder::from_ptr(ctx)) + } + } + + pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { + SslContextBuilder(ctx) } pub fn as_ptr(&self) -> *mut ffi::SSL_CTX { @@ -421,11 +465,13 @@ impl<'a> SslContextRef<'a> { /// Configures the certificate verification method for new connections and /// registers a verification callback. pub fn set_verify_callback<F>(&mut self, mode: SslVerifyMode, verify: F) - where F: Fn(bool, &X509StoreContext) -> bool + Any + 'static + Sync + Send + where F: Fn(bool, &X509StoreContextRef) -> bool + Any + 'static + Sync + Send { unsafe { let verify = Box::new(verify); - ffi::SSL_CTX_set_ex_data(self.as_ptr(), get_verify_data_idx::<F>(), mem::transmute(verify)); + ffi::SSL_CTX_set_ex_data(self.as_ptr(), + get_verify_data_idx::<F>(), + mem::transmute(verify)); ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits as c_int, Some(raw_verify::<F>)); } } @@ -455,18 +501,25 @@ impl<'a> SslContextRef<'a> { } } - pub fn set_read_ahead(&mut self, m: u32) { + pub fn set_read_ahead(&mut self, read_ahead: bool) { unsafe { - ffi::SSL_CTX_set_read_ahead(self.as_ptr(), m as c_long); + ffi::SSL_CTX_set_read_ahead(self.as_ptr(), read_ahead as c_long); } } - fn set_mode(&mut self, mode: c_long) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { ffi::SSL_CTX_set_mode(self.as_ptr(), mode) as c_int }) + pub fn set_mode(&mut self, mode: SslMode) -> SslMode { + unsafe { + let mode = ffi::SSL_CTX_set_mode(self.as_ptr(), mode.bits()); + SslMode::from_bits(mode).unwrap() + } + } + + pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int).map(|_| ()) } } - pub fn set_tmp_dh(&mut self, dh: &DH) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as i32 }) + pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int).map(|_| ()) } } /// Use the default locations of trusted certificates for verification. @@ -475,16 +528,18 @@ impl<'a> SslContextRef<'a> { /// environment variables if present, or defaults specified at OpenSSL /// build time otherwise. pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { ffi::SSL_CTX_set_default_verify_paths(self.as_ptr()) }) + unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) } } - #[allow(non_snake_case)] /// Specifies the file that contains trusted CA certificates. - pub fn set_CA_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ErrorStack> { - let file = CString::new(file.as_ref().as_os_str().to_str().expect("invalid utf8")).unwrap(); - wrap_ssl_result(unsafe { - ffi::SSL_CTX_load_verify_locations(self.as_ptr(), file.as_ptr() as *const _, ptr::null()) - }) + pub fn set_ca_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ErrorStack> { + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_load_verify_locations(self.as_ptr(), + file.as_ptr() as *const _, + ptr::null())) + .map(|_| ()) + } } /// Set the context identifier for sessions @@ -496,9 +551,13 @@ impl<'a> SslContextRef<'a> { /// This value should be set when using client certificates, or each request will fail /// handshake and need to be restarted. pub fn set_session_id_context(&mut self, sid_ctx: &[u8]) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { - ffi::SSL_CTX_set_session_id_context(self.as_ptr(), sid_ctx.as_ptr(), sid_ctx.len() as u32) - }) + unsafe { + assert!(sid_ctx.len() <= c_uint::max_value() as usize); + cvt(ffi::SSL_CTX_set_session_id_context(self.as_ptr(), + sid_ctx.as_ptr(), + sid_ctx.len() as c_uint)) + .map(|_| ()) + } } /// Specifies the file that contains certificate @@ -506,43 +565,39 @@ impl<'a> SslContextRef<'a> { file: P, file_type: X509FileType) -> Result<(), ErrorStack> { - let file = CString::new(file.as_ref().as_os_str().to_str().expect("invalid utf8")).unwrap(); - wrap_ssl_result(unsafe { - ffi::SSL_CTX_use_certificate_file(self.as_ptr(), - file.as_ptr() as *const _, - file_type as c_int) - }) + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_use_certificate_file(self.as_ptr(), + file.as_ptr() as *const _, + file_type.as_raw())) + .map(|_| ()) + } } /// Specifies the file that contains certificate chain pub fn set_certificate_chain_file<P: AsRef<Path>>(&mut self, - file: P, - file_type: X509FileType) + file: P) -> Result<(), ErrorStack> { - let file = CString::new(file.as_ref().as_os_str().to_str().expect("invalid utf8")).unwrap(); - wrap_ssl_result(unsafe { - ffi::SSL_CTX_use_certificate_chain_file(self.as_ptr(), - file.as_ptr() as *const _, - file_type as c_int) - }) + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_use_certificate_chain_file(self.as_ptr(), file.as_ptr() as *const _)) + .map(|_| ()) + } } /// Specifies the certificate pub fn set_certificate(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { ffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr()) }) + unsafe { cvt(ffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr())).map(|_| ()) } } /// Adds a certificate to the certificate chain presented together with the /// certificate specified using set_certificate() - pub fn add_extra_chain_cert(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { - // FIXME this should really just take an X509 by value - let der = try!(cert.to_der()); - let cert = try!(X509::from_der(&der)); + pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { unsafe { - try_ssl!(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.as_ptr())); + try!(cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.as_ptr()) as c_int)); + mem::forget(cert); + Ok(()) } - mem::forget(cert); - Ok(()) } /// Specifies the file that contains private key @@ -550,61 +605,61 @@ impl<'a> SslContextRef<'a> { file: P, file_type: X509FileType) -> Result<(), ErrorStack> { - let file = CString::new(file.as_ref().as_os_str().to_str().expect("invalid utf8")).unwrap(); - wrap_ssl_result(unsafe { - ffi::SSL_CTX_use_PrivateKey_file(self.as_ptr(), - file.as_ptr() as *const _, - file_type as c_int) - }) + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_use_PrivateKey_file(self.as_ptr(), + file.as_ptr() as *const _, + file_type.as_raw())) + .map(|_| ()) + } } /// Specifies the private key - pub fn set_private_key(&mut self, key: &PKey) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr()) }) - } - - /// Check consistency of private key and certificate - pub fn check_private_key(&mut self) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { ffi::SSL_CTX_check_private_key(self.as_ptr()) }) + pub fn set_private_key(&mut self, key: &PKeyRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr())).map(|_| ()) } } pub fn set_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { - let cipher_list = CString::new(cipher_list).unwrap(); - ffi::SSL_CTX_set_cipher_list(self.as_ptr(), cipher_list.as_ptr() as *const _) - }) + let cipher_list = CString::new(cipher_list).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_set_cipher_list(self.as_ptr(), cipher_list.as_ptr() as *const _)) + .map(|_| ()) + } } - /// If `onoff` is set to `true`, enable ECDHE for key exchange with compatible - /// clients, and automatically select an appropriate elliptic curve. + /// If `onoff` is set to `true`, enable ECDHE for key exchange with + /// compatible clients, and automatically select an appropriate elliptic + /// curve. /// - /// This method requires OpenSSL >= 1.0.2 or LibreSSL and the `ecdh_auto` feature. - #[cfg(feature = "ecdh_auto")] + /// Requires the `v102` feature and OpenSSL 1.0.2. + #[cfg(all(feature = "v102", ossl102))] pub fn set_ecdh_auto(&mut self, onoff: bool) -> Result<(), ErrorStack> { - wrap_ssl_result(unsafe { ffi::SSL_CTX_set_ecdh_auto(self.as_ptr(), onoff as c_long) as c_int }) + self._set_ecdh_auto(onoff) + } + + #[cfg(ossl102)] + fn _set_ecdh_auto(&mut self, onoff: bool) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_ecdh_auto(self.as_ptr(), onoff as c_int)).map(|_| ()) } } - pub fn set_options(&mut self, option: SslContextOptions) -> SslContextOptions { - let ret = unsafe { ffi::SSL_CTX_set_options(self.as_ptr(), option.bits()) }; - SslContextOptions::from_bits(ret).unwrap() + pub fn set_options(&mut self, option: SslOption) -> SslOption { + let ret = unsafe { compat::SSL_CTX_set_options(self.as_ptr(), option.bits()) }; + SslOption::from_bits(ret).unwrap() } - pub fn options(&self) -> SslContextOptions { - let ret = unsafe { ffi::SSL_CTX_get_options(self.as_ptr()) }; - SslContextOptions::from_bits(ret).unwrap() + pub fn options(&self) -> SslOption { + let ret = unsafe { compat::SSL_CTX_get_options(self.as_ptr()) }; + SslOption::from_bits(ret).unwrap() } - pub fn clear_options(&mut self, option: SslContextOptions) -> SslContextOptions { - let ret = unsafe { ffi::SSL_CTX_clear_options(self.as_ptr(), option.bits()) }; - SslContextOptions::from_bits(ret).unwrap() + pub fn clear_options(&mut self, option: SslOption) -> SslOption { + let ret = unsafe { compat::SSL_CTX_clear_options(self.as_ptr(), option.bits()) }; + SslOption::from_bits(ret).unwrap() } /// Set the protocols to be used during Next Protocol Negotiation (the protocols /// supported by the application). - /// - /// This method needs the `npn` feature. - #[cfg(feature = "npn")] - pub fn set_npn_protocols(&mut self, protocols: &[&[u8]]) { + pub fn set_npn_protocols(&mut self, protocols: &[&[u8]]) -> Result<(), ErrorStack> { // Firstly, convert the list of protocols to a byte-array that can be passed to OpenSSL // APIs -- a list of length-prefixed strings. let protocols: Box<Vec<u8>> = Box::new(ssl_encode_byte_strings(protocols)); @@ -612,7 +667,9 @@ impl<'a> SslContextRef<'a> { unsafe { // Attach the protocol list to the OpenSSL context structure, // so that we can refer to it within the callback. - ffi::SSL_CTX_set_ex_data(self.as_ptr(), *NPN_PROTOS_IDX, mem::transmute(protocols)); + try!(cvt(ffi::SSL_CTX_set_ex_data(self.as_ptr(), + *NPN_PROTOS_IDX, + Box::into_raw(protocols) as *mut c_void))); // Now register the callback that performs the default protocol // matching based on the client-supported list of protocols that // has been saved. @@ -624,6 +681,7 @@ impl<'a> SslContextRef<'a> { ffi::SSL_CTX_set_next_protos_advertised_cb(self.as_ptr(), raw_next_protos_advertise_cb, ptr::null_mut()); + Ok(()) } } @@ -634,40 +692,58 @@ impl<'a> SslContextRef<'a> { /// /// Note that ordering of the protocols controls the priority with which they are chosen. /// - /// This method needs the `alpn` feature. - #[cfg(feature = "alpn")] - pub fn set_alpn_protocols(&mut self, protocols: &[&[u8]]) { + /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0. + #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] + pub fn set_alpn_protocols(&mut self, protocols: &[&[u8]]) -> Result<(), ErrorStack> { let protocols: Box<Vec<u8>> = Box::new(ssl_encode_byte_strings(protocols)); unsafe { // Set the context's internal protocol list for use if we are a server - ffi::SSL_CTX_set_alpn_protos(self.as_ptr(), protocols.as_ptr(), protocols.len() as c_uint); + let r = ffi::SSL_CTX_set_alpn_protos(self.as_ptr(), + protocols.as_ptr(), + protocols.len() as c_uint); + // fun fact, SSL_CTX_set_alpn_protos has a reversed return code D: + if r != 0 { + return Err(ErrorStack::get()); + } // Rather than use the argument to the callback to contain our data, store it in the // ssl ctx's ex_data so that we can configure a function to free it later. In the // future, it might make sense to pull this into our internal struct Ssl instead of // leaning on openssl and using function pointers. - ffi::SSL_CTX_set_ex_data(self.as_ptr(), *ALPN_PROTOS_IDX, mem::transmute(protocols)); + try!(cvt(ffi::SSL_CTX_set_ex_data(self.as_ptr(), + *ALPN_PROTOS_IDX, + Box::into_raw(protocols) as *mut c_void))); // Now register the callback that performs the default protocol // matching based on the client-supported list of protocols that // has been saved. ffi::SSL_CTX_set_alpn_select_cb(self.as_ptr(), raw_alpn_select_cb, ptr::null_mut()); + + Ok(()) } } + + /// Checks consistency between the private key and certificate. + pub fn check_private_key(&self) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_check_private_key(self.as_ptr())).map(|_| ()) } + } + + pub fn build(self) -> SslContext { + let ctx = SslContext(self.0); + mem::forget(self); + ctx + } } -/// An owned SSL context object. -pub struct SslContext(SslContextRef<'static>); +type_!(SslContext, SslContextRef, ffi::SSL_CTX, ffi::SSL_CTX_free); unsafe impl Send for SslContext {} unsafe impl Sync for SslContext {} -#[cfg(feature = "ssl_context_clone")] impl Clone for SslContext { - /// Requires the `ssl_context_clone` feature. fn clone(&self) -> Self { unsafe { - ::c_helpers::rust_0_8_SSL_CTX_clone(self.as_ptr()); + compat::SSL_CTX_up_ref(self.as_ptr()); SslContext::from_ptr(self.as_ptr()) } } @@ -680,78 +756,57 @@ impl fmt::Debug for SslContext { } } -impl Drop for SslContext { - fn drop(&mut self) { - unsafe { ffi::SSL_CTX_free(self.as_ptr()) } +impl SslContext { + pub fn builder(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> { + SslContextBuilder::new(method) } } -impl Deref for SslContext { - type Target = SslContextRef<'static>; - fn deref(&self) -> &SslContextRef<'static> { - &self.0 - } -} +pub struct CipherBits { + /// The number of secret bits used for the cipher. + pub secret: i32, -impl DerefMut for SslContext { - fn deref_mut(&mut self) -> &mut SslContextRef<'static> { - &mut self.0 - } + /// The number of bits processed by the chosen algorithm. + pub algorithm: i32, } -impl SslContext { - /// Creates a new SSL context. - pub fn new(method: SslMethod) -> Result<SslContext, ErrorStack> { - init(); +pub struct SslCipher(*mut ffi::SSL_CIPHER); - let mut ctx = unsafe { - let ctx = try_ssl_null!(ffi::SSL_CTX_new(method.to_raw())); - SslContext::from_ptr(ctx) - }; +impl OpenSslType for SslCipher { + type CType = ffi::SSL_CIPHER; + type Ref = SslCipherRef; - match method { - #[cfg(feature = "dtlsv1")] - SslMethod::Dtlsv1 => ctx.set_read_ahead(1), - #[cfg(feature = "dtlsv1_2")] - SslMethod::Dtlsv1_2 => ctx.set_read_ahead(1), - _ => {} - } - // this is a bit dubious (?) - try!(ctx.set_mode(ffi::SSL_MODE_AUTO_RETRY | ffi::SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER)); - - Ok(ctx) + unsafe fn from_ptr(ptr: *mut ffi::SSL_CIPHER) -> SslCipher { + SslCipher(ptr) } +} - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContext { - SslContext(SslContextRef::from_ptr(ctx)) - } +impl Deref for SslCipher { + type Target = SslCipherRef; - pub fn as_ptr(&self) -> *mut ffi::SSL_CTX { - (**self).as_ptr() + fn deref(&self) -> &SslCipherRef { + unsafe { SslCipherRef::from_ptr(self.0) } } } - -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>, - _p: (), +impl DerefMut for SslCipher { + fn deref_mut(&mut self) -> &mut SslCipherRef { + unsafe { SslCipherRef::from_ptr_mut(self.0) } + } } +pub struct SslCipherRef(Opaque); -pub struct SslCipher<'a> { - cipher: *const ffi::SSL_CIPHER, - ph: PhantomData<&'a ()>, +impl OpenSslTypeRef for SslCipherRef { + type CType = ffi::SSL_CIPHER; } -impl<'a> SslCipher<'a> { +impl SslCipherRef { /// Returns the name of cipher. - pub fn name(&self) -> &'static str { + pub fn name(&self) -> &str { let name = unsafe { - let ptr = ffi::SSL_CIPHER_get_name(self.cipher); + let ptr = ffi::SSL_CIPHER_get_name(self.as_ptr()); CStr::from_ptr(ptr as *const _) }; @@ -759,9 +814,9 @@ impl<'a> SslCipher<'a> { } /// Returns the SSL/TLS protocol version that first defined the cipher. - pub fn version(&self) -> &'static str { + pub fn version(&self) -> &str { let version = unsafe { - let ptr = ffi::SSL_CIPHER_get_version(self.cipher); + let ptr = ffi::SSL_CIPHER_get_version(self.as_ptr()); CStr::from_ptr(ptr as *const _) }; @@ -771,78 +826,44 @@ impl<'a> SslCipher<'a> { /// 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), - _p: (), - } - } else { - CipherBits { - secret: secret_bits, - algorithm: None, - _p: (), - } + let mut algo_bits = 0; + let secret_bits = ffi::SSL_CIPHER_get_bits(self.as_ptr(), &mut algo_bits); + CipherBits { + secret: secret_bits.into(), + algorithm: algo_bits.into(), } } } /// Returns a textual description of the cipher used - pub fn description(&self) -> Option<String> { + pub fn description(&self) -> String { unsafe { // SSL_CIPHER_description requires a buffer of at least 128 bytes. let mut buf = [0; 128]; - let desc_ptr = ffi::SSL_CIPHER_description(self.cipher, buf.as_mut_ptr(), 128); - - if !desc_ptr.is_null() { - String::from_utf8(CStr::from_ptr(desc_ptr as *const _).to_bytes().to_vec()).ok() - } else { - None - } + let ptr = ffi::SSL_CIPHER_description(self.as_ptr(), buf.as_mut_ptr(), 128); + String::from_utf8(CStr::from_ptr(ptr as *const _).to_bytes().to_vec()).unwrap() } } } -pub struct SslRef<'a>(*mut ffi::SSL, PhantomData<&'a ()>); +type_!(Ssl, SslRef, ffi::SSL, ffi::SSL_free); -unsafe impl<'a> Send for SslRef<'a> {} -unsafe impl<'a> Sync for SslRef<'a> {} - -impl<'a> fmt::Debug for SslRef<'a> { +impl fmt::Debug for SslRef { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("SslRef") - .field("state", &self.state_string_long()) - .finish() + let mut builder = fmt.debug_struct("Ssl"); + builder.field("state", &self.state_string_long()); + if let Some(err) = self.verify_result() { + builder.field("verify_result", &err); + } + builder.finish() } } -impl<'a> SslRef<'a> { - pub unsafe fn from_ptr(ssl: *mut ffi::SSL) -> SslRef<'a> { - SslRef(ssl, PhantomData) - } - - pub fn as_ptr(&self) -> *mut ffi::SSL { - self.0 - } - +impl SslRef { fn get_raw_rbio(&self) -> *mut ffi::BIO { unsafe { ffi::SSL_get_rbio(self.as_ptr()) } } - fn connect(&mut self) -> c_int { - unsafe { ffi::SSL_connect(self.as_ptr()) } - } - - fn accept(&mut self) -> c_int { - unsafe { ffi::SSL_accept(self.as_ptr()) } - } - - fn handshake(&mut self) -> c_int { - unsafe { ffi::SSL_do_handshake(self.as_ptr()) } - } - fn read(&mut self, buf: &mut [u8]) -> c_int { let len = cmp::min(c_int::max_value() as usize, buf.len()) as c_int; unsafe { ffi::SSL_read(self.as_ptr(), buf.as_ptr() as *mut c_void, len) } @@ -872,7 +893,7 @@ impl<'a> SslRef<'a> { /// to the certificate chain. It should return `true` if the certificate /// chain is valid and `false` otherwise. pub fn set_verify_callback<F>(&mut self, mode: SslVerifyMode, verify: F) - where F: Fn(bool, &X509StoreContext) -> bool + Any + 'static + Sync + Send + where F: Fn(bool, &X509StoreContextRef) -> bool + Any + 'static + Sync + Send { unsafe { let verify = Box::new(verify); @@ -883,17 +904,14 @@ impl<'a> SslRef<'a> { } } - pub fn current_cipher(&self) -> Option<SslCipher<'a>> { + pub fn current_cipher(&self) -> Option<&SslCipherRef> { unsafe { let ptr = ffi::SSL_get_current_cipher(self.as_ptr()); if ptr.is_null() { None } else { - Some(SslCipher { - cipher: ptr, - ph: PhantomData, - }) + Some(SslCipherRef::from_ptr(ptr as *mut _)) } } } @@ -919,15 +937,9 @@ impl<'a> SslRef<'a> { /// Sets the host name to be used with SNI (Server Name Indication). pub fn set_hostname(&mut self, hostname: &str) -> Result<(), ErrorStack> { let cstr = CString::new(hostname).unwrap(); - let ret = unsafe { - ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr() as *mut _) - }; - - // For this case, 0 indicates failure. - if ret == 0 { - Err(ErrorStack::get()) - } else { - Ok(()) + unsafe { + cvt(ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr() as *mut _) as c_int) + .map(|_| ()) } } @@ -957,9 +969,6 @@ impl<'a> SslRef<'a> { /// /// The protocol's name is returned is an opaque sequence of bytes. It is up to the client /// to interpret it. - /// - /// This method needs the `npn` feature. - #[cfg(feature = "npn")] pub fn selected_npn_protocol(&self) -> Option<&[u8]> { unsafe { let mut data: *const c_uchar = ptr::null(); @@ -981,8 +990,8 @@ impl<'a> SslRef<'a> { /// The protocol's name is returned is an opaque sequence of bytes. It is up to the client /// to interpret it. /// - /// This method needs the `alpn` feature. - #[cfg(feature = "alpn")] + /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0. + #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] pub fn selected_alpn_protocol(&self) -> Option<&[u8]> { unsafe { let mut data: *const c_uchar = ptr::null(); @@ -1009,139 +1018,99 @@ impl<'a> SslRef<'a> { /// /// The result will be either None, indicating no compression is in use, or /// a string with the compression name. - pub fn compression(&self) -> Option<String> { - let ptr = unsafe { ffi::SSL_get_current_compression(self.as_ptr()) }; - if ptr == ptr::null() { - return None; - } - - let meth = unsafe { ffi::SSL_COMP_get_name(ptr) }; - let s = unsafe { - String::from_utf8(CStr::from_ptr(meth as *const _).to_bytes().to_vec()).unwrap() - }; - - Some(s) + pub fn compression(&self) -> Option<&str> { + self._compression() } - pub fn ssl_method(&self) -> SslMethod { + #[cfg(not(osslconf = "OPENSSL_NO_COMP"))] + fn _compression(&self) -> Option<&str> { unsafe { - let method = ffi::SSL_get_ssl_method(self.as_ptr()); - SslMethod::from_raw(method).unwrap() + let ptr = ffi::SSL_get_current_compression(self.as_ptr()); + if ptr == ptr::null() { + return None; + } + let meth = ffi::SSL_COMP_get_name(ptr); + Some(str::from_utf8(CStr::from_ptr(meth as *const _).to_bytes()).unwrap()) } } + #[cfg(osslconf = "OPENSSL_NO_COMP")] + fn _compression(&self) -> Option<&str> { + None + } + /// Returns the server's name for the current connection - pub fn servername(&self) -> Option<String> { - let name = unsafe { ffi::SSL_get_servername(self.as_ptr(), ffi::TLSEXT_NAMETYPE_host_name) }; - if name == ptr::null() { - return None; - } + pub fn servername(&self) -> Option<&str> { + unsafe { + let name = ffi::SSL_get_servername(self.as_ptr(), ffi::TLSEXT_NAMETYPE_host_name); + if name == ptr::null() { + return None; + } - unsafe { String::from_utf8(CStr::from_ptr(name as *const _).to_bytes().to_vec()).ok() } + Some(str::from_utf8(CStr::from_ptr(name as *const _).to_bytes()).unwrap()) + } } /// Changes the context corresponding to the current connection. pub fn set_ssl_context(&mut self, ctx: &SslContextRef) -> Result<(), ErrorStack> { - unsafe { - try_ssl_null!(ffi::SSL_set_SSL_CTX(self.as_ptr(), ctx.as_ptr())); - } - Ok(()) + unsafe { cvt_p(ffi::SSL_set_SSL_CTX(self.as_ptr(), ctx.as_ptr())).map(|_| ()) } } /// Returns the context corresponding to the current connection - pub fn ssl_context(&self) -> SslContextRef<'a> { + pub fn ssl_context(&self) -> &SslContextRef { unsafe { let ssl_ctx = ffi::SSL_get_SSL_CTX(self.as_ptr()); SslContextRef::from_ptr(ssl_ctx) } } -} -pub struct Ssl(SslRef<'static>); - -impl fmt::Debug for Ssl { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("Ssl") - .field("state", &self.state_string_long()) - .finish() + /// Returns the X509 verification configuration. + /// + /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or 1.1.0. + #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] + pub fn param_mut(&mut self) -> &mut X509VerifyParamRef { + self._param_mut() } -} -impl Drop for Ssl { - fn drop(&mut self) { - unsafe { ffi::SSL_free(self.as_ptr()) } + #[cfg(any(ossl102, ossl110))] + fn _param_mut(&mut self) -> &mut X509VerifyParamRef { + unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) } } -} - -impl Deref for Ssl { - type Target = SslRef<'static>; - fn deref(&self) -> &SslRef<'static> { - &self.0 + /// Returns the result of X509 certificate verification. + pub fn verify_result(&self) -> Option<X509VerifyError> { + unsafe { X509VerifyError::from_raw(ffi::SSL_get_verify_result(self.as_ptr())) } } } -impl DerefMut for Ssl { - fn deref_mut(&mut self) -> &mut SslRef<'static> { - &mut self.0 +unsafe impl Sync for Ssl {} +unsafe impl Send for Ssl {} + +impl fmt::Debug for Ssl { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, fmt) } } impl Ssl { pub fn new(ctx: &SslContext) -> Result<Ssl, ErrorStack> { unsafe { - let ssl = try_ssl_null!(ffi::SSL_new(ctx.as_ptr())); + let ssl = try!(cvt_p(ffi::SSL_new(ctx.as_ptr()))); Ok(Ssl::from_ptr(ssl)) } } - pub unsafe fn from_ptr(ssl: *mut ffi::SSL) -> Ssl { - Ssl(SslRef::from_ptr(ssl)) - } -} - -/// A stream wrapper which handles SSL encryption for an underlying stream. -pub struct SslStream<S> { - ssl: Ssl, - _method: Arc<BioMethod>, // NOTE: this *must* be after the Ssl field so things drop right - _p: PhantomData<S>, -} - -unsafe impl<S: Send> Send for SslStream<S> {} - -impl<S> fmt::Debug for SslStream<S> - where S: fmt::Debug -{ - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("SslStream") - .field("stream", &self.get_ref()) - .field("ssl", &self.ssl()) - .finish() - } -} - -impl<S: Read + Write> SslStream<S> { - fn new_base(ssl: Ssl, stream: S) -> Self { - unsafe { - let (bio, method) = bio::new(stream).unwrap(); - ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); - - SslStream { - ssl: ssl, - _method: method, - _p: PhantomData, - } - } - } - /// Creates an SSL/TLS client operating over the provided stream. - pub fn connect<T: IntoSsl>(ssl: T, stream: S) - -> Result<Self, HandshakeError<S>>{ - let ssl = try!(ssl.into_ssl().map_err(|e| { - HandshakeError::Failure(Error::Ssl(e)) - })); - let mut stream = Self::new_base(ssl, stream); - let ret = stream.ssl.connect(); + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `SslConnector` rather than `Ssl` directly, as it manages that configuration. + pub fn connect<S>(self, stream: S) -> Result<SslStream<S>, HandshakeError<S>> + where S: Read + Write + { + let mut stream = SslStream::new_base(self, stream); + let ret = unsafe { ffi::SSL_connect(stream.ssl.as_ptr()) }; if ret > 0 { Ok(stream) } else { @@ -1153,19 +1122,27 @@ impl<S: Read + Write> SslStream<S> { error: e, })) } - err => Err(HandshakeError::Failure(err)), + err => { + Err(HandshakeError::Failure(MidHandshakeSslStream { + stream: stream, + error: err, + })) + } } } } /// Creates an SSL/TLS server operating over the provided stream. - pub fn accept<T: IntoSsl>(ssl: T, stream: S) - -> Result<Self, HandshakeError<S>> { - let ssl = try!(ssl.into_ssl().map_err(|e| { - HandshakeError::Failure(Error::Ssl(e)) - })); - let mut stream = Self::new_base(ssl, stream); - let ret = stream.ssl.accept(); + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `SslAcceptor` rather than `Ssl` directly, as it manages that configuration. + pub fn accept<S>(self, stream: S) -> Result<SslStream<S>, HandshakeError<S>> + where S: Read + Write + { + let mut stream = SslStream::new_base(self, stream); + let ret = unsafe { ffi::SSL_accept(stream.ssl.as_ptr()) }; if ret > 0 { Ok(stream) } else { @@ -1177,71 +1154,15 @@ impl<S: Read + Write> SslStream<S> { error: e, })) } - err => Err(HandshakeError::Failure(err)), + err => { + Err(HandshakeError::Failure(MidHandshakeSslStream { + stream: stream, + error: err, + })) + } } } } - - /// Like `read`, but returns an `ssl::Error` rather than an `io::Error`. - /// - /// This is particularly useful with a nonblocking socket, where the error - /// value will identify if OpenSSL is waiting on read or write readiness. - pub fn ssl_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { - let ret = self.ssl.read(buf); - if ret >= 0 { - Ok(ret as usize) - } else { - Err(self.make_error(ret)) - } - } - - /// Like `write`, but returns an `ssl::Error` rather than an `io::Error`. - /// - /// This is particularly useful with a nonblocking socket, where the error - /// value will identify if OpenSSL is waiting on read or write readiness. - pub fn ssl_write(&mut self, buf: &[u8]) -> Result<usize, Error> { - let ret = self.ssl.write(buf); - if ret >= 0 { - Ok(ret as usize) - } else { - Err(self.make_error(ret)) - } - } -} - -/// An error or intermediate state after a TLS handshake attempt. -#[derive(Debug)] -pub enum HandshakeError<S> { - /// The handshake failed. - Failure(Error), - /// The handshake was interrupted midway through. - Interrupted(MidHandshakeSslStream<S>), -} - -impl<S: Any + fmt::Debug> stderror::Error for HandshakeError<S> { - fn description(&self) -> &str { - match *self { - HandshakeError::Failure(ref e) => e.description(), - HandshakeError::Interrupted(ref e) => e.error.description(), - } - } - - fn cause(&self) -> Option<&stderror::Error> { - match *self { - HandshakeError::Failure(ref e) => Some(e), - HandshakeError::Interrupted(ref e) => Some(&e.error), - } - } -} - -impl<S: Any + fmt::Debug> fmt::Display for HandshakeError<S> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(f.write_str(stderror::Error::description(self))); - if let Some(e) = stderror::Error::cause(self) { - try!(write!(f, ": {}", e)); - } - Ok(()) - } } /// An SSL stream midway through the handshake process. @@ -1263,7 +1184,7 @@ impl<S> MidHandshakeSslStream<S> { } /// Returns a shared reference to the `Ssl` of the stream. - pub fn ssl(&self) -> &Ssl { + pub fn ssl(&self) -> &SslRef { self.stream.ssl() } @@ -1272,9 +1193,14 @@ impl<S> MidHandshakeSslStream<S> { &self.error } + /// Consumes `self`, returning its error. + pub fn into_error(self) -> Error { + self.error + } + /// Restarts the handshake process. pub fn handshake(mut self) -> Result<SslStream<S>, HandshakeError<S>> { - let ret = self.stream.ssl.handshake(); + let ret = unsafe { ffi::SSL_do_handshake(self.stream.ssl.as_ptr()) }; if ret > 0 { Ok(self.stream) } else { @@ -1284,12 +1210,94 @@ impl<S> MidHandshakeSslStream<S> { self.error = e; Err(HandshakeError::Interrupted(self)) } - err => Err(HandshakeError::Failure(err)), + err => { + self.error = err; + Err(HandshakeError::Failure(self)) + } } } } } +/// A stream wrapper which handles SSL encryption for an underlying stream. +pub struct SslStream<S> { + ssl: Ssl, + _method: BioMethod, // NOTE: this *must* be after the Ssl field so things drop right + _p: PhantomData<S>, +} + +impl<S> fmt::Debug for SslStream<S> + where S: fmt::Debug +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("SslStream") + .field("stream", &self.get_ref()) + .field("ssl", &self.ssl()) + .finish() + } +} + +impl<S: Read + Write> SslStream<S> { + fn new_base(ssl: Ssl, stream: S) -> Self { + unsafe { + let (bio, method) = bio::new(stream).unwrap(); + ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); + + SslStream { + ssl: ssl, + _method: method, + _p: PhantomData, + } + } + } + + /// Like `read`, but returns an `ssl::Error` rather than an `io::Error`. + /// + /// This is particularly useful with a nonblocking socket, where the error + /// value will identify if OpenSSL is waiting on read or write readiness. + pub fn ssl_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { + let ret = self.ssl.read(buf); + if ret >= 0 { + Ok(ret as usize) + } else { + Err(self.make_error(ret)) + } + } + + /// Like `write`, but returns an `ssl::Error` rather than an `io::Error`. + /// + /// This is particularly useful with a nonblocking socket, where the error + /// value will identify if OpenSSL is waiting on read or write readiness. + pub fn ssl_write(&mut self, buf: &[u8]) -> Result<usize, Error> { + let ret = self.ssl.write(buf); + if ret >= 0 { + Ok(ret as usize) + } else { + Err(self.make_error(ret)) + } + } + + /// Shuts down the session. + /// + /// The shutdown process consists of two steps. The first step sends a + /// close notify message to the peer, after which `ShutdownResult::Sent` + /// is returned. The second step awaits the receipt of a close notify + /// message from the peer, after which `ShutdownResult::Received` is + /// returned. + /// + /// While the connection may be closed after the first step, it is + /// recommended to fully shut the session down. In particular, it must + /// be fully shut down if the connection is to be used for further + /// communication in the future. + pub fn shutdown(&mut self) -> Result<ShutdownResult, Error> { + match unsafe { ffi::SSL_shutdown(self.ssl.as_ptr()) } { + 0 => Ok(ShutdownResult::Sent), + 1 => Ok(ShutdownResult::Received), + n => Err(self.make_error(n)), + } + } +} + impl<S> SslStream<S> { fn make_error(&mut self, ret: c_int) -> Error { self.check_panic(); @@ -1319,16 +1327,12 @@ impl<S> SslStream<S> { } } - #[cfg(feature = "nightly")] fn check_panic(&mut self) { if let Some(err) = unsafe { bio::take_panic::<S>(self.ssl.get_raw_rbio()) } { ::std::panic::resume_unwind(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 { @@ -1362,7 +1366,7 @@ impl<S> SslStream<S> { } /// Returns the OpenSSL `Ssl` object associated with this stream. - pub fn ssl(&self) -> &Ssl { + pub fn ssl(&self) -> &SslRef { &self.ssl } } @@ -1397,18 +1401,101 @@ impl<S: Read + Write> Write for SslStream<S> { } } -pub trait IntoSsl { - fn into_ssl(self) -> Result<Ssl, ErrorStack>; +/// The result of a shutdown request. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ShutdownResult { + /// A close notify message has been sent to the peer. + Sent, + + /// A close notify response message has been received from the peer. + Received, } -impl IntoSsl for Ssl { - fn into_ssl(self) -> Result<Ssl, ErrorStack> { - Ok(self) +#[cfg(ossl110)] +mod compat { + use std::ptr; + + use ffi; + use libc::c_int; + + pub use ffi::{SSL_CTX_get_options, SSL_CTX_set_options}; + pub use ffi::{SSL_CTX_clear_options, SSL_CTX_up_ref}; + + pub unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::CRYPTO_get_ex_new_index(ffi::CRYPTO_EX_INDEX_SSL_CTX, + 0, + ptr::null_mut(), + None, + None, + Some(f)) + } + + pub unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::CRYPTO_get_ex_new_index(ffi::CRYPTO_EX_INDEX_SSL, + 0, + ptr::null_mut(), + None, + None, + Some(f)) + } + + pub fn tls_method() -> *const ffi::SSL_METHOD { + unsafe { ffi::TLS_method() } + } + + pub fn dtls_method() -> *const ffi::SSL_METHOD { + unsafe { ffi::DTLS_method() } } } -impl<'a> IntoSsl for &'a SslContext { - fn into_ssl(self) -> Result<Ssl, ErrorStack> { - Ssl::new(self) +#[cfg(ossl10x)] +#[allow(bad_style)] +mod compat { + use std::ptr; + + use ffi; + use libc::{self, c_long, c_ulong, c_int}; + + pub unsafe fn SSL_CTX_get_options(ctx: *const ffi::SSL_CTX) -> c_ulong { + ffi::SSL_CTX_ctrl(ctx as *mut _, ffi::SSL_CTRL_OPTIONS, 0, ptr::null_mut()) as c_ulong + } + + pub unsafe fn SSL_CTX_set_options(ctx: *const ffi::SSL_CTX, op: c_ulong) -> c_ulong { + ffi::SSL_CTX_ctrl(ctx as *mut _, + ffi::SSL_CTRL_OPTIONS, + op as c_long, + ptr::null_mut()) as c_ulong + } + + pub unsafe fn SSL_CTX_clear_options(ctx: *const ffi::SSL_CTX, op: c_ulong) -> c_ulong { + ffi::SSL_CTX_ctrl(ctx as *mut _, + ffi::SSL_CTRL_CLEAR_OPTIONS, + op as c_long, + ptr::null_mut()) as c_ulong + } + + pub unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), None, None, Some(f)) + } + + pub unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::SSL_get_ex_new_index(0, ptr::null_mut(), None, None, Some(f)) + } + + pub unsafe fn SSL_CTX_up_ref(ssl: *mut ffi::SSL_CTX) -> libc::c_int { + ffi::CRYPTO_add_lock(&mut (*ssl).references, + 1, + ffi::CRYPTO_LOCK_SSL_CTX, + "mod.rs\0".as_ptr() as *const _, + line!() as libc::c_int); + 0 + } + + pub fn tls_method() -> *const ffi::SSL_METHOD { + unsafe { ffi::SSLv23_method() } + } + + pub fn dtls_method() -> *const ffi::SSL_METHOD { + unsafe { ffi::DTLSv1_method() } } } diff --git a/openssl/src/ssl/tests/mod.rs b/openssl/src/ssl/tests/mod.rs index 3bbbed03..a84f6b25 100644 --- a/openssl/src/ssl/tests/mod.rs +++ b/openssl/src/ssl/tests/mod.rs @@ -1,5 +1,6 @@ #![allow(unused_imports)] +use std::env; use std::fs::File; use std::io::prelude::*; use std::io::{self, BufReader}; @@ -11,31 +12,26 @@ use std::process::{Command, Child, Stdio, ChildStdin}; use std::thread; use std::time::Duration; -use net2::TcpStreamExt; +use tempdir::TempDir; -use crypto::hash::Type::SHA256; +use hash::MessageDigest; use ssl; use ssl::SSL_VERIFY_PEER; -use ssl::SslMethod::Sslv23; use ssl::{SslMethod, HandshakeError}; -use ssl::error::Error; -use ssl::{SslContext, SslStream}; -use x509::X509StoreContext; -use x509::X509FileType; -use x509::X509; -use crypto::pkey::PKey; - -#[cfg(feature="dtlsv1")] +use ssl::{SslContext, SslStream, Ssl, ShutdownResult, SslConnectorBuilder, SslAcceptorBuilder, + Error}; +use x509::{X509StoreContext, X509, X509_FILETYPE_PEM}; +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] +use x509::verify::X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; +use pkey::PKey; + use std::net::UdpSocket; -#[cfg(feature="dtlsv1")] -use ssl::SslMethod::Dtlsv1; -#[cfg(feature="sslv2")] -use ssl::SslMethod::Sslv2; -#[cfg(feature="dtlsv1")] -use net2::UdpSocketExt; mod select; +static CERT: &'static [u8] = include_bytes!("../../../test/cert.pem"); +static KEY: &'static [u8] = include_bytes!("../../../test/key.pem"); + fn next_addr() -> SocketAddr { use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; static PORT: AtomicUsize = ATOMIC_USIZE_INIT; @@ -46,32 +42,42 @@ fn next_addr() -> SocketAddr { struct Server { p: Child, + _temp: TempDir, } impl Server { fn spawn(args: &[&str], input: Option<Box<FnMut(ChildStdin) + Send>>) -> (Server, SocketAddr) { + let td = TempDir::new("openssl").unwrap(); + let cert = td.path().join("cert.pem"); + let key = td.path().join("key.pem"); + File::create(&cert).unwrap().write_all(CERT).unwrap(); + File::create(&key).unwrap().write_all(KEY).unwrap(); + let addr = next_addr(); let mut child = Command::new("openssl") - .arg("s_server") - .arg("-accept") - .arg(addr.port().to_string()) - .args(args) - .arg("-cert") - .arg("cert.pem") - .arg("-key") - .arg("key.pem") - .arg("-no_dhe") - .current_dir("test") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .stdin(Stdio::piped()) - .spawn() - .unwrap(); + .arg("s_server") + .arg("-accept") + .arg(addr.port().to_string()) + .args(args) + .arg("-cert") + .arg(&cert) + .arg("-key") + .arg(&key) + .arg("-no_dhe") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::piped()) + .spawn() + .unwrap(); let stdin = child.stdin.take().unwrap(); if let Some(mut input) = input { thread::spawn(move || input(stdin)); } - (Server { p: child }, addr) + (Server { + p: child, + _temp: td, + }, + addr) } fn new_tcp(args: &[&str]) -> (Server, TcpStream) { @@ -92,7 +98,6 @@ impl Server { Server::new_tcp(&["-www"]) } - #[cfg(any(feature = "alpn", feature = "npn"))] fn new_alpn() -> (Server, TcpStream) { Server::new_tcp(&["-www", "-nextprotoneg", @@ -101,7 +106,6 @@ impl Server { "http/1.1,spdy/3.1"]) } - #[cfg(feature = "dtlsv1")] fn new_dtlsv1<I>(input: I) -> (Server, UdpConnected) where I: IntoIterator<Item = &'static str>, I::IntoIter: Send + 'static @@ -109,17 +113,17 @@ impl Server { let mut input = input.into_iter(); let (s, addr) = Server::spawn(&["-dtls1"], Some(Box::new(move |mut io| { - for s in input.by_ref() { - if io.write_all(s.as_bytes()).is_err() { - break; - } - } - }))); + for s in input.by_ref() { + if io.write_all(s.as_bytes()).is_err() { + break; + } + } + }))); // 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(Duration::from_millis(100)); - let socket = UdpSocket::bind(next_addr()).unwrap(); + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); socket.connect(&addr).unwrap(); (s, UdpConnected(socket)) } @@ -132,51 +136,18 @@ impl Drop for Server { } } -#[cfg(feature = "dtlsv1")] #[derive(Debug)] struct UdpConnected(UdpSocket); -#[cfg(feature = "dtlsv1")] impl Read for UdpConnected { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - self.0.recv_from(buf).map(|(s, _)| s) + self.0.recv(buf) } } -#[cfg(feature = "dtlsv1")] impl Write for UdpConnected { - #[cfg(unix)] - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - use std::os::unix::prelude::*; - use libc; - let n = unsafe { - libc::send(self.0.as_raw_fd(), - buf.as_ptr() as *const _, - buf.len() as libc::size_t, - 0) - }; - if n < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(n as usize) - } - } - - #[cfg(windows)] fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - use std::os::windows::prelude::*; - use libc; - let n = unsafe { - libc::send(self.0.as_raw_socket(), - buf.as_ptr() as *const _, - buf.len() as libc::c_int, - 0) - }; - if n < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(n as usize) - } + self.0.send(buf) } fn flush(&mut self) -> io::Result<()> { @@ -197,147 +168,139 @@ macro_rules! run_test( use ssl::SslMethod; use ssl::{SslContext, Ssl, SslStream}; use ssl::SSL_VERIFY_PEER; - use crypto::hash::Type::{SHA1, SHA256}; + use hash::MessageDigest; use x509::X509StoreContext; use serialize::hex::FromHex; + use types::OpenSslTypeRef; use super::Server; #[test] fn sslv23() { let (_s, stream) = Server::new(); - $blk(SslMethod::Sslv23, stream); + $blk(SslMethod::tls(), stream); } #[test] - #[cfg(feature="dtlsv1")] + #[cfg_attr(any(windows, target_arch = "arm"), ignore)] // FIXME(#467) fn dtlsv1() { let (_s, stream) = Server::new_dtlsv1(Some("hello")); - $blk(SslMethod::Dtlsv1, stream); + $blk(SslMethod::dtls(), stream); } } ); ); run_test!(new_ctx, |method, _| { - SslContext::new(method).unwrap(); -}); - -run_test!(new_sslstream, |method, stream| { - SslStream::connect(&SslContext::new(method).unwrap(), stream).unwrap(); -}); - -run_test!(get_ssl_method, |method, _| { - let ssl = Ssl::new(&SslContext::new(method).unwrap()).unwrap(); - assert_eq!(ssl.ssl_method(), method); + SslContext::builder(method).unwrap(); }); run_test!(verify_untrusted, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - match SslStream::connect(&ctx, stream) { + match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(_) => panic!("expected failure"), Err(err) => println!("error {:?}", err), } }); run_test!(verify_trusted, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - match SslStream::connect(&ctx, stream) { + match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err), } }); run_test!(verify_untrusted_callback_override_ok, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, |_, _| true); - match SslStream::connect(&ctx, stream) { + match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err), } }); run_test!(verify_untrusted_callback_override_bad, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, |_, _| false); - assert!(SslStream::connect(&ctx, stream).is_err()); + assert!(Ssl::new(&ctx.build()).unwrap().connect(stream).is_err()); }); run_test!(verify_trusted_callback_override_ok, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, |_, _| true); - match ctx.set_CA_file(&Path::new("test/cert.pem")) { + match ctx.set_ca_file(&Path::new("test/cert.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - match SslStream::connect(&ctx, stream) { + match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err), } }); run_test!(verify_trusted_callback_override_bad, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, |_, _| false); - match ctx.set_CA_file(&Path::new("test/cert.pem")) { + match ctx.set_ca_file(&Path::new("test/cert.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - assert!(SslStream::connect(&ctx, stream).is_err()); + assert!(Ssl::new(&ctx.build()).unwrap().connect(stream).is_err()); }); run_test!(verify_callback_load_certs, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, |_, x509_ctx| { assert!(x509_ctx.current_cert().is_some()); true }); - assert!(SslStream::connect(&ctx, stream).is_ok()); + assert!(Ssl::new(&ctx.build()).unwrap().connect(stream).is_ok()); }); run_test!(verify_trusted_get_error_ok, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, |_, x509_ctx| { assert!(x509_ctx.error().is_none()); true }); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - assert!(SslStream::connect(&ctx, stream).is_ok()); + assert!(Ssl::new(&ctx.build()).unwrap().connect(stream).is_ok()); }); run_test!(verify_trusted_get_error_err, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, |_, x509_ctx| { assert!(x509_ctx.error().is_some()); false }); - assert!(SslStream::connect(&ctx, stream).is_err()); + assert!(Ssl::new(&ctx.build()).unwrap().connect(stream).is_err()); }); run_test!(verify_callback_data, |method, stream| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); - // Node id was generated as SHA256 hash of certificate "test/cert.pem" - // in DER format. - // Command: openssl x509 -in test/cert.pem -outform DER | openssl dgst -sha256 - // Please update if "test/cert.pem" will ever change +// Node id was generated as SHA256 hash of certificate "test/cert.pem" +// in DER format. +// Command: openssl x509 -in test/cert.pem -outform DER | openssl dgst -sha256 +// Please update if "test/cert.pem" will ever change let node_hash_str = "59172d9313e84459bcff27f967e79e6e9217e584"; let node_id = node_hash_str.from_hex().unwrap(); ctx.set_verify_callback(SSL_VERIFY_PEER, move |_preverify_ok, x509_ctx| { @@ -345,14 +308,14 @@ run_test!(verify_callback_data, |method, stream| { match cert { None => false, Some(cert) => { - let fingerprint = cert.fingerprint(SHA1).unwrap(); + let fingerprint = cert.fingerprint(MessageDigest::sha1()).unwrap(); fingerprint == node_id } } }); ctx.set_verify_depth(1); - match SslStream::connect(&ctx, stream) { + match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err), } @@ -360,12 +323,11 @@ run_test!(verify_callback_data, |method, stream| { run_test!(ssl_verify_callback, |method, stream| { use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; - use ssl::IntoSsl; static CHECKED: AtomicUsize = ATOMIC_USIZE_INIT; - let ctx = SslContext::new(method).unwrap(); - let mut ssl = ctx.into_ssl().unwrap(); + let ctx = SslContext::builder(method).unwrap(); + let mut ssl = Ssl::new(&ctx.build()).unwrap(); let node_hash_str = "59172d9313e84459bcff27f967e79e6e9217e584"; let node_id = node_hash_str.from_hex().unwrap(); @@ -374,13 +336,13 @@ run_test!(ssl_verify_callback, |method, stream| { match x509.current_cert() { None => false, Some(cert) => { - let fingerprint = cert.fingerprint(SHA1).unwrap(); + let fingerprint = cert.fingerprint(MessageDigest::sha1()).unwrap(); fingerprint == node_id } } }); - match SslStream::connect(ssl, stream) { + match ssl.connect(stream) { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err), } @@ -391,24 +353,24 @@ run_test!(ssl_verify_callback, |method, stream| { // Make sure every write call translates to a write call to the underlying socket. #[test] fn test_write_hits_stream() { - let listener = TcpListener::bind(next_addr()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); let guard = thread::spawn(move || { - let ctx = SslContext::new(Sslv23).unwrap(); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); let stream = TcpStream::connect(addr).unwrap(); - let mut stream = SslStream::connect(&ctx, stream).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().connect(stream).unwrap(); stream.write_all(b"hello").unwrap(); stream }); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_certificate_file(&Path::new("test/cert.pem"), X509FileType::PEM).unwrap(); - ctx.set_private_key_file(&Path::new("test/key.pem"), X509FileType::PEM).unwrap(); + ctx.set_certificate_file(&Path::new("test/cert.pem"), X509_FILETYPE_PEM).unwrap(); + ctx.set_private_key_file(&Path::new("test/key.pem"), X509_FILETYPE_PEM).unwrap(); let stream = listener.accept().unwrap().0; - let mut stream = SslStream::accept(&ctx, stream).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().accept(stream).unwrap(); let mut buf = [0; 5]; assert_eq!(5, stream.read(&mut buf).unwrap()); @@ -423,7 +385,7 @@ fn test_set_certificate_and_private_key() { let cert = include_bytes!("../../../test/cert.pem"); let cert = X509::from_pem(cert).unwrap(); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_private_key(&key).unwrap(); ctx.set_certificate(&cert).unwrap(); @@ -431,18 +393,18 @@ fn test_set_certificate_and_private_key() { } run_test!(get_ctx_options, |method, _| { - let ctx = SslContext::new(method).unwrap(); + let ctx = SslContext::builder(method).unwrap(); ctx.options(); }); run_test!(set_ctx_options, |method, _| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); let opts = ctx.set_options(ssl::SSL_OP_NO_TICKET); assert!(opts.contains(ssl::SSL_OP_NO_TICKET)); }); run_test!(clear_ctx_options, |method, _| { - let mut ctx = SslContext::new(method).unwrap(); + let mut ctx = SslContext::builder(method).unwrap(); ctx.set_options(ssl::SSL_OP_ALL); let opts = ctx.clear_options(ssl::SSL_OP_ALL); assert!(!opts.contains(ssl::SSL_OP_ALL)); @@ -451,17 +413,8 @@ run_test!(clear_ctx_options, |method, _| { #[test] fn test_write() { let (_s, stream) = Server::new(); - let mut stream = SslStream::connect(&SslContext::new(Sslv23).unwrap(), stream).unwrap(); - stream.write_all("hello".as_bytes()).unwrap(); - stream.flush().unwrap(); - stream.write_all(" there".as_bytes()).unwrap(); - stream.flush().unwrap(); -} - -#[test] -fn test_write_direct() { - let (_s, stream) = Server::new(); - let mut stream = SslStream::connect(&SslContext::new(Sslv23).unwrap(), stream).unwrap(); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().connect(stream).unwrap(); stream.write_all("hello".as_bytes()).unwrap(); stream.flush().unwrap(); stream.write_all(" there".as_bytes()).unwrap(); @@ -469,20 +422,21 @@ fn test_write_direct() { } run_test!(get_peer_certificate, |method, stream| { - let stream = SslStream::connect(&SslContext::new(method).unwrap(), stream).unwrap(); + let ctx = SslContext::builder(method).unwrap(); + let stream = Ssl::new(&ctx.build()).unwrap().connect(stream).unwrap(); let cert = stream.ssl().peer_certificate().unwrap(); - let fingerprint = cert.fingerprint(SHA1).unwrap(); + let fingerprint = cert.fingerprint(MessageDigest::sha1()).unwrap(); let node_hash_str = "59172d9313e84459bcff27f967e79e6e9217e584"; let node_id = node_hash_str.from_hex().unwrap(); assert_eq!(node_id, fingerprint) }); #[test] -#[cfg(feature = "dtlsv1")] +#[cfg_attr(any(windows, target_arch = "arm"), ignore)] // FIXME(#467) fn test_write_dtlsv1() { let (_s, stream) = Server::new_dtlsv1(iter::repeat("y\n")); - - let mut stream = SslStream::connect(&SslContext::new(Dtlsv1).unwrap(), stream).unwrap(); + let ctx = SslContext::builder(SslMethod::dtls()).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().connect(stream).unwrap(); stream.write_all(b"hello").unwrap(); stream.flush().unwrap(); stream.write_all(b" there").unwrap(); @@ -492,16 +446,8 @@ fn test_write_dtlsv1() { #[test] fn test_read() { let (_s, tcp) = Server::new(); - let mut stream = SslStream::connect(&SslContext::new(Sslv23).unwrap(), tcp).unwrap(); - stream.write_all("GET /\r\n\r\n".as_bytes()).unwrap(); - stream.flush().unwrap(); - io::copy(&mut stream, &mut io::sink()).ok().expect("read error"); -} - -#[test] -fn test_read_direct() { - let (_s, tcp) = Server::new(); - let mut stream = SslStream::connect(&SslContext::new(Sslv23).unwrap(), tcp).unwrap(); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().connect(tcp).unwrap(); stream.write_all("GET /\r\n\r\n".as_bytes()).unwrap(); stream.flush().unwrap(); io::copy(&mut stream, &mut io::sink()).ok().expect("read error"); @@ -510,7 +456,8 @@ fn test_read_direct() { #[test] fn test_pending() { let (_s, tcp) = Server::new(); - let mut stream = SslStream::connect(&SslContext::new(Sslv23).unwrap(), tcp).unwrap(); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().connect(tcp).unwrap(); stream.write_all("GET /\r\n\r\n".as_bytes()).unwrap(); stream.flush().unwrap(); @@ -533,7 +480,8 @@ fn test_pending() { #[test] fn test_state() { let (_s, tcp) = Server::new(); - let stream = SslStream::connect(&SslContext::new(Sslv23).unwrap(), tcp).unwrap(); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let stream = Ssl::new(&ctx.build()).unwrap().connect(tcp).unwrap(); assert_eq!(stream.ssl().state_string(), "SSLOK "); assert_eq!(stream.ssl().state_string_long(), "SSL negotiation finished successfully"); @@ -542,17 +490,17 @@ fn test_state() { /// Tests that connecting with the client using ALPN, but the server not does not /// break the existing connection behavior. #[test] -#[cfg(feature = "alpn")] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn test_connect_with_unilateral_alpn() { let (_s, stream) = Server::new(); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -564,17 +512,16 @@ fn test_connect_with_unilateral_alpn() { /// Tests that connecting with the client using NPN, but the server not does not /// break the existing connection behavior. #[test] -#[cfg(feature = "npn")] fn test_connect_with_unilateral_npn() { let (_s, stream) = Server::new(); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -586,17 +533,17 @@ fn test_connect_with_unilateral_npn() { /// Tests that when both the client as well as the server use ALPN and their /// lists of supported protocols have an overlap, the correct protocol is chosen. #[test] -#[cfg(feature = "alpn")] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn test_connect_with_alpn_successful_multiple_matching() { let (_s, stream) = Server::new_alpn(); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_alpn_protocols(&[b"spdy/3.1", b"http/1.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_alpn_protocols(&[b"spdy/3.1", b"http/1.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -608,17 +555,17 @@ fn test_connect_with_alpn_successful_multiple_matching() { /// Tests that when both the client as well as the server use NPN and their /// lists of supported protocols have an overlap, the correct protocol is chosen. #[test] -#[cfg(feature = "npn")] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn test_connect_with_npn_successful_multiple_matching() { let (_s, stream) = Server::new_alpn(); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_npn_protocols(&[b"spdy/3.1", b"http/1.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_npn_protocols(&[b"spdy/3.1", b"http/1.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -631,17 +578,17 @@ fn test_connect_with_npn_successful_multiple_matching() { /// lists of supported protocols have an overlap -- with only ONE protocol /// being valid for both. #[test] -#[cfg(feature = "alpn")] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn test_connect_with_alpn_successful_single_match() { let (_s, stream) = Server::new_alpn(); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_alpn_protocols(&[b"spdy/3.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_alpn_protocols(&[b"spdy/3.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -655,17 +602,17 @@ fn test_connect_with_alpn_successful_single_match() { /// lists of supported protocols have an overlap -- with only ONE protocol /// being valid for both. #[test] -#[cfg(feature = "npn")] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn test_connect_with_npn_successful_single_match() { let (_s, stream) = Server::new_alpn(); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_npn_protocols(&[b"spdy/3.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_npn_protocols(&[b"spdy/3.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -677,37 +624,36 @@ fn test_connect_with_npn_successful_single_match() { /// Tests that when the `SslStream` is created as a server stream, the protocols /// are correctly advertised to the client. #[test] -#[cfg(feature = "npn")] fn test_npn_server_advertise_multiple() { - let listener = TcpListener::bind(next_addr()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let localhost = listener.local_addr().unwrap(); // We create a different context instance for the server... let listener_ctx = { - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]); - assert!(ctx.set_certificate_file(&Path::new("test/cert.pem"), X509FileType::PEM) + ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + assert!(ctx.set_certificate_file(&Path::new("test/cert.pem"), X509_FILETYPE_PEM) .is_ok()); - ctx.set_private_key_file(&Path::new("test/key.pem"), X509FileType::PEM) - .unwrap(); - ctx + ctx.set_private_key_file(&Path::new("test/key.pem"), X509_FILETYPE_PEM) + .unwrap(); + ctx.build() }; // Have the listener wait on the connection in a different thread. thread::spawn(move || { let (stream, _) = listener.accept().unwrap(); - let _ = SslStream::accept(&listener_ctx, stream).unwrap(); + Ssl::new(&listener_ctx).unwrap().accept(stream).unwrap(); }); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_npn_protocols(&[b"spdy/3.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_npn_protocols(&[b"spdy/3.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } // Now connect to the socket and make sure the protocol negotiation works... let stream = TcpStream::connect(localhost).unwrap(); - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -718,37 +664,37 @@ fn test_npn_server_advertise_multiple() { /// Tests that when the `SslStream` is created as a server stream, the protocols /// are correctly advertised to the client. #[test] -#[cfg(feature = "alpn")] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn test_alpn_server_advertise_multiple() { - let listener = TcpListener::bind(next_addr()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let localhost = listener.local_addr().unwrap(); // We create a different context instance for the server... let listener_ctx = { - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]); - assert!(ctx.set_certificate_file(&Path::new("test/cert.pem"), X509FileType::PEM) + ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + assert!(ctx.set_certificate_file(&Path::new("test/cert.pem"), X509_FILETYPE_PEM) .is_ok()); - ctx.set_private_key_file(&Path::new("test/key.pem"), X509FileType::PEM) - .unwrap(); - ctx + ctx.set_private_key_file(&Path::new("test/key.pem"), X509_FILETYPE_PEM) + .unwrap(); + ctx.build() }; // Have the listener wait on the connection in a different thread. thread::spawn(move || { let (stream, _) = listener.accept().unwrap(); - let _ = SslStream::accept(&listener_ctx, stream).unwrap(); + Ssl::new(&listener_ctx).unwrap().accept(stream).unwrap(); }); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_alpn_protocols(&[b"spdy/3.1"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { + ctx.set_alpn_protocols(&[b"spdy/3.1"]).unwrap(); + match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), } // Now connect to the socket and make sure the protocol negotiation works... let stream = TcpStream::connect(localhost).unwrap(); - let stream = match SslStream::connect(&ctx, stream) { + let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { Ok(stream) => stream, Err(err) => panic!("Expected success, got {:?}", err), }; @@ -759,87 +705,82 @@ fn test_alpn_server_advertise_multiple() { /// Test that Servers supporting ALPN don't report a protocol when none of their protocols match /// the client's reported protocol. #[test] -#[cfg(feature = "alpn")] +#[cfg(all(feature = "v102", ossl102))] fn test_alpn_server_select_none() { - let listener = TcpListener::bind(next_addr()).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let localhost = listener.local_addr().unwrap(); // We create a different context instance for the server... let listener_ctx = { - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]); - assert!(ctx.set_certificate_file(&Path::new("test/cert.pem"), X509FileType::PEM) + ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + assert!(ctx.set_certificate_file(&Path::new("test/cert.pem"), X509_FILETYPE_PEM) .is_ok()); - ctx.set_private_key_file(&Path::new("test/key.pem"), X509FileType::PEM) - .unwrap(); - ctx + ctx.set_private_key_file(&Path::new("test/key.pem"), X509_FILETYPE_PEM) + .unwrap(); + ctx.build() }; // Have the listener wait on the connection in a different thread. thread::spawn(move || { let (stream, _) = listener.accept().unwrap(); - let _ = SslStream::accept(&listener_ctx, stream).unwrap(); + Ssl::new(&listener_ctx).unwrap().accept(stream).unwrap(); }); - let mut ctx = SslContext::new(Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SSL_VERIFY_PEER); - ctx.set_alpn_protocols(&[b"http/2"]); - match ctx.set_CA_file(&Path::new("test/root-ca.pem")) { - Ok(_) => {} - Err(err) => panic!("Unexpected error {:?}", err), - } + ctx.set_alpn_protocols(&[b"http/2"]).unwrap(); + ctx.set_ca_file(&Path::new("test/root-ca.pem")).unwrap(); // Now connect to the socket and make sure the protocol negotiation works... let stream = TcpStream::connect(localhost).unwrap(); - let stream = match SslStream::connect(&ctx, stream) { - Ok(stream) => stream, - Err(err) => panic!("Expected success, got {:?}", err), - }; + let stream = Ssl::new(&ctx.build()).unwrap().connect(stream).unwrap(); // Since the protocols from the server and client don't overlap at all, no protocol is selected assert_eq!(None, stream.ssl().selected_alpn_protocol()); } +// In 1.1.0, ALPN negotiation failure is a fatal error +#[test] +#[cfg(all(feature = "v110", ossl110))] +fn test_alpn_server_select_none() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let localhost = listener.local_addr().unwrap(); + // We create a different context instance for the server... + let listener_ctx = { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_verify(SSL_VERIFY_PEER); + ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + assert!(ctx.set_certificate_file(&Path::new("test/cert.pem"), X509_FILETYPE_PEM) + .is_ok()); + ctx.set_private_key_file(&Path::new("test/key.pem"), X509_FILETYPE_PEM) + .unwrap(); + ctx.build() + }; + // Have the listener wait on the connection in a different thread. + thread::spawn(move || { + let (stream, _) = listener.accept().unwrap(); + assert!(Ssl::new(&listener_ctx).unwrap().accept(stream).is_err()); + }); -#[cfg(feature="dtlsv1")] -#[cfg(test)] -mod dtlsv1 { - use serialize::hex::FromHex; - use std::net::TcpStream; - use std::thread; - - use crypto::hash::Type::SHA256; - use ssl::SslMethod; - use ssl::SslMethod::Dtlsv1; - use ssl::{SslContext, SslStream}; - use ssl::SSL_VERIFY_PEER; - use x509::X509StoreContext; - - const PROTOCOL: SslMethod = Dtlsv1; - - #[test] - fn test_new_ctx() { - SslContext::new(PROTOCOL).unwrap(); - } + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_verify(SSL_VERIFY_PEER); + ctx.set_alpn_protocols(&[b"http/2"]).unwrap(); + ctx.set_ca_file(&Path::new("test/root-ca.pem")).unwrap(); + // Now connect to the socket and make sure the protocol negotiation works... + let stream = TcpStream::connect(localhost).unwrap(); + assert!(Ssl::new(&ctx.build()).unwrap().connect(stream).is_err()); } #[test] -#[cfg(feature = "dtlsv1")] +#[cfg_attr(any(windows, target_arch = "arm"), ignore)] // FIXME(#467) fn test_read_dtlsv1() { let (_s, stream) = Server::new_dtlsv1(Some("hello")); - let mut stream = SslStream::connect(&SslContext::new(Dtlsv1).unwrap(), stream).unwrap(); + let ctx = SslContext::builder(SslMethod::dtls()).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().connect(stream).unwrap(); let mut buf = [0u8; 100]; assert!(stream.read(&mut buf).is_ok()); } -#[test] -#[cfg(feature = "sslv2")] -fn test_sslv2_connect_failure() { - let (_s, tcp) = Server::new_tcp(&["-no_ssl2", "-www"]); - SslStream::connect(&SslContext::new(Sslv2).unwrap(), tcp) - .err() - .unwrap(); -} - fn wait_io(stream: &TcpStream, read: bool, timeout_ms: u32) -> bool { unsafe { let mut set: select::fd_set = mem::zeroed(); @@ -859,8 +800,7 @@ fn wait_io(stream: &TcpStream, read: bool, timeout_ms: u32) -> bool { } } -fn handshake(res: Result<SslStream<TcpStream>, HandshakeError<TcpStream>>) - -> SslStream<TcpStream> { +fn handshake(res: Result<SslStream<TcpStream>, HandshakeError<TcpStream>>) -> SslStream<TcpStream> { match res { Ok(s) => s, Err(HandshakeError::Interrupted(s)) => { @@ -875,8 +815,8 @@ fn handshake(res: Result<SslStream<TcpStream>, HandshakeError<TcpStream>>) fn test_write_nonblocking() { let (_s, stream) = Server::new(); stream.set_nonblocking(true).unwrap(); - let cx = SslContext::new(Sslv23).unwrap(); - let mut stream = handshake(SslStream::connect(&cx, stream)); + let cx = SslContext::builder(SslMethod::tls()).unwrap().build(); + let mut stream = handshake(Ssl::new(&cx).unwrap().connect(stream)); let mut iterations = 0; loop { @@ -909,12 +849,12 @@ fn test_write_nonblocking() { } #[test] -#[cfg_attr(windows, ignore)] // FIXME flickers on appveyor +#[cfg_attr(any(windows, target_arch = "arm"), ignore)] // FIXME(#467) fn test_read_nonblocking() { let (_s, stream) = Server::new(); stream.set_nonblocking(true).unwrap(); - let cx = SslContext::new(Sslv23).unwrap(); - let mut stream = handshake(SslStream::connect(&cx, stream)); + let cx = SslContext::builder(SslMethod::tls()).unwrap().build(); + let mut stream = handshake(Ssl::new(&cx).unwrap().connect(stream)); let mut iterations = 0; loop { @@ -965,7 +905,6 @@ fn test_read_nonblocking() { #[test] #[should_panic(expected = "blammo")] -#[cfg(feature = "nightly")] fn write_panic() { struct ExplodingStream(TcpStream); @@ -988,13 +927,12 @@ fn write_panic() { let (_s, stream) = Server::new(); let stream = ExplodingStream(stream); - let ctx = SslContext::new(SslMethod::Sslv23).unwrap(); - let _ = SslStream::connect(&ctx, stream); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let _ = Ssl::new(&ctx.build()).unwrap().connect(stream); } #[test] #[should_panic(expected = "blammo")] -#[cfg(feature = "nightly")] fn read_panic() { struct ExplodingStream(TcpStream); @@ -1017,13 +955,12 @@ fn read_panic() { let (_s, stream) = Server::new(); let stream = ExplodingStream(stream); - let ctx = SslContext::new(SslMethod::Sslv23).unwrap(); - let _ = SslStream::connect(&ctx, stream); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let _ = Ssl::new(&ctx.build()).unwrap().connect(stream); } #[test] #[should_panic(expected = "blammo")] -#[cfg(feature = "nightly")] fn flush_panic() { struct ExplodingStream(TcpStream); @@ -1046,32 +983,31 @@ fn flush_panic() { 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 ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let mut stream = Ssl::new(&ctx.build()).unwrap().connect(stream).ok().unwrap(); let _ = stream.flush(); } #[test] fn refcount_ssl_context() { let mut ssl = { - let ctx = SslContext::new(SslMethod::Sslv23).unwrap(); - ssl::Ssl::new(&ctx).unwrap() + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ssl::Ssl::new(&ctx.build()).unwrap() }; { - let new_ctx_a = SslContext::new(SslMethod::Sslv23).unwrap(); + let new_ctx_a = SslContext::builder(SslMethod::tls()).unwrap().build(); let _new_ctx_b = ssl.set_ssl_context(&new_ctx_a); } } #[test] -#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( fn default_verify_paths() { - let mut ctx = SslContext::new(SslMethod::Sslv23).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_default_verify_paths().unwrap(); ctx.set_verify(SSL_VERIFY_PEER); let s = TcpStream::connect("google.com:443").unwrap(); - let mut socket = SslStream::connect(&ctx, s).unwrap(); + let mut socket = Ssl::new(&ctx.build()).unwrap().connect(s).unwrap(); socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); let mut result = vec![]; @@ -1086,6 +1022,172 @@ fn default_verify_paths() { fn add_extra_chain_cert() { let cert = include_bytes!("../../../test/cert.pem"); let cert = X509::from_pem(cert).unwrap(); - let mut ctx = SslContext::new(SslMethod::Sslv23).unwrap(); - ctx.add_extra_chain_cert(&cert).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.add_extra_chain_cert(cert).unwrap(); +} + +#[test] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] +fn verify_valid_hostname() { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_default_verify_paths().unwrap(); + ctx.set_verify(SSL_VERIFY_PEER); + + let mut ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.param_mut().set_hostflags(X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + ssl.param_mut().set_host("google.com").unwrap(); + + let s = TcpStream::connect("google.com:443").unwrap(); + let mut socket = ssl.connect(s).unwrap(); + + socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); + let mut result = vec![]; + socket.read_to_end(&mut result).unwrap(); + + println!("{}", String::from_utf8_lossy(&result)); + assert!(result.starts_with(b"HTTP/1.0")); + assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>")); +} + +#[test] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] +fn verify_invalid_hostname() { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_default_verify_paths().unwrap(); + ctx.set_verify(SSL_VERIFY_PEER); + + let mut ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.param_mut().set_hostflags(X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + ssl.param_mut().set_host("foobar.com").unwrap(); + + let s = TcpStream::connect("google.com:443").unwrap(); + assert!(ssl.connect(s).is_err()); +} + +#[test] +fn connector_valid_hostname() { + let connector = SslConnectorBuilder::new(SslMethod::tls()).unwrap().build(); + + let s = TcpStream::connect("google.com:443").unwrap(); + let mut socket = connector.connect("google.com", s).unwrap(); + + socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); + let mut result = vec![]; + socket.read_to_end(&mut result).unwrap(); + + println!("{}", String::from_utf8_lossy(&result)); + assert!(result.starts_with(b"HTTP/1.0")); + assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>")); +} + +#[test] +fn connector_invalid_hostname() { + let connector = SslConnectorBuilder::new(SslMethod::tls()).unwrap().build(); + + let s = TcpStream::connect("google.com:443").unwrap(); + assert!(connector.connect("foobar.com", s).is_err()); +} + +#[test] +fn connector_client_server_mozilla_intermediate() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + let t = thread::spawn(move || { + let key = PKey::private_key_from_pem(KEY).unwrap(); + let cert = X509::from_pem(CERT).unwrap(); + let connector = + SslAcceptorBuilder::mozilla_intermediate(SslMethod::tls(), &key, &cert, None::<X509>) + .unwrap() + .build(); + let stream = listener.accept().unwrap().0; + let mut stream = connector.accept(stream).unwrap(); + + stream.write_all(b"hello").unwrap(); + }); + + let mut connector = SslConnectorBuilder::new(SslMethod::tls()).unwrap(); + connector.builder_mut().set_ca_file("test/root-ca.pem").unwrap(); + let connector = connector.build(); + + let stream = TcpStream::connect(("127.0.0.1", port)).unwrap(); + let mut stream = connector.connect("foobar.com", stream).unwrap(); + + let mut buf = [0; 5]; + stream.read_exact(&mut buf).unwrap(); + assert_eq!(b"hello", &buf); + + t.join().unwrap(); +} + +#[test] +fn connector_client_server_mozilla_modern() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + let t = thread::spawn(move || { + let key = PKey::private_key_from_pem(KEY).unwrap(); + let cert = X509::from_pem(CERT).unwrap(); + let connector = + SslAcceptorBuilder::mozilla_modern(SslMethod::tls(), &key, &cert, None::<X509>) + .unwrap() + .build(); + let stream = listener.accept().unwrap().0; + let mut stream = connector.accept(stream).unwrap(); + + stream.write_all(b"hello").unwrap(); + }); + + let mut connector = SslConnectorBuilder::new(SslMethod::tls()).unwrap(); + connector.builder_mut().set_ca_file("test/root-ca.pem").unwrap(); + let connector = connector.build(); + + let stream = TcpStream::connect(("127.0.0.1", port)).unwrap(); + let mut stream = connector.connect("foobar.com", stream).unwrap(); + + let mut buf = [0; 5]; + stream.read_exact(&mut buf).unwrap(); + assert_eq!(b"hello", &buf); + + t.join().unwrap(); +} + +#[test] +fn shutdown() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + thread::spawn(move || { + let stream = listener.accept().unwrap().0; + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_certificate_file(&Path::new("test/cert.pem"), X509_FILETYPE_PEM).unwrap(); + ctx.set_private_key_file(&Path::new("test/key.pem"), X509_FILETYPE_PEM).unwrap(); + let ssl = Ssl::new(&ctx.build()).unwrap(); + let mut stream = ssl.accept(stream).unwrap(); + + stream.write_all(b"hello").unwrap(); + let mut buf = [0; 1]; + assert_eq!(stream.read(&mut buf).unwrap(), 0); + assert_eq!(stream.shutdown().unwrap(), ShutdownResult::Received); + }); + + let stream = TcpStream::connect(("127.0.0.1", port)).unwrap(); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let ssl = Ssl::new(&ctx.build()).unwrap(); + let mut stream = ssl.connect(stream).unwrap(); + + let mut buf = [0; 5]; + stream.read_exact(&mut buf).unwrap(); + assert_eq!(b"hello", &buf); + + assert_eq!(stream.shutdown().unwrap(), ShutdownResult::Sent); + assert_eq!(stream.shutdown().unwrap(), ShutdownResult::Received); +} + +fn _check_kinds() { + fn is_send<T: Send>() {} + fn is_sync<T: Sync>() {} + + is_send::<SslStream<TcpStream>>(); + is_sync::<SslStream<TcpStream>>(); } |