mirror of
https://github.com/tokio-rs/tokio.git
synced 2025-10-01 12:20:39 +00:00
Move tokio-tls into workspace (#529)
This commit is contained in:
parent
c89b0b4c8c
commit
afcfefd7e3
@ -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
|
||||
|
@ -32,6 +32,7 @@ members = [
|
||||
"tokio-threadpool",
|
||||
"tokio-timer",
|
||||
"tokio-tcp",
|
||||
"tokio-tls",
|
||||
"tokio-udp",
|
||||
"tokio-uds",
|
||||
]
|
||||
|
50
tokio-tls/Cargo.toml
Normal file
50
tokio-tls/Cargo.toml
Normal file
@ -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",
|
||||
]
|
25
tokio-tls/LICENSE
Normal file
25
tokio-tls/LICENSE
Normal file
@ -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.
|
54
tokio-tls/README.md
Normal file
54
tokio-tls/README.md
Normal file
@ -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.
|
41
tokio-tls/examples/download-rust-lang.rs
Normal file
41
tokio-tls/examples/download-rust-lang.rs
Normal file
@ -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));
|
||||
}
|
BIN
tokio-tls/examples/identity.p12
Normal file
BIN
tokio-tls/examples/identity.p12
Normal file
Binary file not shown.
213
tokio-tls/src/lib.rs
Normal file
213
tokio-tls/src/lib.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
134
tokio-tls/tests/bad.rs
Normal file
134
tokio-tls/tests/bad.rs
Normal file
@ -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"))
|
||||
}
|
118
tokio-tls/tests/google.rs
Normal file
118
tokio-tls/tests/google.rs
Normal file
@ -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());
|
||||
}
|
632
tokio-tls/tests/smoke.rs
Normal file
632
tokio-tls/tests/smoke.rs
Normal file
@ -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 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<CertContext> {
|
||||
unsafe {
|
||||
let mut provider = 0;
|
||||
let mut hkey = 0;
|
||||
|
||||
let mut buffer = "tokio-tls test suite".encode_utf16()
|
||||
.chain(Some(0))
|
||||
.collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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>(T);
|
||||
impl<T> Drop for MyCertContext<T> {
|
||||
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<S> {
|
||||
inner: S,
|
||||
}
|
||||
|
||||
impl<S: Read> Read for OneByte<S> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner.read(&mut buf[..1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Write> Write for OneByte<S> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.inner.write(&buf[..1])
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsyncRead> AsyncRead for OneByte<S> {}
|
||||
impl<S: AsyncWrite> AsyncWrite for OneByte<S> {
|
||||
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]);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user