use std::path::PathBuf;
use std::sync::Arc;
use rustls::ServerConfig;
use tokio_rustls::TlsAcceptor;
use tracing::info;
use crate::acme::AcmeManager;
#[derive(Debug, Clone)]
pub enum TlsMode {
None,
SelfSigned,
Custom { cert_path: String, key_path: String },
Acme {
email: String,
cache_dir: Option<PathBuf>,
},
}
pub fn create_tls_acceptor(mode: &TlsMode) -> anyhow::Result<Option<TlsAcceptor>> {
create_tls_acceptor_for_domain(mode, None)
}
pub fn create_tls_acceptor_for_domain(
mode: &TlsMode,
domain: Option<&str>,
) -> anyhow::Result<Option<TlsAcceptor>> {
match mode {
TlsMode::None => Ok(None),
TlsMode::SelfSigned => {
info!("Generating self-signed TLS certificate");
let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?;
let cert_der = cert.cert.der().clone();
let key_der = cert.key_pair.serialize_der();
let certs = vec![cert_der];
let key = rustls::pki_types::PrivatePkcs8KeyDer::from(key_der).into();
let config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
Ok(Some(TlsAcceptor::from(Arc::new(config))))
}
TlsMode::Custom {
cert_path,
key_path,
} => {
info!("Loading TLS certificate from {cert_path}");
let cert_file = std::fs::read(cert_path)?;
let key_file = std::fs::read(key_path)?;
let certs =
rustls_pemfile::certs(&mut cert_file.as_slice()).collect::<Result<Vec<_>, _>>()?;
let key = rustls_pemfile::private_key(&mut key_file.as_slice())?
.ok_or_else(|| anyhow::anyhow!("no private key found in {key_path}"))?;
let config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
Ok(Some(TlsAcceptor::from(Arc::new(config))))
}
TlsMode::Acme { email, cache_dir } => {
let cache = cache_dir.clone().unwrap_or_else(|| {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".orca/certs")
});
let manager = AcmeManager::new(email.clone(), cache);
let Some(domain) = domain else {
info!("ACME mode: certs will be auto-provisioned on startup");
return Ok(None);
};
match manager.tls_acceptor_for(domain)? {
Some(acceptor) => {
info!(domain, "Loaded cached ACME certificate");
Ok(Some(acceptor))
}
None => {
info!(
domain,
"No cached ACME cert — will auto-provision on startup"
);
Ok(None)
}
}
}
}
}