diff --git a/Cargo.lock b/Cargo.lock index a144bea..65d1134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,12 @@ dependencies = [ "paste", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bindgen" version = "0.69.4" @@ -254,6 +260,7 @@ dependencies = [ "log", "openssl", "rustls", + "rustls-pemfile", "serde_yml", "threadpool", ] @@ -613,6 +620,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64", + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index 1645e9f..89766a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,14 @@ edition = "2021" [dependencies] openssl = { version = "0.10.66", optional = true } rustls = { version = "0.23.13", optional = true } +rustls-pemfile = { version = "2.1.3", optional = true } serde_yml = "0.0.12" log = "0.4.22" colog = "1.3.0" threadpool = "1.8.1" [features] -default = ["use-openssl"] +# default = ["use-openssl"] +default = ["use-rustls"] use-openssl = ["dep:openssl"] -use-rustls = ["dep:rustls"] \ No newline at end of file +use-rustls = ["dep:rustls", "dep:rustls-pemfile"] \ No newline at end of file diff --git a/src/flowgate/server.rs b/src/flowgate/server.rs index 1e2cc1f..d660413 100644 --- a/src/flowgate/server.rs +++ b/src/flowgate/server.rs @@ -1,7 +1,6 @@ use std::{io::{Read, Write}, net::{Shutdown, SocketAddr, TcpListener}, sync::Arc, thread, time::Duration}; use log::info; -use openssl::ssl::{NameType, SniError, SslAcceptor, SslAlert, SslMethod, SslRef}; use threadpool::ThreadPool; use super::Config; @@ -15,7 +14,7 @@ impl FlowgateServer { FlowgateServer { config: Arc::new(config) } } - pub fn start(self) { + pub fn start(&self) { thread::spawn({ let config = Arc::clone(&self.config); @@ -67,9 +66,12 @@ impl FlowgateServer { Some(()) } + #[cfg(feature = "use-openssl")] pub fn run_https( config: Arc ) -> Option<()> { + use openssl::ssl::{NameType, SniError, SslAcceptor, SslAlert, SslMethod, SslRef}; + let listener = TcpListener::bind(&config.https_host).ok()?; let mut cert = SslAcceptor::mozilla_intermediate(SslMethod::tls()).ok()?; @@ -119,6 +121,62 @@ impl FlowgateServer { Some(()) } + #[cfg(feature = "use-rustls")] + pub fn run_https( + config: Arc + ) -> Option<()> { + use std::sync::Arc; + use rustls::{server::ResolvesServerCertUsingSni, ServerConfig}; + use super::ssl_cert::AdoptedConnection; + + let listener = TcpListener::bind(&config.https_host).ok()?; + + let mut cert_resolver = ResolvesServerCertUsingSni::new(); + + for site in config.sites.iter() { + if let Some(cert) = site.ssl { + cert_resolver.add(&site.domain, cert.get_certified_key()); + } + } + + let mut tls_config = Arc::new( + ServerConfig::builder() + .with_no_client_auth() + .with_cert_resolver(Arc::new(cert_resolver)) + ); + + let pool = ThreadPool::new(10); + + info!("HTTPS server runned on {}", &config.https_host); + + for stream in listener.incoming() { + pool.execute({ + let config = config.clone(); + let tls_config = tls_config.clone(); + + move || { + let Ok(mut stream) = stream else { return }; + + let Ok(_) = stream.set_write_timeout(Some(Duration::from_secs(10))) else { return }; + let Ok(_) = stream.set_read_timeout(Some(Duration::from_secs(10))) else { return }; + + let Ok(addr) = stream.peer_addr() else { return }; + + let Some(mut stream) = AdoptedConnection::from_config(tls_config, stream) else { return }; + + Self::accept_stream( + config, + &mut stream, + addr, + true + ); + } + }); + } + + Some(()) + } + pub fn accept_stream( config: Arc, stream: &mut (impl Read + Write), diff --git a/src/flowgate/ssl_cert.rs b/src/flowgate/ssl_cert.rs index 6481ad1..2a43cc3 100644 --- a/src/flowgate/ssl_cert.rs +++ b/src/flowgate/ssl_cert.rs @@ -1,11 +1,16 @@ -use openssl::ssl::{SslContext, SslFiletype, SslMethod}; +#[cfg(feature = "use-openssl")] +use openssl::ssl::SslContext; +#[cfg(feature = "use-openssl")] #[derive(Clone)] pub struct SslCert { - context: Option, + context: SslContext, } +#[cfg(feature = "use-openssl")] fn generate_ctx(cert_file: &str, key_file: &str) -> Option { + use openssl::ssl::{SslFiletype, SslMethod}; + let mut ctx = SslContext::builder(SslMethod::tls()).ok()?; ctx.set_private_key_file(&key_file, SslFiletype::PEM).ok()?; ctx.set_certificate_file(&cert_file, SslFiletype::PEM).ok()?; @@ -13,19 +18,94 @@ fn generate_ctx(cert_file: &str, key_file: &str) -> Option { Some(ctx.build()) } +#[cfg(feature = "use-openssl")] impl SslCert { pub fn new(cert_file: &str, key_file: &str) -> Option { Some(SslCert { - context: match generate_ctx(cert_file, key_file) { - Some(i) => Some(i), - None => { - return None; - } - } + context: generate_ctx(cert_file, key_file)? }) } pub fn get_context(&self) -> SslContext { - self.context.as_ref().unwrap().clone() + self.context.clone() } -} \ No newline at end of file +} + +#[cfg(feature = "use-rustls")] +use rustls::{sign::CertifiedKey, server::Acceptor, ServerConfig, ServerConnection}; +#[cfg(feature = "use-rustls")] +use std::{net::TcpStream, sync::Arc}; + +#[cfg(feature = "use-rustls")] +#[derive(Clone)] +pub struct SslCert { + cert_key: CertifiedKey, +} + +#[cfg(feature = "use-rustls")] +fn generate_cert_key(cert_file: &str, key_file: &str) -> Option { + use rustls::crypto::CryptoProvider; + use std::fs::File; + use std::io::BufReader; + + let key = rustls_pemfile::private_key(&mut BufReader::new(File::open(key_file).ok()?)).ok()??; + let key = CryptoProvider::get_default().unwrap().key_provider.load_private_key(key).ok()?; + + let cert = + rustls_pemfile::public_keys(&mut BufReader::new(File::open(cert_file).ok()?)) + .map(|o| o.unwrap().to_vec().into()) + .collect::>(); + Some(CertifiedKey::new(cert, key)) +} + +#[cfg(feature = "use-rustls")] +impl SslCert { + pub fn new(cert_file: &str, key_file: &str) -> Option { + Some(SslCert { + cert_key: generate_cert_key(cert_file, key_file)?, + }) + } + + pub fn get_certified_key(&self) -> CertifiedKey { + self.cert_key.clone() + } +} + +#[cfg(feature = "use-rustls")] +pub struct AdoptedConnection { + server_connection: ServerConnection, + stream: TcpStream +} + +#[cfg(feature = "use-rustls")] +impl AdoptedConnection { + pub fn new( + server_connection: ServerConnection, + stream: TcpStream + ) -> AdoptedConnection { + AdoptedConnection { + server_connection, + stream + } + } + + pub fn from_config( + server_config: Arc, + mut stream: TcpStream + ) -> Option { + let mut acceptor = Acceptor::default(); + let accepted = loop { + acceptor.read_tls(&mut stream).ok()?; + if let Some(accepted) = acceptor.accept().ok()? { + break accepted; + } + }; + + Some(AdoptedConnection { + server_connection: accepted.into_connection(server_config).ok()?, + stream + }) + } +} + +// TODO: implement Read and Write to AdoptedConnection \ No newline at end of file