Skip to content

Commit

Permalink
add rustls
Browse files Browse the repository at this point in the history
  • Loading branch information
jeizsm committed Jul 30, 2018
1 parent b4ed564 commit 196da6d
Show file tree
Hide file tree
Showing 9 changed files with 413 additions and 47 deletions.
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"

[package.metadata.docs.rs]
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"]

[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
Expand All @@ -37,6 +37,9 @@ tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "tokio-openssl"]

# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"]

# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]

Expand Down Expand Up @@ -104,6 +107,12 @@ tokio-tls = { version="0.1", optional = true }
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }

#rustls
rustls = { version = "0.13", optional = true }
tokio-rustls = { version = "0.7", optional = true }
webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true }

# forked url_encoded
itoa = "0.4"
dtoa = "0.4"
Expand Down
153 changes: 147 additions & 6 deletions src/client/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,25 @@ use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod};
use tokio_openssl::SslConnectorExt;

#[cfg(all(feature = "tls", not(feature = "alpn")))]
use native_tls::{Error as TlsError, TlsConnector};
use native_tls::{Error as TlsError, TlsConnector, TlsStream};
#[cfg(all(feature = "tls", not(feature = "alpn")))]
use tokio_tls::TlsConnectorExt;

#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
use rustls::ClientConfig;
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
use std::io::Error as TLSError;
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
use std::sync::Arc;
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
use tokio_rustls::ClientConfigExt;
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
use webpki::DNSNameRef;
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
use webpki_roots;

use server::IoStream;
use {HAS_OPENSSL, HAS_TLS};
use {HAS_OPENSSL, HAS_TLS, HAS_RUSTLS};

/// Client connector usage stats
#[derive(Default, Message)]
Expand Down Expand Up @@ -139,6 +152,11 @@ pub enum ClientConnectorError {
#[fail(display = "{}", _0)]
SslError(#[cause] TlsError),

/// SSL error
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
#[fail(display = "{}", _0)]
SslError(#[cause] TLSError),

/// Resolver error
#[fail(display = "{}", _0)]
Resolver(#[cause] ResolverError),
Expand Down Expand Up @@ -193,6 +211,8 @@ pub struct ClientConnector {
connector: SslConnector,
#[cfg(all(feature = "tls", not(feature = "alpn")))]
connector: TlsConnector,
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
connector: Arc<ClientConfig>,

stats: ClientConnectorStats,
subscriber: Option<Recipient<ClientConnectorStats>>,
Expand Down Expand Up @@ -262,8 +282,16 @@ impl Default for ClientConnector {
paused: Paused::No,
}
}
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
{
let mut config = ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
ClientConnector::with_connector(Arc::new(config))
}

#[cfg(not(any(feature = "alpn", feature = "tls")))]
#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))]
{
let (tx, rx) = mpsc::unbounded();
ClientConnector {
Expand Down Expand Up @@ -325,7 +353,7 @@ impl ClientConnector {
/// # actix::System::current().stop();
/// Ok(())
/// })
/// );
/// });
/// }
/// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector {
Expand All @@ -352,6 +380,75 @@ impl ClientConnector {
}
}

#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
/// Create `ClientConnector` actor with custom `SslConnector` instance.
///
/// By default `ClientConnector` uses very a simple SSL configuration.
/// With `with_connector` method it is possible to use a custom
/// `SslConnector` object.
///
/// ```rust
/// # #![cfg(feature = "rust-tls")]
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::{future, Future};
/// # use std::io::Write;
/// # use std::process;
/// # use actix_web::actix::Actor;
/// extern crate rustls;
/// extern crate webpki_roots;
/// use actix_web::{actix, client::ClientConnector, client::Connect};
///
/// use rustls::ClientConfig;
/// use std::sync::Arc;
///
/// fn main() {
/// actix::run(|| {
/// // Start `ClientConnector` with custom `ClientConfig`
/// let mut config = ClientConfig::new();
/// config
/// .root_store
/// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
/// let conn = ClientConnector::with_connector(Arc::new(config)).start();
///
/// conn.send(
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
/// .map_err(|_| ())
/// .and_then(|res| {
/// if let Ok(mut stream) = res {
/// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
/// }
/// # actix::System::current().stop();
/// Ok(())
/// })
/// });
/// }
/// ```
pub fn with_connector(connector: Arc<ClientConfig>) -> ClientConnector {
let (tx, rx) = mpsc::unbounded();

ClientConnector {
connector,
stats: ClientConnectorStats::default(),
subscriber: None,
acq_tx: tx,
acq_rx: Some(rx),
resolver: None,
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: Some(HashMap::new()),
wait_timeout: None,
paused: Paused::No,
}
}

/// Set total number of simultaneous connections.
///
/// If limit is 0, the connector has no limit.
Expand Down Expand Up @@ -709,7 +806,51 @@ impl ClientConnector {
}
}

#[cfg(not(any(feature = "alpn", feature = "tls")))]
#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
fut::Either::B(fut::err(()))
}
Ok(stream) => {
act.stats.opened += 1;
if conn.0.ssl {
let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap();
fut::Either::A(
act.connector
.connect_async(host, stream)
.into_actor(act)
.then(move |res, _, _| {
match res {
Err(e) => {
let _ = waiter.tx.send(Err(
ClientConnectorError::SslError(e),
));
}
Ok(stream) => {
let _ =
waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
}
}
fut::ok(())
}),
)
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
fut::Either::B(fut::ok(()))
}
}
}

#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
Expand Down Expand Up @@ -784,7 +925,7 @@ impl Handler<Connect> for ClientConnector {
};

// check ssl availability
if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS {
if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS && !HAS_RUSTLS {
return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported));
}

Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ extern crate openssl;
#[cfg(feature = "openssl")]
extern crate tokio_openssl;

#[cfg(feature = "rust-tls")]
extern crate rustls;
#[cfg(feature = "rust-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rust-tls")]
extern crate webpki;
#[cfg(feature = "rust-tls")]
extern crate webpki_roots;

mod application;
mod body;
mod context;
Expand Down Expand Up @@ -224,6 +233,11 @@ pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature = "tls"))]
pub(crate) const HAS_TLS: bool = false;

