Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ssl-native-tls feature allowing using native-tls as an SSL backend #241

Merged
merged 2 commits into from May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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