From afcfefd7e364d25983d946805e2a78e528c09c41 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Wed, 8 Aug 2018 08:36:17 -0700 Subject: [PATCH] Move tokio-tls into workspace (#529) --- .travis.yml | 4 +- Cargo.toml | 1 + tokio-tls/Cargo.toml | 50 ++ tokio-tls/LICENSE | 25 + tokio-tls/README.md | 54 ++ tokio-tls/examples/download-rust-lang.rs | 41 ++ tokio-tls/examples/identity.p12 | Bin 0 -> 3386 bytes tokio-tls/src/lib.rs | 213 ++++++++ tokio-tls/tests/bad.rs | 134 +++++ tokio-tls/tests/google.rs | 118 +++++ tokio-tls/tests/smoke.rs | 632 +++++++++++++++++++++++ 11 files changed, 1270 insertions(+), 2 deletions(-) create mode 100644 tokio-tls/Cargo.toml create mode 100644 tokio-tls/LICENSE create mode 100644 tokio-tls/README.md create mode 100644 tokio-tls/examples/download-rust-lang.rs create mode 100644 tokio-tls/examples/identity.p12 create mode 100644 tokio-tls/src/lib.rs create mode 100644 tokio-tls/tests/bad.rs create mode 100644 tokio-tls/tests/google.rs create mode 100644 tokio-tls/tests/smoke.rs diff --git a/.travis.yml b/.travis.yml index 13a56c6b0..a35b6879a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,8 +64,8 @@ script: if [[ "$TARGET" ]] then rustup target add $TARGET - cargo check --all --target $TARGET - cargo check --tests --all --target $TARGET + cargo check --all --exclude tokio-tls --target $TARGET + cargo check --tests --all --exclude tokio-tls --target $TARGET else cargo test --all # Disable these tests for now as they are buggy diff --git a/Cargo.toml b/Cargo.toml index bea9bf457..558e444fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "tokio-threadpool", "tokio-timer", "tokio-tcp", + "tokio-tls", "tokio-udp", "tokio-uds", ] diff --git a/tokio-tls/Cargo.toml b/tokio-tls/Cargo.toml new file mode 100644 index 000000000..b49d15afd --- /dev/null +++ b/tokio-tls/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "tokio-tls" +version = "0.2.0" +authors = ["Carl Lerche ", + "Alex Crichton "] +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 000000000..38c1e27b8 --- /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 000000000..b2c065590 --- /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 000000000..23e93fbd0 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..d16abb8c70627bd959667495e578fb6f010a7f1d GIT binary patch literal 3386 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 CKSRy{ literal 0 HcmV?d00001 diff --git a/tokio-tls/src/lib.rs b/tokio-tls/src/lib.rs new file mode 100644 index 000000000..3e763c87f --- /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` 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 { + inner: native_tls::TlsStream, +} + +/// 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 { + inner: MidHandshake, +} + +/// Future returned from `TlsAcceptor::accept` which will resolve +/// once the accept handshake has finished. +pub struct Accept { + inner: MidHandshake, +} + +struct MidHandshake { + inner: Option, HandshakeError>>, +} + +impl TlsStream { + /// Get access to the internal `native_tls::TlsStream` stream which also + /// transitively allows access to `S`. + pub fn get_ref(&self) -> &native_tls::TlsStream { + &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 { + &mut self.inner + } +} + +impl Read for TlsStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +impl Write for TlsStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + + +impl AsyncRead for TlsStream { +} + +impl AsyncWrite for TlsStream { + 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` 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(&self, domain: &str, stream: S) -> Connect + where S: AsyncRead + AsyncWrite, + { + Connect { + inner: MidHandshake { + inner: Some(self.inner.connect(domain, stream)), + }, + } + } +} + +impl From 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` 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(&self, stream: S) -> Accept + where S: AsyncRead + AsyncWrite, + { + Accept { + inner: MidHandshake { + inner: Some(self.inner.accept(stream)), + }, + } + } +} + +impl From for TlsAcceptor { + fn from(inner: native_tls::TlsAcceptor) -> TlsAcceptor { + TlsAcceptor { + inner, + } + } +} + +impl Future for Connect { + type Item = TlsStream; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + self.inner.poll() + } +} + +impl Future for Accept { + type Item = TlsStream; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + self.inner.poll() + } +} + +impl Future for MidHandshake { + type Item = TlsStream; + type Error = Error; + + fn poll(&mut self) -> Poll, 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 000000000..078383934 --- /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 000000000..10a959078 --- /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::().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::().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::().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("") || data.ends_with("")); +} + +// 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 000000000..a6e5d127e --- /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, + pkey_der: Vec, + pkcs12_der: Vec, +} + +#[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 { + 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, Vec) = 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 rigamarole here to generate a new + // self-signed certificate and then insert that into the system store. + // + // This generates some dialogs, so we print what we're doing sometimes, + // and otherwise we just manage the ephemeral certificates. Because + // they're in the system store we always ensure that they're only valid + // for a small period of time (e.g. 1 day). + + fn localhost_cert() -> CertContext { + static INIT: Once = ONCE_INIT; + INIT.call_once(|| { + for cert in local_root_store().certs() { + let name = match cert.friendly_name() { + Ok(name) => name, + Err(_) => continue, + }; + if name != FRIENDLY_NAME { + continue + } + if !cert.is_time_valid().unwrap() { + io::stdout().write_all(br#" + +The tokio-tls test suite is about to delete an old copy of one of its +certificates from your root trust store. This certificate was only valid for one +day and it is no longer needed. The host should be "localhost" and the +description should mention "tokio-tls". + + "#).unwrap(); + cert.delete().unwrap(); + } else { + return + } + } + + install_certificate().unwrap(); + }); + + for cert in local_root_store().certs() { + let name = match cert.friendly_name() { + Ok(name) => name, + Err(_) => continue, + }; + if name == FRIENDLY_NAME { + return cert + } + } + + panic!("couldn't find a cert"); + } + + fn local_root_store() -> CertStore { + if env::var("APPVEYOR").is_ok() { + CertStore::open_local_machine("Root").unwrap() + } else { + CertStore::open_current_user("Root").unwrap() + } + } + + fn install_certificate() -> io::Result { + unsafe { + let mut provider = 0; + let mut hkey = 0; + + let mut buffer = "tokio-tls test suite".encode_utf16() + .chain(Some(0)) + .collect::>(); + let res = CryptAcquireContextW(&mut provider, + buffer.as_ptr(), + ptr::null_mut(), + PROV_RSA_FULL, + CRYPT_MACHINE_KEYSET); + if res != TRUE { + // create a new key container (since it does not exist) + let res = CryptAcquireContextW(&mut provider, + buffer.as_ptr(), + ptr::null_mut(), + PROV_RSA_FULL, + CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET); + if res != TRUE { + return Err(Error::last_os_error()) + } + } + + // create a new keypair (RSA-2048) + let res = CryptGenKey(provider, + AT_SIGNATURE, + 0x0800<<16 | CRYPT_EXPORTABLE, + &mut hkey); + if res != TRUE { + return Err(Error::last_os_error()); + } + + // start creating the certificate + let name = "CN=localhost,O=tokio-tls,OU=tokio-tls,\ + G=tokio_tls".encode_utf16() + .chain(Some(0)) + .collect::>(); + let mut cname_buffer: [WCHAR; UNLEN as usize + 1] = mem::zeroed(); + let mut cname_len = cname_buffer.len() as DWORD; + let res = CertStrToNameW(X509_ASN_ENCODING, + name.as_ptr(), + CERT_X500_NAME_STR, + ptr::null_mut(), + cname_buffer.as_mut_ptr() as *mut u8, + &mut cname_len, + ptr::null_mut()); + if res != TRUE { + return Err(Error::last_os_error()); + } + + let mut subject_issuer = CERT_NAME_BLOB { + cbData: cname_len, + pbData: cname_buffer.as_ptr() as *mut u8, + }; + let mut key_provider = CRYPT_KEY_PROV_INFO { + pwszContainerName: buffer.as_mut_ptr(), + pwszProvName: ptr::null_mut(), + dwProvType: PROV_RSA_FULL, + dwFlags: CRYPT_MACHINE_KEYSET, + cProvParam: 0, + rgProvParam: ptr::null_mut(), + dwKeySpec: AT_SIGNATURE, + }; + let mut sig_algorithm = CRYPT_ALGORITHM_IDENTIFIER { + pszObjId: szOID_RSA_SHA256RSA.as_ptr() as *mut _, + Parameters: mem::zeroed(), + }; + let mut expiration_date: SYSTEMTIME = mem::zeroed(); + GetSystemTime(&mut expiration_date); + let mut file_time: FILETIME = mem::zeroed(); + let res = SystemTimeToFileTime(&mut expiration_date, + &mut file_time); + if res != TRUE { + return Err(Error::last_os_error()); + } + let mut timestamp: u64 = file_time.dwLowDateTime as u64 | + (file_time.dwHighDateTime as u64) << 32; + // one day, timestamp unit is in 100 nanosecond intervals + timestamp += (1E9 as u64) / 100 * (60 * 60 * 24); + file_time.dwLowDateTime = timestamp as u32; + file_time.dwHighDateTime = (timestamp >> 32) as u32; + let res = FileTimeToSystemTime(&file_time, + &mut expiration_date); + if res != TRUE { + return Err(Error::last_os_error()); + } + + // create a self signed certificate + let cert_context = CertCreateSelfSignCertificate( + 0 as ULONG_PTR, + &mut subject_issuer, + 0, + &mut key_provider, + &mut sig_algorithm, + ptr::null_mut(), + &mut expiration_date, + ptr::null_mut()); + if cert_context.is_null() { + return Err(Error::last_os_error()); + } + + // TODO: this is.. a terrible hack. Right now `schannel` + // doesn't provide a public method to go from a raw + // cert context pointer to the `CertContext` structure it + // has, so we just fake it here with a transmute. This'll + // probably break at some point, but hopefully by then + // it'll have a method to do this! + struct MyCertContext(T); + impl Drop for MyCertContext { + fn drop(&mut self) {} + } + + let cert_context = MyCertContext(cert_context); + let cert_context: CertContext = mem::transmute(cert_context); + + try!(cert_context.set_friendly_name(FRIENDLY_NAME)); + + // install the certificate to the machine's local store + io::stdout().write_all(br#" + +The tokio-tls test suite is about to add a certificate to your set of root +and trusted certificates. This certificate should be for the domain "localhost" +with the description related to "tokio-tls". This certificate is only valid +for one day and will be automatically deleted if you re-run the tokio-tls +test suite later. + + "#).unwrap(); + try!(local_root_store().add_cert(&cert_context, + CertAdd::ReplaceExisting)); + Ok(cert_context) + } + } + } +} + +fn native2io(e: native_tls::Error) -> io::Error { + io::Error::new(io::ErrorKind::Other, e) +} + +const AMT: u64 = 128 * 1024; + +#[test] +fn client_to_server() { + drop(env_logger::init()); + let mut l = t!(Runtime::new()); + + // Create a server listening on a port, then figure out what that port is + let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse()))); + let addr = t!(srv.local_addr()); + + let (server_cx, client_cx) = contexts(); + + // Create a future to accept one socket, connect the ssl stream, and then + // read all the data from it. + let socket = srv.incoming().take(1).collect(); + let received = socket.map(|mut socket| { + socket.remove(0) + }).and_then(move |socket| { + server_cx.accept(socket).map_err(native2io) + }).and_then(|socket| { + read_to_end(socket, Vec::new()) + }); + + // Create a future to connect to our server, connect the ssl stream, and + // then write a bunch of data to it. + let client = TcpStream::connect(&addr); + let sent = client.and_then(move |socket| { + client_cx.connect("localhost", socket).map_err(native2io) + }).and_then(|socket| { + copy(io::repeat(9).take(AMT), socket) + }).and_then(|(amt, _repeat, socket)| { + shutdown(socket).map(move |_| amt) + }); + + // Finally, run everything! + let (amt, (_, data)) = t!(l.block_on(sent.join(received))); + assert_eq!(amt, AMT); + assert!(data == vec![9; amt as usize]); +} + +#[test] +fn server_to_client() { + drop(env_logger::init()); + let mut l = t!(Runtime::new()); + + // Create a server listening on a port, then figure out what that port is + let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse()))); + let addr = t!(srv.local_addr()); + + let (server_cx, client_cx) = contexts(); + + let socket = srv.incoming().take(1).collect(); + let sent = socket.map(|mut socket| { + socket.remove(0) + }).and_then(move |socket| { + server_cx.accept(socket).map_err(native2io) + }).and_then(|socket| { + copy(io::repeat(9).take(AMT), socket) + }).and_then(|(amt, _repeat, socket)| { + shutdown(socket).map(move |_| amt) + }); + + let client = TcpStream::connect(&addr); + let received = client.and_then(move |socket| { + client_cx.connect("localhost", socket).map_err(native2io) + }).and_then(|socket| { + read_to_end(socket, Vec::new()) + }); + + // Finally, run everything! + let (amt, (_, data)) = t!(l.block_on(sent.join(received))); + assert_eq!(amt, AMT); + assert!(data == vec![9; amt as usize]); +} + +struct OneByte { + inner: S, +} + +impl Read for OneByte { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(&mut buf[..1]) + } +} + +impl Write for OneByte { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(&buf[..1]) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl AsyncRead for OneByte {} +impl AsyncWrite for OneByte { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.inner.shutdown() + } +} + +#[test] +fn one_byte_at_a_time() { + const AMT: u64 = 1024; + drop(env_logger::init()); + let mut l = t!(Runtime::new()); + + let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse()))); + let addr = t!(srv.local_addr()); + + let (server_cx, client_cx) = contexts(); + + let socket = srv.incoming().take(1).collect(); + let sent = socket.map(|mut socket| { + socket.remove(0) + }).and_then(move |socket| { + server_cx.accept(OneByte { inner: socket }).map_err(native2io) + }).and_then(|socket| { + copy(io::repeat(9).take(AMT), socket) + }).and_then(|(amt, _repeat, socket)| { + shutdown(socket).map(move |_| amt) + }); + + let client = TcpStream::connect(&addr); + let received = client.and_then(move |socket| { + let socket = OneByte { inner: socket }; + client_cx.connect("localhost", socket).map_err(native2io) + }).and_then(|socket| { + read_to_end(socket, Vec::new()) + }); + + let (amt, (_, data)) = t!(l.block_on(sent.join(received))); + assert_eq!(amt, AMT); + assert!(data == vec![9; amt as usize]); +}