feat(ssl): redesign SSL usage
BREAKING CHANGE: Server::https was changed to allow any implementation of Ssl. Server in general was also changed. HttpConnector no longer uses SSL; using HttpsConnector instead.
This commit is contained in:
417
src/net.rs
417
src/net.rs
@@ -4,16 +4,12 @@ use std::fmt;
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::net::{SocketAddr, ToSocketAddrs, TcpStream, TcpListener, Shutdown};
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use openssl::ssl::{Ssl, SslStream, SslContext, SSL_VERIFY_NONE};
|
||||
use openssl::ssl::SslMethod::Sslv23;
|
||||
use openssl::ssl::error::StreamError as SslIoError;
|
||||
use openssl::x509::X509FileType;
|
||||
#[cfg(feature = "openssl")]
|
||||
pub use self::openssl::Openssl;
|
||||
|
||||
use typeable::Typeable;
|
||||
use {traitobject};
|
||||
use traitobject;
|
||||
|
||||
/// The write-status indicating headers have not been written.
|
||||
pub enum Fresh {}
|
||||
@@ -70,9 +66,6 @@ pub trait NetworkConnector {
|
||||
type Stream: Into<Box<NetworkStream + Send>>;
|
||||
/// Connect to a remote address.
|
||||
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream>;
|
||||
/// Sets the given `ContextVerifier` to be used when verifying the SSL context
|
||||
/// on the establishment of a new connection.
|
||||
fn set_ssl_verifier(&mut self, verifier: ContextVerifier);
|
||||
}
|
||||
|
||||
impl<T: NetworkStream + Send> From<T> for Box<NetworkStream + Send> {
|
||||
@@ -143,43 +136,22 @@ impl NetworkStream + Send {
|
||||
}
|
||||
|
||||
/// A `NetworkListener` for `HttpStream`s.
|
||||
pub enum HttpListener {
|
||||
/// Http variant.
|
||||
Http(TcpListener),
|
||||
/// Https variant. The two paths point to the certificate and key PEM files, in that order.
|
||||
Https(TcpListener, Arc<SslContext>)
|
||||
}
|
||||
pub struct HttpListener(TcpListener);
|
||||
|
||||
impl Clone for HttpListener {
|
||||
#[inline]
|
||||
fn clone(&self) -> HttpListener {
|
||||
match *self {
|
||||
HttpListener::Http(ref tcp) => HttpListener::Http(tcp.try_clone().unwrap()),
|
||||
HttpListener::Https(ref tcp, ref ssl) => HttpListener::Https(tcp.try_clone().unwrap(), ssl.clone()),
|
||||
}
|
||||
HttpListener(self.0.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpListener {
|
||||
|
||||
/// Start listening to an address over HTTP.
|
||||
pub fn http<To: ToSocketAddrs>(addr: To) -> ::Result<HttpListener> {
|
||||
Ok(HttpListener::Http(try!(TcpListener::bind(addr))))
|
||||
pub fn new<To: ToSocketAddrs>(addr: To) -> ::Result<HttpListener> {
|
||||
Ok(HttpListener(try!(TcpListener::bind(addr))))
|
||||
}
|
||||
|
||||
/// Start listening to an address over HTTPS.
|
||||
pub fn https<To: ToSocketAddrs>(addr: To, cert: &Path, key: &Path) -> ::Result<HttpListener> {
|
||||
let mut ssl_context = try!(SslContext::new(Sslv23));
|
||||
try!(ssl_context.set_cipher_list("DEFAULT"));
|
||||
try!(ssl_context.set_certificate_file(cert, X509FileType::PEM));
|
||||
try!(ssl_context.set_private_key_file(key, X509FileType::PEM));
|
||||
ssl_context.set_verify(SSL_VERIFY_NONE, None);
|
||||
HttpListener::https_with_context(addr, ssl_context)
|
||||
}
|
||||
|
||||
/// Start listening to an address of HTTPS using the given SslContext
|
||||
pub fn https_with_context<To: ToSocketAddrs>(addr: To, ssl_context: SslContext) -> ::Result<HttpListener> {
|
||||
Ok(HttpListener::Https(try!(TcpListener::bind(addr)), Arc::new(ssl_context)))
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkListener for HttpListener {
|
||||
@@ -187,51 +159,42 @@ impl NetworkListener for HttpListener {
|
||||
|
||||
#[inline]
|
||||
fn accept(&mut self) -> ::Result<HttpStream> {
|
||||
match *self {
|
||||
HttpListener::Http(ref mut tcp) => Ok(HttpStream::Http(CloneTcpStream(try!(tcp.accept()).0))),
|
||||
HttpListener::Https(ref mut tcp, ref ssl_context) => {
|
||||
let stream = CloneTcpStream(try!(tcp.accept()).0);
|
||||
match SslStream::new_server(&**ssl_context, stream) {
|
||||
Ok(ssl_stream) => Ok(HttpStream::Https(ssl_stream)),
|
||||
Err(SslIoError(e)) => {
|
||||
Err(io::Error::new(io::ErrorKind::ConnectionAborted, e).into())
|
||||
},
|
||||
Err(e) => Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(HttpStream(try!(self.0.accept()).0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn local_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
match *self {
|
||||
HttpListener::Http(ref mut tcp) => tcp.local_addr(),
|
||||
HttpListener::Https(ref mut tcp, _) => tcp.local_addr(),
|
||||
}
|
||||
self.0.local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct CloneTcpStream(TcpStream);
|
||||
/// A wrapper around a TcpStream.
|
||||
pub struct HttpStream(pub TcpStream);
|
||||
|
||||
impl Clone for CloneTcpStream{
|
||||
impl Clone for HttpStream {
|
||||
#[inline]
|
||||
fn clone(&self) -> CloneTcpStream {
|
||||
CloneTcpStream(self.0.try_clone().unwrap())
|
||||
fn clone(&self) -> HttpStream {
|
||||
HttpStream(self.0.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for CloneTcpStream {
|
||||
impl fmt::Debug for HttpStream {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("HttpStream(_)")
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for HttpStream {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for CloneTcpStream {
|
||||
impl Write for HttpStream {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.write(buf)
|
||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
||||
self.0.write(msg)
|
||||
}
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
@@ -239,85 +202,26 @@ impl Write for CloneTcpStream {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a TcpStream.
|
||||
#[derive(Clone)]
|
||||
pub enum HttpStream {
|
||||
/// A stream over the HTTP protocol.
|
||||
Http(CloneTcpStream),
|
||||
/// A stream over the HTTP protocol, protected by SSL.
|
||||
Https(SslStream<CloneTcpStream>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for HttpStream {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
HttpStream::Http(_) => write!(fmt, "Http HttpStream"),
|
||||
HttpStream::Https(_) => write!(fmt, "Https HttpStream"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for HttpStream {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
HttpStream::Http(ref mut inner) => inner.read(buf),
|
||||
HttpStream::Https(ref mut inner) => inner.read(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for HttpStream {
|
||||
#[inline]
|
||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
HttpStream::Http(ref mut inner) => inner.write(msg),
|
||||
HttpStream::Https(ref mut inner) => inner.write(msg)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match *self {
|
||||
HttpStream::Http(ref mut inner) => inner.flush(),
|
||||
HttpStream::Https(ref mut inner) => inner.flush(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkStream for HttpStream {
|
||||
#[inline]
|
||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
match *self {
|
||||
HttpStream::Http(ref mut inner) => inner.0.peer_addr(),
|
||||
HttpStream::Https(ref mut inner) => inner.get_mut().0.peer_addr()
|
||||
}
|
||||
self.0.peer_addr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn close(&mut self, how: Shutdown) -> io::Result<()> {
|
||||
#[inline]
|
||||
fn shutdown(tcp: &mut TcpStream, how: Shutdown) -> io::Result<()> {
|
||||
match tcp.shutdown(how) {
|
||||
Ok(_) => Ok(()),
|
||||
// see https://github.com/hyperium/hyper/issues/508
|
||||
Err(ref e) if e.kind() == ErrorKind::NotConnected => Ok(()),
|
||||
err => err
|
||||
}
|
||||
match self.0.shutdown(how) {
|
||||
Ok(_) => Ok(()),
|
||||
// see https://github.com/hyperium/hyper/issues/508
|
||||
Err(ref e) if e.kind() == ErrorKind::NotConnected => Ok(()),
|
||||
err => err
|
||||
}
|
||||
|
||||
match *self {
|
||||
HttpStream::Http(ref mut inner) => shutdown(&mut inner.0, how),
|
||||
HttpStream::Https(ref mut inner) => shutdown(&mut inner.get_mut().0, how)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// A connector that will produce HttpStreams.
|
||||
pub struct HttpConnector(pub Option<ContextVerifier>);
|
||||
|
||||
/// A method that can set verification methods on an SSL context
|
||||
pub type ContextVerifier = Box<Fn(&mut SslContext) -> () + Send + Sync>;
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HttpConnector;
|
||||
|
||||
impl NetworkConnector for HttpConnector {
|
||||
type Stream = HttpStream;
|
||||
@@ -327,19 +231,7 @@ impl NetworkConnector for HttpConnector {
|
||||
Ok(try!(match scheme {
|
||||
"http" => {
|
||||
debug!("http scheme");
|
||||
Ok(HttpStream::Http(CloneTcpStream(try!(TcpStream::connect(addr)))))
|
||||
},
|
||||
"https" => {
|
||||
debug!("https scheme");
|
||||
let stream = CloneTcpStream(try!(TcpStream::connect(addr)));
|
||||
let mut context = try!(SslContext::new(Sslv23));
|
||||
if let Some(ref verifier) = self.0 {
|
||||
verifier(&mut context);
|
||||
}
|
||||
let ssl = try!(Ssl::new(&context));
|
||||
try!(ssl.set_hostname(host));
|
||||
let stream = try!(SslStream::new(&context, stream));
|
||||
Ok(HttpStream::Https(stream))
|
||||
Ok(HttpStream(try!(TcpStream::connect(addr))))
|
||||
},
|
||||
_ => {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput,
|
||||
@@ -347,15 +239,241 @@ impl NetworkConnector for HttpConnector {
|
||||
}
|
||||
}))
|
||||
}
|
||||
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
|
||||
self.0 = Some(verifier);
|
||||
}
|
||||
|
||||
|
||||
/// An abstraction to allow any SSL implementation to be used with HttpsStreams.
|
||||
pub trait Ssl {
|
||||
/// The protected stream.
|
||||
type Stream: NetworkStream + Send + Clone;
|
||||
/// Wrap a client stream with SSL.
|
||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream>;
|
||||
/// Wrap a server stream with SSL.
|
||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream>;
|
||||
}
|
||||
|
||||
/// A stream over the HTTP protocol, possibly protected by SSL.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HttpsStream<S: NetworkStream> {
|
||||
/// A plain text stream.
|
||||
Http(HttpStream),
|
||||
/// A stream protected by SSL.
|
||||
Https(S)
|
||||
}
|
||||
|
||||
impl<S: NetworkStream> Read for HttpsStream<S> {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
HttpsStream::Http(ref mut s) => s.read(buf),
|
||||
HttpsStream::Https(ref mut s) => s.read(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: NetworkStream> Write for HttpsStream<S> {
|
||||
#[inline]
|
||||
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
HttpsStream::Http(ref mut s) => s.write(msg),
|
||||
HttpsStream::Https(ref mut s) => s.write(msg)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match *self {
|
||||
HttpsStream::Http(ref mut s) => s.flush(),
|
||||
HttpsStream::Https(ref mut s) => s.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: NetworkStream> NetworkStream for HttpsStream<S> {
|
||||
#[inline]
|
||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
match *self {
|
||||
HttpsStream::Http(ref mut s) => s.peer_addr(),
|
||||
HttpsStream::Https(ref mut s) => s.peer_addr()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn close(&mut self, how: Shutdown) -> io::Result<()> {
|
||||
match *self {
|
||||
HttpsStream::Http(ref mut s) => s.close(how),
|
||||
HttpsStream::Https(ref mut s) => s.close(how)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Http Listener over SSL.
|
||||
#[derive(Clone)]
|
||||
pub struct HttpsListener<S: Ssl> {
|
||||
listener: HttpListener,
|
||||
ssl: S,
|
||||
}
|
||||
|
||||
impl<S: Ssl> HttpsListener<S> {
|
||||
|
||||
/// Start listening to an address over HTTPS.
|
||||
pub fn new<To: ToSocketAddrs>(addr: To, ssl: S) -> ::Result<HttpsListener<S>> {
|
||||
HttpListener::new(addr).map(|l| HttpsListener {
|
||||
listener: l,
|
||||
ssl: ssl
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<S: Ssl + Clone> NetworkListener for HttpsListener<S> {
|
||||
type Stream = S::Stream;
|
||||
|
||||
#[inline]
|
||||
fn accept(&mut self) -> ::Result<S::Stream> {
|
||||
self.listener.accept().and_then(|s| self.ssl.wrap_server(s))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn local_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
self.listener.local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
/// A connector that can protect HTTP streams using SSL.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HttpsConnector<S: Ssl> {
|
||||
ssl: S
|
||||
}
|
||||
|
||||
impl<S: Ssl> HttpsConnector<S> {
|
||||
/// Create a new connector using the provided SSL implementation.
|
||||
pub fn new(s: S) -> HttpsConnector<S> {
|
||||
HttpsConnector { ssl: s }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Ssl> NetworkConnector for HttpsConnector<S> {
|
||||
type Stream = HttpsStream<S::Stream>;
|
||||
|
||||
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> {
|
||||
let addr = &(host, port);
|
||||
if scheme == "https" {
|
||||
debug!("https scheme");
|
||||
let stream = HttpStream(try!(TcpStream::connect(addr)));
|
||||
self.ssl.wrap_client(stream, host).map(HttpsStream::Https)
|
||||
} else {
|
||||
HttpConnector.connect(host, port, scheme).map(HttpsStream::Http)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
#[doc(hidden)]
|
||||
pub type DefaultConnector = HttpConnector;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[doc(hidden)]
|
||||
pub type DefaultConnector = HttpsConnector<self::openssl::Openssl>;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
mod openssl {
|
||||
use std::io;
|
||||
use std::net::{SocketAddr, Shutdown};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use openssl::ssl::{Ssl, SslContext, SslStream, SslMethod, SSL_VERIFY_NONE};
|
||||
use openssl::ssl::error::StreamError as SslIoError;
|
||||
use openssl::ssl::error::SslError;
|
||||
use openssl::x509::X509FileType;
|
||||
use super::{NetworkStream, HttpStream};
|
||||
|
||||
|
||||
/// An implementation of `Ssl` for OpenSSL.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use hyper::Server;
|
||||
/// use hyper::net::Openssl;
|
||||
///
|
||||
/// let ssl = Openssl::with_cert_and_key("/home/foo/cert", "/home/foo/key").unwrap();
|
||||
/// Server::https("0.0.0.0:443", ssl).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// For complete control, create a `SslContext` with the options you desire
|
||||
/// and then create `Openssl { context: ctx }
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Openssl {
|
||||
/// The `SslContext` from openssl crate.
|
||||
pub context: Arc<SslContext>
|
||||
}
|
||||
|
||||
impl Default for Openssl {
|
||||
fn default() -> Openssl {
|
||||
Openssl {
|
||||
context: Arc::new(SslContext::new(SslMethod::Sslv23).unwrap_or_else(|e| {
|
||||
// if we cannot create a SslContext, that's because of a
|
||||
// serious problem. just crash.
|
||||
panic!("{}", e)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Openssl {
|
||||
/// Ease creating an `Openssl` with a certificate and key.
|
||||
pub fn with_cert_and_key<C, K>(cert: C, key: K) -> Result<Openssl, SslError>
|
||||
where C: AsRef<Path>, K: AsRef<Path> {
|
||||
let mut ctx = try!(SslContext::new(SslMethod::Sslv23));
|
||||
try!(ctx.set_cipher_list("DEFAULT"));
|
||||
try!(ctx.set_certificate_file(cert.as_ref(), X509FileType::PEM));
|
||||
try!(ctx.set_private_key_file(key.as_ref(), X509FileType::PEM));
|
||||
ctx.set_verify(SSL_VERIFY_NONE, None);
|
||||
Ok(Openssl { context: Arc::new(ctx) })
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Ssl for Openssl {
|
||||
type Stream = SslStream<HttpStream>;
|
||||
|
||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
||||
//if let Some(ref verifier) = self.verifier {
|
||||
// verifier(&mut context);
|
||||
//}
|
||||
let ssl = try!(Ssl::new(&self.context));
|
||||
try!(ssl.set_hostname(host));
|
||||
SslStream::new(&self.context, stream).map_err(From::from)
|
||||
}
|
||||
|
||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> {
|
||||
match SslStream::new_server(&self.context, stream) {
|
||||
Ok(ssl_stream) => Ok(ssl_stream),
|
||||
Err(SslIoError(e)) => {
|
||||
Err(io::Error::new(io::ErrorKind::ConnectionAborted, e).into())
|
||||
},
|
||||
Err(e) => Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: NetworkStream> NetworkStream for SslStream<S> {
|
||||
#[inline]
|
||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
self.get_mut().peer_addr()
|
||||
}
|
||||
|
||||
fn close(&mut self, how: Shutdown) -> io::Result<()> {
|
||||
self.get_mut().close(how)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mock::MockStream;
|
||||
use super::{NetworkStream, HttpConnector, NetworkConnector};
|
||||
use super::{NetworkStream};
|
||||
|
||||
#[test]
|
||||
fn test_downcast_box_stream() {
|
||||
@@ -376,13 +494,4 @@ mod tests {
|
||||
assert_eq!(mock, Box::new(MockStream::new()));
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_connector_set_ssl_verifier() {
|
||||
let mut connector = HttpConnector(None);
|
||||
|
||||
connector.set_ssl_verifier(Box::new(|_| {}));
|
||||
|
||||
assert!(connector.0.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user