1use std::io::BufReader;
29use std::sync::Arc;
30use std::time::Duration;
31
32use rustls::pki_types::{CertificateDer, PrivateKeyDer};
33use rustls::sign::CertifiedKey;
34use tokio::sync::RwLock;
35use tokio_rustls::TlsAcceptor;
36use tracing::{debug, error, info, warn};
37
38use crate::config::{ClientAuthMode, OcspStaplingConfig, TlsConfig};
39use crate::error::KipukaError;
40
41pub fn build_tls_acceptor(
48 config: &TlsConfig,
49 hsm: Option<&Arc<kipuka_hsm::HsmContext>>,
50) -> Result<TlsAcceptor, KipukaError> {
51 let server_config = build_server_config(config, hsm)?;
52 Ok(TlsAcceptor::from(Arc::new(server_config)))
53}
54
55pub fn admin_listener_config(est: &TlsConfig, admin: &crate::config::AdminConfig) -> TlsConfig {
57 let mut tls = est.clone();
58 tls.client_auth = match admin.auth_method {
59 crate::config::AdminAuthMethod::Mtls => ClientAuthMode::Required,
60 _ => ClientAuthMode::None,
61 };
62 if let Some(path) = &admin.admin_ca_file {
63 tls.ca_file = path.clone();
64 }
65 tls
66}
67
68fn build_server_config(
70 config: &TlsConfig,
71 hsm: Option<&Arc<kipuka_hsm::HsmContext>>,
72) -> Result<rustls::ServerConfig, KipukaError> {
73 let cert_chain = load_cert_chain(&config.cert_file)?;
74
75 let versions = protocol_versions(&config.min_protocol, &config.max_protocol)?;
77
78 let builder = rustls::ServerConfig::builder_with_protocol_versions(&versions);
80
81 let with_client_auth = match config.client_auth {
83 ClientAuthMode::Required => {
84 let client_verifier = build_client_verifier(&config.ca_file)?;
85 builder.with_client_cert_verifier(client_verifier)
86 }
87 ClientAuthMode::Optional => {
88 let client_verifier = build_optional_client_verifier(&config.ca_file)?;
89 builder.with_client_cert_verifier(client_verifier)
90 }
91 ClientAuthMode::None => builder.with_no_client_auth(),
92 };
93
94 let server_config = if config.key_file.starts_with("pkcs11:") {
96 let hsm_ctx = hsm.ok_or_else(|| {
97 KipukaError::Tls("tls.key_file is a pkcs11: URI but [hsm] is not configured".into())
98 })?;
99
100 let key_label = hsm_ctx
101 .tls_key_label(&config.key_file)
102 .map_err(|e| KipukaError::Tls(format!("HSM TLS key URI: {e}")))?;
103
104 let algorithm = kipuka_hsm::rustls_signer::certificate_algorithm(
105 cert_chain
106 .first()
107 .ok_or_else(|| KipukaError::Tls("empty TLS certificate chain".into()))?
108 .as_ref(),
109 )
110 .map_err(|e| KipukaError::Tls(format!("HSM TLS certificate: {e}")))?;
111
112 info!(
113 key_label = %key_label,
114 algorithm = ?algorithm,
115 "TLS server key backed by PKCS#11 HSM (key never leaves HSM)"
116 );
117
118 kipuka_hsm::rustls_signer::verify_tls_key(hsm_ctx, &key_label, cert_chain[0].as_ref())
119 .map_err(|e| KipukaError::Tls(format!("HSM TLS key validation: {e}")))?;
120
121 let signing_key =
122 kipuka_hsm::Pkcs11SigningKey::new(Arc::clone(hsm_ctx), key_label, algorithm);
123
124 let certified_key = CertifiedKey::new(cert_chain, Arc::new(signing_key));
125 with_client_auth.with_cert_resolver(Arc::new(SingleCertResolver(Arc::new(certified_key))))
126 } else {
127 let private_key = load_private_key(&config.key_file)?;
128 with_client_auth
129 .with_single_cert(cert_chain, private_key)
130 .map_err(|e| KipukaError::Tls(format!("server cert config: {e}")))?
131 };
132
133 Ok(server_config)
134}
135
136#[derive(Debug)]
141struct SingleCertResolver(Arc<CertifiedKey>);
142
143impl rustls::server::ResolvesServerCert for SingleCertResolver {
144 fn resolve(&self, _client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
145 Some(Arc::clone(&self.0))
146 }
147}
148
149fn load_cert_chain(path: &str) -> Result<Vec<CertificateDer<'static>>, KipukaError> {
151 let file = std::fs::File::open(path)
152 .map_err(|e| KipukaError::Tls(format!("cannot open cert file '{path}': {e}")))?;
153 let mut reader = BufReader::new(file);
154
155 let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut reader)
156 .collect::<Result<Vec<_>, _>>()
157 .map_err(|e| KipukaError::Tls(format!("cannot parse cert file '{path}': {e}")))?;
158
159 if certs.is_empty() {
160 return Err(KipukaError::Tls(format!(
161 "no certificates found in '{path}'"
162 )));
163 }
164
165 Ok(certs)
166}
167
168fn load_private_key(path: &str) -> Result<PrivateKeyDer<'static>, KipukaError> {
170 let file = std::fs::File::open(path)
171 .map_err(|e| KipukaError::Tls(format!("cannot open key file '{path}': {e}")))?;
172 let mut reader = BufReader::new(file);
173
174 let key = rustls_pemfile::private_key(&mut reader)
176 .map_err(|e| KipukaError::Tls(format!("cannot parse key file '{path}': {e}")))?
177 .ok_or_else(|| KipukaError::Tls(format!("no private key found in '{path}'")))?;
178
179 Ok(key)
180}
181
182fn load_trust_anchors(ca_file: &str) -> Result<rustls::RootCertStore, KipukaError> {
184 let file = std::fs::File::open(ca_file)
185 .map_err(|e| KipukaError::Tls(format!("cannot open CA file '{ca_file}': {e}")))?;
186 let mut reader = BufReader::new(file);
187
188 let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut reader)
189 .collect::<Result<Vec<_>, _>>()
190 .map_err(|e| KipukaError::Tls(format!("cannot parse CA file '{ca_file}': {e}")))?;
191
192 if certs.is_empty() {
193 return Err(KipukaError::Tls(format!(
194 "no CA certificates found in '{ca_file}'"
195 )));
196 }
197
198 let mut root_store = rustls::RootCertStore::empty();
199 for cert in certs {
200 root_store
201 .add(cert)
202 .map_err(|e| KipukaError::Tls(format!("invalid CA certificate: {e}")))?;
203 }
204
205 Ok(root_store)
206}
207
208fn build_client_verifier(
210 ca_file: &str,
211) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>, KipukaError> {
212 let roots = load_trust_anchors(ca_file)?;
213 let verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(roots))
214 .build()
215 .map_err(|e| KipukaError::Tls(format!("client verifier build: {e}")))?;
216 Ok(verifier)
217}
218
219fn build_optional_client_verifier(
222 ca_file: &str,
223) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>, KipukaError> {
224 let roots = load_trust_anchors(ca_file)?;
225 let verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(roots))
226 .allow_unauthenticated()
227 .build()
228 .map_err(|e| KipukaError::Tls(format!("optional client verifier build: {e}")))?;
229 Ok(verifier)
230}
231
232fn protocol_versions(
234 min: &str,
235 max: &str,
236) -> Result<Vec<&'static rustls::SupportedProtocolVersion>, KipukaError> {
237 let mut versions = Vec::new();
238
239 match (min, max) {
240 ("1.2", "1.2") => {
241 versions.push(&rustls::version::TLS12);
242 }
243 ("1.2", "1.3") => {
244 versions.push(&rustls::version::TLS12);
245 versions.push(&rustls::version::TLS13);
246 }
247 ("1.3", "1.3") => {
248 versions.push(&rustls::version::TLS13);
249 }
250 _ => {
251 return Err(KipukaError::Tls(format!(
252 "unsupported protocol version range: {min}..{max}"
253 )));
254 }
255 }
256
257 Ok(versions)
258}
259
260pub fn compute_channel_binding(cert_der: &[u8]) -> Vec<u8> {
272 use sha2::{Digest, Sha256};
276 let mut hasher = Sha256::new();
277 hasher.update(cert_der);
278 hasher.finalize().to_vec()
279}
280
281#[derive(Debug, Clone)]
294pub struct StapledOcspResponse {
295 pub response_der: Vec<u8>,
297
298 pub fetched_at: std::time::Instant,
300
301 pub next_update: Option<chrono::DateTime<chrono::Utc>>,
306}
307
308pub type OcspResponseHandle = Arc<RwLock<Option<StapledOcspResponse>>>;
313
314pub struct OcspStapler {
333 config: OcspStaplingConfig,
335
336 server_cert_der: Vec<u8>,
338
339 issuer_cert_der: Option<Vec<u8>>,
341
342 response: OcspResponseHandle,
344}
345
346impl OcspStapler {
347 pub fn new(
356 config: OcspStaplingConfig,
357 server_cert_der: Vec<u8>,
358 issuer_cert_der: Option<Vec<u8>>,
359 ) -> Self {
360 Self {
361 config,
362 server_cert_der,
363 issuer_cert_der,
364 response: Arc::new(RwLock::new(None)),
365 }
366 }
367
368 pub fn response_handle(&self) -> OcspResponseHandle {
373 Arc::clone(&self.response)
374 }
375
376 pub async fn run(&self) {
386 let interval = Duration::from_secs(self.config.refresh_interval_secs);
387
388 info!(
389 interval_secs = self.config.refresh_interval_secs,
390 soft_fail = self.config.soft_fail,
391 "OCSP stapler started (RFC 6066 §8)"
392 );
393
394 self.refresh_once().await;
396
397 let mut ticker = tokio::time::interval(interval);
399 ticker.tick().await; loop {
401 ticker.tick().await;
402 self.refresh_once().await;
403 }
404 }
405
406 async fn refresh_once(&self) {
408 debug!("refreshing stapled OCSP response");
409
410 match self.fetch_ocsp_response().await {
411 Ok(response) => {
412 info!("OCSP stapled response refreshed successfully");
413 let mut guard = self.response.write().await;
414 *guard = Some(response);
415 }
416 Err(e) => {
417 if self.config.soft_fail {
418 warn!(
419 error = %e,
420 "OCSP responder unreachable, serving stale response (soft-fail mode)"
421 );
422 } else {
425 error!(
426 error = %e,
427 "OCSP responder unreachable and soft_fail is disabled"
428 );
429 let mut guard = self.response.write().await;
432 *guard = None;
433 }
434 }
435 }
436 }
437
438 async fn fetch_ocsp_response(&self) -> Result<StapledOcspResponse, String> {
448 let responder_url = self
449 .config
450 .responder_url
451 .as_deref()
452 .or_else(|| self.extract_aia_ocsp_url())
453 .ok_or_else(|| {
454 "no OCSP responder URL configured and none found in certificate AIA".to_string()
455 })?
456 .to_string();
457
458 debug!(url = %responder_url, "fetching OCSP response for stapling");
459
460 let issuer_der = self.issuer_cert_der.as_deref().ok_or_else(|| {
461 "issuer certificate is required for OCSP stapling (second cert in chain file)"
462 .to_string()
463 })?;
464
465 let ocsp_config = crate::ocsp::OcspConfig {
467 enabled: true,
468 responder_url: Some(responder_url.clone()),
469 cache_ttl_secs: self.config.refresh_interval_secs,
470 timeout_secs: 10,
471 require_nonce: false, soft_fail: self.config.soft_fail,
473 };
474 let client = crate::ocsp::OcspClient::new(ocsp_config);
475
476 let response_der = client
477 .get_stapled_response(&self.server_cert_der, issuer_der)
478 .await
479 .map_err(|e| format!("OCSP stapling fetch failed: {e}"))?;
480
481 info!(
482 url = %responder_url,
483 response_len = response_der.len(),
484 "OCSP stapled response fetched successfully"
485 );
486
487 let next_update = parse_ocsp_next_update(&response_der);
491
492 Ok(StapledOcspResponse {
493 response_der,
494 fetched_at: std::time::Instant::now(),
495 next_update,
496 })
497 }
498
499 fn extract_aia_ocsp_url(&self) -> Option<&str> {
506 let _ = &self.server_cert_der;
518 None
519 }
520}
521
522fn parse_ocsp_next_update(response_der: &[u8]) -> Option<chrono::DateTime<chrono::Utc>> {
528 use synta::{Decoder, Encoding};
529
530 let ocsp_response: synta_certificate::ocsp::OCSPResponse<'_> =
531 Decoder::new(response_der, Encoding::Der).decode().ok()?;
532
533 let response_bytes = ocsp_response.response_bytes.as_ref()?;
534
535 let basic_response: synta_certificate::ocsp::BasicOCSPResponse<'_> =
536 Decoder::new(response_bytes.response.as_bytes(), Encoding::Der)
537 .decode()
538 .ok()?;
539
540 let first_response = basic_response.tbs_response_data.responses.first()?;
541 let next_update = first_response.next_update.as_ref()?;
542
543 use chrono::TimeZone;
545 chrono::Utc
546 .with_ymd_and_hms(
547 next_update.year as i32,
548 next_update.month as u32,
549 next_update.day as u32,
550 next_update.hour as u32,
551 next_update.minute as u32,
552 next_update.second as u32,
553 )
554 .single()
555}
556
557pub fn has_must_staple_extension(cert_der: &[u8]) -> bool {
564 const MUST_STAPLE_OID_DER: &[u8] =
567 &[0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18];
568
569 cert_der
572 .windows(MUST_STAPLE_OID_DER.len())
573 .any(|window| window == MUST_STAPLE_OID_DER)
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn protocol_versions_1_2_to_1_3() {
582 let versions = protocol_versions("1.2", "1.3").unwrap();
583 assert_eq!(versions.len(), 2);
584 }
585
586 #[test]
587 fn protocol_versions_1_3_only() {
588 let versions = protocol_versions("1.3", "1.3").unwrap();
589 assert_eq!(versions.len(), 1);
590 }
591
592 #[test]
593 fn protocol_versions_invalid() {
594 assert!(protocol_versions("1.0", "1.2").is_err());
595 assert!(protocol_versions("1.3", "1.2").is_err());
596 }
597
598 #[test]
599 fn channel_binding_is_sha256() {
600 let cert_der = b"fake certificate DER bytes";
601 let binding = compute_channel_binding(cert_der);
602 assert_eq!(binding.len(), 32); }
604
605 #[test]
606 fn must_staple_detection_positive() {
607 let mut cert = vec![0x30, 0x20]; cert.extend_from_slice(&[0x00, 0x00]);
611 cert.extend_from_slice(&[0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18]);
613 cert.extend_from_slice(&[0x30, 0x03, 0x02, 0x01, 0x05]);
615 cert.extend_from_slice(&[0x00, 0x00]);
616
617 assert!(has_must_staple_extension(&cert));
618 }
619
620 #[test]
621 fn must_staple_detection_negative() {
622 let cert = b"some certificate bytes without the must-staple OID";
624 assert!(!has_must_staple_extension(cert));
625 }
626
627 #[test]
628 fn ocsp_stapling_config_defaults() {
629 let config = crate::config::OcspStaplingConfig::default();
630 assert!(!config.enabled);
631 assert!(config.responder_url.is_none());
632 assert_eq!(config.refresh_interval_secs, 14400);
633 assert!(config.soft_fail);
634 }
635}