Skip to content

Commit

Permalink
add initial code for the tls boring acceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
GlenDC committed Aug 5, 2024
1 parent 04d5a74 commit 7233839
Show file tree
Hide file tree
Showing 13 changed files with 712 additions and 31 deletions.
276 changes: 253 additions & 23 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ http-range-header = "0.4.0"
httpdate = "1.0"
hyper = "1.4"
hyper-util = "0.1.6"
boring = "4.9.1"
tokio-boring = "4.9.1"
tls-parser = "0.11.0"
ipnet = "2.9.0"
itertools = "0.13.0"
mime = "0.3.17"
Expand Down Expand Up @@ -114,15 +117,16 @@ authors = { workspace = true }
rust-version = { workspace = true }

[features]
default = ["compression"]
full = ["telemetry", "compression"]
default = ["compression", "boring"]
full = ["telemetry", "compression", "boring"]
telemetry = [
"dep:opentelemetry",
"dep:opentelemetry_sdk",
"dep:opentelemetry-semantic-conventions",
]
compression = ["dep:async-compression"]
rustls-ring = ["tokio-rustls/ring", "rustls/ring"]
boring = ["dep:boring", "dep:tokio-boring", "dep:tls-parser"]

[build-dependencies]
rustversion = { workspace = true }
Expand All @@ -138,6 +142,7 @@ async-compression = { workspace = true, features = [
], optional = true }
base64 = { workspace = true }
bitflags = { workspace = true }
boring = { workspace = true, optional = true }
bytes = { workspace = true }
const_format = { workspace = true }
futures-core = { workspace = true }
Expand Down Expand Up @@ -176,7 +181,9 @@ serde = { workspace = true, features = ["derive"] }
serde_html_form = { workspace = true }
serde_json = { workspace = true }
sync_wrapper = { workspace = true }
tls-parser = { workspace = true, optional = true }
tokio = { workspace = true, features = ["macros", "fs", "io-std"] }
tokio-boring = { workspace = true, optional = true }
tokio-graceful = { workspace = true }
tokio-rustls = { workspace = true }
tokio-util = { workspace = true }
Expand Down
14 changes: 14 additions & 0 deletions src/net/address/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@ impl TryFrom<String> for Domain {
}
}

impl<'a> TryFrom<&'a [u8]> for Domain {
type Error = OpaqueError;

fn try_from(name: &'a [u8]) -> Result<Self, Self::Error> {
if is_valid_name(name) {
Ok(Self(Cow::Owned(
String::from_utf8(name.to_vec()).context("convert domain bytes to utf-8 string")?,
)))
} else {
Err(OpaqueError::from_display("invalid domain"))
}
}
}

