aboutsummaryrefslogtreecommitdiff
path: root/openssl/src
diff options
context:
space:
mode:
authorSteven Fackler <[email protected]>2016-10-30 10:18:09 -0700
committerGitHub <[email protected]>2016-10-30 10:18:09 -0700
commita8d328d0b494c2aab8e80f7c1b907a32c7baaeb8 (patch)
tree469c8a353b972ae937f893958c39faa3f5531792 /openssl/src
parentRemove private field in ParsedPkcs12 (diff)
parentPull Curl's CA list for Windows tests (diff)
downloadrust-openssl-a8d328d0b494c2aab8e80f7c1b907a32c7baaeb8.tar.xz
rust-openssl-a8d328d0b494c2aab8e80f7c1b907a32c7baaeb8.zip
Merge pull request #496 from sfackler/connectors
Implement Connector types
Diffstat (limited to 'openssl/src')
-rw-r--r--openssl/src/lib.rs2
-rw-r--r--openssl/src/pkey.rs109
-rw-r--r--openssl/src/ssl/connector.rs359
-rw-r--r--openssl/src/ssl/mod.rs108
-rw-r--r--openssl/src/ssl/tests/mod.rs71
-rw-r--r--openssl/src/verify.rs47
-rw-r--r--openssl/src/x509/mod.rs41
-rw-r--r--openssl/src/x509/verify.rs48
8 files changed, 669 insertions, 116 deletions
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<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
if r.is_null() {
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<Rsa, ErrorStack> {
+ 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<Vec<u8>, 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<Vec<u8>, 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<PKey, ErrorStack> {
@@ -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,55 +164,22 @@ impl PKey {
Ok(())
}
}
+}
- /// Get a reference to the interal RSA key for direct access to the key components
- pub fn rsa(&self) -> Result<Rsa, ErrorStack> {
- 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<Vec<u8>, 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<Vec<u8>, ErrorStack> {
- let mem_bio = try!(MemBio::new());
+impl Drop for PKey {
+ fn drop(&mut self) {
unsafe {
- try!(cvt(ffi::PEM_write_bio_PUBKEY(mem_bio.as_ptr(), self.0)));
+ ffi::EVP_PKEY_free(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 {
- fn drop(&mut self) {
+impl Deref for PKey {
+ type Target = PKeyRef;
+
+ fn deref(&self) -> &PKeyRef {
unsafe {
- ffi::EVP_PKEY_free(self.0);
+ PKeyRef::from_ptr(self.0)
}
}
}
diff --git a/openssl/src/ssl/connector.rs b/openssl/src/ssl/connector.rs
new file mode 100644
index 00000000..da2c03df
--- /dev/null
+++ b/openssl/src/ssl/connector.rs
@@ -0,0 +1,359 @@
+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;
+
+// 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<SslContextBuilder, ErrorStack> {
+ 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)
+}
+
+/// 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, ErrorStack> {
+ ClientConnectorBuilder::new(SslMethod::tls())
+ }
+
+ fn new(method: SslMethod) -> Result<ClientConnectorBuilder, ErrorStack> {
+ 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))
+ }
+
+ /// 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<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 `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<I, T>(private_key: &PKeyRef,
+ certificate: &X509Ref,
+ chain: I)
+ -> Result<ServerConnectorBuilder, ErrorStack>
+ where I: IntoIterator<Item = T>,
+ T: AsRef<X509Ref>
+ {
+ ServerConnectorBuilder::new(SslMethod::tls(), private_key, certificate, chain)
+ }
+
+ fn new<I, T>(method: SslMethod,
+ private_key: &PKeyRef,
+ certificate: &X509Ref,
+ chain: I)
+ -> Result<ServerConnectorBuilder, ErrorStack>
+ where I: IntoIterator<Item = T>,
+ T: AsRef<X509Ref>
+ {
+ 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))
+ }
+
+ /// 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<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(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::<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,
+ }
+ }
+}
diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs
index cc9069dc..cd7c1426 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:
+//!
+//! ```
+//! 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<TcpStream>) {
+//! // ...
+//! }
+//!
+//! 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;
@@ -22,19 +91,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;
-use pkey::PKey;
+#[cfg(any(ossl102, ossl110))]
+use verify::X509VerifyParamRef;
+use pkey::PKeyRef;
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;
@@ -339,6 +411,7 @@ pub enum SniError {
NoAck,
}
+/// A builder for `SslContext`s.
pub struct SslContextBuilder(*mut ffi::SSL_CTX);
impl Drop for SslContextBuilder {
@@ -529,7 +602,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(|_| ())
}
@@ -790,6 +863,7 @@ impl SslCipherRef {
}
}
+/// A reference to an `Ssl`.
pub struct SslRef(Opaque);
unsafe impl Send for SslRef {}
@@ -1030,6 +1104,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()))
}
@@ -1096,6 +1175,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<S>(self, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
where S: Read + Write
{
@@ -1123,6 +1207,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<S>(self, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
where S: Read + Write
{
@@ -1153,6 +1242,8 @@ impl Ssl {
/// 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.
@@ -1162,6 +1253,7 @@ pub enum HandshakeError<S> {
impl<S: Any + fmt::Debug> stderror::Error 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",
}
@@ -1169,6 +1261,7 @@ impl<S: Any + fmt::Debug> stderror::Error for HandshakeError<S> {
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 +1271,7 @@ 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)));
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 +1283,12 @@ impl<S: Any + fmt::Debug> fmt::Display for HandshakeError<S> {
}
}
+impl<S> From<ErrorStack> for HandshakeError<S> {
+ fn from(e: ErrorStack) -> HandshakeError<S> {
+ HandshakeError::SetupFailure(e)
+ }
+}
+
/// An SSL stream midway through the handshake process.
#[derive(Debug)]
pub struct MidHandshakeSslStream<S> {
diff --git a/openssl/src/ssl/tests/mod.rs b/openssl/src/ssl/tests/mod.rs
index ca4af6f7..5a8067c9 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<Box<FnMut(ChildStdin) + Send>>) -> (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");
@@ -1019,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();
@@ -1045,9 +1043,8 @@ 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);
@@ -1069,9 +1066,8 @@ 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);
@@ -1085,6 +1081,59 @@ fn invalid_hostname() {
}
#[test]
+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"</HTML>\r\n") || result.ends_with(b"</html>"));
+}
+
+#[test]
+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::<X509>).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();
let port = listener.local_addr().unwrap().port();
diff --git a/openssl/src/verify.rs b/openssl/src/verify.rs
new file mode 100644
index 00000000..ceb3a6c8
--- /dev/null
+++ b/openssl/src/verify.rs
@@ -0,0 +1,47 @@
+use libc::c_uint;
+use ffi;
+
+use cvt;
+use error::ErrorStack;
+use opaque::Opaque;
+
+bitflags! {
+ pub flags X509CheckFlags: c_uint {
+ const X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
+ const X509_CHECK_FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS,
+ const X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS = ffi::X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS,
+ const X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS = ffi::X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS,
+ const X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS
+ = ffi::X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
+ /// Requires the `v110` feature and OpenSSL 1.1.0.
+ #[cfg(all(feature = "v110", ossl110))]
+ const X509_CHECK_FLAG_NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
+ }
+}
+
+pub struct X509VerifyParamRef(Opaque);
+
+impl X509VerifyParamRef {
+ pub unsafe fn from_ptr_mut<'a>(ptr: *mut ffi::X509_VERIFY_PARAM) -> &'a mut X509VerifyParamRef {
+ &mut *(ptr as *mut _)
+ }
+
+ pub fn as_ptr(&self) -> *mut ffi::X509_VERIFY_PARAM {
+ self as *const _ as *mut _
+ }
+
+ pub fn set_hostflags(&mut self, hostflags: X509CheckFlags) {
+ unsafe {
+ ffi::X509_VERIFY_PARAM_set_hostflags(self.as_ptr(), hostflags.bits);
+ }
+ }
+
+ pub fn set_host(&mut self, host: &str) -> Result<(), ErrorStack> {
+ unsafe {
+ cvt(ffi::X509_VERIFY_PARAM_set1_host(self.as_ptr(),
+ host.as_ptr() as *const _,
+ host.len()))
+ .map(|_| ())
+ }
+ }
+}
diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs
index b92462d4..b7cbe363 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;
@@ -17,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;
@@ -37,12 +38,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;
-use self::extension::{ExtensionType, Extension};
+use x509::extension::{ExtensionType, Extension};
+
+pub mod extension;
#[cfg(test)]
mod tests;
@@ -277,7 +278,7 @@ impl X509Generator {
}
/// Sets the certificate public-key, then self-sign and return it
- pub fn sign(&self, p_key: &PKey) -> Result<X509, ErrorStack> {
+ pub fn sign(&self, p_key: &PKeyRef) -> Result<X509, ErrorStack> {
ffi::init();
unsafe {
@@ -329,7 +330,7 @@ impl X509Generator {
}
/// Obtain a certificate signing request (CSR)
- pub fn request(&self, p_key: &PKey) -> Result<X509Req, ErrorStack> {
+ pub fn request(&self, p_key: &PKeyRef) -> Result<X509Req, ErrorStack> {
let cert = match self.sign(p_key) {
Ok(c) => c,
Err(x) => return Err(x),
@@ -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<X509Ref> for X509 {
+ fn as_ref(&self) -> &X509Ref {
+ &*self
+ }
+}
+
+impl Borrow<X509Ref> for X509 {
+ fn borrow(&self) -> &X509Ref {
+ &*self
+ }
+}
+
pub struct X509NameRef(Opaque);
impl X509NameRef {
diff --git a/openssl/src/x509/verify.rs b/openssl/src/x509/verify.rs
index 77095edc..8cb123e6 100644
--- a/openssl/src/x509/verify.rs
+++ b/openssl/src/x509/verify.rs
@@ -2,50 +2,4 @@
//!
//! Requires the `v102` or `v110` features and OpenSSL 1.0.2 or 1.1.0.
-use libc::c_uint;
-use ffi;
-
-use cvt;
-use error::ErrorStack;
-use opaque::Opaque;
-
-bitflags! {
- pub flags X509CheckFlags: c_uint {
- const X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
- const X509_CHECK_FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS,
- const X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS = ffi::X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS,
- const X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS = ffi::X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS,
- const X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS
- = ffi::X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
- /// Requires the `v110` feature and OpenSSL 1.1.0.
- #[cfg(all(feature = "v110", ossl110))]
- const X509_CHECK_FLAG_NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
- }
-}
-
-pub struct X509VerifyParamRef(Opaque);
-
-impl X509VerifyParamRef {
- pub unsafe fn from_ptr_mut<'a>(ptr: *mut ffi::X509_VERIFY_PARAM) -> &'a mut X509VerifyParamRef {
- &mut *(ptr as *mut _)
- }
-
- pub fn as_ptr(&self) -> *mut ffi::X509_VERIFY_PARAM {
- self as *const _ as *mut _
- }
-
- pub fn set_hostflags(&mut self, hostflags: X509CheckFlags) {
- unsafe {
- ffi::X509_VERIFY_PARAM_set_hostflags(self.as_ptr(), hostflags.bits);
- }
- }
-
- pub fn set_host(&mut self, host: &str) -> Result<(), ErrorStack> {
- unsafe {
- cvt(ffi::X509_VERIFY_PARAM_set1_host(self.as_ptr(),
- host.as_ptr() as *const _,
- host.len()))
- .map(|_| ())
- }
- }
-}
+pub use ::verify::*;