Skip to content

Commit

Permalink
Merge pull request #241 from zvolin/ssl-native-tls
Browse files Browse the repository at this point in the history
Add ssl-native-tls feature allowing using native-tls as an SSL backend
  • Loading branch information
bradfier committed May 16, 2023
2 parents 33b0f26 + 06c4fb3 commit e221563
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 23 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yaml
Expand Up @@ -10,6 +10,7 @@ jobs:
- default
- ssl-openssl
- ssl-rustls
- ssl-native-tls
steps:
- uses: actions/checkout@v2
- name: Install stable toolchain
Expand Down Expand Up @@ -38,11 +39,12 @@ jobs:
rust:
- stable
- nightly
- 1.56
- 1.57
features:
- default
- ssl-openssl
- ssl-rustls
- ssl-native-tls
steps:
- uses: actions/checkout@v2
- name: Install toolchain
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Expand Up @@ -9,12 +9,14 @@ keywords = ["http", "server", "web"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/tiny-http/tiny-http"
edition = "2018"
rust-version = "1.57"

[features]
default = ["log"]
ssl = ["ssl-openssl"]
ssl-openssl = ["openssl", "zeroize"]
ssl-rustls = ["rustls", "rustls-pemfile", "zeroize"]
ssl-native-tls = ["native-tls", "zeroize"]

[dependencies]
ascii = "1.0"
Expand All @@ -26,6 +28,7 @@ openssl = { version = "0.10", optional = true }
rustls = { version = "0.20", optional = true }
rustls-pemfile = { version = "0.2.1", optional = true }
zeroize = { version = "1", optional = true }
native-tls = { version = "0.2", optional = true }

[dev-dependencies]
rustc-serialize = "0.3"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -14,7 +14,7 @@ What does **tiny-http** handle?
- Accepting and managing connections to the clients
- Parsing requests
- Requests pipelining
- HTTPS (using either OpenSSL or Rustls)
- HTTPS (using either OpenSSL, Rustls or native-tls)
- Transfer-Encoding and Content-Encoding
- Turning user input (eg. POST input) into a contiguous UTF-8 string (**not implemented yet**)
- Ranges (**not implemented yet**)
Expand Down
12 changes: 10 additions & 2 deletions examples/ssl.rs
@@ -1,11 +1,19 @@
extern crate tiny_http;

#[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
#[cfg(not(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
)))]
fn main() {
println!("This example requires one of the supported `ssl-*` features to be enabled");
}

#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
fn main() {
use tiny_http::{Response, Server};

Expand Down
56 changes: 46 additions & 10 deletions src/lib.rs
Expand Up @@ -91,7 +91,11 @@
#![deny(rust_2018_idioms)]
#![allow(clippy::match_like_matches_macro)]

#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
use zeroize::Zeroizing;

use std::error::Error;
Expand Down Expand Up @@ -203,7 +207,11 @@ impl Server {
}

/// Shortcut for an HTTPS server on a specific address.
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
#[inline]
pub fn https<A>(
addr: A,
Expand Down Expand Up @@ -256,22 +264,42 @@ impl Server {
};

// building the SSL capabilities
#[cfg(all(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
all(feature = "ssl-openssl", feature = "ssl-rustls"),
all(feature = "ssl-openssl", feature = "ssl-native-tls"),
all(feature = "ssl-native-tls", feature = "ssl-rustls"),
))]
compile_error!(
"Features 'ssl-openssl' and 'ssl-rustls' must not be enabled at the same time"
"Only one feature from 'ssl-openssl', 'ssl-rustls', 'ssl-native-tls' can be enabled at the same time"
);
#[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
#[cfg(not(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
)))]
type SslContext = ();
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
type SslContext = crate::ssl::SslContextImpl;
let ssl: Option<SslContext> = {
match ssl_config {
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Some(config) => Some(SslContext::from_pem(
config.certificate,
Zeroizing::new(config.private_key),
)?),
#[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
#[cfg(not(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
)))]
Some(_) => return Err(
"Building a server with SSL requires enabling the `ssl` feature in tiny-http"
.into(),
Expand All @@ -297,7 +325,11 @@ impl Server {
use util::RefinedTcpStream;
let (read_closable, write_closable) = match ssl {
None => RefinedTcpStream::new(sock),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Some(ref ssl) => {
// trying to apply SSL over the connection
// if an error occurs, we just close the socket and resume listening
Expand All @@ -308,7 +340,11 @@ impl Server {

RefinedTcpStream::new(sock)
}
#[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
#[cfg(not(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
)))]
Some(ref _ssl) => unreachable!(),
};

Expand Down
7 changes: 7 additions & 0 deletions src/ssl.rs
Expand Up @@ -18,3 +18,10 @@ pub(crate) mod rustls;
pub(crate) use self::rustls::RustlsContext as SslContextImpl;
#[cfg(feature = "ssl-rustls")]
pub(crate) use self::rustls::RustlsStream as SslStream;

