pax_global_header00006660000000000000000000000064145772476100014527gustar00rootroot0000000000000052 comment=3924baf49578f558f2f30d9a4f27c74df93599f0 futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/000077500000000000000000000000001457724761000214665ustar00rootroot00000000000000futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/.gitignore000066400000000000000000000000571457724761000234600ustar00rootroot00000000000000/target **/*.rs.bk Cargo.lock .DS_Store /.idea futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/Cargo.toml000066400000000000000000000021201457724761000234110ustar00rootroot00000000000000[package] name = "futures-rustls" version = "0.26.0" authors = ["quininer kel "] license = "MIT/Apache-2.0" repository = "https://github.com/quininer/futures-rustls" homepage = "https://github.com/quininer/futures-rustls" documentation = "https://docs.rs/futures-rustls" readme = "README.md" description = "Asynchronous TLS/SSL streams for futures using Rustls." categories = ["asynchronous", "cryptography", "network-programming"] edition = "2018" [dependencies] futures-io = "0.3" rustls = { version = "0.23", default-features = false, features = ["std"] } pki-types = { package = "rustls-pki-types", version = "1" } [features] default = ["aws-lc-rs", "tls12", "logging"] aws-lc-rs = ["rustls/aws_lc_rs"] aws_lc_rs = ["aws-lc-rs"] early-data = [] fips = ["rustls/fips"] logging = ["rustls/logging"] ring = ["rustls/ring"] tls12 = ["rustls/tls12"] [dev-dependencies] smol = "1" futures-util = { version = "0.3.1", features = [ "io" ] } lazy_static = "1" rustls-pemfile = "2" webpki-roots = "0.26" webpki = { version = "0.102", package = "rustls-webpki", default-features = false } futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/LICENSE-APACHE000066400000000000000000000251201457724761000234120ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 quininer kel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/LICENSE-MIT000066400000000000000000000020401457724761000231160ustar00rootroot00000000000000Copyright (c) 2020 quininer kel 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. futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/README.md000066400000000000000000000026101457724761000227440ustar00rootroot00000000000000# futures-rustls [![crates](https://img.shields.io/crates/v/futures-rustls.svg)](https://crates.io/crates/futures-rustls) [![docs.rs](https://docs.rs/futures-rustls/badge.svg)](https://docs.rs/futures-rustls/) Asynchronous TLS/SSL streams for futures using [Rustls](https://github.com/rustls/rustls). ### Basic Structure of a Client ```rust use webpki::DNSNameRef; use futures_rustls::{ TlsConnector, rustls::ClientConfig }; // ... let mut config = ClientConfig::new(); config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); let config = TlsConnector::from(Arc::new(config)); let dnsname = DNSNameRef::try_from_ascii_str("www.rust-lang.org").unwrap(); let stream = TcpStream::connect(&addr).await?; let mut stream = config.connect(dnsname, stream).await?; // ... ``` ### License & Origin This project is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. This started as a fork of [tokio-rustls](https://github.com/rustls/tokio-rustls). ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in futures-rustls by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/000077500000000000000000000000001457724761000222555ustar00rootroot00000000000000futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/client.rs000066400000000000000000000210071457724761000241010ustar00rootroot00000000000000use super::*; use crate::common::IoSession; /// A wrapper around an underlying raw stream which implements the TLS or SSL /// protocol. #[derive(Debug)] pub struct TlsStream { pub(crate) io: IO, pub(crate) session: ClientConnection, pub(crate) state: TlsState, #[cfg(feature = "early-data")] pub(crate) early_waker: Option, } impl TlsStream { #[inline] pub fn get_ref(&self) -> (&IO, &ClientConnection) { (&self.io, &self.session) } #[inline] pub fn get_mut(&mut self) -> (&mut IO, &mut ClientConnection) { (&mut self.io, &mut self.session) } #[inline] pub fn into_inner(self) -> (IO, ClientConnection) { (self.io, self.session) } } #[cfg(feature = "early-data")] fn poll_handle_early_data( state: &mut TlsState, stream: &mut Stream, early_waker: &mut Option, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> where IO: AsyncRead + AsyncWrite + Unpin, { if let TlsState::EarlyData(pos, data) = state { use std::io::Write; // write early data if let Some(mut early_data) = stream.session.early_data() { let mut written = 0; for buf in bufs { if buf.is_empty() { continue; } let len = match early_data.write(buf) { Ok(0) => break, Ok(n) => n, Err(err) => return Poll::Ready(Err(err)), }; written += len; data.extend_from_slice(&buf[..len]); if len < buf.len() { break; } } if written != 0 { return Poll::Ready(Ok(written)); } } // complete handshake while stream.session.is_handshaking() { ready!(stream.handshake(cx))?; } // write early data (fallback) if !stream.session.is_early_data_accepted() { while *pos < data.len() { let len = ready!(stream.as_mut_pin().poll_write(cx, &data[*pos..]))?; *pos += len; } } // end *state = TlsState::Stream; if let Some(waker) = early_waker.take() { waker.wake(); } } Poll::Ready(Ok(0)) } #[cfg(unix)] impl AsRawFd for TlsStream where S: AsRawFd, { fn as_raw_fd(&self) -> RawFd { self.get_ref().0.as_raw_fd() } } #[cfg(windows)] impl AsRawSocket for TlsStream where S: AsRawSocket, { fn as_raw_socket(&self) -> RawSocket { self.get_ref().0.as_raw_socket() } } impl IoSession for TlsStream { type Io = IO; type Session = ClientConnection; #[inline] fn skip_handshake(&self) -> bool { self.state.is_early_data() } #[inline] fn get_mut(&mut self) -> (&mut TlsState, &mut Self::Io, &mut Self::Session) { (&mut self.state, &mut self.io, &mut self.session) } #[inline] fn into_io(self) -> Self::Io { self.io } } impl AsyncRead for TlsStream where IO: AsyncRead + AsyncWrite + Unpin, { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { match self.state { #[cfg(feature = "early-data")] TlsState::EarlyData(..) => { let this = self.get_mut(); // In the EarlyData state, we have not really established a Tls connection. // Before writing data through `AsyncWrite` and completing the tls handshake, // we ignore read readiness and return to pending. // // In order to avoid event loss, // we need to register a waker and wake it up after tls is connected. if this .early_waker .as_ref() .filter(|waker| cx.waker().will_wake(waker)) .is_none() { this.early_waker = Some(cx.waker().clone()); } Poll::Pending } TlsState::Stream | TlsState::WriteShutdown => { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); match stream.as_mut_pin().poll_read(cx, buf) { Poll::Ready(Ok(n)) => { if n == 0 || stream.eof { this.state.shutdown_read(); } Poll::Ready(Ok(n)) } Poll::Ready(Err(err)) if err.kind() == io::ErrorKind::ConnectionAborted => { this.state.shutdown_read(); Poll::Ready(Err(err)) } output => output, } } TlsState::ReadShutdown | TlsState::FullyShutdown => Poll::Ready(Ok(0)), } } } impl AsyncWrite for TlsStream where IO: AsyncRead + AsyncWrite + Unpin, { /// Note: that it does not guarantee the final data to be sent. /// To be cautious, you must manually call `flush`. fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); #[cfg(feature = "early-data")] { let bufs = [io::IoSlice::new(buf)]; let written = ready!(poll_handle_early_data( &mut this.state, &mut stream, &mut this.early_waker, cx, &bufs ))?; if written != 0 { return Poll::Ready(Ok(written)); } } stream.as_mut_pin().poll_write(cx, buf) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); #[cfg(feature = "early-data")] { let written = ready!(poll_handle_early_data( &mut this.state, &mut stream, &mut this.early_waker, cx, bufs ))?; if written != 0 { return Poll::Ready(Ok(written)); } } stream.as_mut_pin().poll_write_vectored(cx, bufs) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); #[cfg(feature = "early-data")] { if let TlsState::EarlyData(ref mut pos, ref mut data) = this.state { // complete handshake while stream.session.is_handshaking() { ready!(stream.handshake(cx))?; } // write early data (fallback) if !stream.session.is_early_data_accepted() { while *pos < data.len() { let len = ready!(stream.as_mut_pin().poll_write(cx, &data[*pos..]))?; *pos += len; } } this.state = TlsState::Stream; if let Some(waker) = this.early_waker.take() { waker.wake(); } } } stream.as_mut_pin().poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { #[cfg(feature = "early-data")] { // complete handshake if matches!(self.state, TlsState::EarlyData(..)) { ready!(self.as_mut().poll_flush(cx))?; } } if self.state.writeable() { self.session.send_close_notify(); self.state.shutdown_write(); } let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); stream.as_mut_pin().poll_close(cx) } } futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/common/000077500000000000000000000000001457724761000235455ustar00rootroot00000000000000futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/common/handshake.rs000066400000000000000000000057061457724761000260510ustar00rootroot00000000000000use crate::common::{Stream, SyncWriteAdapter, TlsState}; use futures_io::{AsyncRead, AsyncWrite}; use rustls::server::AcceptedAlert; use rustls::{ConnectionCommon, SideData}; use std::future::Future; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::task::{Context, Poll}; use std::{io, mem}; pub(crate) trait IoSession { type Io; type Session; fn skip_handshake(&self) -> bool; fn get_mut(&mut self) -> (&mut TlsState, &mut Self::Io, &mut Self::Session); fn into_io(self) -> Self::Io; } pub(crate) enum MidHandshake { Handshaking(IS), End, SendAlert { io: IS::Io, alert: AcceptedAlert, error: io::Error, }, Error { io: IS::Io, error: io::Error, }, } impl Future for MidHandshake where IS: IoSession + Unpin, IS::Io: AsyncRead + AsyncWrite + Unpin, IS::Session: DerefMut + Deref> + Unpin, SD: SideData, { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); let mut stream = match mem::replace(this, MidHandshake::End) { MidHandshake::Handshaking(stream) => stream, MidHandshake::SendAlert { mut io, mut alert, error, } => loop { match alert.write(&mut SyncWriteAdapter { io: &mut io, cx }) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { *this = MidHandshake::SendAlert { io, error, alert }; return Poll::Pending; } Err(_) | Ok(0) => return Poll::Ready(Err((error, io))), Ok(_) => {} }; }, // Starting the handshake returned an error; fail the future immediately. MidHandshake::Error { io, error } => return Poll::Ready(Err((error, io))), _ => panic!("unexpected polling after handshake"), }; if !stream.skip_handshake() { let (state, io, session) = stream.get_mut(); let mut tls_stream = Stream::new(io, session).set_eof(!state.readable()); macro_rules! try_poll { ( $e:expr ) => { match $e { Poll::Ready(Ok(_)) => (), Poll::Ready(Err(err)) => return Poll::Ready(Err((err, stream.into_io()))), Poll::Pending => { *this = MidHandshake::Handshaking(stream); return Poll::Pending; } } }; } while tls_stream.session.is_handshaking() { try_poll!(tls_stream.handshake(cx)); } try_poll!(Pin::new(&mut tls_stream).poll_flush(cx)); } Poll::Ready(Ok(stream)) } } futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/common/mod.rs000066400000000000000000000304341457724761000246760ustar00rootroot00000000000000mod handshake; use futures_io::{AsyncRead, AsyncWrite}; pub(crate) use handshake::{IoSession, MidHandshake}; use rustls::{ConnectionCommon, SideData}; use std::io::{self, IoSlice, Read, Write}; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::task::{Context, Poll}; #[derive(Debug)] pub enum TlsState { #[cfg(feature = "early-data")] EarlyData(usize, Vec), Stream, ReadShutdown, WriteShutdown, FullyShutdown, } impl TlsState { #[inline] pub fn shutdown_read(&mut self) { match *self { TlsState::WriteShutdown | TlsState::FullyShutdown => *self = TlsState::FullyShutdown, _ => *self = TlsState::ReadShutdown, } } #[inline] pub fn shutdown_write(&mut self) { match *self { TlsState::ReadShutdown | TlsState::FullyShutdown => *self = TlsState::FullyShutdown, _ => *self = TlsState::WriteShutdown, } } #[inline] pub fn writeable(&self) -> bool { !matches!(*self, TlsState::WriteShutdown | TlsState::FullyShutdown) } #[inline] pub fn readable(&self) -> bool { !matches!(*self, TlsState::ReadShutdown | TlsState::FullyShutdown) } #[inline] #[cfg(feature = "early-data")] pub fn is_early_data(&self) -> bool { matches!(self, TlsState::EarlyData(..)) } #[inline] #[cfg(not(feature = "early-data"))] pub const fn is_early_data(&self) -> bool { false } } pub struct Stream<'a, IO, C> { pub io: &'a mut IO, pub session: &'a mut C, pub eof: bool, } impl<'a, IO: AsyncRead + AsyncWrite + Unpin, C, SD> Stream<'a, IO, C> where C: DerefMut + Deref>, SD: SideData, { pub fn new(io: &'a mut IO, session: &'a mut C) -> Self { Stream { io, session, // The state so far is only used to detect EOF, so either Stream // or EarlyData state should both be all right. eof: false, } } pub fn set_eof(mut self, eof: bool) -> Self { self.eof = eof; self } pub fn as_mut_pin(&mut self) -> Pin<&mut Self> { Pin::new(self) } pub fn read_io(&mut self, cx: &mut Context) -> Poll> { let mut reader = SyncReadAdapter { io: self.io, cx }; let n = match self.session.read_tls(&mut reader) { Ok(n) => n, Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => return Poll::Pending, Err(err) => return Poll::Ready(Err(err)), }; let stats = self.session.process_new_packets().map_err(|err| { // In case we have an alert to send describing this error, // try a last-gasp write -- but don't predate the primary // error. let _ = self.write_io(cx); io::Error::new(io::ErrorKind::InvalidData, err) })?; if stats.peer_has_closed() && self.session.is_handshaking() { return Poll::Ready(Err(io::Error::new( io::ErrorKind::UnexpectedEof, "tls handshake alert", ))); } Poll::Ready(Ok(n)) } pub fn write_io(&mut self, cx: &mut Context) -> Poll> { let mut writer = SyncWriteAdapter { io: self.io, cx }; match self.session.write_tls(&mut writer) { Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => Poll::Pending, result => Poll::Ready(result), } } pub fn handshake(&mut self, cx: &mut Context) -> Poll> { let mut wrlen = 0; let mut rdlen = 0; loop { let mut write_would_block = false; let mut read_would_block = false; let mut need_flush = false; while self.session.wants_write() { match self.write_io(cx) { Poll::Ready(Ok(n)) => { wrlen += n; need_flush = true; } Poll::Pending => { write_would_block = true; break; } Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), } } if need_flush { match Pin::new(&mut self.io).poll_flush(cx) { Poll::Ready(Ok(())) => (), Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), Poll::Pending => write_would_block = true, } } while !self.eof && self.session.wants_read() { match self.read_io(cx) { Poll::Ready(Ok(0)) => self.eof = true, Poll::Ready(Ok(n)) => rdlen += n, Poll::Pending => { read_would_block = true; break; } Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), } } return match (self.eof, self.session.is_handshaking()) { (true, true) => { let err = io::Error::new(io::ErrorKind::UnexpectedEof, "tls handshake eof"); Poll::Ready(Err(err)) } (_, false) => Poll::Ready(Ok((rdlen, wrlen))), (_, true) if write_would_block || read_would_block => { if rdlen != 0 || wrlen != 0 { Poll::Ready(Ok((rdlen, wrlen))) } else { Poll::Pending } } (..) => continue, }; } } } impl<'a, IO: AsyncRead + AsyncWrite + Unpin, C, SD> AsyncRead for Stream<'a, IO, C> where C: DerefMut + Deref>, SD: SideData, { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let mut io_pending = false; // read a packet while !self.eof && self.session.wants_read() { match self.read_io(cx) { Poll::Ready(Ok(0)) => { break; } Poll::Ready(Ok(_)) => (), Poll::Pending => { io_pending = true; break; } Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), } } match self.session.reader().read(buf) { // If Rustls returns `Ok(0)` (while `buf` is non-empty), the peer closed the // connection with a `CloseNotify` message and no more data will be forthcoming. // // Rustls yielded more data: advance the buffer, then see if more data is coming. // // We don't need to modify `self.eof` here, because it is only a temporary mark. // rustls will only return 0 if is has received `CloseNotify`, // in which case no additional processing is required. Ok(n) => Poll::Ready(Ok(n)), // Rustls doesn't have more data to yield, but it believes the connection is open. Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => { if !io_pending { // If `wants_read()` is satisfied, rustls will not return `WouldBlock`. // but if it does, we can try again. // // If the rustls state is abnormal, it may cause a cyclic wakeup. // but tokio's cooperative budget will prevent infinite wakeup. cx.waker().wake_by_ref(); } Poll::Pending } Err(err) => Poll::Ready(Err(err)), } } } impl<'a, IO: AsyncRead + AsyncWrite + Unpin, C, SD> AsyncWrite for Stream<'a, IO, C> where C: DerefMut + Deref>, SD: SideData, { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { let mut pos = 0; while pos != buf.len() { let mut would_block = false; match self.session.writer().write(&buf[pos..]) { Ok(n) => pos += n, Err(err) => return Poll::Ready(Err(err)), }; while self.session.wants_write() { match self.write_io(cx) { Poll::Ready(Ok(0)) | Poll::Pending => { would_block = true; break; } Poll::Ready(Ok(_)) => (), Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), } } return match (pos, would_block) { (0, true) => Poll::Pending, (n, true) => Poll::Ready(Ok(n)), (_, false) => continue, }; } Poll::Ready(Ok(pos)) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.session.writer().flush()?; while self.session.wants_write() { ready!(self.write_io(cx))?; } Pin::new(&mut self.io).poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { while self.session.wants_write() { ready!(self.write_io(cx))?; } Poll::Ready(match ready!(Pin::new(&mut self.io).poll_close(cx)) { Ok(()) => Ok(()), // When trying to shutdown, not being connected seems fine Err(err) if err.kind() == io::ErrorKind::NotConnected => Ok(()), Err(err) => Err(err), }) } fn poll_write_vectored( mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { if bufs.iter().all(|buf| buf.is_empty()) { return Poll::Ready(Ok(0)); } loop { let mut would_block = false; let written = self.session.writer().write_vectored(bufs)?; while self.session.wants_write() { match self.write_io(cx) { Poll::Ready(Ok(0)) | Poll::Pending => { would_block = true; break; } Poll::Ready(Ok(_)) => (), Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), } } return match (written, would_block) { (0, true) => Poll::Pending, (0, false) => continue, (n, _) => Poll::Ready(Ok(n)), }; } } } /// An adapter that implements a [`Read`] interface for [`AsyncRead`] types and an /// associated [`Context`]. /// /// Turns `Poll::Pending` into `WouldBlock`. pub struct SyncReadAdapter<'a, 'b, T> { pub io: &'a mut T, pub cx: &'a mut Context<'b>, } impl<'a, 'b, T: AsyncRead + Unpin> Read for SyncReadAdapter<'a, 'b, T> { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { match Pin::new(&mut self.io).poll_read(self.cx, buf) { Poll::Ready(Ok(n)) => Ok(n), Poll::Ready(Err(err)) => Err(err), Poll::Pending => Err(io::ErrorKind::WouldBlock.into()), } } } pub(crate) struct SyncWriteAdapter<'a, 'b, T> { pub(crate) io: &'a mut T, pub(crate) cx: &'a mut Context<'b>, } impl<'a, 'b, T: Unpin> SyncWriteAdapter<'a, 'b, T> { #[inline] fn poll_with( &mut self, f: impl FnOnce(Pin<&mut T>, &mut Context<'_>) -> Poll>, ) -> io::Result { match f(Pin::new(&mut self.io), self.cx) { Poll::Ready(result) => result, Poll::Pending => Err(io::ErrorKind::WouldBlock.into()), } } } impl<'a, 'b, T: AsyncWrite + Unpin> Write for SyncWriteAdapter<'a, 'b, T> { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.poll_with(|io, cx| io.poll_write(cx, buf)) } #[inline] fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { self.poll_with(|io, cx| io.poll_write_vectored(cx, bufs)) } fn flush(&mut self) -> io::Result<()> { self.poll_with(|io, cx| io.poll_flush(cx)) } } #[cfg(test)] mod test_stream; futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/common/test_stream.rs000066400000000000000000000222171457724761000264510ustar00rootroot00000000000000use super::Stream; use futures_io::{AsyncRead, AsyncWrite}; use futures_util::future::poll_fn; use futures_util::io::{AsyncReadExt, AsyncWriteExt}; use futures_util::task::noop_waker_ref; use rustls::{ClientConnection, Connection, RootCertStore, ServerConnection}; use rustls_pemfile::{certs, private_key}; use std::io::{self, BufReader, Cursor, Read, Write}; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; struct Good<'a>(&'a mut Connection); impl<'a> AsyncRead for Good<'a> { fn poll_read( mut self: Pin<&mut Self>, _cx: &mut Context<'_>, mut buf: &mut [u8], ) -> Poll> { Poll::Ready(self.0.write_tls(buf.by_ref())) } } impl<'a> AsyncWrite for Good<'a> { fn poll_write( mut self: Pin<&mut Self>, _cx: &mut Context<'_>, mut buf: &[u8], ) -> Poll> { let len = self.0.read_tls(buf.by_ref())?; self.0 .process_new_packets() .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; Poll::Ready(Ok(len)) } fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { self.0 .process_new_packets() .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; Poll::Ready(Ok(())) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.0.send_close_notify(); dbg!("sent close notify"); self.poll_flush(cx) } } struct Pending; impl AsyncRead for Pending { fn poll_read( self: Pin<&mut Self>, _cx: &mut Context<'_>, _: &mut [u8], ) -> Poll> { Poll::Pending } } impl AsyncWrite for Pending { fn poll_write( self: Pin<&mut Self>, _cx: &mut Context<'_>, _buf: &[u8], ) -> Poll> { Poll::Pending } fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } struct Expected(Cursor>); impl AsyncRead for Expected { fn poll_read( self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let this = self.get_mut(); let n = std::io::Read::read(&mut this.0, buf)?; Poll::Ready(Ok(n)) } } impl AsyncWrite for Expected { fn poll_write( self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Poll::Ready(Ok(buf.len())) } fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } #[test] fn stream_good() -> io::Result<()> { const FILE: &[u8] = include_bytes!("../../README.md"); let fut = async { let (server, mut client) = make_pair(); let mut server = Connection::from(server); poll_fn(|cx| do_handshake(&mut client, &mut server, cx)).await?; io::copy(&mut Cursor::new(FILE), &mut server.writer())?; server.send_close_notify(); let mut server = Connection::from(server); { let mut good = Good(&mut server); let mut stream = Stream::new(&mut good, &mut client); let mut buf = Vec::new(); dbg!(stream.read_to_end(&mut buf).await)?; assert_eq!(buf, FILE); dbg!(stream.write_all(b"Hello World!").await)?; stream.session.send_close_notify(); dbg!(stream.close().await)?; } let mut buf = String::new(); dbg!(server.process_new_packets()).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; dbg!(server.reader().read_to_string(&mut buf))?; assert_eq!(buf, "Hello World!"); Ok(()) as io::Result<()> }; smol::block_on(fut) } #[test] fn stream_bad() -> io::Result<()> { let fut = async { let (server, mut client) = make_pair(); let mut server = Connection::from(server); poll_fn(|cx| do_handshake(&mut client, &mut server, cx)).await?; client.set_buffer_limit(Some(1024)); let mut bad = Pending; let mut stream = Stream::new(&mut bad, &mut client); assert_eq!( poll_fn(|cx| stream.as_mut_pin().poll_write(cx, &[0x42; 8])).await?, 8 ); assert_eq!( poll_fn(|cx| stream.as_mut_pin().poll_write(cx, &[0x42; 8])).await?, 8 ); let r = poll_fn(|cx| stream.as_mut_pin().poll_write(cx, &[0x00; 1024])).await?; // fill buffer assert!(r < 1024); let mut cx = Context::from_waker(noop_waker_ref()); let ret = stream.as_mut_pin().poll_write(&mut cx, &[0x01]); assert!(ret.is_pending()); Ok(()) as io::Result<()> }; smol::block_on(fut) } #[test] fn stream_handshake() -> io::Result<()> { let fut = async { let (server, mut client) = make_pair(); let mut server = Connection::from(server); { let mut good = Good(&mut server); let mut stream = Stream::new(&mut good, &mut client); let (r, w) = poll_fn(|cx| stream.handshake(cx)).await?; assert!(r > 0); assert!(w > 0); poll_fn(|cx| stream.handshake(cx)).await?; // finish server handshake } assert!(!server.is_handshaking()); assert!(!client.is_handshaking()); Ok(()) as io::Result<()> }; smol::block_on(fut) } #[test] fn stream_handshake_eof() -> io::Result<()> { let fut = async { let (_, mut client) = make_pair(); let mut bad = Expected(Cursor::new(Vec::new())); let mut stream = Stream::new(&mut bad, &mut client); let mut cx = Context::from_waker(noop_waker_ref()); let r = stream.handshake(&mut cx); assert_eq!( r.map_err(|err| err.kind()), Poll::Ready(Err(io::ErrorKind::UnexpectedEof)) ); Ok(()) as io::Result<()> }; smol::block_on(fut) } // see https://github.com/tokio-rs/tls/issues/77 #[test] fn stream_handshake_regression_issues_77() -> io::Result<()> { let fut = async { let (_, mut client) = make_pair(); let mut bad = Expected(Cursor::new(b"\x15\x03\x01\x00\x02\x02\x00".to_vec())); let mut stream = Stream::new(&mut bad, &mut client); let mut cx = Context::from_waker(noop_waker_ref()); let r = stream.handshake(&mut cx); assert_eq!( r.map_err(|err| err.kind()), Poll::Ready(Err(io::ErrorKind::UnexpectedEof)) ); Ok(()) as io::Result<()> }; smol::block_on(fut) } #[test] fn stream_eof() -> io::Result<()> { let fut = async { let (server, mut client) = make_pair(); let mut server = Connection::from(server); poll_fn(|cx| do_handshake(&mut client, &mut server, cx)).await?; let mut bad = Expected(Cursor::new(Vec::new())); let mut stream = Stream::new(&mut bad, &mut client); let mut buf = Vec::new(); let result = stream.read_to_end(&mut buf).await; assert_eq!( result.err().map(|e| e.kind()), Some(io::ErrorKind::UnexpectedEof) ); Ok(()) as io::Result<()> }; smol::block_on(fut) } fn make_pair() -> (ServerConnection, ClientConnection) { use std::convert::TryFrom; const CERT: &str = include_str!("../../tests/end.cert"); const CHAIN: &str = include_str!("../../tests/end.chain"); const RSA: &str = include_str!("../../tests/end.rsa"); let cert = certs(&mut BufReader::new(Cursor::new(CERT))) .collect::, _>>() .unwrap(); let key = private_key(&mut BufReader::new(Cursor::new(RSA))).unwrap(); let sconfig = rustls::ServerConfig::builder() .with_no_client_auth() .with_single_cert(cert, key.unwrap()) .unwrap(); let server = ServerConnection::new(Arc::new(sconfig)).unwrap(); let domain = pki_types::ServerName::try_from("testserver.com") .unwrap() .to_owned(); let mut client_root_cert_store = RootCertStore::empty(); let mut chain = BufReader::new(Cursor::new(CHAIN)); let certs = certs(&mut chain).collect::, _>>().unwrap(); client_root_cert_store.add_parsable_certificates(certs); let cconfig = rustls::ClientConfig::builder() .with_root_certificates(client_root_cert_store) .with_no_client_auth(); let client = ClientConnection::new(Arc::new(cconfig), domain).unwrap(); (server, client) } fn do_handshake( client: &mut ClientConnection, server: &mut Connection, cx: &mut Context<'_>, ) -> Poll> { let mut good = Good(server); let mut stream = Stream::new(&mut good, client); while stream.session.is_handshaking() { ready!(stream.handshake(cx))?; } while stream.session.wants_write() { ready!(stream.write_io(cx))?; } Poll::Ready(Ok(())) } futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/lib.rs000066400000000000000000000324041457724761000233740ustar00rootroot00000000000000//! Asynchronous TLS/SSL streams for futures using [Rustls](https://github.com/rustls/rustls). macro_rules! ready { ( $e:expr ) => { match $e { std::task::Poll::Ready(t) => t, std::task::Poll::Pending => return std::task::Poll::Pending, } }; } pub mod client; mod common; pub mod server; use common::{MidHandshake, Stream, TlsState}; use futures_io::{AsyncRead, AsyncWrite}; use rustls::server::AcceptedAlert; use rustls::{ClientConfig, ClientConnection, CommonState, ServerConfig, ServerConnection}; use std::future::Future; use std::io; #[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd}; #[cfg(windows)] use std::os::windows::io::{AsRawSocket, RawSocket}; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; pub use pki_types; pub use rustls; /// A wrapper around a `rustls::ClientConfig`, providing an async `connect` method. #[derive(Clone)] pub struct TlsConnector { inner: Arc, #[cfg(feature = "early-data")] early_data: bool, } /// A wrapper around a `rustls::ServerConfig`, providing an async `accept` method. #[derive(Clone)] pub struct TlsAcceptor { inner: Arc, } impl From> for TlsConnector { fn from(inner: Arc) -> TlsConnector { TlsConnector { inner, #[cfg(feature = "early-data")] early_data: false, } } } impl From> for TlsAcceptor { fn from(inner: Arc) -> TlsAcceptor { TlsAcceptor { inner } } } impl TlsConnector { /// Enable 0-RTT. /// /// If you want to use 0-RTT, /// You must also set `ClientConfig.enable_early_data` to `true`. #[cfg(feature = "early-data")] pub fn early_data(mut self, flag: bool) -> TlsConnector { self.early_data = flag; self } #[inline] pub fn connect(&self, domain: pki_types::ServerName<'static>, stream: IO) -> Connect where IO: AsyncRead + AsyncWrite + Unpin, { self.connect_with(domain, stream, |_| ()) } pub fn connect_with( &self, domain: pki_types::ServerName<'static>, stream: IO, f: F, ) -> Connect where IO: AsyncRead + AsyncWrite + Unpin, F: FnOnce(&mut ClientConnection), { let mut session = match ClientConnection::new(self.inner.clone(), domain) { Ok(session) => session, Err(error) => { return Connect(MidHandshake::Error { io: stream, // TODO(eliza): should this really return an `io::Error`? // Probably not... error: io::Error::new(io::ErrorKind::Other, error), }); } }; f(&mut session); Connect(MidHandshake::Handshaking(client::TlsStream { io: stream, #[cfg(not(feature = "early-data"))] state: TlsState::Stream, #[cfg(feature = "early-data")] state: if self.early_data && session.early_data().is_some() { TlsState::EarlyData(0, Vec::new()) } else { TlsState::Stream }, #[cfg(feature = "early-data")] early_waker: None, session, })) } } impl TlsAcceptor { #[inline] pub fn accept(&self, stream: IO) -> Accept where IO: AsyncRead + AsyncWrite + Unpin, { self.accept_with(stream, |_| ()) } pub fn accept_with(&self, stream: IO, f: F) -> Accept where IO: AsyncRead + AsyncWrite + Unpin, F: FnOnce(&mut ServerConnection), { let mut session = match ServerConnection::new(self.inner.clone()) { Ok(session) => session, Err(error) => { return Accept(MidHandshake::Error { io: stream, // TODO(eliza): should this really return an `io::Error`? // Probably not... error: io::Error::new(io::ErrorKind::Other, error), }); } }; f(&mut session); Accept(MidHandshake::Handshaking(server::TlsStream { session, io: stream, state: TlsState::Stream, })) } } pub struct LazyConfigAcceptor { acceptor: rustls::server::Acceptor, io: Option, alert: Option<(rustls::Error, AcceptedAlert)>, } impl LazyConfigAcceptor where IO: AsyncRead + AsyncWrite + Unpin, { #[inline] pub fn new(acceptor: rustls::server::Acceptor, io: IO) -> Self { Self { acceptor, io: Some(io), alert: None, } } } impl Future for LazyConfigAcceptor where IO: AsyncRead + AsyncWrite + Unpin, { type Output = Result, io::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); loop { let io = match this.io.as_mut() { Some(io) => io, None => { return Poll::Ready(Err(io::Error::new( io::ErrorKind::Other, "acceptor cannot be polled after acceptance", ))) } }; if let Some((err, mut alert)) = this.alert.take() { match alert.write(&mut common::SyncWriteAdapter { io, cx }) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { this.alert = Some((err, alert)); return Poll::Pending; } Ok(0) | Err(_) => { return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, err))) } Ok(_) => { this.alert = Some((err, alert)); continue; } }; } let mut reader = common::SyncReadAdapter { io, cx }; match this.acceptor.read_tls(&mut reader) { Ok(0) => return Err(io::ErrorKind::UnexpectedEof.into()).into(), Ok(_) => {} Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Poll::Pending, Err(e) => return Err(e).into(), } match this.acceptor.accept() { Ok(Some(accepted)) => { let io = this.io.take().unwrap(); return Poll::Ready(Ok(StartHandshake { accepted, io })); } Ok(None) => {} Err((err, alert)) => { this.alert = Some((err, alert)); } } } } } pub struct StartHandshake { accepted: rustls::server::Accepted, io: IO, } impl StartHandshake where IO: AsyncRead + AsyncWrite + Unpin, { pub fn client_hello(&self) -> rustls::server::ClientHello<'_> { self.accepted.client_hello() } pub fn into_stream(self, config: Arc) -> Accept { self.into_stream_with(config, |_| ()) } pub fn into_stream_with(self, config: Arc, f: F) -> Accept where F: FnOnce(&mut ServerConnection), { let mut conn = match self.accepted.into_connection(config) { Ok(conn) => conn, Err((error, alert)) => { return Accept(MidHandshake::SendAlert { io: self.io, // TODO(eliza): should this really return an `io::Error`? // Probably not... error: io::Error::new(io::ErrorKind::Other, error), alert, }); } }; f(&mut conn); Accept(MidHandshake::Handshaking(server::TlsStream { session: conn, io: self.io, state: TlsState::Stream, })) } } /// Future returned from `TlsConnector::connect` which will resolve /// once the connection handshake has finished. pub struct Connect(MidHandshake>); /// Future returned from `TlsAcceptor::accept` which will resolve /// once the accept handshake has finished. pub struct Accept(MidHandshake>); /// Like [Connect], but returns `IO` on failure. pub struct FallibleConnect(MidHandshake>); /// Like [Accept], but returns `IO` on failure. pub struct FallibleAccept(MidHandshake>); impl Connect { #[inline] pub fn into_fallible(self) -> FallibleConnect { FallibleConnect(self.0) } } impl Accept { #[inline] pub fn into_fallible(self) -> FallibleAccept { FallibleAccept(self.0) } } impl Future for Connect { type Output = io::Result>; #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.0).poll(cx).map_err(|(err, _)| err) } } impl Future for Accept { type Output = io::Result>; #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.0).poll(cx).map_err(|(err, _)| err) } } impl Future for FallibleConnect { type Output = Result, (io::Error, IO)>; #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.0).poll(cx) } } impl Future for FallibleAccept { type Output = Result, (io::Error, IO)>; #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.0).poll(cx) } } /// Unified TLS stream type /// /// This abstracts over the inner `client::TlsStream` and `server::TlsStream`, so you can use /// a single type to keep both client- and server-initiated TLS-encrypted connections. #[derive(Debug)] pub enum TlsStream { Client(client::TlsStream), Server(server::TlsStream), } impl TlsStream { pub fn get_ref(&self) -> (&T, &CommonState) { use TlsStream::*; match self { Client(io) => { let (io, session) = io.get_ref(); (io, session) } Server(io) => { let (io, session) = io.get_ref(); (io, session) } } } pub fn get_mut(&mut self) -> (&mut T, &mut CommonState) { use TlsStream::*; match self { Client(io) => { let (io, session) = io.get_mut(); (io, &mut *session) } Server(io) => { let (io, session) = io.get_mut(); (io, &mut *session) } } } } impl From> for TlsStream { fn from(s: client::TlsStream) -> Self { Self::Client(s) } } impl From> for TlsStream { fn from(s: server::TlsStream) -> Self { Self::Server(s) } } #[cfg(unix)] impl AsRawFd for TlsStream where S: AsRawFd, { fn as_raw_fd(&self) -> RawFd { self.get_ref().0.as_raw_fd() } } #[cfg(windows)] impl AsRawSocket for TlsStream where S: AsRawSocket, { fn as_raw_socket(&self) -> RawSocket { self.get_ref().0.as_raw_socket() } } impl AsyncRead for TlsStream where T: AsyncRead + AsyncWrite + Unpin, { #[inline] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { match self.get_mut() { TlsStream::Client(x) => Pin::new(x).poll_read(cx, buf), TlsStream::Server(x) => Pin::new(x).poll_read(cx, buf), } } } impl AsyncWrite for TlsStream where T: AsyncRead + AsyncWrite + Unpin, { #[inline] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { match self.get_mut() { TlsStream::Client(x) => Pin::new(x).poll_write(cx, buf), TlsStream::Server(x) => Pin::new(x).poll_write(cx, buf), } } #[inline] fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.get_mut() { TlsStream::Client(x) => Pin::new(x).poll_flush(cx), TlsStream::Server(x) => Pin::new(x).poll_flush(cx), } } #[inline] fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.get_mut() { TlsStream::Client(x) => Pin::new(x).poll_close(cx), TlsStream::Server(x) => Pin::new(x).poll_close(cx), } } #[inline] fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { match self.get_mut() { TlsStream::Client(x) => Pin::new(x).poll_write_vectored(cx, bufs), TlsStream::Server(x) => Pin::new(x).poll_write_vectored(cx, bufs), } } } futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/src/server.rs000066400000000000000000000103171457724761000241330ustar00rootroot00000000000000use super::*; use crate::common::IoSession; /// A wrapper around an underlying raw stream which implements the TLS or SSL /// protocol. #[derive(Debug)] pub struct TlsStream { pub(crate) io: IO, pub(crate) session: ServerConnection, pub(crate) state: TlsState, } impl TlsStream { #[inline] pub fn get_ref(&self) -> (&IO, &ServerConnection) { (&self.io, &self.session) } #[inline] pub fn get_mut(&mut self) -> (&mut IO, &mut ServerConnection) { (&mut self.io, &mut self.session) } #[inline] pub fn into_inner(self) -> (IO, ServerConnection) { (self.io, self.session) } } impl IoSession for TlsStream { type Io = IO; type Session = ServerConnection; #[inline] fn skip_handshake(&self) -> bool { false } #[inline] fn get_mut(&mut self) -> (&mut TlsState, &mut Self::Io, &mut Self::Session) { (&mut self.state, &mut self.io, &mut self.session) } #[inline] fn into_io(self) -> Self::Io { self.io } } impl AsyncRead for TlsStream where IO: AsyncRead + AsyncWrite + Unpin, { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); match &this.state { TlsState::Stream | TlsState::WriteShutdown => { match stream.as_mut_pin().poll_read(cx, buf) { Poll::Ready(Ok(n)) => { if n == 0 || stream.eof { this.state.shutdown_read(); } Poll::Ready(Ok(n)) } Poll::Ready(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => { this.state.shutdown_read(); Poll::Ready(Err(err)) } output => output, } } TlsState::ReadShutdown | TlsState::FullyShutdown => Poll::Ready(Ok(0)), #[cfg(feature = "early-data")] s => unreachable!("server TLS can not hit this state: {:?}", s), } } } impl AsyncWrite for TlsStream where IO: AsyncRead + AsyncWrite + Unpin, { /// Note: that it does not guarantee the final data to be sent. /// To be cautious, you must manually call `flush`. fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); stream.as_mut_pin().poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); stream.as_mut_pin().poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.state.writeable() { self.session.send_close_notify(); self.state.shutdown_write(); } let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); stream.as_mut_pin().poll_close(cx) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { let this = self.get_mut(); let mut stream = Stream::new(&mut this.io, &mut this.session).set_eof(!this.state.readable()); stream.as_mut_pin().poll_write_vectored(cx, bufs) } } #[cfg(unix)] impl AsRawFd for TlsStream where IO: AsRawFd, { fn as_raw_fd(&self) -> RawFd { self.get_ref().0.as_raw_fd() } } #[cfg(windows)] impl AsRawSocket for TlsStream where IO: AsRawSocket, { fn as_raw_socket(&self) -> RawSocket { self.get_ref().0.as_raw_socket() } } futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/tests/000077500000000000000000000000001457724761000226305ustar00rootroot00000000000000futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/tests/badssl.rs000066400000000000000000000050371457724761000244530ustar00rootroot00000000000000use std::convert::TryFrom; use std::io; use std::net::ToSocketAddrs; use std::sync::Arc; use futures_util::io::{AsyncReadExt, AsyncWriteExt}; use smol::net::TcpStream; use futures_rustls::{ client::TlsStream, rustls::{self, ClientConfig}, pki_types::{ServerName}, TlsConnector, }; async fn get( config: Arc, domain: &str, port: u16, ) -> io::Result<(TlsStream, String)> { let connector = TlsConnector::from(config); let input = format!("GET / HTTP/1.0\r\nHost: {}\r\n\r\n", domain); let addr = (domain, port).to_socket_addrs()?.next().unwrap(); let domain = ServerName::try_from(domain).unwrap().to_owned(); let mut buf = Vec::new(); let stream = TcpStream::connect(&addr).await?; let mut stream = connector.connect(domain, stream).await?; stream.write_all(input.as_bytes()).await?; stream.flush().await?; stream.read_to_end(&mut buf).await?; Ok((stream, String::from_utf8(buf).unwrap())) } #[test] fn test_tls12() -> io::Result<()> { let fut = async { let root_store = rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(), }; let config = rustls::ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS12]) .with_root_certificates(root_store) .with_no_client_auth(); let config = Arc::new(config); let domain = "tls-v1-2.badssl.com"; let (_, output) = get(config.clone(), domain, 1012).await?; assert!( output.contains("tls-v1-2.badssl.com"), "failed badssl test, output: {}", output ); Ok(()) }; smol::block_on(fut) } #[ignore] #[should_panic] #[test] fn test_tls13() { unimplemented!("todo https://github.com/chromium/badssl.com/pull/373"); } #[test] fn test_modern() -> io::Result<()> { let fut = async { let root_store = rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(), }; let config = rustls::ClientConfig::builder() .with_root_certificates(root_store) .with_no_client_auth(); let config = Arc::new(config); let domain = "mozilla-modern.badssl.com"; let (_, output) = get(config.clone(), domain, 443).await?; assert!( output.contains("mozilla-modern.badssl.com"), "failed badssl test, output: {}", output ); Ok(()) }; smol::block_on(fut) } futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/tests/end.cert000066400000000000000000000027101457724761000242550ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEGDCCAoCgAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMTAyMzE2NDAwNFoX DTI5MDQxNDE2NDAwNFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcKY2pRsvWI15QbTiNltzOLx47yloS ZeTVt+TnQXJTxpCwhcpfBs3pho2IEbNMSRsGMjcAmWR9f3mkmx1gb59oM1VufgU3 pZIC3lvFtd7TKcTJ/Que0BR9rkiy0UJRIPSynQHAGBDPNOZlVOgBqNypk5WL9aKc R72wATriZu+L85Cq/DkPXQUtJa90I+4kLXuigzxqr3Qlj4q+pJUCuwQ03WQAfNZq c3Fi7p+AfNb9AXXXYmb+L8hejsOBg+N67PWdwUYaHDcnLhwKIG19ABxiEsHsAfS7 WJLOdgg3UJ02ml6rbTiZJHRD4/1dLy9csIbi47MHkuKRkKDfaGkSuzL1AgMBAAGj gdYwgdMwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFJj/V++C I9CPyrz2aPeET2akBsMMMEIGA1UdIwQ7MDmAFBphgmEjkvorojzRIxQ9C1UoQzyc oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswUwYDVR0RBEwwSoIO dGVzdHNlcnZlci5jb22HBMYzZAGCFXNlY29uZC50ZXN0c2VydmVyLmNvbYcQIAEN uAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBgQC3rmDg BeS1hv91KewOoHmmZlh3jm4wGxqbGymXEpL9N3vtMESnsgvJZWN9vBDdp5MbznLL BdjK+cbwj2E6qjP2xA08xwy7jS2A7dQMN0EMHKgJC/9ExgGkXD973ii5SVebpRgS A9Vq9GXtzr/N0h7vmdlg0ewVQ/3zkLbVDc2dFKNRa+KRX5uZ+n0QUCvPoL446TK2 blaw0tPB4dzNHUUIgV/I4ChCSwVyvtG+RZ9Qjxcb4vWt8sowikWSDlm7jWt5Dg9n kTddYgU/1sUzGoih2jeZslR0m2vsTrgEk1sOLdmUKD5btRbvyoCB7nrGoO2f6G5d Ygl13Uh6ZaYgZik4aMwOgRuq6ageUqXxMw/mUwXvu+JTAlGSaCankVehVNzZnrp2 1Vj28u0h2ISaMKIQNd9i0R+Zf3uW7u8v9gyMvbvGOvIGgmqHPzulxTDHQHEKziGv QjvABXuAnB7FChhaIqexfH82aCvaSjVm1laz7r/2kbLpTEV2WYUrG65IV3s= -----END CERTIFICATE----- futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/tests/end.chain000066400000000000000000000067121457724761000244100ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEwDCCAqigAwIBAgIBezANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9wb255 dG93biBSU0EgQ0EwHhcNMjMxMDIzMTY0MDA0WhcNMzMxMDIwMTY0MDA0WjAsMSow KAYDVQQDDCFwb255dG93biBSU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwggGiMA0G CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC5yg9Avocjcf2c9mAMTEtwp5ays+Jq zEHnEXTgJGNxrim3lsJY5bz+1T6KtjNnySRwK4aFkGJ2IpH2R2VMmRBTRFaJWEjh oa1Xc8UcT8BZA21N5iQUROIG61tZi30wNp5hqI9LE4oII1rAOdxXa8jmcTR2o2Ch ZP0Q7MQqXn6ecKB4W9R5V3s5UCle2f5fvSJ57oqoN00sIG5Su4hSC9jV/yGrAIMK XBiOy7lPr8LJzrKV5qmvns1KDCG3sxjrtnWFCAflZTaHS3ygyH9KuPIf1MF9BIaT gm+O3VBU/anvQ5DBusxDgytelfTFElPkvtW8KFRnLCCgZ7A6Y9fevBXdbOgr7AK3 o6tIEcixzcRMnUTmeXIN5NJJw/mmbGLr5wPY0FthwqD3Bn8A92OFUo1iEMjrpYS/ 4maRdlW7kIFw3yct12REhEaaY8Adtag3DBfY7b0zb4txWOFS+OjZ8/Q4oISn9zT2 VZ8d+WxOcp04gpaXxZSBgk1D/jL2tmgH480CAwEAAaN/MH0wHQYDVR0OBBYEFBph gmEjkvorojzRIxQ9C1UoQzycMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEF BQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAfBgNVHSMEGDAWgBQRkqgX iCF/UsC7CJ4iEaxsvUzdujANBgkqhkiG9w0BAQsFAAOCAgEAhuWKXWI/SfArbnzD n1IWrvt6RLRR7tU5tqLDtEpzh5t+LxOibTicNCzuKGk7fj+dod2552d5NYs5DKID B5pchKaJYeH5uDGlCcCCgsDG3xTFTeCI2HEd5Ros+FPRqBrAUhiObs9sPbQ6gcAU qaeeRWrVRxfO1w6N0y8om8tpQsCX5KR5qhOkIJsOSg6b6Iigl1abVb2v6iqGM8rC oCghrecTNWumYfLtOXEwCu89hYYUoGEt6nvGHIwhU/xUAo8/IKo0rYGIbJRCuX02 FhrHEWJMqAWZvs0Cx4F4g9xGfpyzxBuL9H/FTGq+XKXjQBGDRECuIq7hl9ccK+1K TxQadSm3C0Ap7dOLAdYN9P8K7Ql3AX0nNQG6AX8CzRgm4Flontq4h4XURjFLAhbr bKZ/tPaUcRYEAONegiTwbvm1akECKEz3n+toMDNTK5Cai51r19kKT01eQ97oo/wq vO/CyVjHr6dmof2/GLJ6v5TdcPz68TdX1G65bmX26SphhAwxC0PtWEfxzdyrdmVg a+60GrKo6WYzkiAX2PP+QsSASGFKgm/wulvKZ+F03Tl0Cv7VgLDXD6c3aOngeev6 +1nlrYGt6m1RWH2xBZjhnUOsk3q1VbgSFVHAzTtEg/vIf73FlM5pO4zfXzyObML3 5SNs5aaltQMH+hFDr5OgXypQ1uY= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFFTCCAv2gAwIBAgIUWGj6bltbjWrqNVeP8QkDGMmNV5AwDQYJKoZIhvcNAQEL BQAwGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMB4XDTIzMTAyMzE2NDAwMloX DTMzMTAyMDE2NDAwMlowGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvOn06bbCkCE7wuwCXojGngKuPj5k oNB1k99U2X4CNiyPez3EhHFTRJ2sZ8XMf+mgVVS8QBmJb17mHzPDbKlVVqm8W5jV n0q0AMFBTxbPzupI9puISAlrnnP1EgX4DM8WfPlzIYmVwR5G80qSKy1YLjiQlI8J N3E5HBQiTrVyjBpSAyAujhhDJ0pjkBRA1CuwU3wL4OM6VlRnaEXV4RiUxsQVnyy3 15x2VIPYjWm4pj6HLbxvReTuJO+kZy1OJnkAY5f5OMXYbabcp5JBHDafrRh/C1ls iCRzhfHuaxeMMSHSOSeiN7yrE23tVB/F+dQ3k3MQVziuMGngK0GJ+aYbQ9bo3JPf kuUk0WMMGNfjnEPJ9WHOiEAaG90IF94s1oR3JKa7RepmCazf9hA7/2RMxlnxzhUl JiZyNVG3HpnNzd37VGOpLt2UXhdtWNhcwUwHKXuAE2QYTVkQsCfEW+es/yN05Vyn DHocS8vGReS9Jc+ABqpqF8nXd/BKUNrLI7hSZAP0MNeoHTWY0XBXxICeLGeU0S4B fVe0WFmnuS0Mw/bowuG186lXbzZCqf8v0/95D+NoQdbv7M5bKN8Y/EC/+FbQHeuk rL0ISplPxmLq9H1Ldt0P91Yc3FbMSvg7m5eTlYPFWuiFW7XKjIAtIsihEtGeDneT C0+yenpLAEGOpOsCAwEAAaNTMFEwHQYDVR0OBBYEFBGSqBeIIX9SwLsIniIRrGy9 TN26MB8GA1UdIwQYMBaAFBGSqBeIIX9SwLsIniIRrGy9TN26MA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQELBQADggIBACzcRcYKjUFndZjGhGQwGBtbZlSq4SX1 YGhqO8sov3uQiWhtSFcL3qTJy69pxB1nfTDiloMFGaXORYJgvyRnD3fZugFiTp+C LRcQnStiZZWxsCARLQ78FOTy8hMxA5U47BE9h4Ut8eIbDsvBaGYWYGntUqSf0qjK tFjmmmeQI8EKv1YI3gDnkgoGNwycmXXYhPct4sjRnl158B60bneJwSCrla/BmLfM PYHCzF7cE29k2n4oi4QUCaFh2Ozmrw14UuEfv6MSGzDXoKxHSs0YMLE8/AF7YeHv 6Wrd2BUOYMCmP0JK4s/JOoeNCWWZ1aA53C1Ch961/XToXzJILmCK9SaAYV/cu+Md U26s8gRRVfW29OML9F5Caue4jruFc7GEv1zjM2tuSFk2Io2itZXfTed3JDuf49qu AJoksI+J5iLzo55oL5wnYmtKlcF16EWaPXKDX8SlFWqAU1np9wpU9OZeB6g8UbAV 3TrMQ/sN6CKpPnasJPBIowapKZ3sGf/PQnZeEE5SJiBinm703xrSu13cm19tdGTE SMGg6QerUfO9MWypM1ZAd8/TWH1a3rv6ezoyp+3HxoA6J5E0kIp9TswK+pVwxqHb RkVObe8gIk+Q4q45bYuhdn/jZrQ4VNSi5/LJDvqIF/HDF3FzTRcoynIFrOJXNC7s K4XIODjx7ooL -----END CERTIFICATE----- futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/tests/end.rsa000066400000000000000000000032501457724761000241050ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcKY2pRsvWI15Q bTiNltzOLx47yloSZeTVt+TnQXJTxpCwhcpfBs3pho2IEbNMSRsGMjcAmWR9f3mk mx1gb59oM1VufgU3pZIC3lvFtd7TKcTJ/Que0BR9rkiy0UJRIPSynQHAGBDPNOZl VOgBqNypk5WL9aKcR72wATriZu+L85Cq/DkPXQUtJa90I+4kLXuigzxqr3Qlj4q+ pJUCuwQ03WQAfNZqc3Fi7p+AfNb9AXXXYmb+L8hejsOBg+N67PWdwUYaHDcnLhwK IG19ABxiEsHsAfS7WJLOdgg3UJ02ml6rbTiZJHRD4/1dLy9csIbi47MHkuKRkKDf aGkSuzL1AgMBAAECggEAPIdoHRwLuonqyi3dGIBboIwvwbx2WwyMh32Rf0rtBb9X 1FBUPR//yK5nhvtm0hhfR6rSmGWTzTUY2nqS7wqBRwKmAETBZnUs2YEMtiS0aFXZ drcNVNJcb9lUv2Ts2KU44UptSsTTCkS++ykGyD4GvNCIkvwrgs/uhM8xw0ENpuoD F5DuD3xRoKEn7QqbkcjRjMGVspTxEDekN6oSTmg6LnXlq21AXEWvtzHXaEK4Z9o7 +ROGT6e+YCRMYXm0pErTeurzWZ1heFCPbUy74131kYRsOaPntpzRO2g4tYIoEcoL snxT1WvgZSdbMiNlXbKRshrz+hcdISzRksyAfdetmQKBgQD8jnWiKV9yyk354aEL 5Wj5/7zR7pWeLcJiIdNjtbDf3Dat3nl9Y5oS5qQWTGwmCrkKm3pMlnDFax5L09lb QvWqFzL8LfikHhaMjL24yBhrSpkpIVmuwFbvp+2207C/CN1q+HMyNjIHiCFtsmz+ CaAcgvDxQAjkOUweJbM+9qHuswKBgQDfKgXmB/h/edk1GcOUES7KsxHao/Wjip30 FMkYLKNEgOMfQNwbtjQokVqN1XAhlFSVWzpu3o2Cp3lE361X993zfxOHGBaSpFtW HqwzhqVa/6JSF3bIJZ92TozOVpj/SLtPpVYscGuX4fusJaZESnR6G5HxP3BuIRzj WGj9v1irtwKBgQC4ykOn8z2zb/K98yky2xiEU2qE8Fzo7/JaewWA32Aba7VWYoHi DQ6e5cMJzcET1KSv7jL99tVsnYP9V3SiIcw+N9tGSRy0eI9nJ1Np8H5tgpeuUIie JH13vRdhvmKEZKgdrMwc4SqZ+DD9Yp/9AG32bph87K2Uz5eyN0N/vlWdoQKBgCBA E3Al0LyjYEdzPLdmMEvsyvS84x/KRX9jjZgnw0SAF9KJbgW3NBx0vRBoOvcvV3Xq JCynju3FxBm29XcUBAhPYgQQ8BorFGQCtMIRgE4BSXTxnSHZhgzz1xdunleuLBub ejOb34CokNVU741I8UJAP7wMffP8Zw9X1HxvLaJLAoGBAJIM6ljFh8XykIrR6xtj XdspTVKQhkNmSe2aFU7ujEYWgrSoJO9szICsRPRhbb6PGy+QmStcEEJrrJMP8ojc oYAd8Q3x008AuMcjSCWNuUggTYZOaBVoPzg6de5NfN08lcqwJVZRP7bx74CmfIIM Hd+2ikk85J6Ho/SQ/ViQLYQD -----END PRIVATE KEY----- futures-rustls-3924baf49578f558f2f30d9a4f27c74df93599f0/tests/test.rs000066400000000000000000000107401457724761000241570ustar00rootroot00000000000000use futures_rustls::{TlsAcceptor, TlsConnector}; use futures_util::future::TryFutureExt; use futures_util::io::{copy, AsyncReadExt, AsyncWriteExt}; use lazy_static::lazy_static; use rustls::ClientConfig; use rustls_pemfile::{certs, private_key}; use smol::net::{TcpListener, TcpStream}; use smol::Timer; use std::convert::TryFrom; use std::io::{BufReader, Cursor}; use std::net::SocketAddr; use std::sync::mpsc::channel; use std::sync::Arc; use std::{io, thread}; const CERT: &str = include_str!("end.cert"); const CHAIN: &[u8] = include_bytes!("end.chain"); const RSA: &str = include_str!("end.rsa"); lazy_static! { static ref TEST_SERVER: (SocketAddr, &'static str, &'static [u8]) = { let cert = certs(&mut BufReader::new(Cursor::new(CERT))) .collect::, _>>() .unwrap(); let key = private_key(&mut BufReader::new(Cursor::new(RSA))) .unwrap() .unwrap(); let config = rustls::ServerConfig::builder() .with_no_client_auth() .with_single_cert(cert, key) .unwrap(); let acceptor = TlsAcceptor::from(Arc::new(config)); let (send, recv) = channel(); thread::spawn(move || { let done = async move { let addr = SocketAddr::from(([127, 0, 0, 1], 0)); let listener = TcpListener::bind(&addr).await?; send.send(listener.local_addr()?).unwrap(); loop { let (stream, _) = listener.accept().await?; let acceptor = acceptor.clone(); let fut = async move { let stream = acceptor.accept(stream).await?; let (mut reader, mut writer) = stream.split(); copy(&mut reader, &mut writer).await?; Ok(()) as io::Result<()> } .unwrap_or_else(|err| eprintln!("server: {:?}", err)); smol::spawn(fut).detach(); } } .unwrap_or_else(|err: io::Error| eprintln!("server: {:?}", err)); smol::block_on(done); }); let addr = recv.recv().unwrap(); (addr, "testserver.com", CHAIN) }; } fn start_server() -> &'static (SocketAddr, &'static str, &'static [u8]) { &*TEST_SERVER } async fn start_client(addr: SocketAddr, domain: &str, config: Arc) -> io::Result<()> { const FILE: &[u8] = include_bytes!("../README.md"); let domain = pki_types::ServerName::try_from(domain).unwrap().to_owned(); let config = TlsConnector::from(config); let mut buf = vec![0; FILE.len()]; let stream = TcpStream::connect(&addr).await?; let mut stream = config.connect(domain, stream).await?; stream.write_all(FILE).await?; stream.flush().await?; stream.read_exact(&mut buf).await?; assert_eq!(buf, FILE); Ok(()) } #[test] fn pass() -> io::Result<()> { let fut = async { let (addr, domain, chain) = start_server(); // TODO: not sure how to resolve this right now but since // TcpStream::bind now returns a future it creates a race // condition until its ready sometimes. use std::time::*; Timer::after(Duration::from_secs(1)).await; let chain = certs(&mut std::io::Cursor::new(*chain)) .collect::, _>>() .unwrap(); let mut root_store = rustls::RootCertStore::empty(); root_store.add_parsable_certificates(chain); let config = rustls::ClientConfig::builder() .with_root_certificates(root_store) .with_no_client_auth(); let config = Arc::new(config); start_client(*addr, domain, config).await?; Ok(()) }; smol::block_on(fut) } #[test] fn fail() -> io::Result<()> { let fut = async { let (addr, domain, chain) = start_server(); let chain = certs(&mut std::io::Cursor::new(*chain)) .collect::, _>>() .unwrap(); let mut root_store = rustls::RootCertStore::empty(); root_store.add_parsable_certificates(chain); let config = rustls::ClientConfig::builder() .with_root_certificates(root_store) .with_no_client_auth(); let config = Arc::new(config); assert_ne!(domain, &"google.com"); let ret = start_client(*addr, "google.com", config).await; assert!(ret.is_err()); Ok(()) }; smol::block_on(fut) }