impl TryFrom<Vec<u8>> for Domain {
type Error = OpaqueError;

Expand Down
24 changes: 24 additions & 0 deletions src/tls/boring/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! boring based TLS support for rama.

pub mod server;

pub mod dep {
//! Dependencies for rama boring modules.
//!
//! Exported for your convenience.

pub mod boring {
//! Re-export of the [`boring`] crate.
//!
//! [`boring`]: https://docs.rs/boring

pub use boring::*;
}

pub mod tokio_boring {
//! Full Re-export of the [`tokio-boring`] crate.
//!
//! [`tokio-boring`]: https://docs.rs/tokio-boring
pub use tokio_boring::*;
}
}
12 changes: 12 additions & 0 deletions src/tls/boring/server/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::tls::ApplicationProtocol;

#[derive(Clone, Debug, Default)]
/// Common configuration for a set of server sessions.
pub struct ServerConfig {
/// Set the ALPN protocols supported by the service's inner application service.
pub alpn_protocols: Vec<ApplicationProtocol>,
/// Disable the superificial verification in this Tls acceptor.
pub disable_verify: bool,
/// Write logging information to facilitate tls interception.
pub keylog_filename: Option<String>,
}
35 changes: 35 additions & 0 deletions src/tls/boring/server/layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use super::{ServerConfig, TlsAcceptorService};
use crate::service::Layer;
use std::sync::Arc;

/// A [`Layer`] which wraps the given service with a [`TlsAcceptorService`].
#[derive(Debug, Clone)]
pub struct TlsAcceptorLayer {
config: Arc<ServerConfig>,
store_client_hello: bool,
}

impl TlsAcceptorLayer {
/// Creates a new [`TlsAcceptorLayer`] using the given [`ServerConfig`],
/// which is used to configure the inner TLS acceptor.
pub fn new(config: ServerConfig) -> Self {
Self {
config: Arc::new(config),
store_client_hello: false,
}
}

/// Set that the client hello should be stored
pub fn with_store_client_hello(mut self, store: bool) -> Self {
self.store_client_hello = store;
self
}
}

impl<S> Layer<S> for TlsAcceptorLayer {
type Service = TlsAcceptorService<S>;

fn layer(&self, inner: S) -> Self::Service {
TlsAcceptorService::new(self.config.clone(), inner, self.store_client_hello)
}
}
23 changes: 23 additions & 0 deletions src/tls/boring/server/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Boring(ssl) server support for Rama.
//!
//! This module provides a [`TlsAcceptorLayer`] to accept TLS connections and a [`TlsAcceptorService`] to handle them.
//!
//! # Examples
//!
//! See the [Examples Directory](https://github.com/plabayo/rama/tree/main/examples):
//!
//! - [/examples/tls_boring_termination.rs](https://github.com/plabayo/rama/tree/main/examples/tls_boring_termination.rs):
//! Spawns a mini handmade http server, as well as a TLS termination proxy, forwarding the
//! plain text stream to the first. TODO

mod config;
#[doc(inline)]
pub use config::ServerConfig;

mod service;
#[doc(inline)]
pub use service::{TlsAcceptorError, TlsAcceptorService};

mod layer;
#[doc(inline)]
pub use layer::TlsAcceptorLayer;
201 changes: 201 additions & 0 deletions src/tls/boring/server/service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use super::ServerConfig;
use crate::{
error::{ErrorContext, ErrorExt, OpaqueError},
net::stream::Stream,
service::{Context, Service},
tls::{
boring::dep::{
boring::ssl::{SslAcceptor, SslMethod, SslVerifyMode},
tokio_boring::SslStream,
},
client::ClientHello,
SecureTransport,
},
};
use parking_lot::Mutex;
use std::{fmt, sync::Arc};

/// A [`Service`] which accepts TLS connections and delegates the underlying transport
/// stream to the given service.
pub struct TlsAcceptorService<S> {
config: Arc<ServerConfig>,
store_client_hello: bool,
inner: S,
}

impl<S> TlsAcceptorService<S> {
/// Creates a new [`TlsAcceptorService`].
pub fn new(config: Arc<ServerConfig>, inner: S, store_client_hello: bool) -> Self {
Self {
config,
store_client_hello,
inner,
}
}

define_inner_service_accessors!();
}

impl<S: std::fmt::Debug> std::fmt::Debug for TlsAcceptorService<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TlsAcceptorService")
.field("config", &self.config)
.field("store_client_hello", &self.store_client_hello)
.field("inner", &self.inner)
.finish()
}
}

impl<S> Clone for TlsAcceptorService<S>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
store_client_hello: self.store_client_hello,
inner: self.inner.clone(),
}
}
}

