1use anyhow::{Context, Result};
6use rcgen::{Certificate, CertificateParams, DistinguishedName, DnType, KeyPair, SanType};
7use rustls::pki_types::{CertificateDer, PrivateKeyDer};
8use rustls::ServerConfig;
9use std::sync::Arc;
10use time::{Duration, OffsetDateTime};
11use tracing::{debug, info};
12
13#[derive(Debug, Clone)]
15pub struct CertificateSpec {
16 pub common_name: String,
18 pub san_dns_names: Vec<String>,
20 pub validity_days: i64,
22 pub organization: Option<String>,
24 pub organizational_unit: Option<String>,
26}
27
28impl Default for CertificateSpec {
29 fn default() -> Self {
30 Self {
31 common_name: "netget-dns-server".to_string(),
32 san_dns_names: vec!["localhost".to_string(), "*.local".to_string()],
33 validity_days: 365,
34 organization: Some("NetGet".to_string()),
35 organizational_unit: Some("DNS Server".to_string()),
36 }
37 }
38}
39
40impl CertificateSpec {
41 pub fn from_llm_params(
43 common_name: Option<String>,
44 san_dns_names: Option<Vec<String>>,
45 validity_days: Option<i64>,
46 organization: Option<String>,
47 organizational_unit: Option<String>,
48 ) -> Self {
49 let defaults = Self::default();
50 Self {
51 common_name: common_name.unwrap_or(defaults.common_name),
52 san_dns_names: san_dns_names.unwrap_or(defaults.san_dns_names),
53 validity_days: validity_days.unwrap_or(defaults.validity_days),
54 organization: organization.or(defaults.organization),
55 organizational_unit: organizational_unit.or(defaults.organizational_unit),
56 }
57 }
58}
59
60pub fn generate_self_signed_cert(spec: &CertificateSpec) -> Result<(Certificate, KeyPair)> {
63 info!("Generating self-signed TLS certificate");
64 debug!("Certificate spec: {:?}", spec);
65
66 let mut params = CertificateParams::default();
67
68 let mut dn = DistinguishedName::new();
70 dn.push(DnType::CommonName, &spec.common_name);
71
72 if let Some(ref org) = spec.organization {
73 dn.push(DnType::OrganizationName, org);
74 }
75
76 if let Some(ref ou) = spec.organizational_unit {
77 dn.push(DnType::OrganizationalUnitName, ou);
78 }
79
80 params.distinguished_name = dn;
81
82 params.subject_alt_names = spec
84 .san_dns_names
85 .iter()
86 .map(|name| {
87 name.clone()
88 .try_into()
89 .map(SanType::DnsName)
90 .with_context(|| format!("Invalid certificate DNS name: {name:?}"))
91 })
92 .collect::<Result<Vec<_>>>()?;
93
94 anyhow::ensure!(
97 spec.validity_days > 0,
98 "Certificate validity_days must be positive"
99 );
100 let validity_seconds = spec
101 .validity_days
102 .checked_mul(86_400)
103 .context("Certificate validity_days is too large")?;
104 let now = OffsetDateTime::now_utc();
105 params.not_before = now;
106 params.not_after = now
107 .checked_add(Duration::seconds(validity_seconds))
108 .context("Certificate validity_days exceeds the supported date range")?;
109
110 let key_pair = KeyPair::generate().context("Failed to generate key pair")?;
112
113 let cert = params
114 .self_signed(&key_pair)
115 .context("Failed to create self-signed certificate")?;
116
117 info!(
118 "Successfully generated self-signed certificate for CN={}",
119 spec.common_name
120 );
121
122 Ok((cert, key_pair))
123}
124
125pub fn create_rustls_server_config(
127 cert: &Certificate,
128 key_pair: &KeyPair,
129) -> Result<Arc<ServerConfig>> {
130 let cert_der = cert.der();
132 let cert_der_owned = CertificateDer::from(cert_der.to_vec());
133
134 let key_der_vec = key_pair.serialize_der();
136 let key_der_owned = PrivateKeyDer::try_from(key_der_vec)
137 .map_err(|e| anyhow::anyhow!("Failed to parse private key DER: {}", e))?;
138
139 let _ = rustls::crypto::ring::default_provider().install_default();
142
143 let config = ServerConfig::builder()
145 .with_no_client_auth()
146 .with_single_cert(vec![cert_der_owned], key_der_owned)
147 .context("Failed to create rustls ServerConfig")?;
148
149 Ok(Arc::new(config))
150}
151
152pub fn generate_default_tls_config() -> Result<Arc<ServerConfig>> {
154 let spec = CertificateSpec::default();
155 let (cert, key_pair) = generate_self_signed_cert(&spec)?;
156 create_rustls_server_config(&cert, &key_pair)
157}
158
159pub fn generate_default_tls_config_with_alpn(alpn: &[&str]) -> Result<Arc<ServerConfig>> {
170 let config = generate_default_tls_config()?;
171 let mut config = Arc::try_unwrap(config)
173 .map_err(|_| anyhow::anyhow!("TLS config was unexpectedly shared before ALPN was set"))?;
174 config.alpn_protocols = alpn.iter().map(|p| p.as_bytes().to_vec()).collect();
175 Ok(Arc::new(config))
176}
177
178pub fn generate_custom_tls_config(
180 common_name: Option<String>,
181 san_dns_names: Option<Vec<String>>,
182 validity_days: Option<i64>,
183 organization: Option<String>,
184 organizational_unit: Option<String>,
185) -> Result<Arc<ServerConfig>> {
186 let spec = CertificateSpec::from_llm_params(
187 common_name,
188 san_dns_names,
189 validity_days,
190 organization,
191 organizational_unit,
192 );
193
194 let (cert, key_pair) = generate_self_signed_cert(&spec)?;
195 create_rustls_server_config(&cert, &key_pair)
196}
197
198pub fn load_tls_config_from_files(cert_path: &str, key_path: &str) -> Result<Arc<ServerConfig>> {
200 info!("Loading TLS certificate from files");
201 debug!("Certificate path: {}", cert_path);
202 debug!("Key path: {}", key_path);
203
204 let cert_pem = std::fs::read_to_string(cert_path)
206 .with_context(|| format!("Failed to read certificate file: {}", cert_path))?;
207
208 let key_pem = std::fs::read_to_string(key_path)
210 .with_context(|| format!("Failed to read private key file: {}", key_path))?;
211
212 let cert_der = rustls_pemfile::certs(&mut cert_pem.as_bytes())
214 .collect::<Result<Vec<_>, _>>()
215 .context("Failed to parse certificate PEM")?;
216
217 if cert_der.is_empty() {
218 return Err(anyhow::anyhow!("No certificates found in certificate file"));
219 }
220
221 let mut key_reader = key_pem.as_bytes();
223 let key_der = rustls_pemfile::private_key(&mut key_reader)
224 .context("Failed to parse private key PEM")?
225 .ok_or_else(|| anyhow::anyhow!("No private key found in key file"))?;
226
227 let _ = rustls::crypto::ring::default_provider().install_default();
230
231 let config = ServerConfig::builder()
233 .with_no_client_auth()
234 .with_single_cert(cert_der, key_der)
235 .context("Failed to create rustls ServerConfig from loaded certificate")?;
236
237 info!("Successfully loaded TLS certificate from files");
238
239 Ok(Arc::new(config))
240}
241
242pub fn create_tls_config(
247 cert_path: Option<&str>,
248 key_path: Option<&str>,
249 common_name: Option<String>,
250 san_dns_names: Option<Vec<String>>,
251 validity_days: Option<i64>,
252 organization: Option<String>,
253 organizational_unit: Option<String>,
254) -> Result<Arc<ServerConfig>> {
255 match (cert_path, key_path) {
256 (Some(cert), Some(key)) => {
257 load_tls_config_from_files(cert, key)
259 }
260 (Some(_), None) | (None, Some(_)) => Err(anyhow::anyhow!(
261 "Both cert_path and key_path must be provided together"
262 )),
263 (None, None) => {
264 if common_name.is_some()
266 || san_dns_names.is_some()
267 || validity_days.is_some()
268 || organization.is_some()
269 || organizational_unit.is_some()
270 {
271 generate_custom_tls_config(
273 common_name,
274 san_dns_names,
275 validity_days,
276 organization,
277 organizational_unit,
278 )
279 } else {
280 generate_default_tls_config()
282 }
283 }
284 }
285}
286
287pub fn get_tls_startup_parameters() -> Vec<crate::llm::actions::ParameterDefinition> {
289 use crate::llm::actions::ParameterDefinition;
290
291 vec![
292 ParameterDefinition {
293 name: "tls_enabled".to_string(),
294 type_hint: "boolean".to_string(),
295 description: "Enable TLS/SSL encryption (default: false for HTTP/HTTP2, always true for QUIC)".to_string(),
296 required: false,
297 example: serde_json::json!(true),
298 default: None,
299 },
300 ParameterDefinition {
301 name: "cert_path".to_string(),
302 type_hint: "string".to_string(),
303 description: "Path to TLS certificate file (PEM format). If not provided, a self-signed certificate will be generated.".to_string(),
304 required: false,
305 example: serde_json::json!("/path/to/cert.pem"),
306 default: None,
307 },
308 ParameterDefinition {
309 name: "key_path".to_string(),
310 type_hint: "string".to_string(),
311 description: "Path to TLS private key file (PEM format). Required if cert_path is provided.".to_string(),
312 required: false,
313 example: serde_json::json!("/path/to/key.pem"),
314 default: None,
315 },
316 ParameterDefinition {
317 name: "common_name".to_string(),
318 type_hint: "string".to_string(),
319 description: "Common Name (CN) for self-signed certificate (default: netget-dns-server)".to_string(),
320 required: false,
321 example: serde_json::json!("example.com"),
322 default: None,
323 },
324 ParameterDefinition {
325 name: "san_dns_names".to_string(),
326 type_hint: "array".to_string(),
327 description: "Subject Alternative Names (DNS names) for self-signed certificate (default: [\"localhost\", \"*.local\"])".to_string(),
328 required: false,
329 example: serde_json::json!(["example.com", "*.example.com"]),
330 default: None,
331 },
332 ParameterDefinition {
333 name: "validity_days".to_string(),
334 type_hint: "number".to_string(),
335 description: "Positive certificate validity period in days for self-signed certificate, within the supported date range (default: 365)".to_string(),
336 required: false,
337 example: serde_json::json!(365),
338 default: None,
339 },
340 ParameterDefinition {
341 name: "organization".to_string(),
342 type_hint: "string".to_string(),
343 description: "Organization name for self-signed certificate (default: NetGet)".to_string(),
344 required: false,
345 example: serde_json::json!("My Organization"),
346 default: None,
347 },
348 ParameterDefinition {
349 name: "organizational_unit".to_string(),
350 type_hint: "string".to_string(),
351 description: "Organizational unit for self-signed certificate".to_string(),
352 required: false,
353 example: serde_json::json!("IT Department"),
354 default: None,
355 },
356 ]
357}
358
359pub fn extract_tls_config_from_params(
361 params: &crate::protocol::StartupParams,
362) -> Result<Option<Arc<ServerConfig>>> {
363 let tls_enabled = params.get_optional_bool("tls_enabled")?.unwrap_or(false);
365
366 if !tls_enabled {
367 return Ok(None);
368 }
369
370 extract_required_tls_config_from_params(params).map(Some)
371}
372
373pub fn extract_required_tls_config_from_params(
375 params: &crate::protocol::StartupParams,
376) -> Result<Arc<ServerConfig>> {
377 let cert_path = params.get_optional_string("cert_path")?;
379 let key_path = params.get_optional_string("key_path")?;
380 let common_name = params.get_optional_string("common_name")?;
381 let san_dns_names = params
382 .get_optional_array("san_dns_names")?
383 .map(|arr| {
384 arr.iter()
385 .enumerate()
386 .map(|(index, value)| {
387 value
388 .as_str()
389 .map(str::to_owned)
390 .with_context(|| format!("san_dns_names[{index}] must be a string"))
391 })
392 .collect::<Result<Vec<_>>>()
393 })
394 .transpose()?;
395 let validity_days = params.get_optional_i64("validity_days")?;
396 let organization = params.get_optional_string("organization")?;
397 let organizational_unit = params.get_optional_string("organizational_unit")?;
398
399 create_tls_config(
401 cert_path.as_deref(),
402 key_path.as_deref(),
403 common_name,
404 san_dns_names,
405 validity_days,
406 organization,
407 organizational_unit,
408 )
409}