1use crate::core::prelude::*;
2use rcgen::{Certificate, CertificateParams, DistinguishedName};
3use rustls::{Certificate as RustlsCertificate, PrivateKey, ServerConfig};
4use rustls_pemfile::{certs, pkcs8_private_keys};
5use std::fs;
6use std::io::BufReader;
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10#[derive(Debug)]
11pub struct TlsManager {
12 cert_dir: PathBuf,
13 validity_days: u32,
14}
15
16impl TlsManager {
17 pub fn new(cert_dir: &str, validity_days: u32) -> Result<Self> {
18 let exe_path = std::env::current_exe().map_err(AppError::Io)?;
19 let base_dir = exe_path.parent().ok_or_else(|| {
20 AppError::Validation("Cannot determine executable directory".to_string())
21 })?;
22
23 let cert_path = base_dir.join(cert_dir);
24 fs::create_dir_all(&cert_path).map_err(AppError::Io)?;
25
26 Ok(Self {
27 cert_dir: cert_path,
28 validity_days,
29 })
30 }
31
32 pub fn get_rustls_config(&self, server_name: &str, port: u16) -> Result<Arc<ServerConfig>> {
33 let cert_file = self.get_cert_path(server_name, port);
34 let key_file = self.get_key_path(server_name, port);
35
36 if !cert_file.exists() || !key_file.exists() {
38 self.generate_certificate(server_name, port)?;
39 }
40
41 let cert_chain = self.load_certificates(&cert_file)?;
43 let private_key = self.load_private_key(&key_file)?;
44
45 let config = ServerConfig::builder()
47 .with_safe_defaults()
48 .with_no_client_auth()
49 .with_single_cert(cert_chain, private_key)
50 .map_err(|e| AppError::Validation(format!("TLS config error: {}", e)))?;
51
52 Ok(Arc::new(config))
53 }
54
55 fn generate_certificate(&self, server_name: &str, port: u16) -> Result<()> {
56 log::info!("Generating TLS certificate for {}:{}", server_name, port);
57
58 let subject_alt_names = if server_name == "proxy" {
60 vec![
61 "localhost".to_string(),
62 "127.0.0.1".to_string(),
63 "*.localhost".to_string(), "proxy.localhost".to_string(),
65 ]
66 } else {
67 vec![
68 "localhost".to_string(),
69 "127.0.0.1".to_string(),
70 format!("{}.localhost", server_name), format!("{}:{}", server_name, port),
72 ]
73 };
74
75 let mut params = CertificateParams::new(subject_alt_names);
76
77 let mut dn = DistinguishedName::new();
79 dn.push(rcgen::DnType::OrganizationName, "Rush Sync Server");
80
81 let common_name = if server_name == "proxy" {
82 "*.localhost" } else {
84 &format!("{}.localhost", server_name)
85 };
86
87 dn.push(rcgen::DnType::CommonName, common_name);
88 params.distinguished_name = dn;
89
90 params.not_before = time::OffsetDateTime::now_utc() - time::Duration::days(1);
92 params.not_after =
93 time::OffsetDateTime::now_utc() + time::Duration::days(self.validity_days as i64);
94
95 params.key_usages = vec![
96 rcgen::KeyUsagePurpose::DigitalSignature,
97 rcgen::KeyUsagePurpose::KeyEncipherment,
98 ];
99
100 params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
101
102 let cert = Certificate::from_params(params)
104 .map_err(|e| AppError::Validation(format!("Certificate generation failed: {}", e)))?;
105
106 let cert_pem = cert.serialize_pem().map_err(|e| {
107 AppError::Validation(format!("Certificate serialization failed: {}", e))
108 })?;
109 let key_pem = cert.serialize_private_key_pem();
110
111 let cert_file = self.get_cert_path(server_name, port);
112 let key_file = self.get_key_path(server_name, port);
113
114 fs::write(&cert_file, cert_pem).map_err(AppError::Io)?;
115 fs::write(&key_file, key_pem).map_err(AppError::Io)?;
116
117 #[cfg(unix)]
118 {
119 use std::os::unix::fs::PermissionsExt;
120 let mut perms = fs::metadata(&key_file).map_err(AppError::Io)?.permissions();
121 perms.set_mode(0o600);
122 fs::set_permissions(&key_file, perms).map_err(AppError::Io)?;
123 }
124
125 log::info!("TLS certificate generated with CN: {}", common_name);
126 log::info!("Certificate: {:?}", cert_file);
127 log::info!("Private Key: {:?}", key_file);
128
129 Ok(())
130 }
131
132 fn load_certificates(&self, path: &Path) -> Result<Vec<RustlsCertificate>> {
133 let cert_file = fs::File::open(path).map_err(AppError::Io)?;
134 let mut reader = BufReader::new(cert_file);
135
136 let cert_chain = certs(&mut reader)
137 .map_err(|e| AppError::Validation(format!("Certificate parsing error: {}", e)))?
138 .into_iter()
139 .map(RustlsCertificate)
140 .collect();
141
142 Ok(cert_chain)
143 }
144
145 fn load_private_key(&self, path: &Path) -> Result<PrivateKey> {
146 let key_file = fs::File::open(path).map_err(AppError::Io)?;
147 let mut reader = BufReader::new(key_file);
148
149 let keys = pkcs8_private_keys(&mut reader)
150 .map_err(|e| AppError::Validation(format!("Private key parsing error: {}", e)))?;
151
152 if keys.is_empty() {
153 return Err(AppError::Validation("No private key found".to_string()));
154 }
155
156 Ok(PrivateKey(keys[0].clone()))
157 }
158
159 fn get_cert_path(&self, server_name: &str, port: u16) -> PathBuf {
160 self.cert_dir.join(format!("{}-{}.cert", server_name, port))
161 }
162
163 fn get_key_path(&self, server_name: &str, port: u16) -> PathBuf {
164 self.cert_dir.join(format!("{}-{}.key", server_name, port))
165 }
166
167 pub fn certificate_exists(&self, server_name: &str, port: u16) -> bool {
168 let cert_file = self.get_cert_path(server_name, port);
169 let key_file = self.get_key_path(server_name, port);
170 cert_file.exists() && key_file.exists()
171 }
172
173 pub fn remove_certificate(&self, server_name: &str, port: u16) -> Result<()> {
174 let cert_file = self.get_cert_path(server_name, port);
175 let key_file = self.get_key_path(server_name, port);
176
177 if cert_file.exists() {
178 fs::remove_file(&cert_file).map_err(AppError::Io)?;
179 log::info!("Removed certificate: {:?}", cert_file);
180 }
181
182 if key_file.exists() {
183 fs::remove_file(&key_file).map_err(AppError::Io)?;
184 log::info!("Removed private key: {:?}", key_file);
185 }
186
187 Ok(())
188 }
189
190 pub fn get_certificate_info(&self, server_name: &str, port: u16) -> Option<CertificateInfo> {
191 let cert_file = self.get_cert_path(server_name, port);
192
193 if !cert_file.exists() {
194 return None;
195 }
196
197 let metadata = fs::metadata(&cert_file).ok()?;
198 let size = metadata.len();
199 let modified = metadata.modified().ok()?;
200
201 Some(CertificateInfo {
202 cert_path: cert_file,
203 key_path: self.get_key_path(server_name, port),
204 file_size: size,
205 created: modified,
206 valid_days: self.validity_days,
207 })
208 }
209
210 pub fn list_certificates(&self) -> Result<Vec<CertificateInfo>> {
211 let mut certificates = Vec::new();
212
213 let entries = fs::read_dir(&self.cert_dir).map_err(AppError::Io)?;
214
215 for entry in entries {
216 let entry = entry.map_err(AppError::Io)?;
217 let path = entry.path();
218
219 if path.extension().and_then(|s| s.to_str()) == Some("cert") {
220 if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
221 if let Some((server, port_str)) = stem.rsplit_once('-') {
223 if let Ok(port) = port_str.parse::<u16>() {
224 if let Some(info) = self.get_certificate_info(server, port) {
225 certificates.push(info);
226 }
227 }
228 }
229 }
230 }
231 }
232
233 certificates.sort_by(|a, b| b.created.cmp(&a.created));
234 Ok(certificates)
235 }
236
237 pub fn get_production_config(&self, domain: &str) -> Result<Arc<ServerConfig>> {
238 let cert_file = self.cert_dir.join(format!("{}.fullchain.pem", domain));
240 let key_file = self.cert_dir.join(format!("{}.privkey.pem", domain));
241
242 if cert_file.exists() && key_file.exists() {
243 log::info!("Loading existing Let's Encrypt certificate for {}", domain);
244 let cert_chain = self.load_certificates(&cert_file)?;
245 let private_key = self.load_private_key(&key_file)?;
246
247 let config = ServerConfig::builder()
248 .with_safe_defaults()
249 .with_no_client_auth()
250 .with_single_cert(cert_chain, private_key)
251 .map_err(|e| AppError::Validation(format!("TLS config error: {}", e)))?;
252
253 return Ok(Arc::new(config));
254 }
255
256 log::warn!("No Let's Encrypt certificate found for {}", domain);
257 log::info!("Using self-signed certificate for development");
258
259 self.get_rustls_config("proxy", 443)
261 }
262}
263
264#[derive(Debug)]
265pub struct CertificateInfo {
266 pub cert_path: PathBuf,
267 pub key_path: PathBuf,
268 pub file_size: u64,
269 pub created: std::time::SystemTime,
270 pub valid_days: u32,
271}
272
273impl CertificateInfo {
274 pub fn is_expired(&self) -> bool {
275 if let Ok(elapsed) = self.created.elapsed() {
276 elapsed.as_secs() > (self.valid_days as u64 * 24 * 60 * 60)
277 } else {
278 true
279 }
280 }
281
282 pub fn days_until_expiry(&self) -> i64 {
283 if let Ok(elapsed) = self.created.elapsed() {
284 let elapsed_days = elapsed.as_secs() / (24 * 60 * 60);
285 (self.valid_days as i64) - (elapsed_days as i64)
286 } else {
287 0
288 }
289 }
290}