summaryrefslogtreecommitdiffstats
path: root/tokio-tls
diff options
context:
space:
mode:
authorSean McArthur <sean@seanmonstar.com>2018-08-08 08:36:17 -0700
committerCarl Lerche <me@carllerche.com>2018-08-08 08:36:17 -0700
commitafcfefd7e364d25983d946805e2a78e528c09c41 (patch)
tree6134acdfb661a020807c7a8da97a9d353c2b44ec /tokio-tls
parentc89b0b4c8c15b05fb74a3d2b51ba7564f1b954ae (diff)
Move tokio-tls into workspace (#529)
Diffstat (limited to 'tokio-tls')
-rw-r--r--tokio-tls/Cargo.toml50
-rw-r--r--tokio-tls/LICENSE25
-rw-r--r--tokio-tls/README.md54
-rw-r--r--tokio-tls/examples/download-rust-lang.rs41
-rw-r--r--tokio-tls/examples/identity.p12bin0 -> 3386 bytes
-rw-r--r--tokio-tls/src/lib.rs213
-rw-r--r--tokio-tls/tests/bad.rs134
-rw-r--r--tokio-tls/tests/google.rs118
-rw-r--r--tokio-tls/tests/smoke.rs632
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
new file mode 100644
index 00000000..d16abb8c
--- /dev/null
+++ b/tokio-tls/examples/identity.p12
Binary files differ
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