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