diff options
author | Sean McArthur <sean@seanmonstar.com> | 2018-08-08 08:36:17 -0700 |
---|---|---|
committer | Carl Lerche <me@carllerche.com> | 2018-08-08 08:36:17 -0700 |
commit | afcfefd7e364d25983d946805e2a78e528c09c41 (patch) | |
tree | 6134acdfb661a020807c7a8da97a9d353c2b44ec /tokio-tls | |
parent | c89b0b4c8c15b05fb74a3d2b51ba7564f1b954ae (diff) |
Move tokio-tls into workspace (#529)
Diffstat (limited to 'tokio-tls')
-rw-r--r-- | tokio-tls/Cargo.toml | 50 | ||||
-rw-r--r-- | tokio-tls/LICENSE | 25 | ||||
-rw-r--r-- | tokio-tls/README.md | 54 | ||||
-rw-r--r-- | tokio-tls/examples/download-rust-lang.rs | 41 | ||||
-rw-r--r-- | tokio-tls/examples/identity.p12 | bin | 0 -> 3386 bytes | |||
-rw-r--r-- | tokio-tls/src/lib.rs | 213 | ||||
-rw-r--r-- | tokio-tls/tests/bad.rs | 134 | ||||
-rw-r--r-- | tokio-tls/tests/google.rs | 118 | ||||
-rw-r--r-- | tokio-tls/tests/smoke.rs | 632 |
9 files changed, 1267 insertions, 0 deletions
diff --git a/tokio-tls/Cargo.toml b/tokio-tls/Cargo.toml new file mode 100644 index 00000000..b49d15af --- /dev/null +++ b/tokio-tls/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "tokio-tls" +version = "0.2.0" +authors = ["Carl Lerche <me@carllerche.com>", + "Alex Crichton <alex@alexcrichton.com>"] +license = "MIT" +repository = "https://github.com/tokio-rs/tokio" +homepage = "https://tokio.rs" +documentation = "https://docs.rs/tokio-tls" +description = """ +An implementation of TLS/SSL streams for Tokio giving an implementation of TLS +for nonblocking I/O streams. +""" +categories = ["asynchronous", "network-programming"] + +[badges] +travis-ci = { repository = "tokio-rs/tokio-tls" } + +[dependencies] +futures = "0.1.23" +native-tls = "0.2" +tokio-io = { version = "0.1", path = "../tokio-io" } + +[dev-dependencies] +tokio = { version = "0.1", path = "../" } +cfg-if = "0.1" +env_logger = { version = "0.4", default-features = false } + +[target.'cfg(all(not(target_os = "macos"), not(windows), not(target_os = "ios")))'.dev-dependencies] +openssl = "0.10" + +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dev-dependencies] +security-framework = "0.2" + +[target.'cfg(windows)'.dev-dependencies] +schannel = "0.1" + +[target.'cfg(windows)'.dev-dependencies.winapi] +version = "0.3" +features = [ + "lmcons", + "basetsd", + "minwinbase", + "minwindef", + "ntdef", + "sysinfoapi", + "timezoneapi", + "wincrypt", + "winerror", +] diff --git a/tokio-tls/LICENSE b/tokio-tls/LICENSE new file mode 100644 index 00000000..38c1e27b --- /dev/null +++ b/tokio-tls/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2018 Tokio Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/tokio-tls/README.md b/tokio-tls/README.md new file mode 100644 index 00000000..b2c06559 --- /dev/null +++ b/tokio-tls/README.md @@ -0,0 +1,54 @@ +# tokio-tls + +An implementation of TLS/SSL streams for Tokio built on top of the [`native-tls` +crate] + +[Documentation](https://docs.rs/tokio-tls) + +[`native-tls` crate]: https://github.com/sfackler/rust-native-tls + +## Usage + +First, add this to your `Cargo.toml`: + +```toml +[dependencies] +native-tls = "0.2" +tokio-tls = "0.2" +``` + +Next, add this to your crate: + +```rust +extern crate native_tls; +extern crate tokio_tls; + +use tokio_tls::{TlsConnector, TlsAcceptor}; +``` + +You can find few examples how to use this crate in examples directory (using TLS in +hyper server or client). + +By default the `native-tls` crate currently uses the "platform appropriate" +backend for a TLS implementation. This means: + +* On Windows, [SChannel] is used +* On OSX, [SecureTransport] is used +* Everywhere else, [OpenSSL] is used + +[SChannel]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa380123%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 +[SecureTransport]: https://developer.apple.com/reference/security/1654508-secure_transport +[OpenSSL]: https://www.openssl.org/ + +Typically these selections mean that you don't have to worry about a portability +when using TLS, these libraries are all normally installed by default. + +## License + +This project is licensed under the [MIT license](./LICENSE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Tokio by you, shall be licensed as MIT, without any additional +terms or conditions. diff --git a/tokio-tls/examples/download-rust-lang.rs b/tokio-tls/examples/download-rust-lang.rs new file mode 100644 index 00000000..23e93fbd --- /dev/null +++ b/tokio-tls/examples/download-rust-lang.rs @@ -0,0 +1,41 @@ +extern crate futures; +extern crate native_tls; +extern crate tokio; +extern crate tokio_io; +extern crate tokio_tls; + +use std::io; +use std::net::ToSocketAddrs; + +use futures::Future; +use native_tls::TlsConnector; +use tokio::net::TcpStream; +use tokio::runtime::Runtime; + +fn main() { + let mut runtime = Runtime::new().unwrap(); + let addr = "www.rust-lang.org:443".to_socket_addrs().unwrap().next().unwrap(); + + let socket = TcpStream::connect(&addr); + let cx = TlsConnector::builder().build().unwrap(); + let cx = tokio_tls::TlsConnector::from(cx); + + let tls_handshake = socket.and_then(move |socket| { + cx.connect("www.rust-lang.org", socket).map_err(|e| { + io::Error::new(io::ErrorKind::Other, e) + }) + }); + let request = tls_handshake.and_then(|socket| { + tokio_io::io::write_all(socket, "\ + GET / HTTP/1.0\r\n\ + Host: www.rust-lang.org\r\n\ + \r\n\ + ".as_bytes()) + }); + let response = request.and_then(|(socket, _)| { + tokio_io::io::read_to_end(socket, Vec::new()) + }); + + let (_, data) = runtime.block_on(response).unwrap(); + println!("{}", String::from_utf8_lossy(&data)); +} diff --git a/tokio-tls/examples/identity.p12 b/tokio-tls/examples/identity.p12 Binary files differnew file mode 100644 index 00000000..d16abb8c --- /dev/null +++ b/tokio-tls/examples/identity.p12 diff --git a/tokio-tls/src/lib.rs b/tokio-tls/src/lib.rs new file mode 100644 index 00000000..3e763c87 --- /dev/null +++ b/tokio-tls/src/lib.rs @@ -0,0 +1,213 @@ +//! Async TLS streams +//! +//! This library is an implementation of TLS streams using the most appropriate +//! system library by default for negotiating the connection. That is, on +//! Windows this library uses SChannel, on OSX it uses SecureTransport, and on +//! other platforms it uses OpenSSL. +//! +//! Each TLS stream implements the `Read` and `Write` traits to interact and +//! interoperate with the rest of the futures I/O ecosystem. Client connections +//! initiated from this crate verify hostnames automatically and by default. +//! +//! This crate primarily exports this ability through two newtypes, +//! `TlsConnector` and `TlsAcceptor`. These newtypes augment the +//! functionality provided by the `native-tls` crate, on which this crate is +//! built. Configuration of TLS parameters is still primarily done through the +//! `native-tls` crate. + +#![deny(missing_docs)] +#![doc(html_root_url = "https://docs.rs/tokio-tls/0.1")] + +extern crate futures; +extern crate native_tls; +#[macro_use] +extern crate tokio_io; + +use std::io::{self, Read, Write}; + +use futures::{Poll, Future, Async}; +use native_tls::{HandshakeError, Error}; +use tokio_io::{AsyncRead, AsyncWrite}; + +/// A wrapper around an underlying raw stream which implements the TLS or SSL +/// protocol. +/// +/// A `TlsStream<S>` represents a handshake that has been completed successfully +/// and both the server and the client are ready for receiving and sending +/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written +/// to a `TlsStream` are encrypted when passing through to `S`. +#[derive(Debug)] +pub struct TlsStream<S> { + inner: native_tls::TlsStream<S>, +} + +/// A wrapper around a `native_tls::TlsConnector`, providing an async `connect` +/// method. +pub struct TlsConnector { + inner: native_tls::TlsConnector, +} + +/// A wrapper around a `native_tls::TlsAcceptor`, providing an async `accept` +/// method. +pub struct TlsAcceptor { + inner: native_tls::TlsAcceptor, +} + +/// Future returned from `TlsConnector::connect` which will resolve +/// once the connection handshake has finished. +pub struct Connect<S> { + inner: MidHandshake<S>, +} + +/// Future returned from `TlsAcceptor::accept` which will resolve +/// once the accept handshake has finished. +pub struct Accept<S> { + inner: MidHandshake<S>, +} + +struct MidHandshake<S> { + inner: Option<Result<native_tls::TlsStream<S>, HandshakeError<S>>>, +} + +impl<S> TlsStream<S> { + /// Get access to the internal `native_tls::TlsStream` stream which also + /// transitively allows access to `S`. + pub fn get_ref(&self) -> &native_tls::TlsStream<S> { + &self.inner + } + + /// Get mutable access to the internal `native_tls::TlsStream` stream which + /// also transitively allows mutable access to `S`. + pub fn get_mut(&mut self) -> &mut native_tls::TlsStream<S> { + &mut self.inner + } +} + +impl<S: Read + Write> Read for TlsStream<S> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.inner.read(buf) + } +} + +impl<S: Read + Write> Write for TlsStream<S> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.inner.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + + +impl<S: AsyncRead + AsyncWrite> AsyncRead for TlsStream<S> { +} + +impl<S: AsyncRead + AsyncWrite> AsyncWrite for TlsStream<S> { + fn shutdown(&mut self) -> Poll<(), io::Error> { + try_nb!(self.inner.shutdown()); + self.inner.get_mut().shutdown() + } +} + +impl TlsConnector { + /// Connects the provided stream with this connector, assuming the provided + /// domain. + /// + /// This function will internally call `TlsConnector::connect` to connect + /// the stream and returns a future representing the resolution of the + /// connection operation. The returned future will resolve to either + /// `TlsStream<S>` or `Error` depending if it's successful or not. + /// + /// This is typically used for clients who have already established, for + /// example, a TCP connection to a remote server. That stream is then + /// provided here to perform the client half of a connection to a + /// TLS-powered server. + pub fn connect<S>(&self, domain: &str, stream: S) -> Connect<S> + where S: AsyncRead + AsyncWrite, + { + Connect { + inner: MidHandshake { + inner: Some(self.inner.connect(domain, stream)), + }, + } + } +} + +impl From<native_tls::TlsConnector> for TlsConnector { + fn from(inner: native_tls::TlsConnector) -> TlsConnector { + TlsConnector { + inner, + } + } +} + +impl TlsAcceptor { + /// Accepts a new client connection with the provided stream. + /// + /// This function will internally call `TlsAcceptor::accept` to connect + /// the stream and returns a future representing the resolution of the + /// connection operation. The returned future will resolve to either + /// `TlsStream<S>` or `Error` depending if it's successful or not. + /// + /// This is typically used after a new socket has been accepted from a + /// `TcpListener`. That socket is then passed to this function to perform + /// the server half of accepting a client connection. + pub fn accept<S>(&self, stream: S) -> Accept<S> + where S: AsyncRead + AsyncWrite, + { + Accept { + inner: MidHandshake { + inner: Some(self.inner.accept(stream)), + }, + } + } +} + +impl From<native_tls::TlsAcceptor> for TlsAcceptor { + fn from(inner: native_tls::TlsAcceptor) -> TlsAcceptor { + TlsAcceptor { + inner, + } + } +} + +impl<S: AsyncRead + AsyncWrite> Future for Connect<S> { + type Item = TlsStream<S>; + type Error = Error; + + fn poll(&mut self) -> Poll<TlsStream<S>, Error> { + self.inner.poll() + } +} + +impl<S: AsyncRead + AsyncWrite> Future for Accept<S> { + type Item = TlsStream<S>; + type Error = Error; + + fn poll(&mut self) -> Poll<TlsStream<S>, Error> { + self.inner.poll() + } +} + +impl<S: AsyncRead + AsyncWrite> Future for MidHandshake<S> { + type Item = TlsStream<S>; + type Error = Error; + + fn poll(&mut self) -> Poll<TlsStream<S>, Error> { + match self.inner.take().expect("cannot poll MidHandshake twice") { + Ok(stream) => Ok(TlsStream { inner: stream }.into()), + Err(HandshakeError::Failure(e)) => Err(e), + Err(HandshakeError::WouldBlock(s)) => { + match s.handshake() { + Ok(stream) => Ok(TlsStream { inner: stream }.into()), + Err(HandshakeError::Failure(e)) => Err(e), + Err(HandshakeError::WouldBlock(s)) => { + self.inner = Some(Err(HandshakeError::WouldBlock(s))); + Ok(Async::NotReady) + } + } + } + } + } +} diff --git a/tokio-tls/tests/bad.rs b/tokio-tls/tests/bad.rs new file mode 100644 index 00000000..07838393 --- /dev/null +++ b/tokio-tls/tests/bad.rs @@ -0,0 +1,134 @@ +extern crate env_logger; +extern crate futures; +extern crate native_tls; +extern crate tokio; +extern crate tokio_tls; + +#[macro_use] +extern crate cfg_if; + +use std::io::{self, Error}; +use std::net::ToSocketAddrs; + +use futures::Future; +use native_tls::TlsConnector; +use tokio::net::TcpStream; +use tokio::runtime::Runtime; + +macro_rules! t { + ($e:expr) => (match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {:?}", stringify!($e), e), + }) +} + +cfg_if! { + if #[cfg(feature = "force-rustls")] { + fn verify_failed(err: &Error, s: &str) { + let err = err.to_string(); + assert!(err.contains(s), "bad error: {}", err); + } + + fn assert_expired_error(err: &Error) { + verify_failed(err, "CertExpired"); + } + + fn assert_wrong_host(err: &Error) { + verify_failed(err, "CertNotValidForName"); + } + + fn assert_self_signed(err: &Error) { + verify_failed(err, "UnknownIssuer"); + } + + fn assert_untrusted_root(err: &Error) { + verify_failed(err, "UnknownIssuer"); + } + } else if #[cfg(any(feature = "force-openssl", + all(not(target_os = "macos"), + not(target_os = "windows"), + not(target_os = "ios"))))] { + extern crate openssl; + + fn verify_failed(err: &Error) { + assert!(format!("{}", err).contains("certificate verify failed")) + } + + use verify_failed as assert_expired_error; + use verify_failed as assert_wrong_host; + use verify_failed as assert_self_signed; + use verify_failed as assert_untrusted_root; + } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { + + fn assert_invalid_cert_chain(err: &Error) { + assert!(format!("{}", err).contains("was not trusted.")) + } + + use assert_invalid_cert_chain as assert_expired_error; + use assert_invalid_cert_chain as assert_wrong_host; + use assert_invalid_cert_chain as assert_self_signed; + use assert_invalid_cert_chain as assert_untrusted_root; + } else { + fn assert_expired_error(err: &Error) { + let s = err.to_string(); + assert!(s.contains("system clock"), "error = {:?}", s); + } + + fn assert_wrong_host(err: &Error) { + let s = err.to_string(); + assert!(s.contains("CN name"), "error = {:?}", s); + } + + fn assert_self_signed(err: &Error) { + let s = err.to_string(); + assert!(s.contains("root certificate which is not trusted"), "error = {:?}", s); + } + + use assert_self_signed as assert_untrusted_root; + } +} + +fn get_host(host: &'static str) -> Error { + drop(env_logger::init()); + + let addr = format!("{}:443", host); + let addr = t!(addr.to_socket_addrs()).next().unwrap(); + + let mut l = t!(Runtime::new()); + let client = TcpStream::connect(&addr); + let data = client.and_then(move |socket| { + let builder = TlsConnector::builder(); + let cx = builder.build().unwrap(); + let cx = tokio_tls::TlsConnector::from(cx); + cx.connect(host, socket).map_err(|e| { + Error::new(io::ErrorKind::Other, e) + }) + }); + + let res = l.block_on(data); + assert!(res.is_err()); + res.err().unwrap() +} + +#[test] +fn expired() { + assert_expired_error(&get_host("expired.badssl.com")) +} + +// TODO: the OSX builders on Travis apparently fail this tests spuriously? +// passes locally though? Seems... bad! +#[test] +#[cfg_attr(all(target_os = "macos", feature = "force-openssl"), ignore)] +fn wrong_host() { + assert_wrong_host(&get_host("wrong.host.badssl.com")) +} + +#[test] +fn self_signed() { + assert_self_signed(&get_host("self-signed.badssl.com")) +} + +#[test] +fn untrusted_root() { + assert_untrusted_root(&get_host("untrusted-root.badssl.com")) +} diff --git a/tokio-tls/tests/google.rs b/tokio-tls/tests/google.rs new file mode 100644 index 00000000..10a95907 --- /dev/null +++ b/tokio-tls/tests/google.rs @@ -0,0 +1,118 @@ +extern crate env_logger; +extern crate futures; +extern crate native_tls; +extern crate tokio_io; +extern crate tokio_tls; +extern crate tokio; + +#[macro_use] +extern crate cfg_if; + +use std::io; +use std::net::ToSocketAddrs; + +use futures::Future; +use native_tls::TlsConnector; +use tokio_io::io::{flush, read_to_end, write_all}; +use tokio::net::TcpStream; +use tokio::runtime::Runtime; + +macro_rules! t { + ($e:expr) => (match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {:?}", stringify!($e), e), + }) +} + +cfg_if! { + if #[cfg(feature = "force-rustls")] { + fn assert_bad_hostname_error(err: &io::Error) { + let err = err.to_string(); + assert!(err.contains("CertNotValidForName"), "bad error: {}", err); + } + } else if #[cfg(any(feature = "force-openssl", + all(not(target_os = "macos"), + not(target_os = "windows"), + not(target_os = "ios"))))] { + extern crate openssl; + + fn assert_bad_hostname_error(err: &io::Error) { + let err = err.get_ref().unwrap(); + let err = err.downcast_ref::<native_tls::Error>().unwrap(); + assert!(format!("{}", err).contains("certificate verify failed")); + } + } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { + fn assert_bad_hostname_error(err: &io::Error) { + let err = err.get_ref().unwrap(); + let err = err.downcast_ref::<native_tls::Error>().unwrap(); + assert!(format!("{}", err).contains("was not trusted.")); + } + } else { + fn assert_bad_hostname_error(err: &io::Error) { + let err = err.get_ref().unwrap(); + let err = err.downcast_ref::<native_tls::Error>().unwrap(); + assert!(format!("{}", err).contains("CN name")); + } + } +} + +fn native2io(e: native_tls::Error) -> io::Error { + io::Error::new(io::ErrorKind::Other, e) +} + +#[test] +fn fetch_google() { + drop(env_logger::init()); + + // First up, resolve google.com + let addr = t!("google.com:443".to_socket_addrs()).next().unwrap(); + + // Create an event loop and connect a socket to our resolved address.c + let mut l = t!(Runtime::new()); + let client = TcpStream::connect(&addr); + + + // Send off the request by first negotiating an SSL handshake, then writing + // of our request, then flushing, then finally read off the response. + let data = client.and_then(move |socket| { + let builder = TlsConnector::builder(); + let connector = t!(builder.build()); + let connector = tokio_tls::TlsConnector::from(connector); + connector.connect("google.com", socket).map_err(native2io) + }) + .and_then(|socket| write_all(socket, b"GET / HTTP/1.0\r\n\r\n")) + .and_then(|(socket, _)| flush(socket)) + .and_then(|socket| read_to_end(socket, Vec::new())); + + let (_, data) = t!(l.block_on(data)); + + // any response code is fine + assert!(data.starts_with(b"HTTP/1.0 ")); + + let data = String::from_utf8_lossy(&data); + let data = data.trim_right(); + assert!(data.ends_with("</html>") || data.ends_with("</HTML>")); +} + +// see comment in bad.rs for ignore reason +#[cfg_attr(all(target_os = "macos", feature = "force-openssl"), ignore)] +#[test] +fn wrong_hostname_error() { + drop(env_logger::init()); + + let addr = t!("google.com:443".to_socket_addrs()).next().unwrap(); + + let mut l = t!(Runtime::new()); + let client = TcpStream::connect(&addr); + let data = client.and_then(move |socket| { + let builder = TlsConnector::builder(); + let connector = t!(builder.build()); + let connector = tokio_tls::TlsConnector::from(connector); + connector.connect("rust-lang.org", socket) + .map_err(native2io) + }); + + let res = l.block_on(data); + assert!(res.is_err()); + assert_bad_hostname_error(&res.err().unwrap()); +} diff --git a/tokio-tls/tests/smoke.rs b/tokio-tls/tests/smoke.rs new file mode 100644 index 00000000..a6e5d127 --- /dev/null +++ b/tokio-tls/tests/smoke.rs @@ -0,0 +1,632 @@ +extern crate env_logger; +extern crate futures; +extern crate native_tls; +extern crate tokio; +extern crate tokio_io; +extern crate tokio_tls; + +#[macro_use] +extern crate cfg_if; + +use std::io::{self, Read, Write}; +use std::process::Command; + +use futures::{Future, Poll}; +use futures::stream::Stream; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_io::io::{read_to_end, copy, shutdown}; +use tokio::runtime::Runtime; +use tokio::net::{TcpListener, TcpStream}; +use native_tls::{TlsConnector, TlsAcceptor, Identity}; + +macro_rules! t { + ($e:expr) => (match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {:?}", stringify!($e), e), + }) +} + +#[allow(dead_code)] +struct Keys { + cert_der: Vec<u8>, + pkey_der: Vec<u8>, + pkcs12_der: Vec<u8>, +} + +#[allow(dead_code)] +fn openssl_keys() -> &'static Keys { + static INIT: Once = ONCE_INIT; + static mut KEYS: *mut Keys = 0 as *mut _; + + INIT.call_once(|| { + let path = t!(env::current_exe()); + let path = path.parent().unwrap(); + let keyfile = path.join("test.key"); + let certfile = path.join("test.crt"); + let config = path.join("openssl.config"); + + File::create(&config).unwrap().write_all(b"\ + [req]\n\ + distinguished_name=dn\n\ + [ dn ]\n\ + CN=localhost\n\ + [ ext ]\n\ + basicConstraints=CA:FALSE,pathlen:0\n\ + subjectAltName = @alt_names + [alt_names] + DNS.1 = localhost + ").unwrap(); + + let subj = "/C=US/ST=Denial/L=Sprintfield/O=Dis/CN=localhost"; + let output = t!(Command::new("openssl") + .arg("req") + .arg("-nodes") + .arg("-x509") + .arg("-newkey").arg("rsa:2048") + .arg("-config").arg(&config) + .arg("-extensions").arg("ext") + .arg("-subj").arg(subj) + .arg("-keyout").arg(&keyfile) + .arg("-out").arg(&certfile) + .arg("-days").arg("1") + .output()); + assert!(output.status.success()); + + let crtout = t!(Command::new("openssl") + .arg("x509") + .arg("-outform").arg("der") + .arg("-in").arg(&certfile) + .output()); + assert!(crtout.status.success()); + let keyout = t!(Command::new("openssl") + .arg("rsa") + .arg("-outform").arg("der") + .arg("-in").arg(&keyfile) + .output()); + assert!(keyout.status.success()); + + let pkcs12out = t!(Command::new("openssl") + .arg("pkcs12") + .arg("-export") + .arg("-nodes") + .arg("-inkey").arg(&keyfile) + .arg("-in").arg(&certfile) + .arg("-password").arg("pass:foobar") + .output()); + assert!(pkcs12out.status.success()); + + let keys = Box::new(Keys { + cert_der: crtout.stdout, + pkey_der: keyout.stdout, + pkcs12_der: pkcs12out.stdout, + }); + unsafe { + KEYS = Box::into_raw(keys); + } + }); + unsafe { + &*KEYS + } +} + +cfg_if! { + if #[cfg(feature = "rustls")] { + extern crate webpki; + extern crate untrusted; + + use std::env; + use std::fs::File; + use std::process::Command; + use std::sync::{ONCE_INIT, Once}; + + use untrusted::Input; + use webpki::trust_anchor_util; + + fn server_cx() -> io::Result<ServerContext> { + let mut cx = ServerContext::new(); + + let (cert, key) = keys(); + cx.config_mut() + .set_single_cert(vec![cert.to_vec()], key.to_vec()); + + Ok(cx) + } + + fn configure_client(cx: &mut ClientContext) { + let (cert, _key) = keys(); + let cert = Input::from(cert); + let anchor = trust_anchor_util::cert_der_as_trust_anchor(cert).unwrap(); + cx.config_mut().root_store.add_trust_anchors(&[anchor]); + } + + // Like OpenSSL we generate certificates on the fly, but for OSX we + // also have to put them into a specific keychain. We put both the + // certificates and the keychain next to our binary. + // + // Right now I don't know of a way to programmatically create a + // self-signed certificate, so we just fork out to the `openssl` binary. + fn keys() -> (&'static [u8], &'static [u8]) { + static INIT: Once = ONCE_INIT; + static mut KEYS: *mut (Vec<u8>, Vec<u8>) = 0 as *mut _; + + INIT.call_once(|| { + let (key, cert) = openssl_keys(); + let path = t!(env::current_exe()); + let path = path.parent().unwrap(); + let keyfile = path.join("test.key"); + let certfile = path.join("test.crt"); + let config = path.join("openssl.config"); + + File::create(&config).unwrap().write_all(b"\ + [req]\n\ + distinguished_name=dn\n\ + [ dn ]\n\ + CN=localhost\n\ + [ ext ]\n\ + basicConstraints=CA:FALSE,pathlen:0\n\ + subjectAltName = @alt_names + [alt_names] + DNS.1 = localhost + ").unwrap(); + + let subj = "/C=US/ST=Denial/L=Sprintfield/O=Dis/CN=localhost"; + let output = t!(Command::new("openssl") + .arg("req") + .arg("-nodes") + .arg("-x509") + .arg("-newkey").arg("rsa:2048") + .arg("-config").arg(&config) + .arg("-extensions").arg("ext") + .arg("-subj").arg(subj) + .arg("-keyout").arg(&keyfile) + .arg("-out").arg(&certfile) + .arg("-days").arg("1") + .output()); + assert!(output.status.success()); + + let crtout = t!(Command::new("openssl") + .arg("x509") + .arg("-outform").arg("der") + .arg("-in").arg(&certfile) + .output()); + assert!(crtout.status.success()); + let keyout = t!(Command::new("openssl") + .arg("rsa") + .arg("-outform").arg("der") + .arg("-in").arg(&keyfile) + .output()); + assert!(keyout.status.success()); + + let cert = crtout.stdout; + let key = keyout.stdout; + unsafe { + KEYS = Box::into_raw(Box::new((cert, key))); + } + }); + unsafe { + (&(*KEYS).0, &(*KEYS).1) + } + } + } else if #[cfg(any(feature = "force-openssl", + all(not(target_os = "macos"), + not(target_os = "windows"), + not(target_os = "ios"))))] { + extern crate openssl; + + use std::fs::File; + use std::env; + use std::sync::{Once, ONCE_INIT}; + + fn contexts() -> (tokio_tls::TlsAcceptor, tokio_tls::TlsConnector) { + let keys = openssl_keys(); + + let pkcs12 = t!(Identity::from_pkcs12(&keys.pkcs12_der, "foobar")); + let srv = TlsAcceptor::builder(pkcs12); + + let cert = t!(native_tls::Certificate::from_der(&keys.cert_der)); + + let mut client = TlsConnector::builder(); + t!(client.add_root_certificate(cert).build()); + + (t!(srv.build()).into(), t!(client.build()).into()) + } + } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { + extern crate security_framework; + + use std::env; + use std::fs::File; + use std::sync::{Once, ONCE_INIT}; + + fn contexts() -> (tokio_tls::TlsAcceptor, tokio_tls::TlsConnector) { + let keys = openssl_keys(); + + let pkcs12 = t!(Identity::from_pkcs12(&keys.pkcs12_der, "foobar")); + let srv = TlsAcceptor::builder(pkcs12); + + let cert = native_tls::Certificate::from_der(&keys.cert_der).unwrap(); + let mut client = TlsConnector::builder(); + client.add_root_certificate(cert); + + (t!(srv.build()).into(), t!(client.build()).into()) + } + } else { + extern crate schannel; + extern crate winapi; + + use std::env; + use std::fs::File; + use std::io::Error; + use std::mem; + use std::ptr; + use std::sync::{Once, ONCE_INIT}; + + use schannel::cert_context::CertContext; + use schannel::cert_store::{CertStore, CertAdd, Memory}; + use winapi::shared::basetsd::*; + use winapi::shared::lmcons::*; + use winapi::shared::minwindef::*; + use winapi::shared::ntdef::WCHAR; + use winapi::um::minwinbase::*; + use winapi::um::sysinfoapi::*; + use winapi::um::timezoneapi::*; + use winapi::um::wincrypt::*; + + const FRIENDLY_NAME: &'static str = "tokio-tls localhost testing cert"; + + fn contexts() -> (tokio_tls::TlsAcceptor, tokio_tls::TlsConnector) { + let cert = localhost_cert(); + let mut store = t!(Memory::new()).into_store(); + t!(store.add_cert(&cert, CertAdd::Always)); + let pkcs12_der = t!(store.export_pkcs12("foobar")); + let pkcs12 = t!(Identity::from_pkcs12(&pkcs12_der, "foobar")); + + let srv = TlsAcceptor::builder(pkcs12); + let client = TlsConnector::builder(); + (t!(srv.build()).into(), t!(client.build()).into()) + } + + // ==================================================================== + // Magic! + // + // Lots of magic is happening here to wrangle certificates for running + // these tests on Windows. For more information see the test suite + // in the schannel-rs crate as this is just coyping that. + // + // The general gist of this though is that the only way to add custom + // trusted certificates is to add it to the system store of trust. To + // do that we go through the whole ri |