diff --git a/Cargo.lock b/Cargo.lock index 8e3048b6..be3e0ae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,7 +161,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -172,7 +172,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -205,7 +205,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" dependencies = [ - "bindgen", + "bindgen 0.69.4", "cc", "cmake", "dunce", @@ -288,6 +288,26 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.72", +] + [[package]] name = "bindgen" version = "0.69.4" @@ -307,7 +327,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.72", "which", ] @@ -338,6 +358,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "boring" +version = "4.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795e8d96c51071a5479717ca96cb9741b7a41ee6469291a2f06a0b5ce80da52b" +dependencies = [ + "bitflags", + "boring-sys", + "foreign-types", + "libc", + "once_cell", +] + +[[package]] +name = "boring-sys" +version = "4.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feba0d697defa95b2fc181e0e37d55a0547df5c1f8a253af2c614d45bb597f80" +dependencies = [ + "bindgen 0.68.1", + "cmake", + "fs_extra", + "fslock", +] + [[package]] name = "brotli" version = "6.0.0" @@ -445,7 +490,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -586,7 +631,7 @@ checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -610,7 +655,16 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn", + "syn 2.0.72", +] + +[[package]] +name = "enum_primitive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +dependencies = [ + "num-traits 0.1.43", ] [[package]] @@ -673,6 +727,33 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -688,6 +769,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" @@ -747,7 +838,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -1340,6 +1431,28 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff943d68b88d0b87a6e0d58615e8fa07f9fd5a1319fa0a72efc1f62275c79a7" +dependencies = [ + "nom", + "nom-derive-impl", + "rustversion", +] + +[[package]] +name = "nom-derive-impl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b9a93a84b0d3ec3e70e02d332dc33ac6dfac9cde63e17fcb77172dededa62" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1356,6 +1469,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.2" @@ -1489,6 +1620,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pem" version = "3.0.4" @@ -1505,6 +1642,44 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -1522,7 +1697,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -1565,7 +1740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.72", ] [[package]] @@ -1597,7 +1772,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -1634,6 +1809,7 @@ dependencies = [ "async-compression", "base64 0.22.1", "bitflags", + "boring", "brotli", "bytes", "const_format", @@ -1680,7 +1856,9 @@ dependencies = [ "serde_json", "sync_wrapper 1.0.1", "tempfile", + "tls-parser", "tokio", + "tokio-boring", "tokio-graceful", "tokio-rustls", "tokio-test", @@ -1725,7 +1903,7 @@ dependencies = [ "proc-macro2", "quote", "rama", - "syn", + "syn 2.0.72", "trybuild", ] @@ -1858,6 +2036,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.34" @@ -2001,7 +2188,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2073,6 +2260,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -2116,6 +2309,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.72" @@ -2204,7 +2408,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2251,6 +2455,20 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls-parser" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409206e2de64edbf7ea99a44ac31680daf9ef1a57895fb3c5bd738a903691be0" +dependencies = [ + "enum_primitive", + "nom", + "nom-derive", + "phf", + "phf_codegen", + "rusticata-macros", +] + [[package]] name = "tokio" version = "1.39.2" @@ -2269,6 +2487,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-boring" +version = "4.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91079576e91ca33bfcc2d12aae1cf33844b9e13d98cfcf0b29ec921c41b9782" +dependencies = [ + "boring", + "boring-sys", + "once_cell", + "tokio", +] + [[package]] name = "tokio-graceful" version = "0.1.6" @@ -2290,7 +2520,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2456,7 +2686,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2618,7 +2848,7 @@ checksum = "e3ff69ef74c5a1ed36dfc2a56064d90d422ec83fd8fd0e9c470ceb3d731c506f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2663,7 +2893,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -2685,7 +2915,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2779,7 +3009,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2790,7 +3020,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -3005,7 +3235,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -3025,7 +3255,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 55bf83a9..67a1e099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -114,8 +117,8 @@ 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", @@ -123,6 +126,7 @@ telemetry = [ ] 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 } @@ -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 } @@ -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 } diff --git a/src/net/address/domain.rs b/src/net/address/domain.rs index 85904041..c7bd8eba 100644 --- a/src/net/address/domain.rs +++ b/src/net/address/domain.rs @@ -138,6 +138,20 @@ impl TryFrom for Domain { } } +impl<'a> TryFrom<&'a [u8]> for Domain { + type Error = OpaqueError; + + fn try_from(name: &'a [u8]) -> Result { + 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> for Domain { type Error = OpaqueError; diff --git a/src/tls/boring/mod.rs b/src/tls/boring/mod.rs new file mode 100644 index 00000000..4e2b929c --- /dev/null +++ b/src/tls/boring/mod.rs @@ -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::*; + } +} diff --git a/src/tls/boring/server/config.rs b/src/tls/boring/server/config.rs new file mode 100644 index 00000000..b966c56c --- /dev/null +++ b/src/tls/boring/server/config.rs @@ -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, + /// Disable the superificial verification in this Tls acceptor. + pub disable_verify: bool, + /// Write logging information to facilitate tls interception. + pub keylog_filename: Option, +} diff --git a/src/tls/boring/server/layer.rs b/src/tls/boring/server/layer.rs new file mode 100644 index 00000000..f4df7ea3 --- /dev/null +++ b/src/tls/boring/server/layer.rs @@ -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, + 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 Layer for TlsAcceptorLayer { + type Service = TlsAcceptorService; + + fn layer(&self, inner: S) -> Self::Service { + TlsAcceptorService::new(self.config.clone(), inner, self.store_client_hello) + } +} diff --git a/src/tls/boring/server/mod.rs b/src/tls/boring/server/mod.rs new file mode 100644 index 00000000..56700b19 --- /dev/null +++ b/src/tls/boring/server/mod.rs @@ -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; diff --git a/src/tls/boring/server/service.rs b/src/tls/boring/server/service.rs new file mode 100644 index 00000000..a1d950fe --- /dev/null +++ b/src/tls/boring/server/service.rs @@ -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 { + config: Arc, + store_client_hello: bool, + inner: S, +} + +impl TlsAcceptorService { + /// Creates a new [`TlsAcceptorService`]. + pub fn new(config: Arc, inner: S, store_client_hello: bool) -> Self { + Self { + config, + store_client_hello, + inner, + } + } + + define_inner_service_accessors!(); +} + +impl std::fmt::Debug for TlsAcceptorService { + 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 Clone for TlsAcceptorService +where + S: Clone, +{ + fn clone(&self) -> Self { + Self { + config: self.config.clone(), + store_client_hello: self.store_client_hello, + inner: self.inner.clone(), + } + } +} + +impl Service for TlsAcceptorService +where + T: Send + Sync + 'static, + IO: Stream + Unpin + 'static, + S: Service>, +{ + type Response = S::Response; + type Error = TlsAcceptorError; + + async fn serve(&self, mut ctx: Context, stream: IO) -> Result { + // 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 { + /// 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 fmt::Debug for TlsAcceptorError { + 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 std::fmt::Display for TlsAcceptorError +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 std::error::Error for TlsAcceptorError +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, + } + } +} diff --git a/src/tls/client/hello/boring.rs b/src/tls/client/hello/boring.rs new file mode 100644 index 00000000..c3f490af --- /dev/null +++ b/src/tls/client/hello/boring.rs @@ -0,0 +1,108 @@ +use super::ClientHelloExtension; +use crate::error::{ErrorContext, OpaqueError}; +use crate::net::address::Domain; +use crate::tls::boring::dep::boring; +use crate::tls::{ + ApplicationProtocol, CipherSuite, ECPointFormat, ProtocolVersion, SignatureScheme, + SupportedGroup, +}; + +#[inline] +fn u8_pair_to_u16_enum>((msb, lsb): (u8, u8)) -> T { + let n = ((msb as u16) << 8) | (lsb as u16); + T::from(n) +} + +impl<'ssl> TryFrom> for super::ClientHello { + type Error = OpaqueError; + + fn try_from(value: boring::ssl::ClientHello<'ssl>) -> Result { + let (_, msg) = + tls_parser::parse_tls_message_handshake(value.as_bytes()).map_err(|err| { + OpaqueError::from_display(format!("parse boring client hello: {err:?}")) + })?; + + let client_hello = match &msg { + tls_parser::TlsMessage::Handshake(tls_parser::TlsMessageHandshake::ClientHello( + hello, + )) => hello, + _ => return Err(OpaqueError::from_display("unexpected tls message parsed")), + }; + + let cipher_suites: Vec<_> = client_hello + .ciphers + .iter() + .map(|c| CipherSuite::from(c.0)) + .collect(); + + let mut extensions = Vec::new(); + if let Some(mut ext) = client_hello.ext { + while !ext.is_empty() { + let (new_ext, parsed_ext) = + tls_parser::parse_tls_extension(ext).map_err(|err| { + OpaqueError::from_display(format!( + "parse boring client hello extension: {err:?}" + )) + })?; + match parsed_ext { + tls_parser::TlsExtension::SNI(list) => { + if list.len() != 1 { + return Err(OpaqueError::from_display( + "one and only 1 server name expected", + )); + } + if list[0].0 != tls_parser::SNIType::HostName { + return Err(OpaqueError::from_display("unexpected SNI type")); + } + let domain = + Domain::try_from(list[0].1).context("parse server name as domain")?; + extensions.push(ClientHelloExtension::ServerName(domain)); + } + tls_parser::TlsExtension::EllipticCurves(v) => { + extensions.push(ClientHelloExtension::SupportedGroups( + v.iter().map(|c| SupportedGroup::from(c.0)).collect(), + )); + } + tls_parser::TlsExtension::EcPointFormats(s) => { + extensions.push(ClientHelloExtension::ECPointFormats( + s.iter().map(|u| ECPointFormat::from(*u)).collect(), + )); + } + tls_parser::TlsExtension::SignatureAlgorithms(v) => { + extensions.push(ClientHelloExtension::SignatureAlgorithms( + v.iter().map(|u| SignatureScheme::from(*u)).collect(), + )); + } + tls_parser::TlsExtension::ALPN(v) => { + extensions.push(ClientHelloExtension::ApplicationLayerProtocolNegotiation( + v.iter().map(|u| ApplicationProtocol::from(*u)).collect(), + )); + } + tls_parser::TlsExtension::SupportedVersions(v) => { + extensions.push(ClientHelloExtension::SupportedVersions( + v.iter().map(|v| ProtocolVersion::from(v.0)).collect(), + )); + } + _ => { + let total_size = ext.len() - new_ext.len(); + if total_size <= 4 { + return Err(OpaqueError::from_display( + "unexpected raw tls extension byte lenght", + )); + } + extensions.push(ClientHelloExtension::Opaque { + id: u8_pair_to_u16_enum((ext[0], ext[1])), + data: ext[4..total_size].to_vec(), + }); + } + } + ext = new_ext; + } + } + + Ok(Self { + cipher_suites, + extensions, + }) + } +} diff --git a/src/tls/client/hello/mod.rs b/src/tls/client/hello/mod.rs index bd9e04ae..3d0e7e63 100644 --- a/src/tls/client/hello/mod.rs +++ b/src/tls/client/hello/mod.rs @@ -8,6 +8,9 @@ use crate::{ mod rustls; +#[cfg(feature = "boring")] +mod boring; + #[derive(Debug, Clone)] /// When a client first connects to a server, it is required to send /// the ClientHello as its first message. diff --git a/src/tls/enums/mod.rs b/src/tls/enums/mod.rs index cdf9a765..c3654870 100644 --- a/src/tls/enums/mod.rs +++ b/src/tls/enums/mod.rs @@ -1,6 +1,8 @@ #![allow(missing_docs)] #![allow(non_camel_case_types)] +use crate::error::OpaqueError; + mod rustls; // https://www.rfc-editor.org/rfc/rfc8701.html @@ -39,7 +41,7 @@ macro_rules! enum_builder { ) => { $(#[$comment])* #[non_exhaustive] - #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] $enum_vis enum $enum_name { $( $enum_var),* ,Unknown(u8) @@ -80,7 +82,7 @@ macro_rules! enum_builder { ) => { $(#[$comment])* #[non_exhaustive] - #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] $enum_vis enum $enum_name { $( $enum_var),* ,Unknown(u16) @@ -895,6 +897,22 @@ enum_builder! { } } +impl ApplicationProtocol { + pub fn encode_wire_format(&self, w: &mut impl std::io::Write) -> std::io::Result { + let b = self.as_bytes(); + if b.len() > 255 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + OpaqueError::from_display("application protocol is too large"), + )); + } + + w.write_all(&[b.len() as u8])?; + w.write_all(b)?; + Ok(b.len() + 1) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/tls/mod.rs b/src/tls/mod.rs index 16c50753..7fb70b43 100644 --- a/src/tls/mod.rs +++ b/src/tls/mod.rs @@ -10,6 +10,9 @@ pub use enums::{ pub mod client; pub mod rustls; +#[cfg(feature = "boring")] +pub mod boring; + #[derive(Debug, Clone)] /// Context information that can be provided `https` connectors`, /// to configure the connection in function on an https tunnel. diff --git a/src/tls/rustls/server/service.rs b/src/tls/rustls/server/service.rs index 3254ce06..64b8d497 100644 --- a/src/tls/rustls/server/service.rs +++ b/src/tls/rustls/server/service.rs @@ -4,13 +4,12 @@ use crate::{ tls::{ client::ClientHello, rustls::dep::{ - rustls::server::Acceptor, + rustls::{server::Acceptor, ServerConfig}, tokio_rustls::{server::TlsStream, LazyConfigAcceptor, TlsAcceptor}, }, SecureTransport, }, }; -use rustls::ServerConfig; use std::{fmt, sync::Arc}; use super::{ServerConfigProvider, TlsClientConfigHandler}; @@ -36,9 +35,13 @@ impl TlsAcceptorService { define_inner_service_accessors!(); } -impl std::fmt::Debug for TlsAcceptorService { +impl std::fmt::Debug for TlsAcceptorService { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TlsAcceptorService").finish() + f.debug_struct("TlsAcceptorService") + .field("config", &self.config) + .field("client_config_handler", &self.client_config_handler) + .field("inner", &self.inner) + .finish() } }