1use std::fmt;
53
54use serde::{Deserialize, Serialize};
55
56use crate::security::errors::{Result, SecurityError};
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
62#[non_exhaustive]
63pub enum TlsVersion {
64 V1_0,
66 V1_1,
68 V1_2,
70 V1_3,
72}
73
74impl fmt::Display for TlsVersion {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::V1_0 => write!(f, "TLS 1.0"),
78 Self::V1_1 => write!(f, "TLS 1.1"),
79 Self::V1_2 => write!(f, "TLS 1.2"),
80 Self::V1_3 => write!(f, "TLS 1.3"),
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct TlsConnection {
92 pub is_secure: bool,
94
95 pub version: TlsVersion,
97
98 pub has_client_cert: bool,
100
101 pub client_cert_valid: bool,
104}
105
106impl TlsConnection {
107 #[must_use]
109 pub const fn new_http() -> Self {
110 Self {
111 is_secure: false,
112 version: TlsVersion::V1_2, has_client_cert: false,
114 client_cert_valid: false,
115 }
116 }
117
118 #[must_use]
120 pub const fn new_secure(version: TlsVersion) -> Self {
121 Self {
122 is_secure: true,
123 version,
124 has_client_cert: false,
125 client_cert_valid: false,
126 }
127 }
128
129 #[must_use]
131 pub const fn new_secure_with_client_cert(version: TlsVersion) -> Self {
132 Self {
133 is_secure: true,
134 version,
135 has_client_cert: true,
136 client_cert_valid: true,
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146pub struct TlsConfig {
147 pub tls_required: bool,
149
150 pub mtls_required: bool,
152
153 pub min_version: TlsVersion,
155}
156
157impl TlsConfig {
158 #[must_use]
164 pub const fn permissive() -> Self {
165 Self {
166 tls_required: false,
167 mtls_required: false,
168 min_version: TlsVersion::V1_2,
169 }
170 }
171
172 #[must_use]
178 pub const fn standard() -> Self {
179 Self {
180 tls_required: true,
181 mtls_required: false,
182 min_version: TlsVersion::V1_2,
183 }
184 }
185
186 #[must_use]
192 pub const fn strict() -> Self {
193 Self {
194 tls_required: true,
195 mtls_required: true,
196 min_version: TlsVersion::V1_3,
197 }
198 }
199}
200
201#[derive(Debug, Clone)]
206pub struct TlsEnforcer {
207 config: TlsConfig,
208}
209
210impl TlsEnforcer {
211 #[must_use]
213 pub const fn from_config(config: TlsConfig) -> Self {
214 Self { config }
215 }
216
217 #[must_use]
219 pub const fn permissive() -> Self {
220 Self::from_config(TlsConfig::permissive())
221 }
222
223 #[must_use]
225 pub const fn standard() -> Self {
226 Self::from_config(TlsConfig::standard())
227 }
228
229 #[must_use]
231 pub const fn strict() -> Self {
232 Self::from_config(TlsConfig::strict())
233 }
234
235 pub fn validate_connection(&self, conn: &TlsConnection) -> Result<()> {
250 if self.config.tls_required && !conn.is_secure {
252 return Err(SecurityError::TlsRequired {
253 detail: "HTTPS required, but connection is HTTP".to_string(),
254 });
255 }
256
257 if conn.is_secure && conn.version < self.config.min_version {
259 return Err(SecurityError::TlsVersionTooOld {
260 current: conn.version,
261 required: self.config.min_version,
262 });
263 }
264
265 if self.config.mtls_required && !conn.has_client_cert {
267 return Err(SecurityError::MtlsRequired {
268 detail: "Client certificate required, but none provided".to_string(),
269 });
270 }
271
272 if conn.has_client_cert && !conn.client_cert_valid {
274 return Err(SecurityError::InvalidClientCert {
275 detail: "Client certificate provided but validation failed".to_string(),
276 });
277 }
278
279 Ok(())
280 }
281
282 #[must_use]
284 pub const fn config(&self) -> &TlsConfig {
285 &self.config
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
298 fn test_http_allowed_when_tls_not_required() {
299 let enforcer = TlsEnforcer::permissive();
300 let conn = TlsConnection::new_http();
301
302 enforcer
303 .validate_connection(&conn)
304 .unwrap_or_else(|e| panic!("expected HTTP allowed when TLS not required: {e}"));
305 }
306
307 #[test]
308 fn test_http_rejected_when_tls_required() {
309 let enforcer = TlsEnforcer::standard();
310 let conn = TlsConnection::new_http();
311
312 let result = enforcer.validate_connection(&conn);
313 assert!(matches!(result, Err(SecurityError::TlsRequired { .. })));
314 }
315
316 #[test]
317 fn test_https_allowed_when_tls_required() {
318 let enforcer = TlsEnforcer::standard();
319 let conn = TlsConnection::new_secure(TlsVersion::V1_3);
320
321 enforcer
322 .validate_connection(&conn)
323 .unwrap_or_else(|e| panic!("expected HTTPS allowed when TLS required: {e}"));
324 }
325
326 #[test]
331 fn test_tls_1_0_rejected_when_min_1_3() {
332 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure(TlsVersion::V1_0);
334
335 let result = enforcer.validate_connection(&conn);
336 assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
337 }
338
339 #[test]
340 fn test_tls_1_2_rejected_when_min_1_3() {
341 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure(TlsVersion::V1_2);
343
344 let result = enforcer.validate_connection(&conn);
345 assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
346 }
347
348 #[test]
349 fn test_tls_1_3_allowed_when_min_1_2() {
350 let enforcer = TlsEnforcer::standard(); let conn = TlsConnection::new_secure(TlsVersion::V1_3);
352
353 enforcer
354 .validate_connection(&conn)
355 .unwrap_or_else(|e| panic!("expected TLS 1.3 allowed when min 1.2: {e}"));
356 }
357
358 #[test]
359 fn test_tls_1_2_allowed_when_min_1_2() {
360 let enforcer = TlsEnforcer::standard(); let conn = TlsConnection::new_secure(TlsVersion::V1_2);
362
363 enforcer
364 .validate_connection(&conn)
365 .unwrap_or_else(|e| panic!("expected TLS 1.2 allowed when min 1.2: {e}"));
366 }
367
368 #[test]
369 fn test_tls_version_check_skipped_for_http() {
370 let enforcer = TlsEnforcer::permissive();
372 let conn = TlsConnection::new_http();
373
374 enforcer
376 .validate_connection(&conn)
377 .unwrap_or_else(|e| panic!("expected version check skipped for HTTP: {e}"));
378 }
379
380 #[test]
385 fn test_client_cert_optional_when_mtls_not_required() {
386 let enforcer = TlsEnforcer::standard(); let conn = TlsConnection::new_secure(TlsVersion::V1_3);
388
389 enforcer.validate_connection(&conn).unwrap_or_else(|e| {
390 panic!("expected no client cert needed when mTLS not required: {e}")
391 });
392 }
393
394 #[test]
395 fn test_client_cert_required_when_mtls_required() {
396 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure(TlsVersion::V1_3);
398
399 let result = enforcer.validate_connection(&conn);
400 assert!(matches!(result, Err(SecurityError::MtlsRequired { .. })));
401 }
402
403 #[test]
404 fn test_client_cert_allowed_when_mtls_required() {
405 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
407
408 enforcer.validate_connection(&conn).unwrap_or_else(|e| {
409 panic!("expected valid client cert accepted when mTLS required: {e}")
410 });
411 }
412
413 #[test]
418 fn test_invalid_cert_rejected() {
419 let enforcer = TlsEnforcer::strict();
420 let conn = TlsConnection {
421 is_secure: true,
422 version: TlsVersion::V1_3,
423 has_client_cert: true,
424 client_cert_valid: false, };
426
427 let result = enforcer.validate_connection(&conn);
428 assert!(matches!(result, Err(SecurityError::InvalidClientCert { .. })));
429 }
430
431 #[test]
432 fn test_valid_cert_accepted() {
433 let enforcer = TlsEnforcer::strict();
434 let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
435
436 enforcer
437 .validate_connection(&conn)
438 .unwrap_or_else(|e| panic!("expected valid cert accepted: {e}"));
439 }
440
441 #[test]
446 fn test_all_3_tls_settings_enforced_together() {
447 let enforcer = TlsEnforcer::strict();
448 let valid_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
452 enforcer
453 .validate_connection(&valid_conn)
454 .unwrap_or_else(|e| panic!("expected all checks to pass: {e}"));
455
456 let http_conn = TlsConnection::new_http();
458 assert!(matches!(
459 enforcer.validate_connection(&http_conn),
460 Err(SecurityError::TlsRequired { .. })
461 ));
462
463 let old_tls_conn = TlsConnection::new_secure(TlsVersion::V1_2);
465 assert!(matches!(
466 enforcer.validate_connection(&old_tls_conn),
467 Err(SecurityError::TlsVersionTooOld { .. })
468 ));
469
470 let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
472 assert!(matches!(
473 enforcer.validate_connection(&no_cert_conn),
474 Err(SecurityError::MtlsRequired { .. })
475 ));
476 }
477
478 #[test]
483 fn test_error_messages_clear_and_loggable() {
484 let enforcer = TlsEnforcer::strict();
485
486 let tls_required_err = enforcer.validate_connection(&TlsConnection::new_http());
487 if let Err(SecurityError::TlsRequired { detail }) = tls_required_err {
488 assert!(!detail.is_empty());
489 assert!(detail.contains("HTTP") || detail.contains("HTTPS"));
490 } else {
491 panic!("Expected TlsRequired error");
492 }
493
494 let tls_version_err =
495 enforcer.validate_connection(&TlsConnection::new_secure(TlsVersion::V1_0));
496 if let Err(SecurityError::TlsVersionTooOld { current, required }) = tls_version_err {
497 assert_eq!(current, TlsVersion::V1_0);
498 assert_eq!(required, TlsVersion::V1_3);
499 } else {
500 panic!("Expected TlsVersionTooOld error");
501 }
502 }
503
504 #[test]
509 fn test_permissive_config() {
510 let config = TlsConfig::permissive();
511 assert!(!config.tls_required);
512 assert!(!config.mtls_required);
513 assert_eq!(config.min_version, TlsVersion::V1_2);
514 }
515
516 #[test]
517 fn test_standard_config() {
518 let config = TlsConfig::standard();
519 assert!(config.tls_required);
520 assert!(!config.mtls_required);
521 assert_eq!(config.min_version, TlsVersion::V1_2);
522 }
523
524 #[test]
525 fn test_strict_config() {
526 let config = TlsConfig::strict();
527 assert!(config.tls_required);
528 assert!(config.mtls_required);
529 assert_eq!(config.min_version, TlsVersion::V1_3);
530 }
531
532 #[test]
533 fn test_enforcer_helpers() {
534 let permissive = TlsEnforcer::permissive();
535 assert!(!permissive.config().tls_required);
536
537 let standard = TlsEnforcer::standard();
538 assert!(standard.config().tls_required);
539
540 let strict = TlsEnforcer::strict();
541 assert!(strict.config().mtls_required);
542 }
543
544 #[test]
549 fn test_tls_version_display() {
550 assert_eq!(TlsVersion::V1_0.to_string(), "TLS 1.0");
551 assert_eq!(TlsVersion::V1_1.to_string(), "TLS 1.1");
552 assert_eq!(TlsVersion::V1_2.to_string(), "TLS 1.2");
553 assert_eq!(TlsVersion::V1_3.to_string(), "TLS 1.3");
554 }
555
556 #[test]
557 fn test_tls_version_ordering() {
558 assert!(TlsVersion::V1_0 < TlsVersion::V1_1);
559 assert!(TlsVersion::V1_1 < TlsVersion::V1_2);
560 assert!(TlsVersion::V1_2 < TlsVersion::V1_3);
561 assert!(TlsVersion::V1_3 > TlsVersion::V1_2);
562 }
563
564 #[test]
565 fn test_tls_connection_helpers() {
566 let http_conn = TlsConnection::new_http();
567 assert!(!http_conn.is_secure);
568
569 let secure_conn = TlsConnection::new_secure(TlsVersion::V1_3);
570 assert!(secure_conn.is_secure);
571 assert!(!secure_conn.has_client_cert);
572
573 let mtls_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
574 assert!(mtls_conn.is_secure);
575 assert!(mtls_conn.has_client_cert);
576 assert!(mtls_conn.client_cert_valid);
577 }
578
579 #[test]
584 fn test_custom_config_from_individual_settings() {
585 let config = TlsConfig {
586 tls_required: true,
587 mtls_required: false,
588 min_version: TlsVersion::V1_2,
589 };
590
591 let enforcer = TlsEnforcer::from_config(config);
592
593 let http_conn = TlsConnection::new_http();
595 assert!(matches!(
596 enforcer.validate_connection(&http_conn),
597 Err(SecurityError::TlsRequired { .. })
598 ));
599
600 let secure_conn = TlsConnection::new_secure(TlsVersion::V1_2);
602 enforcer
603 .validate_connection(&secure_conn)
604 .unwrap_or_else(|e| panic!("expected HTTPS with TLS 1.2 to pass: {e}"));
605
606 let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
608 enforcer
609 .validate_connection(&no_cert_conn)
610 .unwrap_or_else(|e| panic!("expected HTTPS without client cert to pass: {e}"));
611 }
612
613 #[test]
614 fn test_http_with_certificate_info_still_fails_when_tls_required() {
615 let enforcer = TlsEnforcer::standard(); let http_with_cert_info = TlsConnection {
619 is_secure: false, version: TlsVersion::V1_2,
621 has_client_cert: true,
622 client_cert_valid: true,
623 };
624
625 assert!(matches!(
626 enforcer.validate_connection(&http_with_cert_info),
627 Err(SecurityError::TlsRequired { .. })
628 ));
629 }
630}