diff options
author | metamuffin <metamuffin@disroot.org> | 2024-08-18 22:02:03 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-08-18 22:02:03 +0200 |
commit | 311d8dc828446d4cfd936706782d2d6b22ed5d86 (patch) | |
tree | 256d785ff19a2346f7db79e6ebeb0e5c2508e80a | |
parent | f46b05a0afa776b39764bb3ddf8ecab6cde13a2c (diff) | |
download | gnix-311d8dc828446d4cfd936706782d2d6b22ed5d86.tar gnix-311d8dc828446d4cfd936706782d2d6b22ed5d86.tar.bz2 gnix-311d8dc828446d4cfd936706782d2d6b22ed5d86.tar.zst |
new cert loading method
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/certs.rs | 86 | ||||
-rw-r--r-- | src/config.rs | 4 | ||||
-rw-r--r-- | src/main.rs | 35 |
5 files changed, 104 insertions, 28 deletions
@@ -607,8 +607,10 @@ dependencies = [ "percent-encoding", "pin-project", "rand", + "ring", "rustls", "rustls-pemfile", + "rustls-webpki", "serde", "serde_yaml", "thiserror", @@ -1280,6 +1282,7 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -16,8 +16,10 @@ hex = "0.4.3" # TLS rustls-pemfile = "2.1.3" -rustls = "0.23.12" +rustls = { version="0.23.12", default-features = false, features=["ring"] } tokio-rustls = "0.26.0" +rustls-webpki = "0.102.6" +ring = "0.17.8" # Async stuff tokio = { version = "1.39.2", features = ["full"] } diff --git a/src/certs.rs b/src/certs.rs new file mode 100644 index 0000000..c1d0ad1 --- /dev/null +++ b/src/certs.rs @@ -0,0 +1,86 @@ +use anyhow::{anyhow, Context, Result}; +use log::debug; +use rustls::{ + crypto::CryptoProvider, + pki_types::{CertificateDer, PrivateKeyDer}, + server::{ClientHello, ResolvesServerCert}, + sign::CertifiedKey, +}; +use std::{ + collections::HashMap, + fs::File, + io::BufReader, + path::{Path, PathBuf}, + sync::Arc, +}; +use webpki::EndEntityCert; + +#[derive(Debug)] +pub struct CertPool { + provider: &'static Arc<CryptoProvider>, + domains: HashMap<String, Arc<CertifiedKey>>, +} + +impl Default for CertPool { + fn default() -> Self { + Self { + provider: CryptoProvider::get_default().unwrap(), + domains: Default::default(), + } + } +} + +impl CertPool { + pub fn load(roots: &[PathBuf]) -> Result<Self> { + let mut s = Self::default(); + for r in roots { + s.load_recursive(&r)?; + } + Ok(s) + } + + fn load_recursive(&mut self, path: &Path) -> Result<()> { + if !path.is_dir() { + return Ok(()); + } + let keypath = path.join("privkey.pem"); + let certpath = if path.join("fullchain.pem").exists() { + path.join("fullchain.pem") + } else { + path.join("cert.pem") + }; + if keypath.exists() && certpath.exists() { + let certs = load_certs(&certpath)?; + let key = load_private_key(&keypath)?; + let skey = self.provider.key_provider.load_private_key(key)?; + for c in &certs { + let eec = EndEntityCert::try_from(c).unwrap(); + for name in eec.valid_dns_names() { + debug!("loaded key for {name:?}"); + let ck = CertifiedKey::new(certs.clone(), skey.clone()); + self.domains.insert(name.to_owned(), Arc::new(ck)); + } + } + } + Ok(()) + } +} + +impl ResolvesServerCert for CertPool { + fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> { + Some(self.domains.get(client_hello.server_name()?)?.clone()) + } +} + +fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> { + let mut reader = BufReader::new(File::open(path).context("reading tls certs")?); + let certs = rustls_pemfile::certs(&mut reader) + .try_collect::<Vec<_>>() + .context("parsing tls certs")?; + Ok(certs) +} +fn load_private_key(path: &Path) -> Result<PrivateKeyDer<'static>> { + let mut reader = BufReader::new(File::open(path).context("reading tls private key")?); + let keys = rustls_pemfile::private_key(&mut reader).context("parsing tls private key")?; + keys.ok_or(anyhow!("no private key found")) +} diff --git a/src/config.rs b/src/config.rs index e3b0d3c..6ef392d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,8 +59,8 @@ pub struct HttpConfig { pub struct HttpsConfig { #[serde(deserialize_with = "string_or_seq")] pub bind: Vec<SocketAddr>, - pub tls_cert: PathBuf, - pub tls_key: PathBuf, + #[serde(deserialize_with = "seq_or_not")] + pub cert_path: Vec<PathBuf>, } // try deser Vec<T> but fall back to deser T and putting that in Vec diff --git a/src/main.rs b/src/main.rs index eecb946..56a9b19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,13 +2,15 @@ #![feature(slice_split_once)] #![feature(iterator_try_collect)] +pub mod certs; pub mod config; pub mod error; pub mod helper; pub mod modules; use aes_gcm_siv::{aead::generic_array::GenericArray, Aes256GcmSiv, KeyInit}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; +use certs::CertPool; use config::{setup_file_watch, Config, NODE_KINDS}; use error::ServiceError; use futures::future::try_join_all; @@ -24,15 +26,8 @@ use hyper::{ }; use log::{debug, error, info, warn, LevelFilter}; use modules::{NodeContext, MODULES}; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use std::{ - collections::HashMap, - io::BufReader, - net::SocketAddr, - path::{Path, PathBuf}, - process::exit, - str::FromStr, - sync::Arc, + collections::HashMap, net::SocketAddr, path::PathBuf, process::exit, str::FromStr, sync::Arc, }; use tokio::{ fs::File, @@ -58,6 +53,10 @@ async fn main() -> anyhow::Result<()> { .parse_env("LOG") .init(); + rustls::crypto::ring::default_provider() + .install_default() + .unwrap(); + NODE_KINDS .write() .unwrap() @@ -147,11 +146,10 @@ async fn serve_https(state: Arc<State>) -> Result<()> { None => return Ok(()), }; let tls_config = { - let certs = load_certs(&https_config.tls_cert)?; - let key = load_private_key(&https_config.tls_key)?; + let certs = CertPool::load(&https_config.cert_path)?; let mut cfg = rustls::ServerConfig::builder() .with_no_client_auth() - .with_single_cert(certs, key)?; + .with_cert_resolver(Arc::new(certs)); cfg.alpn_protocols = vec![ // b"h2".to_vec(), b"http/1.1".to_vec(), @@ -222,19 +220,6 @@ pub async fn serve_stream<T: Unpin + Send + 'static + hyper::rt::Read + hyper::r } } -fn load_certs(path: &Path) -> anyhow::Result<Vec<CertificateDer<'static>>> { - let mut reader = BufReader::new(std::fs::File::open(path).context("reading tls certs")?); - let certs = rustls_pemfile::certs(&mut reader) - .try_collect::<Vec<_>>() - .context("parsing tls certs")?; - Ok(certs) -} -fn load_private_key(path: &Path) -> anyhow::Result<PrivateKeyDer<'static>> { - let mut reader = BufReader::new(std::fs::File::open(path).context("reading tls private key")?); - let keys = rustls_pemfile::private_key(&mut reader).context("parsing tls private key")?; - keys.ok_or(anyhow!("no private key found")) -} - async fn service( state: Arc<State>, config: Arc<Config>, |