#![deny(missing_docs)]
#![warn(rust_2018_idioms)]
#![doc(html_root_url = "https://docs.rs/rustls-connector/0.8.1/")]
pub use rustls;
pub use webpki;
pub use webpki_roots;
use rustls::{ClientConfig, ClientSession, Session, StreamOwned};
use std::{
error::Error,
fmt::{self, Debug},
io::{self, Read, Write},
sync::Arc,
};
pub type TlsStream<S> = StreamOwned<ClientSession, S>;
pub struct RustlsConnector {
config: Arc<ClientConfig>,
}
impl Default for RustlsConnector {
fn default() -> Self {
let mut config = ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
config.into()
}
}
impl From<ClientConfig> for RustlsConnector {
fn from(config: ClientConfig) -> Self {
Arc::new(config).into()
}
}
impl From<Arc<ClientConfig>> for RustlsConnector {
fn from(config: Arc<ClientConfig>) -> Self {
Self { config }
}
}
impl RustlsConnector {
pub fn new(config: ClientConfig) -> Self {
config.into()
}
pub fn connect<S: Debug + Read + Send + Sync + Write + 'static>(
&self,
domain: &str,
stream: S,
) -> Result<TlsStream<S>, HandshakeError<S>> {
let session = ClientSession::new(
&self.config,
webpki::DNSNameRef::try_from_ascii_str(domain).map_err(|err| {
HandshakeError::Failure(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid domain name ({}): {}", err, domain),
))
})?,
);
MidHandshakeTlsStream { session, stream }.handshake()
}
}
#[derive(Debug)]
pub struct MidHandshakeTlsStream<S: Read + Write> {
session: ClientSession,
stream: S,
}
impl<S: Debug + Read + Send + Sync + Write + 'static> MidHandshakeTlsStream<S> {
pub fn get_ref(&self) -> &S {
&self.stream
}
pub fn get_mut(&mut self) -> &S {
&mut self.stream
}
pub fn handshake(mut self) -> Result<TlsStream<S>, HandshakeError<S>> {
if let Err(e) = self.session.complete_io(&mut self.stream) {
if e.kind() == io::ErrorKind::WouldBlock {
if self.session.is_handshaking() {
return Err(HandshakeError::WouldBlock(self));
}
} else {
return Err(e.into());
}
}
Ok(TlsStream::new(self.session, self.stream))
}
}
impl<S: Read + Write> fmt::Display for MidHandshakeTlsStream<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MidHandshakeTlsStream")
}
}
#[derive(Debug)]
pub enum HandshakeError<S: Read + Send + Sync + Write + 'static> {
WouldBlock(MidHandshakeTlsStream<S>),
Failure(io::Error),
}
impl<S: Debug + Read + Send + Sync + Write + 'static> fmt::Display for HandshakeError<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HandshakeError::WouldBlock(_) => write!(f, "WouldBlock hit during handshake"),
HandshakeError::Failure(err) => write!(f, "IO error: {}", err),
}
}
}
impl<S: Debug + Read + Send + Sync + Write + 'static> Error for HandshakeError<S> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
HandshakeError::Failure(err) => Some(err),
_ => None,
}
}
}
impl<S: Debug + Read + Send + Sync + Write + 'static> From<io::Error> for HandshakeError<S> {
fn from(err: io::Error) -> Self {
HandshakeError::Failure(err)
}
}