impl<T, S, IO> Service<T, IO> for TlsAcceptorService<S>
where
T: Send + Sync + 'static,
IO: Stream + Unpin + 'static,
S: Service<T, SslStream<IO>>,
{
type Response = S::Response;
type Error = TlsAcceptorError<S::Error>;

async fn serve(&self, mut ctx: Context<T>, stream: IO) -> Result<Self::Response, Self::Error> {
// let acceptor = TlsAcceptor::from(self.config.clone());

let mut acceptor_builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls_server())
.context("create boring ssl acceptor")
.map_err(TlsAcceptorError::Accept)?;

acceptor_builder.set_grease_enabled(true);
acceptor_builder
.set_default_verify_paths()
.context("build boring ssl acceptor: set default verify paths")
.map_err(TlsAcceptorError::Accept)?;

if self.config.disable_verify {
acceptor_builder.set_verify_callback(SslVerifyMode::NONE, |_valid, _store| true);
}

let mut maybe_client_hello = if self.store_client_hello {
let maybe_client_hello = Arc::new(Mutex::new(None));
let cb_maybe_client_hello = maybe_client_hello.clone();
acceptor_builder.set_select_certificate_callback(move |boring_client_hello| {
let maybe_client_hello = match ClientHello::try_from(boring_client_hello) {
Ok(ch) => Some(ch),
Err(err) => {
tracing::warn!(err = %err, "failed to extract boringssl client hello");
None
}
};
*cb_maybe_client_hello.lock() = maybe_client_hello;
Ok(())
});
Some(maybe_client_hello)
} else {
None
};

if !self.config.alpn_protocols.is_empty() {
let mut buf = vec![];
for alpn in &self.config.alpn_protocols {
alpn.encode_wire_format(&mut buf)
.context("build boring ssl acceptor: encode alpn")
.map_err(TlsAcceptorError::Accept)?;
}
acceptor_builder
.set_alpn_protos(&buf[..])
.context("build boring ssl acceptor: set alpn")
.map_err(TlsAcceptorError::Accept)?;
}

if let Some(keylog_filename) = &self.config.keylog_filename {
// open file in append mode and write keylog to it with callback
let file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(keylog_filename)
.context("build boring ssl acceptor: set keylog: open file")
.map_err(TlsAcceptorError::Accept)?;
acceptor_builder.set_keylog_callback(move |_, line| {
use std::io::Write;
let line = format!("{}\n", line);
let mut file = &file;
let _ = file.write_all(line.as_bytes());
});
}

let acceptor = acceptor_builder.build();

let stream = tokio_boring::accept(&acceptor, stream)
.await
.map_err(|err| match err.as_io_error() {
Some(err) => OpaqueError::from_display(err.to_string())
.context("boring ssl acceptor: accept"),
None => OpaqueError::from_display("boring ssl acceptor: accept"),
})
.map_err(TlsAcceptorError::Accept)?;

let secure_transport = maybe_client_hello
.take()
.and_then(|maybe_client_hello| maybe_client_hello.lock().take())
.map(SecureTransport::with_client_hello)
.unwrap_or_default();
ctx.insert(secure_transport);

self.inner
.serve(ctx, stream)
.await
.map_err(TlsAcceptorError::Service)
}
}

/// Errors that can happen when using [`TlsAcceptorService`].
pub enum TlsAcceptorError<E> {
/// An error occurred while accepting a TLS connection.
Accept(OpaqueError),
/// An error occurred while serving the underlying transport stream
/// using the inner service.
Service(E),
}

impl<E: fmt::Debug> fmt::Debug for TlsAcceptorError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Accept(err) => write!(f, "TlsAcceptorError::Accept({err:?})"),
Self::Service(err) => write!(f, "TlsAcceptorError::Service({err:?})"),
}
}
}

impl<E> std::fmt::Display for TlsAcceptorError<E>
where
E: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TlsAcceptorError::Accept(e) => write!(f, "accept error: {}", e),
TlsAcceptorError::Service(e) => write!(f, "service error: {}", e),
}
}
}

impl<E> std::error::Error for TlsAcceptorError<E>
where
E: std::fmt::Debug + std::fmt::Display,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
TlsAcceptorError::Accept(e) => Some(e),
TlsAcceptorError::Service(_) => None,
}
}
}
Loading

0 comments on commit 7233839

Please sign in to comment.