#[cfg(feature = "ssl-native-tls")]
pub(crate) mod native_tls;
#[cfg(feature = "ssl-native-tls")]
pub(crate) use self::native_tls::NativeTlsContext as SslContextImpl;
#[cfg(feature = "ssl-native-tls")]
pub(crate) use self::native_tls::NativeTlsStream as SslStream;
84 changes: 84 additions & 0 deletions src/ssl/native_tls.rs
@@ -0,0 +1,84 @@
use crate::connection::Connection;
use crate::util::refined_tcp_stream::Stream as RefinedStream;
use std::error::Error;
use std::io::{Read, Write};
use std::net::{Shutdown, SocketAddr};
use std::sync::{Arc, Mutex};
use zeroize::Zeroizing;

/// A wrapper around a `native_tls` stream.
///
/// Uses an internal Mutex to permit disparate reader & writer threads to access the stream independently.
#[derive(Clone)]
pub(crate) struct NativeTlsStream(Arc<Mutex<native_tls::TlsStream<Connection>>>);

// These struct methods form the implict contract for swappable TLS implementations
impl NativeTlsStream {
pub(crate) fn peer_addr(&mut self) -> std::io::Result<Option<SocketAddr>> {
self.0
.lock()
.expect("Failed to lock SSL stream mutex")
.get_mut()
.peer_addr()
}

pub(crate) fn shutdown(&mut self, how: Shutdown) -> std::io::Result<()> {
self.0
.lock()
.expect("Failed to lock SSL stream mutex")
.get_mut()
.shutdown(how)
}
}

impl Read for NativeTlsStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.0
.lock()
.expect("Failed to lock SSL stream mutex")
.read(buf)
}
}

impl Write for NativeTlsStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0
.lock()
.expect("Failed to lock SSL stream mutex")
.write(buf)
}

fn flush(&mut self) -> std::io::Result<()> {
self.0
.lock()
.expect("Failed to lock SSL stream mutex")
.flush()
}
}

pub(crate) struct NativeTlsContext(native_tls::TlsAcceptor);

impl NativeTlsContext {
pub fn from_pem(
certificates: Vec<u8>,
private_key: Zeroizing<Vec<u8>>,
) -> Result<Self, Box<dyn Error + Send + Sync>> {
let identity = native_tls::Identity::from_pkcs8(&certificates, &private_key)?;
let acceptor = native_tls::TlsAcceptor::new(identity)?;
Ok(Self(acceptor))
}

pub fn accept(
&self,
stream: Connection,
) -> Result<NativeTlsStream, Box<dyn Error + Send + Sync + 'static>> {
let stream = self.0.accept(stream)?;
Ok(NativeTlsStream(Arc::new(Mutex::new(stream))))
}
}

impl From<NativeTlsStream> for RefinedStream {
fn from(stream: NativeTlsStream) -> Self {
RefinedStream::Https(stream)
}
}
54 changes: 45 additions & 9 deletions src/util/refined_tcp_stream.rs
Expand Up @@ -3,20 +3,32 @@ use std::io::{Read, Write};
use std::net::{Shutdown, SocketAddr};

use crate::connection::Connection;
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
use crate::ssl::SslStream;

pub(crate) enum Stream {
Http(Connection),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Https(SslStream),
}

impl Clone for Stream {
fn clone(&self) -> Self {
match self {
Stream::Http(tcp_stream) => Stream::Http(tcp_stream.try_clone().unwrap()),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Stream::Https(ssl_stream) => Stream::Https(ssl_stream.clone()),
}
}
Expand All @@ -32,23 +44,35 @@ impl Stream {
fn secure(&self) -> bool {
match self {
Stream::Http(_) => false,
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Stream::Https(_) => true,
}
}

fn peer_addr(&mut self) -> IoResult<Option<SocketAddr>> {
match self {
Stream::Http(tcp_stream) => tcp_stream.peer_addr(),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Stream::Https(ssl_stream) => ssl_stream.peer_addr(),
}
}

fn shutdown(&mut self, how: Shutdown) -> IoResult<()> {
match self {
Stream::Http(tcp_stream) => tcp_stream.shutdown(how),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Stream::Https(ssl_stream) => ssl_stream.shutdown(how),
}
}
Expand All @@ -58,7 +82,11 @@ impl Read for Stream {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
match self {
Stream::Http(tcp_stream) => tcp_stream.read(buf),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Stream::Https(ssl_stream) => ssl_stream.read(buf),
}
}
Expand All @@ -68,15 +96,23 @@ impl Write for Stream {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
match self {
Stream::Http(tcp_stream) => tcp_stream.write(buf),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Stream::Https(ssl_stream) => ssl_stream.write(buf),
}
}

fn flush(&mut self) -> IoResult<()> {
match self {
Stream::Http(tcp_stream) => tcp_stream.flush(),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Stream::Https(ssl_stream) => ssl_stream.flush(),
}
}
Expand Down

0 comments on commit e221563

Please sign in to comment.