#[cfg(feature = "rust-tls")]
pub(crate) const HAS_RUSTLS: bool = true;
#[cfg(not(feature = "rust-tls"))]
pub(crate) const HAS_RUSTLS: bool = false;

pub mod dev {
//! The `actix-web` prelude for library developers
//!
Expand Down
43 changes: 43 additions & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,46 @@ impl IoStream for TlsStream<TcpStream> {
self.get_mut().get_mut().set_linger(dur)
}
}

#[cfg(feature = "rust-tls")]
use rustls::{ClientSession, ServerSession};
#[cfg(feature = "rust-tls")]
use tokio_rustls::TlsStream;

#[cfg(feature = "rust-tls")]
impl IoStream for TlsStream<TcpStream, ClientSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}

#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}

#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}

#[cfg(feature = "rust-tls")]
impl IoStream for TlsStream<TcpStream, ServerSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}

#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}

#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}
51 changes: 51 additions & 0 deletions src/server/srv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ use native_tls::TlsAcceptor;
#[cfg(feature = "alpn")]
use openssl::ssl::{AlpnError, SslAcceptorBuilder};

#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;

use super::channel::{HttpChannel, WrapperStream};
use super::settings::{ServerSettings, WorkerSettings};
use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
Expand All @@ -42,6 +45,14 @@ fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> {
Ok(())
}

#[cfg(all(feature = "rust-tls", not(feature = "alpn")))]
fn configure_alpn(builder: &mut Arc<ServerConfig>) -> io::Result<()> {
Arc::<ServerConfig>::get_mut(builder)
.unwrap()
.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]);
Ok(())
}

/// An HTTP Server
pub struct HttpServer<H>
where
Expand Down Expand Up @@ -265,6 +276,26 @@ where
Ok(self)
}

#[cfg(all(feature = "rust-tls", not(feature = "alpn")))]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
mut self, lst: net::TcpListener, mut builder: Arc<ServerConfig>,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Rustls(builder.clone()),
});
Ok(self)
}

fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
let mut err = None;
let mut succ = false;
Expand Down Expand Up @@ -343,6 +374,26 @@ where
Ok(self)
}

#[cfg(all(feature = "rust-tls", not(feature = "alpn")))]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S: net::ToSocketAddrs>(
mut self, addr: S, mut builder: Arc<ServerConfig>,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}

let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Rustls(builder.clone());
s
}));
Ok(self)
}

fn start_workers(
&mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
Expand Down
Loading

0 comments on commit 196da6d

Please sign in to comment.