parent
c59daba22e
commit
9c9431be30
@ -1,11 +0,0 @@ |
||||
# 0.3.0 (December 23, 2020) |
||||
|
||||
- Upgrade to `tokio 1.0`. |
||||
|
||||
# 0.2.0 (October 16, 2020) |
||||
|
||||
- Upgrade to `tokio 0.3`. |
||||
|
||||
# 0.1.0 (January 9th, 2019) |
||||
|
||||
- Initial release from `tokio-tls 0.3` |
@ -1,41 +0,0 @@ |
||||
[package] |
||||
name = "tokio-native-tls" |
||||
# When releasing to crates.io: |
||||
# - Remove path dependencies |
||||
# - Update html_root_url. |
||||
# - Update doc url |
||||
# - Cargo.toml |
||||
# - README.md |
||||
# - Update CHANGELOG.md. |
||||
# - Create "v0.1.x" git tag. |
||||
version = "0.3.1" |
||||
edition = "2018" |
||||
authors = ["Tokio Contributors <team@tokio.rs>"] |
||||
license = "MIT" |
||||
repository = "https://github.com/tokio-rs/tls" |
||||
homepage = "https://tokio.rs" |
||||
documentation = "https://docs.rs/tokio-native-tls" |
||||
description = """ |
||||
An implementation of TLS/SSL streams for Tokio using native-tls giving an implementation of TLS |
||||
for nonblocking I/O streams. |
||||
""" |
||||
categories = ["asynchronous", "network-programming"] |
||||
|
||||
[dependencies] |
||||
native-tls = "0.2" |
||||
tokio = "1.0" |
||||
|
||||
[features] |
||||
vendored = ["native-tls/vendored"] |
||||
|
||||
[dev-dependencies] |
||||
tokio = { version = "1.0", features = ["macros", "rt", "rt-multi-thread", "io-util", "net"] } |
||||
cfg-if = "1.0" |
||||
env_logger = { version = "0.10", default-features = false } |
||||
futures = { version = "0.3.0", features = ["async-await"] } |
||||
|
||||
tempfile = "3.1" |
||||
lazy_static = "1.4.0" |
||||
|
||||
[package.metadata.docs.rs] |
||||
all-features = true |
@ -1,25 +0,0 @@ |
||||
Copyright (c) 2019 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. |
@ -1,18 +0,0 @@ |
||||
# tokio-native-tls |
||||
[![github actions](https://github.com/tokio-rs/tls/workflows/CI/badge.svg)](https://github.com/tokio-rs/tls/actions) |
||||
[![crates](https://img.shields.io/crates/v/tokio-native-tls.svg)](https://crates.io/crates/tokio-native-tls) |
||||
[![license](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/tokio-rs/tls/blob/master/tokio-native-tls/LICENSE) |
||||
[![docs.rs](https://docs.rs/tokio-native-tls/badge.svg)](https://docs.rs/tokio-native-tls/) |
||||
|
||||
An implementation of TLS/SSL streams for Tokio built on top of the |
||||
[`native-tls`](https://crates.io/crates/native-tls) crate. |
||||
|
||||
## 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. |
@ -1,39 +0,0 @@ |
||||
// #![warn(rust_2018_idioms)]
|
||||
|
||||
use native_tls::TlsConnector; |
||||
use std::error::Error; |
||||
use std::net::ToSocketAddrs; |
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt}; |
||||
use tokio::net::TcpStream; |
||||
|
||||
#[tokio::main] |
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> { |
||||
let addr = "www.rust-lang.org:443" |
||||
.to_socket_addrs()? |
||||
.next() |
||||
.ok_or("failed to resolve www.rust-lang.org")?; |
||||
|
||||
let socket = TcpStream::connect(&addr).await?; |
||||
let cx = TlsConnector::builder().build()?; |
||||
let cx = tokio_native_tls::TlsConnector::from(cx); |
||||
|
||||
let mut socket = cx.connect("www.rust-lang.org", socket).await?; |
||||
|
||||
socket |
||||
.write_all( |
||||
"\ |
||||
GET / HTTP/1.0\r\n\ |
||||
Host: www.rust-lang.org\r\n\ |
||||
\r\n\ |
||||
" |
||||
.as_bytes(), |
||||
) |
||||
.await?; |
||||
|
||||
let mut data = Vec::new(); |
||||
socket.read_to_end(&mut data).await?; |
||||
|
||||
// println!("data: {:?}", &data);
|
||||
println!("{}", String::from_utf8_lossy(&data[..])); |
||||
Ok(()) |
||||
} |
@ -1,52 +0,0 @@ |
||||
#![warn(rust_2018_idioms)] |
||||
|
||||
// A tiny async TLS echo server with Tokio
|
||||
use native_tls::Identity; |
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt}; |
||||
use tokio::net::TcpListener; |
||||
|
||||
/**
|
||||
an example to setup a tls server. |
||||
how to test: |
||||
wget https://127.0.0.1:12345 --no-check-certificate
|
||||
*/ |
||||
#[tokio::main] |
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> { |
||||
// Bind the server's socket
|
||||
let addr = "127.0.0.1:12345".to_string(); |
||||
let tcp: TcpListener = TcpListener::bind(&addr).await?; |
||||
|
||||
// Create the TLS acceptor.
|
||||
let der = include_bytes!("identity.p12"); |
||||
let cert = Identity::from_pkcs12(der, "mypass")?; |
||||
let tls_acceptor = |
||||
tokio_native_tls::TlsAcceptor::from(native_tls::TlsAcceptor::builder(cert).build()?); |
||||
loop { |
||||
// Asynchronously wait for an inbound socket.
|
||||
let (socket, remote_addr) = tcp.accept().await?; |
||||
let tls_acceptor = tls_acceptor.clone(); |
||||
println!("accept connection from {}", remote_addr); |
||||
tokio::spawn(async move { |
||||
// Accept the TLS connection.
|
||||
let mut tls_stream = tls_acceptor.accept(socket).await.expect("accept error"); |
||||
// In a loop, read data from the socket and write the data back.
|
||||
|
||||
let mut buf = [0; 1024]; |
||||
let n = tls_stream |
||||
.read(&mut buf) |
||||
.await |
||||
.expect("failed to read data from socket"); |
||||
|
||||
if n == 0 { |
||||
return; |
||||
} |
||||
println!("read={}", unsafe { |
||||
String::from_utf8_unchecked(buf[0..n].into()) |
||||
}); |
||||
tls_stream |
||||
.write_all(&buf[0..n]) |
||||
.await |
||||
.expect("failed to write data to socket"); |
||||
}); |
||||
} |
||||
} |
Binary file not shown.
@ -1,384 +0,0 @@ |
||||
#![doc(html_root_url = "https://docs.rs/tokio-native-tls/0.3.0")] |
||||
#![warn(
|
||||
missing_debug_implementations, |
||||
missing_docs, |
||||
rust_2018_idioms, |
||||
unreachable_pub |
||||
)] |
||||
#![deny(rustdoc::broken_intra_doc_links)] |
||||
#![doc(test(
|
||||
no_crate_inject, |
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) |
||||
))] |
||||
|
||||
//! 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.
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; |
||||
|
||||
use crate::native_tls::{Error, HandshakeError, MidHandshakeTlsStream}; |
||||
use std::fmt; |
||||
use std::future::Future; |
||||
use std::io::{self, Read, Write}; |
||||
use std::marker::Unpin; |
||||
#[cfg(unix)] |
||||
use std::os::unix::io::{AsRawFd, RawFd}; |
||||
#[cfg(windows)] |
||||
use std::os::windows::io::{AsRawSocket, RawSocket}; |
||||
use std::pin::Pin; |
||||
use std::ptr::null_mut; |
||||
use std::task::{Context, Poll}; |
||||
|
||||
/// An intermediate wrapper for the inner stream `S`.
|
||||
#[derive(Debug)] |
||||
pub struct AllowStd<S> { |
||||
inner: S, |
||||
context: *mut (), |
||||
} |
||||
|
||||
impl<S> AllowStd<S> { |
||||
/// Returns a shared reference to the inner stream.
|
||||
pub fn get_ref(&self) -> &S { |
||||
&self.inner |
||||
} |
||||
|
||||
/// Returns a mutable reference to the inner stream.
|
||||
pub fn get_mut(&mut self) -> &mut S { |
||||
&mut self.inner |
||||
} |
||||
} |
||||
|
||||
/// 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>(native_tls::TlsStream<AllowStd<S>>); |
||||
|
||||
/// A wrapper around a `native_tls::TlsConnector`, providing an async `connect`
|
||||
/// method.
|
||||
#[derive(Clone)] |
||||
pub struct TlsConnector(native_tls::TlsConnector); |
||||
|
||||
/// A wrapper around a `native_tls::TlsAcceptor`, providing an async `accept`
|
||||
/// method.
|
||||
#[derive(Clone)] |
||||
pub struct TlsAcceptor(native_tls::TlsAcceptor); |
||||
|
||||
struct MidHandshake<S>(Option<MidHandshakeTlsStream<AllowStd<S>>>); |
||||
|
||||
enum StartedHandshake<S> { |
||||
Done(TlsStream<S>), |
||||
Mid(MidHandshakeTlsStream<AllowStd<S>>), |
||||
} |
||||
|
||||
struct StartedHandshakeFuture<F, S>(Option<StartedHandshakeFutureInner<F, S>>); |
||||
struct StartedHandshakeFutureInner<F, S> { |
||||
f: F, |
||||
stream: S, |
||||
} |
||||
|
||||
struct Guard<'a, S>(&'a mut TlsStream<S>) |
||||
where |
||||
AllowStd<S>: Read + Write; |
||||
|
||||
impl<S> Drop for Guard<'_, S> |
||||
where |
||||
AllowStd<S>: Read + Write, |
||||
{ |
||||
fn drop(&mut self) { |
||||
(self.0).0.get_mut().context = null_mut(); |
||||
} |
||||
} |
||||
|
||||
// *mut () context is neither Send nor Sync
|
||||
unsafe impl<S: Send> Send for AllowStd<S> {} |
||||
unsafe impl<S: Sync> Sync for AllowStd<S> {} |
||||
|
||||
impl<S> AllowStd<S> |
||||
where |
||||
S: Unpin, |
||||
{ |
||||
fn with_context<F, R>(&mut self, f: F) -> io::Result<R> |
||||
where |
||||
F: FnOnce(&mut Context<'_>, Pin<&mut S>) -> Poll<io::Result<R>>, |
||||
{ |
||||
unsafe { |
||||
assert!(!self.context.is_null()); |
||||
let waker = &mut *(self.context as *mut _); |
||||
match f(waker, Pin::new(&mut self.inner)) { |
||||
Poll::Ready(r) => r, |
||||
Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<S> Read for AllowStd<S> |
||||
where |
||||
S: AsyncRead + Unpin, |
||||
{ |
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
||||
let mut buf = ReadBuf::new(buf); |
||||
self.with_context(|ctx, stream| stream.poll_read(ctx, &mut buf))?; |
||||
Ok(buf.filled().len()) |
||||
} |
||||
} |
||||
|
||||
impl<S> Write for AllowStd<S> |
||||
where |
||||
S: AsyncWrite + Unpin, |
||||
{ |
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
||||
self.with_context(|ctx, stream| stream.poll_write(ctx, buf)) |
||||
} |
||||
|
||||
fn flush(&mut self) -> io::Result<()> { |
||||
self.with_context(|ctx, stream| stream.poll_flush(ctx)) |
||||
} |
||||
} |
||||
|
||||
impl<S> TlsStream<S> { |
||||
fn with_context<F, R>(&mut self, ctx: &mut Context<'_>, f: F) -> Poll<io::Result<R>> |
||||
where |
||||
F: FnOnce(&mut native_tls::TlsStream<AllowStd<S>>) -> io::Result<R>, |
||||
AllowStd<S>: Read + Write, |
||||
{ |
||||
self.0.get_mut().context = ctx as *mut _ as *mut (); |
||||
let g = Guard(self); |
||||
match f(&mut (g.0).0) { |
||||
Ok(v) => Poll::Ready(Ok(v)), |
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Poll::Pending, |
||||
Err(e) => Poll::Ready(Err(e)), |
||||
} |
||||
} |
||||
|
||||
/// Returns a shared reference to the inner stream.
|
||||
pub fn get_ref(&self) -> &native_tls::TlsStream<AllowStd<S>> { |
||||
&self.0 |
||||
} |
||||
|
||||
/// Returns a mutable reference to the inner stream.
|
||||
pub fn get_mut(&mut self) -> &mut native_tls::TlsStream<AllowStd<S>> { |
||||
&mut self.0 |
||||
} |
||||
} |
||||
|
||||
impl<S> AsyncRead for TlsStream<S> |
||||
where |
||||
S: AsyncRead + AsyncWrite + Unpin, |
||||
{ |
||||
fn poll_read( |
||||
mut self: Pin<&mut Self>, |
||||
ctx: &mut Context<'_>, |
||||
buf: &mut ReadBuf<'_>, |
||||
) -> Poll<io::Result<()>> { |
||||
self.with_context(ctx, |s| { |
||||
let n = s.read(buf.initialize_unfilled())?; |
||||
buf.advance(n); |
||||
Ok(()) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl<S> AsyncWrite for TlsStream<S> |
||||
where |
||||
S: AsyncRead + AsyncWrite + Unpin, |
||||
{ |
||||
fn poll_write( |
||||
mut self: Pin<&mut Self>, |
||||
ctx: &mut Context<'_>, |
||||
buf: &[u8], |
||||
) -> Poll<io::Result<usize>> { |
||||
self.with_context(ctx, |s| s.write(buf)) |
||||
} |
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> { |
||||
self.with_context(ctx, |s| s.flush()) |
||||
} |
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> { |
||||
self.with_context(ctx, |s| s.shutdown()) |
||||
} |
||||
} |
||||
|
||||
#[cfg(unix)] |
||||
impl<S> AsRawFd for TlsStream<S> |
||||
where |
||||
S: AsRawFd, |
||||
{ |
||||
fn as_raw_fd(&self) -> RawFd { |
||||
self.get_ref().get_ref().get_ref().as_raw_fd() |
||||
} |
||||
} |
||||
|
||||
#[cfg(windows)] |
||||
impl<S> AsRawSocket for TlsStream<S> |
||||
where |
||||
S: AsRawSocket, |
||||
{ |
||||
fn as_raw_socket(&self) -> RawSocket { |
||||
self.get_ref().get_ref().get_ref().as_raw_socket() |
||||
} |
||||
} |
||||
|
||||
async fn handshake<F, S>(f: F, stream: S) -> Result<TlsStream<S>, Error> |
||||
where |
||||
F: FnOnce( |
||||
AllowStd<S>, |
||||
) -> Result<native_tls::TlsStream<AllowStd<S>>, HandshakeError<AllowStd<S>>> |
||||
+ Unpin, |
||||
S: AsyncRead + AsyncWrite + Unpin, |
||||
{ |
||||
let start = StartedHandshakeFuture(Some(StartedHandshakeFutureInner { f, stream })); |
||||
|
||||
match start.await { |
||||
Err(e) => Err(e), |
||||
Ok(StartedHandshake::Done(s)) => Ok(s), |
||||
Ok(StartedHandshake::Mid(s)) => MidHandshake(Some(s)).await, |
||||
} |
||||
} |
||||
|
||||
impl<F, S> Future for StartedHandshakeFuture<F, S> |
||||
where |
||||
F: FnOnce( |
||||
AllowStd<S>, |
||||
) -> Result<native_tls::TlsStream<AllowStd<S>>, HandshakeError<AllowStd<S>>> |
||||
+ Unpin, |
||||
S: Unpin, |
||||
AllowStd<S>: Read + Write, |
||||
{ |
||||
type Output = Result<StartedHandshake<S>, Error>; |
||||
|
||||
fn poll( |
||||
mut self: Pin<&mut Self>, |
||||
ctx: &mut Context<'_>, |
||||
) -> Poll<Result<StartedHandshake<S>, Error>> { |
||||
let inner = self.0.take().expect("future polled after completion"); |
||||
let stream = AllowStd { |
||||
inner: inner.stream, |
||||
context: ctx as *mut _ as *mut (), |
||||
}; |
||||
|
||||
match (inner.f)(stream) { |
||||
Ok(mut s) => { |
||||
s.get_mut().context = null_mut(); |
||||
Poll::Ready(Ok(StartedHandshake::Done(TlsStream(s)))) |
||||
} |
||||
Err(HandshakeError::WouldBlock(mut s)) => { |
||||
s.get_mut().context = null_mut(); |
||||
Poll::Ready(Ok(StartedHandshake::Mid(s))) |
||||
} |
||||
Err(HandshakeError::Failure(e)) => Poll::Ready(Err(e)), |
||||
} |
||||
} |
||||
} |
||||
|
||||
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 async fn connect<S>(&self, domain: &str, stream: S) -> Result<TlsStream<S>, Error> |
||||
where |
||||
S: AsyncRead + AsyncWrite + Unpin, |
||||
{ |
||||
handshake(move |s| self.0.connect(domain, s), stream).await |
||||
} |
||||
} |
||||
|
||||
impl fmt::Debug for TlsConnector { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
f.debug_struct("TlsConnector").finish() |
||||
} |
||||
} |
||||
|
||||
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 async fn accept<S>(&self, stream: S) -> Result<TlsStream<S>, Error> |
||||
where |
||||
S: AsyncRead + AsyncWrite + Unpin, |
||||
{ |
||||
handshake(move |s| self.0.accept(s), stream).await |
||||
} |
||||
} |
||||
|
||||
impl fmt::Debug for TlsAcceptor { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
f.debug_struct("TlsAcceptor").finish() |
||||
} |
||||
} |
||||
|
||||
impl From<native_tls::TlsAcceptor> for TlsAcceptor { |
||||
fn from(inner: native_tls::TlsAcceptor) -> TlsAcceptor { |
||||
TlsAcceptor(inner) |
||||
} |
||||
} |
||||
|
||||
impl<S: AsyncRead + AsyncWrite + Unpin> Future for MidHandshake<S> { |
||||
type Output = Result<TlsStream<S>, Error>; |
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
||||
let mut_self = self.get_mut(); |
||||
let mut s = mut_self.0.take().expect("future polled after completion"); |
||||
|
||||
s.get_mut().context = cx as *mut _ as *mut (); |
||||
match s.handshake() { |
||||
Ok(mut s) => { |
||||
s.get_mut().context = null_mut(); |
||||
Poll::Ready(Ok(TlsStream(s))) |
||||
} |
||||
Err(HandshakeError::WouldBlock(mut s)) => { |
||||
s.get_mut().context = null_mut(); |
||||
mut_self.0 = Some(s); |
||||
Poll::Pending |
||||
} |
||||
Err(HandshakeError::Failure(e)) => Poll::Ready(Err(e)), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// re-export native_tls
|
||||
pub mod native_tls { |
||||
pub use native_tls::*; |
||||
} |
@ -1,122 +0,0 @@ |
||||
#![warn(rust_2018_idioms)] |
||||
|
||||
use cfg_if::cfg_if; |
||||
use native_tls::TlsConnector; |
||||
use std::io::{self, Error}; |
||||
use std::net::ToSocketAddrs; |
||||
use tokio::net::TcpStream; |
||||
|
||||
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"))))] { |
||||
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 crate::assert_invalid_cert_chain as assert_expired_error; |
||||
use crate::assert_invalid_cert_chain as assert_wrong_host; |
||||
use crate::assert_invalid_cert_chain as assert_self_signed; |
||||
use crate::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; |
||||
} |
||||
} |
||||
|
||||
async fn get_host(host: &'static str) -> Error { |
||||
drop(env_logger::try_init()); |
||||
|
||||
let addr = format!("{}:443", host); |
||||
let addr = t!(addr.to_socket_addrs()).next().unwrap(); |
||||
|
||||
let socket = t!(TcpStream::connect(&addr).await); |
||||
let builder = TlsConnector::builder(); |
||||
let cx = t!(builder.build()); |
||||
let cx = tokio_native_tls::TlsConnector::from(cx); |
||||
let res = cx |
||||
.connect(host, socket) |
||||
.await |
||||
.map_err(|e| Error::new(io::ErrorKind::Other, e)); |
||||
|
||||
assert!(res.is_err()); |
||||
res.err().unwrap() |
||||
} |
||||
|
||||
#[tokio::test] |
||||
async fn expired() { |
||||
assert_expired_error(&get_host("expired.badssl.com").await) |
||||
} |
||||
|
||||
// TODO: the OSX builders on Travis apparently fail this tests spuriously?
|
||||
// passes locally though? Seems... bad!
|
||||
#[tokio::test] |
||||
#[cfg_attr(all(target_os = "macos", feature = "force-openssl"), ignore)] |
||||
async fn wrong_host() { |
||||
assert_wrong_host(&get_host("wrong.host.badssl.com").await) |
||||
} |
||||
|
||||
#[tokio::test] |
||||
async fn self_signed() { |
||||
assert_self_signed(&get_host("self-signed.badssl.com").await) |
||||
} |
||||
|
||||
#[tokio::test] |
||||
async fn untrusted_root() { |
||||
assert_untrusted_root(&get_host("untrusted-root.badssl.com").await) |
||||
} |
Binary file not shown.
@ -1,99 +0,0 @@ |
||||
#![warn(rust_2018_idioms)] |
||||
|
||||
use cfg_if::cfg_if; |
||||
use native_tls::TlsConnector; |
||||
use std::io; |
||||
use std::net::ToSocketAddrs; |
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt}; |
||||
use tokio::net::TcpStream; |
||||
|
||||
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"))))] { |
||||
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")); |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[tokio::test] |
||||
async fn fetch_google() { |
||||
drop(env_logger::try_init()); |
||||
|
||||
// First up, resolve google.com
|
||||
let addr = t!("google.com:443".to_socket_addrs()).next().unwrap(); |
||||
|
||||
let socket = TcpStream::connect(&addr).await.unwrap(); |
||||
|
||||
// Send off the request by first negotiating an SSL handshake, then writing
|
||||
// of our request, then flushing, then finally read off the response.
|
||||
let builder = TlsConnector::builder(); |
||||
let connector = t!(builder.build()); |
||||
let connector = tokio_native_tls::TlsConnector::from(connector); |
||||
let mut socket = t!(connector.connect("google.com", socket).await); |
||||
t!(socket.write_all(b"GET / HTTP/1.0\r\n\r\n").await); |
||||
let mut data = Vec::new(); |
||||
t!(socket.read_to_end(&mut data).await); |
||||
|
||||
// any response code is fine
|
||||
assert!(data.starts_with(b"HTTP/1.0 ")); |
||||
|
||||
let data = String::from_utf8_lossy(&data); |
||||
let data = data.trim_end(); |
||||
assert!(data.ends_with("</html>") || data.ends_with("</HTML>")); |
||||
} |
||||
|
||||
fn native2io(e: native_tls::Error) -> io::Error { |
||||
io::Error::new(io::ErrorKind::Other, e) |
||||
} |
||||
|
||||
// see comment in bad.rs for ignore reason
|
||||
#[cfg_attr(all(target_os = "macos", feature = "force-openssl"), ignore)] |
||||
#[tokio::test] |
||||
async fn wrong_hostname_error() { |
||||
drop(env_logger::try_init()); |
||||
|
||||
let addr = t!("google.com:443".to_socket_addrs()).next().unwrap(); |
||||
|
||||
let socket = t!(TcpStream::connect(&addr).await); |
||||
let builder = TlsConnector::builder(); |
||||
let connector = t!(builder.build()); |
||||
let connector = tokio_native_tls::TlsConnector::from(connector); |
||||
let res = connector |
||||
.connect("rust-lang.org", socket) |
||||
.await |
||||
.map_err(native2io); |
||||
|
||||
assert!(res.is_err()); |
||||
assert_bad_hostname_error(&res.err().unwrap()); |
||||
} |
Binary file not shown.
Binary file not shown.
@ -1,172 +0,0 @@ |
||||
use futures::join; |
||||
use lazy_static::lazy_static; |
||||
use native_tls::{Certificate, Identity}; |
||||
use std::{fs, io::Error, path::PathBuf, process::Command}; |
||||
use tokio::{ |
||||
io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}, |
||||
net::{TcpListener, TcpStream}, |
||||
}; |
||||
use tokio_native_tls::{TlsAcceptor, TlsConnector}; |
||||
|
||||
lazy_static! { |
||||
static ref CERT_DIR: PathBuf = { |
||||
if cfg!(unix) { |
||||
let dir = tempfile::TempDir::new().unwrap(); |
||||
let path = dir.path().to_str().unwrap(); |
||||
|
||||
Command::new("sh") |
||||
.arg("-c") |
||||
.arg(format!("./scripts/generate-certificate.sh {}", path)) |
||||
.output() |
||||
.expect("failed to execute process"); |
||||
|
||||
dir.into_path() |
||||
} else { |
||||
PathBuf::from("tests") |
||||
} |
||||
}; |
||||
} |
||||
|
||||
#[tokio::test] |
||||
async fn client_to_server() { |
||||
let srv = TcpListener::bind("127.0.0.1:0").await.unwrap(); |
||||
let addr = srv.local_addr().unwrap(); |
||||
|
||||
let (server_tls, client_tls) = context(); |
||||
|
||||
// Create a future to accept one socket, connect the ssl stream, and then
|
||||
// read all the data from it.
|
||||
let server = async move { |
||||
let (socket, _) = srv.accept().await.unwrap(); |
||||
let mut socket = server_tls.accept(socket).await.unwrap(); |
||||
|
||||
// Verify access to all of the nested inner streams (e.g. so that peer
|
||||
// certificates can be accessed). This is just a compile check.
|
||||
let native_tls_stream: &native_tls::TlsStream<_> = socket.get_ref(); |
||||
let _peer_cert = native_tls_stream.peer_certificate().unwrap(); |
||||
let allow_std_stream: &tokio_native_tls::AllowStd<_> = native_tls_stream.get_ref(); |
||||
let _tokio_tcp_stream: &tokio::net::TcpStream = allow_std_stream.get_ref(); |
||||
|
||||
let mut data = Vec::new(); |
||||
socket.read_to_end(&mut data).await.unwrap(); |
||||
data |
||||
}; |
||||
|
||||
// Create a future to connect to our server, connect the ssl stream, and
|
||||
// then write a bunch of data to it.
|
||||
let client = async move { |
||||
let socket = TcpStream::connect(&addr).await.unwrap(); |
||||
let socket = client_tls.connect("foobar.com", socket).await.unwrap(); |
||||
copy_data(socket).await |
||||
}; |
||||
|
||||
// Finally, run everything!
|
||||
let (data, _) = join!(server, client); |
||||
// assert_eq!(amt, AMT);
|
||||
assert!(data == vec![9; AMT]); |
||||
} |
||||
|
||||
#[tokio::test] |
||||
async fn server_to_client() { |
||||
// Create a server listening on a port, then figure out what that port is
|
||||
let srv = TcpListener::bind("127.0.0.1:0").await.unwrap(); |
||||
let addr = srv.local_addr().unwrap(); |
||||
|
||||
let (server_tls, client_tls) = context(); |
||||
|
||||
let server = async move { |
||||
let (socket, _) = srv.accept().await.unwrap(); |
||||
let socket = server_tls.accept(socket).await.unwrap(); |
||||
copy_data(socket).await |
||||
}; |
||||
|
||||
let client = async move { |
||||
let socket = TcpStream::connect(&addr).await.unwrap(); |
||||
let mut socket = client_tls.connect("foobar.com", socket).await.unwrap(); |
||||
let mut data = Vec::new(); |
||||
socket.read_to_end(&mut data).await.unwrap(); |
||||
data |
||||
}; |
||||
|
||||
// Finally, run everything!
|
||||
let (_, data) = join!(server, client); |
||||
assert!(data == vec![9; AMT]); |
||||
} |
||||
|
||||
#[tokio::test] |
||||
async fn one_byte_at_a_time() { |
||||
const AMT: usize = 1024; |
||||
|
||||
let srv = TcpListener::bind("127.0.0.1:0").await.unwrap(); |
||||
let addr = srv.local_addr().unwrap(); |
||||
|
||||
let (server_tls, client_tls) = context(); |
||||
|
||||
let server = async move { |
||||
let (socket, _) = srv.accept().await.unwrap(); |
||||
let mut socket = server_tls.accept(socket).await.unwrap(); |
||||
let mut amt = 0; |
||||
for b in std::iter::repeat(9).take(AMT) { |
||||
let data = [b as u8]; |
||||
socket.write_all(&data).await.unwrap(); |
||||
amt += 1; |
||||
} |
||||
amt |
||||
}; |
||||
|
||||
let client = async move { |
||||
let socket = TcpStream::connect(&addr).await.unwrap(); |
||||
let mut socket = client_tls.connect("foobar.com", socket).await.unwrap(); |
||||
let mut data = Vec::new(); |
||||
loop { |
||||
let mut buf = [0; 1]; |
||||
match socket.read_exact(&mut buf).await { |
||||
Ok(_) => data.extend_from_slice(&buf), |
||||
Err(ref err) if err.kind() == std::io::ErrorKind::UnexpectedEof => break, |
||||
Err(err) => panic!("{}", err), |
||||
} |
||||
} |
||||
data |
||||
}; |
||||
|
||||
let (amt, data) = join!(server, client); |
||||
assert_eq!(amt, AMT); |
||||
assert!(data == vec![9; AMT as usize]); |
||||
} |
||||
|
||||
fn context() -> (TlsAcceptor, TlsConnector) { |
||||
let pkcs12 = fs::read(CERT_DIR.join("identity.p12")).unwrap(); |
||||
let der = fs::read(CERT_DIR.join("root-ca.der")).unwrap(); |
||||
|
||||
let identity = Identity::from_pkcs12(&pkcs12, "mypass").unwrap(); |
||||
let acceptor = native_tls::TlsAcceptor::builder(identity).build().unwrap(); |
||||
|
||||
let cert = Certificate::from_der(&der).unwrap(); |
||||
let connector = native_tls::TlsConnector::builder() |
||||
.add_root_certificate(cert) |
||||
.build() |
||||
.unwrap(); |
||||
|
||||
(acceptor.into(), connector.into()) |
||||
} |
||||
|
||||
const AMT: usize = 128 * 1024; |
||||
|
||||
async fn copy_data<W: AsyncWrite + Unpin>(mut w: W) -> Result<usize, Error> { |
||||
let mut data = vec![9; AMT as usize]; |
||||
let mut amt = 0; |
||||
while !data.is_empty() { |
||||
let written = w.write(&data).await?; |
||||
if written <= data.len() { |
||||
amt += written; |
||||
data.resize(data.len() - written, 0); |
||||
} else { |
||||
w.write_all(&data).await?; |
||||
amt += data.len(); |
||||
break; |
||||
} |
||||
|
||||
println!("remaining: {}", data.len()); |
||||
} |
||||
Ok(amt) |
||||
} |
Loading…
Reference in new issue