1use std::path::PathBuf;
7use std::sync::Arc;
8
9use rustls::ServerConfig;
10use tokio_rustls::TlsAcceptor;
11use tracing::info;
12
13use crate::acme::AcmeManager;
14
15#[derive(Debug, Clone)]
17pub enum TlsMode {
18 None,
20 SelfSigned,
22 Custom { cert_path: String, key_path: String },
24 Acme {
29 email: String,
31 cache_dir: Option<PathBuf>,
34 },
35}
36
37pub fn create_tls_acceptor(mode: &TlsMode) -> anyhow::Result<Option<TlsAcceptor>> {
43 create_tls_acceptor_for_domain(mode, None)
44}
45
46pub fn create_tls_acceptor_for_domain(
48 mode: &TlsMode,
49 domain: Option<&str>,
50) -> anyhow::Result<Option<TlsAcceptor>> {
51 match mode {
52 TlsMode::None => Ok(None),
53 TlsMode::SelfSigned => {
54 info!("Generating self-signed TLS certificate");
55 let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?;
56 let cert_der = cert.cert.der().clone();
57 let key_der = cert.key_pair.serialize_der();
58
59 let certs = vec![cert_der];
60 let key = rustls::pki_types::PrivatePkcs8KeyDer::from(key_der).into();
61
62 let config = ServerConfig::builder()
63 .with_no_client_auth()
64 .with_single_cert(certs, key)?;
65
66 Ok(Some(TlsAcceptor::from(Arc::new(config))))
67 }
68 TlsMode::Custom {
69 cert_path,
70 key_path,
71 } => {
72 info!("Loading TLS certificate from {cert_path}");
73 let cert_file = std::fs::read(cert_path)?;
74 let key_file = std::fs::read(key_path)?;
75
76 let certs =
77 rustls_pemfile::certs(&mut cert_file.as_slice()).collect::<Result<Vec<_>, _>>()?;
78 let key = rustls_pemfile::private_key(&mut key_file.as_slice())?
79 .ok_or_else(|| anyhow::anyhow!("no private key found in {key_path}"))?;
80
81 let config = ServerConfig::builder()
82 .with_no_client_auth()
83 .with_single_cert(certs, key)?;
84
85 Ok(Some(TlsAcceptor::from(Arc::new(config))))
86 }
87 TlsMode::Acme { email, cache_dir } => {
88 let cache = cache_dir.clone().unwrap_or_else(|| {
89 dirs::home_dir()
90 .unwrap_or_else(|| PathBuf::from("."))
91 .join(".orca/certs")
92 });
93 let manager = AcmeManager::new(email.clone(), cache);
94
95 let Some(domain) = domain else {
96 info!("ACME mode: certs will be auto-provisioned on startup");
99 return Ok(None);
100 };
101
102 match manager.tls_acceptor_for(domain)? {
103 Some(acceptor) => {
104 info!(domain, "Loaded cached ACME certificate");
105 Ok(Some(acceptor))
106 }
107 None => {
108 info!(
109 domain,
110 "No cached ACME cert — will auto-provision on startup"
111 );
112 Ok(None)
113 }
114 }
115 }
116 }
117}