use std::path::Path;
use std::sync::Arc;
use axum_server::tls_rustls::RustlsConfig;
use eyre::{Result, eyre};
use rcgen::{CertifiedKey, generate_simple_self_signed};
use rustls::ServerConfig;
use rustls::crypto::aws_lc_rs;
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
pub struct TlsMaterial {
certs: Vec<CertificateDer<'static>>,
key: PrivateKeyDer<'static>,
}
impl Clone for TlsMaterial {
fn clone(&self) -> Self {
Self {
certs: self.certs.clone(),
key: self.key.clone_key(),
}
}
}
impl TlsMaterial {
pub fn self_signed() -> Result<Self> {
let subject_alt_names = vec!["localhost".to_string(), "127.0.0.1".to_string()];
let CertifiedKey { cert, signing_key } = generate_simple_self_signed(subject_alt_names)
.map_err(|e| eyre!("self-signed certificate generation failed: {e}"))?;
let certs = vec![cert.der().clone()];
let key = PrivateKeyDer::try_from(signing_key.serialize_der())
.map_err(|e| eyre!("failed to encode generated private key: {e}"))?;
Ok(Self { certs, key })
}
pub fn from_pem_files(cert_path: &Path, key_path: &Path) -> Result<Self> {
let certs: Vec<CertificateDer<'static>> = CertificateDer::pem_file_iter(cert_path)
.map_err(|e| eyre!("reading certificate {}: {e}", cert_path.display()))?
.collect::<std::result::Result<_, _>>()
.map_err(|e| eyre!("parsing certificate {}: {e}", cert_path.display()))?;
if certs.is_empty() {
return Err(eyre!("no certificates found in {}", cert_path.display()));
}
let key = PrivateKeyDer::from_pem_file(key_path)
.map_err(|e| eyre!("reading private key {}: {e}", key_path.display()))?;
Ok(Self { certs, key })
}
pub fn server_config(&self, alpn: &[&[u8]]) -> Result<ServerConfig> {
let mut config =
ServerConfig::builder_with_provider(Arc::new(aws_lc_rs::default_provider()))
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|e| eyre!("rustls TLS 1.3 setup failed: {e}"))?
.with_no_client_auth()
.with_single_cert(self.certs.clone(), self.key.clone_key())
.map_err(|e| eyre!("rustls server config (cert/key) failed: {e}"))?;
config.alpn_protocols = alpn.iter().map(|p| p.to_vec()).collect();
config.max_early_data_size = u32::MAX;
Ok(config)
}
pub fn axum_rustls_config(&self) -> Result<RustlsConfig> {
let server_config = self.server_config(&[b"h2"])?;
Ok(RustlsConfig::from_config(Arc::new(server_config)))
}
}