From 1a288da86ce1ca94b5a0b3eac8750e5ffd03e8e7 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Fri, 28 Oct 2016 22:14:44 -0700 Subject: [PATCH 1/9] Make verification unconditionally exposed internally --- openssl/src/lib.rs | 2 ++ openssl/src/{x509 => }/verify.rs | 0 openssl/src/x509/mod.rs | 8 ++++---- 3 files changed, 6 insertions(+), 4 deletions(-) rename openssl/src/{x509 => }/verify.rs (100%) diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index b191ec5b..acdc2ea8 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -42,6 +42,8 @@ pub mod ssl; pub mod symm; pub mod version; pub mod x509; +#[cfg(any(ossl102, ossl110))] +mod verify; pub fn cvt_p(r: *mut T) -> Result<*mut T, ErrorStack> { if r.is_null() { diff --git a/openssl/src/x509/verify.rs b/openssl/src/verify.rs similarity index 100% rename from openssl/src/x509/verify.rs rename to openssl/src/verify.rs diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index b92462d4..cc6b73bb 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -37,12 +37,12 @@ use ffi::{ ASN1_STRING_get0_data as ASN1_STRING_data, }; -pub mod extension; - #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -pub mod verify; +pub use verify; -use self::extension::{ExtensionType, Extension}; +use x509::extension::{ExtensionType, Extension}; + +pub mod extension; #[cfg(test)] mod tests; From 4c7a5a418ee6a71f26fb6cc720a36b5bca6f3376 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 14:02:26 -0700 Subject: [PATCH 2/9] Implement client and server connectors --- openssl/src/ssl/connector.rs | 329 +++++++++++++++++++++++++++++++++++ openssl/src/ssl/mod.rs | 23 ++- openssl/src/ssl/tests/mod.rs | 70 +++++++- openssl/src/x509/mod.rs | 29 ++- 4 files changed, 437 insertions(+), 14 deletions(-) create mode 100644 openssl/src/ssl/connector.rs diff --git a/openssl/src/ssl/connector.rs b/openssl/src/ssl/connector.rs new file mode 100644 index 00000000..aed01f8c --- /dev/null +++ b/openssl/src/ssl/connector.rs @@ -0,0 +1,329 @@ +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::PKey; +use x509::X509Ref; + +// apps/dh2048.pem +const DHPARAM_PEM: &'static str = r#" +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxOb +IlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjft +awv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT +mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhgh +fDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq +5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg== +-----END DH PARAMETERS----- + +These are the 2048-bit DH parameters from "More Modular Exponential +(MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)": +https://tools.ietf.org/html/rfc3526 + +See https://tools.ietf.org/html/rfc2412 for how they were generated."#; + +fn ctx(method: SslMethod) -> Result { + let mut ctx = try!(SslContextBuilder::new(method)); + + // options to enable and cipher list lifted from libcurl + 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; + ctx.set_options(opts); + + Ok(ctx) +} + +pub struct ClientConnectorBuilder(SslContextBuilder); + +impl ClientConnectorBuilder { + pub fn tls() -> Result { + ClientConnectorBuilder::new(SslMethod::tls()) + } + + fn new(method: SslMethod) -> Result { + let mut ctx = try!(ctx(method)); + try!(ctx.set_default_verify_paths()); + try!(ctx.set_cipher_list("ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH")); + + Ok(ClientConnectorBuilder(ctx)) + } + + pub fn context(&self) -> &SslContextBuilder { + &self.0 + } + + pub fn context_mut(&mut self) -> &mut SslContextBuilder { + &mut self.0 + } + + pub fn build(self) -> ClientConnector { + ClientConnector(self.0.build()) + } +} + +pub struct ClientConnector(SslContext); + +impl ClientConnector { + pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> + 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) + } +} + +pub struct ServerConnectorBuilder(SslContextBuilder); + +impl ServerConnectorBuilder { + pub fn tls(private_key: &PKey, + certificate: &X509Ref, + chain: I) + -> Result + where I: IntoIterator, + T: AsRef + { + ServerConnectorBuilder::new(SslMethod::tls(), private_key, certificate, chain) + } + + fn new(method: SslMethod, + private_key: &PKey, + certificate: &X509Ref, + chain: I) + -> Result + where I: IntoIterator, + T: AsRef + { + let mut ctx = try!(ctx(method)); + ctx.set_options(ssl::SSL_OP_SINGLE_DH_USE | ssl::SSL_OP_CIPHER_SERVER_PREFERENCE); + let dh = try!(Dh::from_pem(DHPARAM_PEM.as_bytes())); + try!(ctx.set_tmp_dh(&dh)); + 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")); + 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(ServerConnectorBuilder(ctx)) + } + + pub fn context(&self) -> &SslContextBuilder { + &self.0 + } + + pub fn context_mut(&mut self) -> &mut SslContextBuilder { + &mut self.0 + } + + pub fn build(self) -> ServerConnector { + ServerConnector(self.0.build()) + } +} + +pub struct ServerConnector(SslContext); + +impl ServerConnector { + pub fn connect(&self, stream: S) -> Result, HandshakeError> + 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(not(any(ossl102, ossl110)))] +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(not(any(ossl102, ossl110)))] +mod verify { + use std::net::IpAddr; + + use nid; + use x509::{X509StoreContextRef, X509Ref, GeneralNames, X509NameRef}; + + 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: &GeneralNames) -> 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.text_by_nid(nid::COMMONNAME) { + // 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::().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 { + // 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, + } + } +} diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index cc9069dc..19b21d9d 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -22,19 +22,22 @@ use ffi; use {init, cvt, cvt_p}; use dh::Dh; use x509::{X509StoreContextRef, X509FileType, X509, X509Ref, X509VerifyError}; -#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -use x509::verify::X509VerifyParamRef; +#[cfg(any(ossl102, ossl110))] +use verify::X509VerifyParamRef; use pkey::PKey; use error::ErrorStack; use opaque::Opaque; pub mod error; +mod connector; mod bio; #[cfg(test)] mod tests; use self::bio::BioMethod; +pub use ssl::connector::{ClientConnectorBuilder, ClientConnector, ServerConnectorBuilder, + ServerConnector}; #[doc(inline)] pub use ssl::error::Error; @@ -1030,6 +1033,11 @@ impl SslRef { /// 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() + } + + #[cfg(any(ossl102, ossl110))] + fn _param_mut(&mut self) -> &mut X509VerifyParamRef { unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) } @@ -1153,6 +1161,8 @@ impl Ssl { /// An error or intermediate state after a TLS handshake attempt. #[derive(Debug)] pub enum HandshakeError { + /// Setup failed. + SetupFailure(ErrorStack), /// The handshake failed. Failure(MidHandshakeSslStream), /// The handshake was interrupted midway through. @@ -1162,6 +1172,7 @@ pub enum HandshakeError { impl stderror::Error for HandshakeError { fn description(&self) -> &str { match *self { + HandshakeError::SetupFailure(_) => "stream setup failed", HandshakeError::Failure(_) => "the handshake failed", HandshakeError::Interrupted(_) => "the handshake was interrupted", } @@ -1169,6 +1180,7 @@ impl stderror::Error for HandshakeError { fn cause(&self) -> Option<&stderror::Error> { match *self { + HandshakeError::SetupFailure(ref e) => Some(e), HandshakeError::Failure(ref s) | HandshakeError::Interrupted(ref s) => Some(s.error()), } } @@ -1178,6 +1190,7 @@ impl fmt::Display for HandshakeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(f.write_str(stderror::Error::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() { @@ -1189,6 +1202,12 @@ impl fmt::Display for HandshakeError { } } +impl From for HandshakeError { + fn from(e: ErrorStack) -> HandshakeError { + HandshakeError::SetupFailure(e) + } +} + /// An SSL stream midway through the handshake process. #[derive(Debug)] pub struct MidHandshakeSslStream { diff --git a/openssl/src/ssl/tests/mod.rs b/openssl/src/ssl/tests/mod.rs index ca4af6f7..f98f9928 100644 --- a/openssl/src/ssl/tests/mod.rs +++ b/openssl/src/ssl/tests/mod.rs @@ -18,8 +18,8 @@ use hash::MessageDigest; use ssl; use ssl::SSL_VERIFY_PEER; use ssl::{SslMethod, HandshakeError}; -use ssl::error::Error; -use ssl::{SslContext, SslStream, Ssl, ShutdownResult}; +use ssl::{SslContext, SslStream, Ssl, ShutdownResult, ClientConnectorBuilder, + ServerConnectorBuilder, Error}; use x509::X509StoreContextRef; use x509::X509FileType; use x509::X509; @@ -31,6 +31,9 @@ use std::net::UdpSocket; 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,10 +49,6 @@ struct Server { impl Server { fn spawn(args: &[&str], input: Option>) -> (Server, SocketAddr) { - static CERT: &'static [u8] = include_bytes!("../../../test/cert.pem"); - static KEY: &'static [u8] = include_bytes!("../../../test/key.pem"); - - let td = TempDir::new("openssl").unwrap(); let cert = td.path().join("cert.pem"); let key = td.path().join("key.pem"); @@ -1047,7 +1046,7 @@ fn add_extra_chain_cert() { #[test] #[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -fn valid_hostname() { +fn verify_valid_hostname() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_default_verify_paths().unwrap(); ctx.set_verify(SSL_VERIFY_PEER); @@ -1071,7 +1070,7 @@ fn valid_hostname() { #[test] #[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -fn invalid_hostname() { +fn verify_invalid_hostname() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_default_verify_paths().unwrap(); ctx.set_verify(SSL_VERIFY_PEER); @@ -1084,6 +1083,61 @@ fn invalid_hostname() { assert!(ssl.connect(s).is_err()); } +#[test] +#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( +fn connector_valid_hostname() { + let connector = ClientConnectorBuilder::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"\r\n") || result.ends_with(b"")); +} + +#[test] +#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( +fn connector_invalid_hostname() { + let connector = ClientConnectorBuilder::tls().unwrap().build(); + + let s = TcpStream::connect("google.com:443").unwrap(); + assert!(connector.connect("foobar.com", s).is_err()); +} + +#[test] +fn connector_client_server() { + 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 = ServerConnectorBuilder::tls(&key, &cert, None::).unwrap().build(); + let stream = listener.accept().unwrap().0; + let mut stream = connector.connect(stream).unwrap(); + + stream.write_all(b"hello").unwrap(); + }); + + let mut connector = ClientConnectorBuilder::tls().unwrap(); + connector.context_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(); diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index cc6b73bb..9fd6812a 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -1,4 +1,5 @@ use libc::{c_char, c_int, c_long, c_ulong, c_void}; +use std::borrow::Borrow; use std::cmp; use std::collections::HashMap; use std::error::Error; @@ -447,6 +448,17 @@ impl X509Ref { } } +impl ToOwned for X509Ref { + type Owned = X509; + + fn to_owned(&self) -> X509 { + unsafe { + compat::X509_up_ref(self.as_ptr()); + X509::from_ptr(self.as_ptr()) + } + } +} + /// An owned public key certificate. pub struct X509(*mut ffi::X509); @@ -491,10 +503,7 @@ impl Deref for X509 { impl Clone for X509 { fn clone(&self) -> X509 { - unsafe { - compat::X509_up_ref(self.as_ptr()); - X509::from_ptr(self.as_ptr()) - } + self.to_owned() } } @@ -504,6 +513,18 @@ impl Drop for X509 { } } +impl AsRef for X509 { + fn as_ref(&self) -> &X509Ref { + &*self + } +} + +impl Borrow for X509 { + fn borrow(&self) -> &X509Ref { + &*self + } +} + pub struct X509NameRef(Opaque); impl X509NameRef { From 57d10ebbc3c04d5089b034b9d88f40c302783c96 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 14:19:09 -0700 Subject: [PATCH 3/9] Add PKeyRef --- openssl/src/pkey.rs | 111 +++++++++++++++++++++-------------- openssl/src/ssl/connector.rs | 6 +- openssl/src/ssl/mod.rs | 4 +- 3 files changed, 71 insertions(+), 50 deletions(-) diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs index e62bbbd9..4cbfcce7 100644 --- a/openssl/src/pkey.rs +++ b/openssl/src/pkey.rs @@ -1,6 +1,7 @@ use libc::{c_void, c_char, c_int}; use std::ptr; use std::mem; +use std::ops::Deref; use ffi; use {cvt, cvt_p}; @@ -9,13 +10,66 @@ use dsa::Dsa; use rsa::Rsa; use error::ErrorStack; use util::{CallbackState, invoke_passwd_cb}; +use opaque::Opaque; +/// A borrowed `PKey`. +pub struct PKeyRef(Opaque); + +impl PKeyRef { + pub unsafe fn from_ptr<'a>(ptr: *mut ffi::EVP_PKEY) -> &'a PKeyRef { + &*(ptr as *mut _) + } + + pub fn as_ptr(&self) -> *mut ffi::EVP_PKEY { + self as *const _ as *mut _ + } + + /// Get a reference to the interal RSA key for direct access to the key components + pub fn rsa(&self) -> Result { + unsafe { + let rsa = try!(cvt_p(ffi::EVP_PKEY_get1_RSA(self.as_ptr()))); + // this is safe as the ffi increments a reference counter to the internal key + Ok(Rsa::from_ptr(rsa)) + } + } + + /// Stores private key as a PEM + // FIXME: also add password and encryption + pub fn private_key_to_pem(&self) -> Result, ErrorStack> { + let mem_bio = try!(MemBio::new()); + unsafe { + try!(cvt(ffi::PEM_write_bio_PrivateKey(mem_bio.as_ptr(), + self.as_ptr(), + ptr::null(), + ptr::null_mut(), + -1, + None, + ptr::null_mut()))); + + } + Ok(mem_bio.get_buf().to_owned()) + } + + /// Stores public key as a PEM + pub fn public_key_to_pem(&self) -> Result, ErrorStack> { + let mem_bio = try!(MemBio::new()); + unsafe { + try!(cvt(ffi::PEM_write_bio_PUBKEY(mem_bio.as_ptr(), self.as_ptr()))); + } + Ok(mem_bio.get_buf().to_owned()) + } + + pub fn public_eq(&self, other: &PKeyRef) -> bool { + unsafe { ffi::EVP_PKEY_cmp(self.as_ptr(), other.as_ptr()) == 1 } + } +} + +/// Represents a public key, optionally with a private key attached. pub struct PKey(*mut ffi::EVP_PKEY); unsafe impl Send for PKey {} unsafe impl Sync for PKey {} -/// Represents a public key, optionally with a private key attached. impl PKey { /// Create a new `PKey` containing an RSA key. pub fn from_rsa(rsa: Rsa) -> Result { @@ -101,7 +155,7 @@ impl PKey { } } - /// assign RSA key to this pkey + /// Assign an RSA key to this pkey. pub fn set_rsa(&mut self, rsa: &Rsa) -> Result<(), ErrorStack> { unsafe { // this needs to be a reference as the set1_RSA ups the reference count @@ -110,49 +164,6 @@ impl PKey { Ok(()) } } - - /// Get a reference to the interal RSA key for direct access to the key components - pub fn rsa(&self) -> Result { - unsafe { - let rsa = try!(cvt_p(ffi::EVP_PKEY_get1_RSA(self.0))); - // this is safe as the ffi increments a reference counter to the internal key - Ok(Rsa::from_ptr(rsa)) - } - } - - /// Stores private key as a PEM - // FIXME: also add password and encryption - pub fn private_key_to_pem(&self) -> Result, ErrorStack> { - let mem_bio = try!(MemBio::new()); - unsafe { - try!(cvt(ffi::PEM_write_bio_PrivateKey(mem_bio.as_ptr(), - self.0, - ptr::null(), - ptr::null_mut(), - -1, - None, - ptr::null_mut()))); - - } - Ok(mem_bio.get_buf().to_owned()) - } - - /// Stores public key as a PEM - pub fn public_key_to_pem(&self) -> Result, ErrorStack> { - let mem_bio = try!(MemBio::new()); - unsafe { - try!(cvt(ffi::PEM_write_bio_PUBKEY(mem_bio.as_ptr(), self.0))); - } - Ok(mem_bio.get_buf().to_owned()) - } - - pub fn as_ptr(&self) -> *mut ffi::EVP_PKEY { - return self.0; - } - - pub fn public_eq(&self, other: &PKey) -> bool { - unsafe { ffi::EVP_PKEY_cmp(self.0, other.0) == 1 } - } } impl Drop for PKey { @@ -163,6 +174,16 @@ impl Drop for PKey { } } +impl Deref for PKey { + type Target = PKeyRef; + + fn deref(&self) -> &PKeyRef { + unsafe { + PKeyRef::from_ptr(self.0) + } + } +} + #[cfg(test)] mod tests { #[test] diff --git a/openssl/src/ssl/connector.rs b/openssl/src/ssl/connector.rs index aed01f8c..62f0a284 100644 --- a/openssl/src/ssl/connector.rs +++ b/openssl/src/ssl/connector.rs @@ -4,7 +4,7 @@ use dh::Dh; use error::ErrorStack; use ssl::{self, SslMethod, SslContextBuilder, SslContext, Ssl, SSL_VERIFY_PEER, SslStream, HandshakeError}; -use pkey::PKey; +use pkey::PKeyRef; use x509::X509Ref; // apps/dh2048.pem @@ -85,7 +85,7 @@ impl ClientConnector { pub struct ServerConnectorBuilder(SslContextBuilder); impl ServerConnectorBuilder { - pub fn tls(private_key: &PKey, + pub fn tls(private_key: &PKeyRef, certificate: &X509Ref, chain: I) -> Result @@ -96,7 +96,7 @@ impl ServerConnectorBuilder { } fn new(method: SslMethod, - private_key: &PKey, + private_key: &PKeyRef, certificate: &X509Ref, chain: I) -> Result diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 19b21d9d..9e39d8bf 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -24,7 +24,7 @@ use dh::Dh; use x509::{X509StoreContextRef, X509FileType, X509, X509Ref, X509VerifyError}; #[cfg(any(ossl102, ossl110))] use verify::X509VerifyParamRef; -use pkey::PKey; +use pkey::PKeyRef; use error::ErrorStack; use opaque::Opaque; @@ -532,7 +532,7 @@ impl SslContextBuilder { } /// Specifies the private key - pub fn set_private_key(&mut self, key: &PKey) -> Result<(), ErrorStack> { + 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(|_| ()) } From e72533c058967f56b302e40c63175cb1b078d052 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 15:00:46 -0700 Subject: [PATCH 4/9] Docs for connectors --- openssl/src/ssl/connector.rs | 30 ++++++++++++++++++++++++++++++ openssl/src/ssl/mod.rs | 12 ++++++++++++ 2 files changed, 42 insertions(+) diff --git a/openssl/src/ssl/connector.rs b/openssl/src/ssl/connector.rs index 62f0a284..da2c03df 100644 --- a/openssl/src/ssl/connector.rs +++ b/openssl/src/ssl/connector.rs @@ -40,9 +40,13 @@ fn ctx(method: SslMethod) -> Result { Ok(ctx) } +/// A builder for `ClientConnector`s. pub struct ClientConnectorBuilder(SslContextBuilder); impl ClientConnectorBuilder { + /// Creates a new builder for TLS connections. + /// + /// The default configuration is based off of libcurl's and is subject to change. pub fn tls() -> Result { ClientConnectorBuilder::new(SslMethod::tls()) } @@ -55,22 +59,35 @@ impl ClientConnectorBuilder { Ok(ClientConnectorBuilder(ctx)) } + /// Returns a shared reference to the inner `SslContextBuilder`. pub fn context(&self) -> &SslContextBuilder { &self.0 } + /// Returns a mutable reference to the inner `SslContextBuilder`. pub fn context_mut(&mut self) -> &mut SslContextBuilder { &mut self.0 } + /// Consumes the builder, returning a `ClientConnector`. pub fn build(self) -> ClientConnector { ClientConnector(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 ClientConnector(SslContext); impl ClientConnector { + /// Initiates a client-side TLS session on a stream. + /// + /// The domain is used for SNI and hostname verification. pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> where S: Read + Write { @@ -82,9 +99,14 @@ impl ClientConnector { } } +/// A builder for `ServerConnector`s. pub struct ServerConnectorBuilder(SslContextBuilder); impl ServerConnectorBuilder { + /// Creates a new builder for server-side TLS connections. + /// + /// The default configuration is based off of the intermediate profile of Mozilla's SSL + /// Configuration Generator, and is subject to change. pub fn tls(private_key: &PKeyRef, certificate: &X509Ref, chain: I) @@ -127,22 +149,30 @@ impl ServerConnectorBuilder { Ok(ServerConnectorBuilder(ctx)) } + /// Returns a shared reference to the inner `SslContextBuilder`. pub fn context(&self) -> &SslContextBuilder { &self.0 } + /// Returns a mutable reference to the inner `SslContextBuilder`. pub fn context_mut(&mut self) -> &mut SslContextBuilder { &mut self.0 } + /// Consumes the builder, returning a `ServerConnector`. pub fn build(self) -> ServerConnector { ServerConnector(self.0.build()) } } +/// 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 ServerConnector(SslContext); impl ServerConnector { + /// Initiates a server-side TLS session on a stream. pub fn connect(&self, stream: S) -> Result, HandshakeError> where S: Read + Write { diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 9e39d8bf..ddf27460 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -342,6 +342,7 @@ pub enum SniError { NoAck, } +/// A builder for `SslContext`s. pub struct SslContextBuilder(*mut ffi::SSL_CTX); impl Drop for SslContextBuilder { @@ -793,6 +794,7 @@ impl SslCipherRef { } } +/// A reference to an `Ssl`. pub struct SslRef(Opaque); unsafe impl Send for SslRef {} @@ -1104,6 +1106,11 @@ impl Ssl { } /// Creates an SSL/TLS client operating over the provided stream. + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `ClientConnector` rather than `Ssl` directly, as it manages that configuration. pub fn connect(self, stream: S) -> Result, HandshakeError> where S: Read + Write { @@ -1131,6 +1138,11 @@ impl Ssl { } /// Creates an SSL/TLS server operating over the provided stream. + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `ServerConnector` rather than `Ssl` directly, as it manages that configuration. pub fn accept(self, stream: S) -> Result, HandshakeError> where S: Read + Write { From 85169e5a61645a411e67b4753b7455a0514271b2 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 15:02:07 -0700 Subject: [PATCH 5/9] Fix reexport --- openssl/src/x509/mod.rs | 2 +- openssl/src/x509/verify.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 openssl/src/x509/verify.rs diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 9fd6812a..31ed11b4 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -39,7 +39,7 @@ use ffi::{ }; #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -pub use verify; +pub mod verify; use x509::extension::{ExtensionType, Extension}; diff --git a/openssl/src/x509/verify.rs b/openssl/src/x509/verify.rs new file mode 100644 index 00000000..aa264ba9 --- /dev/null +++ b/openssl/src/x509/verify.rs @@ -0,0 +1 @@ +pub use ::verify::*; From c2b38d8bb3f141e531a3e636b8a86a073d78f316 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 15:02:36 -0700 Subject: [PATCH 6/9] Move docs --- openssl/src/verify.rs | 4 ---- openssl/src/x509/verify.rs | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openssl/src/verify.rs b/openssl/src/verify.rs index 77095edc..ceb3a6c8 100644 --- a/openssl/src/verify.rs +++ b/openssl/src/verify.rs @@ -1,7 +1,3 @@ -//! X509 certificate verification -//! -//! Requires the `v102` or `v110` features and OpenSSL 1.0.2 or 1.1.0. - use libc::c_uint; use ffi; diff --git a/openssl/src/x509/verify.rs b/openssl/src/x509/verify.rs index aa264ba9..8cb123e6 100644 --- a/openssl/src/x509/verify.rs +++ b/openssl/src/x509/verify.rs @@ -1 +1,5 @@ +//! X509 certificate verification +//! +//! Requires the `v102` or `v110` features and OpenSSL 1.0.2 or 1.1.0. + pub use ::verify::*; From c89f2c0be08bc77afc2f8a45e488ab8e394f0f3d Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 16:37:26 -0700 Subject: [PATCH 7/9] Use PKeyRef in X509Generator --- openssl/src/x509/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 31ed11b4..b7cbe363 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -18,7 +18,7 @@ use asn1::Asn1TimeRef; use bio::{MemBio, MemBioSlice}; use crypto::CryptoString; use hash::MessageDigest; -use pkey::PKey; +use pkey::{PKey, PKeyRef}; use rand::rand_bytes; use error::ErrorStack; use ffi; @@ -278,7 +278,7 @@ impl X509Generator { } /// Sets the certificate public-key, then self-sign and return it - pub fn sign(&self, p_key: &PKey) -> Result { + pub fn sign(&self, p_key: &PKeyRef) -> Result { ffi::init(); unsafe { @@ -330,7 +330,7 @@ impl X509Generator { } /// Obtain a certificate signing request (CSR) - pub fn request(&self, p_key: &PKey) -> Result { + pub fn request(&self, p_key: &PKeyRef) -> Result { let cert = match self.sign(p_key) { Ok(c) => c, Err(x) => return Err(x), From 761dd780c179d667c1b0ee6dd0a6e8e05c24ca46 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 17:25:01 -0700 Subject: [PATCH 8/9] Add module level docs --- openssl/src/ssl/mod.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index ddf27460..ae7abea9 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -1,3 +1,72 @@ +//! SSL/TLS support. +//! +//! The `ClientConnector` and `ServerConnector` 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: +//! +//! ```no_run +//! use openssl::ssl::ClientConnectorBuilder; +//! use std::io::{Read, Write}; +//! use std::net::TcpStream; +//! +//! let connector = ClientConnectorBuilder::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::{ServerConnectorBuilder, 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 connector = ServerConnectorBuilder::tls(&identity.pkey, &identity.cert, &identity.chain) +//! .unwrap() +//! .build(); +//! let connector = Arc::new(connector); +//! +//! let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); +//! +//! fn handle_client(stream: SslStream) { +//! // ... +//! } +//! +//! for stream in listener.incoming() { +//! match stream { +//! Ok(stream) => { +//! let connector = connector.clone(); +//! thread::spawn(move || { +//! let stream = connector.connect(stream).unwrap(); +//! handle_client(stream); +//! }); +//! } +//! Err(e) => { /* connection failed */ } +//! } +//! } +//! ``` use libc::{c_int, c_void, c_long, c_ulong}; use std::any::Any; use std::any::TypeId; From 23fe1e85e96a1148da1b9bfee570880770eca3cb Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 29 Oct 2016 18:16:45 -0700 Subject: [PATCH 9/9] Pull Curl's CA list for Windows tests --- appveyor.yml | 2 ++ openssl/src/ssl/mod.rs | 2 +- openssl/src/ssl/tests/mod.rs | 5 ----- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9a7a3dc7..3e23044e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,5 @@ environment: + SSL_CERT_FILE: "C:\\OpenSSL\\cacert.pem" matrix: # 1.1.0, 64/32 bit - TARGET: i686-pc-windows-gnu @@ -23,6 +24,7 @@ install: # install OpenSSL - ps: Start-FileDownload "http://slproweb.com/download/Win${env:BITS}OpenSSL-${env:OPENSSL_VERSION}.exe" - Win%BITS%OpenSSL-%OPENSSL_VERSION%.exe /SILENT /VERYSILENT /SP- /DIR="C:\OpenSSL" + - ps: Start-FileDownload "https://curl.haxx.se/ca/cacert.pem" -FileName "C:\OpenSSL\cacert.pem" # Install Rust - curl -sSf -o rustup-init.exe https://win.rustup.rs/ diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index ae7abea9..cd7c1426 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -7,7 +7,7 @@ //! //! To connect as a client to a remote server: //! -//! ```no_run +//! ``` //! use openssl::ssl::ClientConnectorBuilder; //! use std::io::{Read, Write}; //! use std::net::TcpStream; diff --git a/openssl/src/ssl/tests/mod.rs b/openssl/src/ssl/tests/mod.rs index f98f9928..5a8067c9 100644 --- a/openssl/src/ssl/tests/mod.rs +++ b/openssl/src/ssl/tests/mod.rs @@ -1018,7 +1018,6 @@ fn refcount_ssl_context() { } #[test] -#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( fn default_verify_paths() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_default_verify_paths().unwrap(); @@ -1044,7 +1043,6 @@ fn add_extra_chain_cert() { } #[test] -#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn verify_valid_hostname() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); @@ -1068,7 +1066,6 @@ fn verify_valid_hostname() { } #[test] -#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] fn verify_invalid_hostname() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); @@ -1084,7 +1081,6 @@ fn verify_invalid_hostname() { } #[test] -#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( fn connector_valid_hostname() { let connector = ClientConnectorBuilder::tls().unwrap().build(); @@ -1101,7 +1097,6 @@ fn connector_valid_hostname() { } #[test] -#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :( fn connector_invalid_hostname() { let connector = ClientConnectorBuilder::tls().unwrap().build();