diff --git a/src/gencert.rs b/src/cert.rs similarity index 56% rename from src/gencert.rs rename to src/cert.rs index 33ff027..8aac2ee 100644 --- a/src/gencert.rs +++ b/src/cert.rs @@ -6,16 +6,29 @@ //! methods on it or anything. //! //! [`Server::set_certificate_generation_mode()`]: crate::Server::set_certificate_generation_mode() -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, Context, Result, ensure}; +#[cfg(feature = "certgen")] +use anyhow::bail; + use rustls::ServerConfig; -use std::fs; -use std::io::{stdin, stdout, Write}; -use std::path::Path; +use std::{path::PathBuf, sync::Arc}; +#[cfg(feature = "certgen")] +use std::{ + fs, + io::{stdin, stdout, Write}, + path::Path, +}; + +use rustls::internal::msgs::handshake::DigitallySignedStruct; +use tokio_rustls::rustls; +use rustls::*; #[derive(Clone, Debug)] -/// The mode to use for determining the domains to use for a new certificate. Only -/// applies to [`CertGenMode::gencert()`]. +#[cfg(feature = "certgen")] +/// The mode to use for determining the domains to use for a new certificate. +/// +/// Used to configure a [`Server`] using [`Server::set_certificate_generation_mode()`] pub enum CertGenMode { /// Do not generate any certificates. Error if not available. @@ -28,6 +41,7 @@ pub enum CertGenMode { Interactive, } +#[cfg(feature = "certgen")] impl CertGenMode { /// Generate a new self-signed certificate /// @@ -83,7 +97,7 @@ impl CertGenMode { /// /// Returns an error if a certificate is not found **and** cannot be generated. pub fn load_or_generate(self, to: &mut ServerConfig, cert: impl AsRef, key: impl AsRef) -> Result<()> { - match (crate::load_cert_chain(&cert.as_ref().into()), crate::load_key(&key.as_ref().into())) { + match (load_cert_chain(&cert.as_ref().into()), load_key(&key.as_ref().into())) { (Ok(cert_chain), Ok(key)) => { to.set_single_cert(cert_chain, key) .context("Failed to use loaded TLS certificate")?; @@ -101,6 +115,7 @@ impl CertGenMode { } } +#[cfg(feature = "certgen")] /// Attempt to get domains by prompting the user /// /// Guaranteed to return at least one domain. The user is provided `localhost` as a @@ -108,7 +123,7 @@ impl CertGenMode { /// /// ## Panics /// Panics if reading from stdin or writing to stdout returns an error. -pub fn prompt_domains() -> Vec { +fn prompt_domains() -> Vec { let mut domains = Vec::with_capacity(1); let mut input = String::with_capacity(8); println!("Now generating self-signed certificate..."); @@ -131,3 +146,107 @@ pub fn prompt_domains() -> Vec { } } } + +pub fn tls_config( + cert_path: &PathBuf, + key_path: &PathBuf, + #[cfg(feature = "certgen")] + mode: CertGenMode, +) -> Result> { + let mut config = ServerConfig::new(AllowAnonOrSelfsignedClient::new()); + + #[cfg(feature = "certgen")] + mode.load_or_generate(&mut config, cert_path, key_path)?; + + #[cfg(not(feature = "certgen"))] { + let cert_chain = load_cert_chain(cert_path) + .context("Failed to load TLS certificate")?; + let key = load_key(key_path) + .context("Failed to load TLS key")?; + config.set_single_cert(cert_chain, key) + .context("Failed to use loaded TLS certificate")?; + } + + Ok(config.into()) +} + +fn load_cert_chain(cert_path: &PathBuf) -> Result> { + let certs = std::fs::File::open(cert_path) + .with_context(|| format!("Failed to open `{:?}`", cert_path))?; + let mut certs = std::io::BufReader::new(certs); + let certs = rustls::internal::pemfile::certs(&mut certs) + .map_err(|_| anyhow!("failed to load certs `{:?}`", cert_path))?; + + Ok(certs) +} + +fn load_key(key_path: &PathBuf) -> Result { + let keys = std::fs::File::open(key_path) + .with_context(|| format!("Failed to open `{:?}`", key_path))?; + let mut keys = std::io::BufReader::new(keys); + let mut keys = rustls::internal::pemfile::pkcs8_private_keys(&mut keys) + .map_err(|_| anyhow!("failed to load key `{:?}`", key_path))?; + + ensure!(!keys.is_empty(), "no key found"); + + let key = keys.swap_remove(0); + + Ok(key) +} + +/// A client cert verifier that accepts all connections +/// +/// Unfortunately, rustls doesn't provide a ClientCertVerifier that accepts self-signed +/// certificates, so we need to implement this ourselves. +struct AllowAnonOrSelfsignedClient { } + +impl AllowAnonOrSelfsignedClient { + + /// Create a new verifier + fn new() -> Arc { + Arc::new(Self {}) + } + +} + +impl ClientCertVerifier for AllowAnonOrSelfsignedClient { + + fn client_auth_root_subjects( + &self, + _: Option<&webpki::DNSName> + ) -> Option { + Some(Vec::new()) + } + + fn client_auth_mandatory(&self, _sni: Option<&webpki::DNSName>) -> Option { + Some(false) + } + + // the below methods are a hack until webpki doesn't break with certain certs + + fn verify_client_cert( + &self, + _: &[Certificate], + _: Option<&webpki::DNSName> + ) -> Result { + Ok(ClientCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 829f354..31d020d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,19 +102,15 @@ use tokio::{ }; #[cfg(feature = "ratelimiting")] use tokio::time::interval; -#[cfg(feature = "gemini_srv")] -use rustls::ClientCertVerifier; -#[cfg(feature = "gemini_srv")] -use rustls::internal::msgs::handshake::DigitallySignedStruct; -#[cfg(feature = "gemini_srv")] -use tokio_rustls::{rustls, TlsAcceptor}; -#[cfg(feature = "gemini_srv")] -use rustls::*; use anyhow::*; use crate::util::opt_timeout; use routing::RoutingNode; #[cfg(feature = "ratelimiting")] use ratelimiting::RateLimiter; +#[cfg(feature = "gemini_srv")] +use tokio_rustls::TlsAcceptor; +#[cfg(feature = "gemini_srv")] +use rustls::Session; pub mod types; pub mod util; @@ -124,13 +120,13 @@ pub mod handling; pub mod ratelimiting; #[cfg(feature = "user_management")] pub mod user_management; -#[cfg(feature = "certgen")] -pub mod gencert; +#[cfg(feature = "gemini_srv")] +mod cert; #[cfg(feature="user_management")] use user_management::UserManager; #[cfg(feature = "certgen")] -use gencert::CertGenMode; +pub use cert::CertGenMode; pub use uriparse as uri; pub use types::*; @@ -723,7 +719,7 @@ impl Server { fn build(mut self) -> Result { #[cfg(feature = "gemini_srv")] - let config = tls_config( + let config = cert::tls_config( &self.cert_path, &self.key_path, #[cfg(feature="certgen")] @@ -825,115 +821,5 @@ async fn prune_ratelimit_log(rate_limits: Arc>>) } } -#[cfg(feature = "gemini_srv")] -fn tls_config( - cert_path: &PathBuf, - key_path: &PathBuf, - #[cfg(feature = "certgen")] - mode: CertGenMode, -) -> Result> { - let mut config = ServerConfig::new(AllowAnonOrSelfsignedClient::new()); - - #[cfg(feature = "certgen")] - mode.load_or_generate(&mut config, cert_path, key_path)?; - - #[cfg(not(feature = "certgen"))] { - let cert_chain = load_cert_chain(cert_path) - .context("Failed to load TLS certificate")?; - let key = load_key(key_path) - .context("Failed to load TLS key")?; - config.set_single_cert(cert_chain, key) - .context("Failed to use loaded TLS certificate")?; - } - - Ok(config.into()) -} - -#[cfg(feature = "gemini_srv")] -fn load_cert_chain(cert_path: &PathBuf) -> Result> { - let certs = std::fs::File::open(cert_path) - .with_context(|| format!("Failed to open `{:?}`", cert_path))?; - let mut certs = std::io::BufReader::new(certs); - let certs = rustls::internal::pemfile::certs(&mut certs) - .map_err(|_| anyhow!("failed to load certs `{:?}`", cert_path))?; - - Ok(certs) -} - -#[cfg(feature = "gemini_srv")] -fn load_key(key_path: &PathBuf) -> Result { - let keys = std::fs::File::open(key_path) - .with_context(|| format!("Failed to open `{:?}`", key_path))?; - let mut keys = std::io::BufReader::new(keys); - let mut keys = rustls::internal::pemfile::pkcs8_private_keys(&mut keys) - .map_err(|_| anyhow!("failed to load key `{:?}`", key_path))?; - - ensure!(!keys.is_empty(), "no key found"); - - let key = keys.swap_remove(0); - - Ok(key) -} - -#[cfg(feature = "gemini_srv")] -/// A client cert verifier that accepts all connections -/// -/// Unfortunately, rustls doesn't provide a ClientCertVerifier that accepts self-signed -/// certificates, so we need to implement this ourselves. -struct AllowAnonOrSelfsignedClient { } - -#[cfg(feature = "gemini_srv")] -impl AllowAnonOrSelfsignedClient { - - /// Create a new verifier - fn new() -> Arc { - Arc::new(Self {}) - } - -} - -#[cfg(feature = "gemini_srv")] -impl ClientCertVerifier for AllowAnonOrSelfsignedClient { - - fn client_auth_root_subjects( - &self, - _: Option<&webpki::DNSName> - ) -> Option { - Some(Vec::new()) - } - - fn client_auth_mandatory(&self, _sni: Option<&webpki::DNSName>) -> Option { - Some(false) - } - - // the below methods are a hack until webpki doesn't break with certain certs - - fn verify_client_cert( - &self, - _: &[Certificate], - _: Option<&webpki::DNSName> - ) -> Result { - Ok(ClientCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &Certificate, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &Certificate, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } -} - #[cfg(feature = "ratelimiting")] enum Never {}