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
15pub(crate) fn with_h2_alpn(mut config: ServerConfig) -> ServerConfig {
18 config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
19 config
20}
21
22#[derive(Debug, Clone)]
24pub enum TlsMode {
25 None,
27 SelfSigned,
29 Custom { cert_path: String, key_path: String },
31 Acme {
36 email: String,
38 cache_dir: Option<PathBuf>,
41 },
42}
43
44pub fn create_tls_acceptor(mode: &TlsMode) -> anyhow::Result<Option<TlsAcceptor>> {
50 create_tls_acceptor_for_domain(mode, None)
51}
52
53pub fn create_tls_acceptor_for_domain(
55 mode: &TlsMode,
56 domain: Option<&str>,
57) -> anyhow::Result<Option<TlsAcceptor>> {
58 match mode {
59 TlsMode::None => Ok(None),
60 TlsMode::SelfSigned => {
61 info!("Generating self-signed TLS certificate");
62 let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?;
63 let cert_der = cert.cert.der().clone();
64 let key_der = cert.key_pair.serialize_der();
65
66 let certs = vec![cert_der];
67 let key = rustls::pki_types::PrivatePkcs8KeyDer::from(key_der).into();
68
69 let config = with_h2_alpn(
70 ServerConfig::builder()
71 .with_no_client_auth()
72 .with_single_cert(certs, key)?,
73 );
74
75 Ok(Some(TlsAcceptor::from(Arc::new(config))))
76 }
77 TlsMode::Custom {
78 cert_path,
79 key_path,
80 } => {
81 info!("Loading TLS certificate from {cert_path}");
82 let cert_file = std::fs::read(cert_path)?;
83 let key_file = std::fs::read(key_path)?;
84
85 let certs =
86 rustls_pemfile::certs(&mut cert_file.as_slice()).collect::<Result<Vec<_>, _>>()?;
87 let key = rustls_pemfile::private_key(&mut key_file.as_slice())?
88 .ok_or_else(|| anyhow::anyhow!("no private key found in {key_path}"))?;
89
90 let config = with_h2_alpn(
91 ServerConfig::builder()
92 .with_no_client_auth()
93 .with_single_cert(certs, key)?,
94 );
95
96 Ok(Some(TlsAcceptor::from(Arc::new(config))))
97 }
98 TlsMode::Acme { email, cache_dir } => {
99 let cache = cache_dir.clone().unwrap_or_else(|| {
100 dirs::home_dir()
101 .unwrap_or_else(|| PathBuf::from("."))
102 .join(".orca/certs")
103 });
104 let manager = AcmeManager::new(email.clone(), cache);
105 manager.secure_key_material();
106
107 let Some(domain) = domain else {
108 info!("ACME mode: certs will be auto-provisioned on startup");
111 return Ok(None);
112 };
113
114 match manager.tls_acceptor_for(domain)? {
115 Some(acceptor) => {
116 info!(domain, "Loaded cached ACME certificate");
117 Ok(Some(acceptor))
118 }
119 None => {
120 info!(
121 domain,
122 "No cached ACME cert — will auto-provision on startup"
123 );
124 Ok(None)
125 }
126 }
127 }
128 }
129}