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)]
62pub enum TlsVersion {
63 V1_0,
65 V1_1,
67 V1_2,
69 V1_3,
71}
72
73impl fmt::Display for TlsVersion {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::V1_0 => write!(f, "TLS 1.0"),
77 Self::V1_1 => write!(f, "TLS 1.1"),
78 Self::V1_2 => write!(f, "TLS 1.2"),
79 Self::V1_3 => write!(f, "TLS 1.3"),
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct TlsConnection {
91 pub is_secure: bool,
93
94 pub version: TlsVersion,
96
97 pub has_client_cert: bool,
99
100 pub client_cert_valid: bool,
103}
104
105impl TlsConnection {
106 #[must_use]
108 pub fn new_http() -> Self {
109 Self {
110 is_secure: false,
111 version: TlsVersion::V1_2, has_client_cert: false,
113 client_cert_valid: false,
114 }
115 }
116
117 #[must_use]
119 pub fn new_secure(version: TlsVersion) -> Self {
120 Self {
121 is_secure: true,
122 version,
123 has_client_cert: false,
124 client_cert_valid: false,
125 }
126 }
127
128 #[must_use]
130 pub fn new_secure_with_client_cert(version: TlsVersion) -> Self {
131 Self {
132 is_secure: true,
133 version,
134 has_client_cert: true,
135 client_cert_valid: true,
136 }
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
145pub struct TlsConfig {
146 pub tls_required: bool,
148
149 pub mtls_required: bool,
151
152 pub min_version: TlsVersion,
154}
155
156impl TlsConfig {
157 #[must_use]
163 pub fn permissive() -> Self {
164 Self {
165 tls_required: false,
166 mtls_required: false,
167 min_version: TlsVersion::V1_2,
168 }
169 }
170
171 #[must_use]
177 pub fn standard() -> Self {
178 Self {
179 tls_required: true,
180 mtls_required: false,
181 min_version: TlsVersion::V1_2,
182 }
183 }
184
185 #[must_use]
191 pub fn strict() -> Self {
192 Self {
193 tls_required: true,
194 mtls_required: true,
195 min_version: TlsVersion::V1_3,
196 }
197 }
198}
199
200#[derive(Debug, Clone)]
205pub struct TlsEnforcer {
206 config: TlsConfig,
207}
208
209impl TlsEnforcer {
210 #[must_use]
212 pub fn from_config(config: TlsConfig) -> Self {
213 Self { config }
214 }
215
216 #[must_use]
218 pub fn permissive() -> Self {
219 Self::from_config(TlsConfig::permissive())
220 }
221
222 #[must_use]
224 pub fn standard() -> Self {
225 Self::from_config(TlsConfig::standard())
226 }
227
228 #[must_use]
230 pub fn strict() -> Self {
231 Self::from_config(TlsConfig::strict())
232 }
233
234 pub fn validate_connection(&self, conn: &TlsConnection) -> Result<()> {
244 if self.config.tls_required && !conn.is_secure {
246 return Err(SecurityError::TlsRequired {
247 detail: "HTTPS required, but connection is HTTP".to_string(),
248 });
249 }
250
251 if conn.is_secure && conn.version < self.config.min_version {
253 return Err(SecurityError::TlsVersionTooOld {
254 current: conn.version,
255 required: self.config.min_version,
256 });
257 }
258
259 if self.config.mtls_required && !conn.has_client_cert {
261 return Err(SecurityError::MtlsRequired {
262 detail: "Client certificate required, but none provided".to_string(),
263 });
264 }
265
266 if conn.has_client_cert && !conn.client_cert_valid {
268 return Err(SecurityError::InvalidClientCert {
269 detail: "Client certificate provided but validation failed".to_string(),
270 });
271 }
272
273 Ok(())
274 }
275
276 #[must_use]
278 pub const fn config(&self) -> &TlsConfig {
279 &self.config
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
292 fn test_http_allowed_when_tls_not_required() {
293 let enforcer = TlsEnforcer::permissive();
294 let conn = TlsConnection::new_http();
295
296 assert!(enforcer.validate_connection(&conn).is_ok());
297 }
298
299 #[test]
300 fn test_http_rejected_when_tls_required() {
301 let enforcer = TlsEnforcer::standard();
302 let conn = TlsConnection::new_http();
303
304 let result = enforcer.validate_connection(&conn);
305 assert!(matches!(result, Err(SecurityError::TlsRequired { .. })));
306 }
307
308 #[test]
309 fn test_https_allowed_when_tls_required() {
310 let enforcer = TlsEnforcer::standard();
311 let conn = TlsConnection::new_secure(TlsVersion::V1_3);
312
313 assert!(enforcer.validate_connection(&conn).is_ok());
314 }
315
316 #[test]
321 fn test_tls_1_0_rejected_when_min_1_3() {
322 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure(TlsVersion::V1_0);
324
325 let result = enforcer.validate_connection(&conn);
326 assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
327 }
328
329 #[test]
330 fn test_tls_1_2_rejected_when_min_1_3() {
331 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure(TlsVersion::V1_2);
333
334 let result = enforcer.validate_connection(&conn);
335 assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
336 }
337
338 #[test]
339 fn test_tls_1_3_allowed_when_min_1_2() {
340 let enforcer = TlsEnforcer::standard(); let conn = TlsConnection::new_secure(TlsVersion::V1_3);
342
343 assert!(enforcer.validate_connection(&conn).is_ok());
344 }
345
346 #[test]
347 fn test_tls_1_2_allowed_when_min_1_2() {
348 let enforcer = TlsEnforcer::standard(); let conn = TlsConnection::new_secure(TlsVersion::V1_2);
350
351 assert!(enforcer.validate_connection(&conn).is_ok());
352 }
353
354 #[test]
355 fn test_tls_version_check_skipped_for_http() {
356 let enforcer = TlsEnforcer::permissive();
358 let conn = TlsConnection::new_http();
359
360 assert!(enforcer.validate_connection(&conn).is_ok());
362 }
363
364 #[test]
369 fn test_client_cert_optional_when_mtls_not_required() {
370 let enforcer = TlsEnforcer::standard(); let conn = TlsConnection::new_secure(TlsVersion::V1_3);
372
373 assert!(enforcer.validate_connection(&conn).is_ok());
374 }
375
376 #[test]
377 fn test_client_cert_required_when_mtls_required() {
378 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure(TlsVersion::V1_3);
380
381 let result = enforcer.validate_connection(&conn);
382 assert!(matches!(result, Err(SecurityError::MtlsRequired { .. })));
383 }
384
385 #[test]
386 fn test_client_cert_allowed_when_mtls_required() {
387 let enforcer = TlsEnforcer::strict(); let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
389
390 assert!(enforcer.validate_connection(&conn).is_ok());
391 }
392
393 #[test]
398 fn test_invalid_cert_rejected() {
399 let enforcer = TlsEnforcer::strict();
400 let conn = TlsConnection {
401 is_secure: true,
402 version: TlsVersion::V1_3,
403 has_client_cert: true,
404 client_cert_valid: false, };
406
407 let result = enforcer.validate_connection(&conn);
408 assert!(matches!(result, Err(SecurityError::InvalidClientCert { .. })));
409 }
410
411 #[test]
412 fn test_valid_cert_accepted() {
413 let enforcer = TlsEnforcer::strict();
414 let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
415
416 assert!(enforcer.validate_connection(&conn).is_ok());
417 }
418
419 #[test]
424 fn test_all_3_tls_settings_enforced_together() {
425 let enforcer = TlsEnforcer::strict();
426 let valid_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
430 assert!(enforcer.validate_connection(&valid_conn).is_ok());
431
432 let http_conn = TlsConnection::new_http();
434 assert!(matches!(
435 enforcer.validate_connection(&http_conn),
436 Err(SecurityError::TlsRequired { .. })
437 ));
438
439 let old_tls_conn = TlsConnection::new_secure(TlsVersion::V1_2);
441 assert!(matches!(
442 enforcer.validate_connection(&old_tls_conn),
443 Err(SecurityError::TlsVersionTooOld { .. })
444 ));
445
446 let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
448 assert!(matches!(
449 enforcer.validate_connection(&no_cert_conn),
450 Err(SecurityError::MtlsRequired { .. })
451 ));
452 }
453
454 #[test]
459 fn test_error_messages_clear_and_loggable() {
460 let enforcer = TlsEnforcer::strict();
461
462 let tls_required_err = enforcer.validate_connection(&TlsConnection::new_http());
463 if let Err(SecurityError::TlsRequired { detail }) = tls_required_err {
464 assert!(!detail.is_empty());
465 assert!(detail.contains("HTTP") || detail.contains("HTTPS"));
466 } else {
467 panic!("Expected TlsRequired error");
468 }
469
470 let tls_version_err =
471 enforcer.validate_connection(&TlsConnection::new_secure(TlsVersion::V1_0));
472 if let Err(SecurityError::TlsVersionTooOld { current, required }) = tls_version_err {
473 assert_eq!(current, TlsVersion::V1_0);
474 assert_eq!(required, TlsVersion::V1_3);
475 } else {
476 panic!("Expected TlsVersionTooOld error");
477 }
478 }
479
480 #[test]
485 fn test_permissive_config() {
486 let config = TlsConfig::permissive();
487 assert!(!config.tls_required);
488 assert!(!config.mtls_required);
489 assert_eq!(config.min_version, TlsVersion::V1_2);
490 }
491
492 #[test]
493 fn test_standard_config() {
494 let config = TlsConfig::standard();
495 assert!(config.tls_required);
496 assert!(!config.mtls_required);
497 assert_eq!(config.min_version, TlsVersion::V1_2);
498 }
499
500 #[test]
501 fn test_strict_config() {
502 let config = TlsConfig::strict();
503 assert!(config.tls_required);
504 assert!(config.mtls_required);
505 assert_eq!(config.min_version, TlsVersion::V1_3);
506 }
507
508 #[test]
509 fn test_enforcer_helpers() {
510 let permissive = TlsEnforcer::permissive();
511 assert!(!permissive.config().tls_required);
512
513 let standard = TlsEnforcer::standard();
514 assert!(standard.config().tls_required);
515
516 let strict = TlsEnforcer::strict();
517 assert!(strict.config().mtls_required);
518 }
519
520 #[test]
525 fn test_tls_version_display() {
526 assert_eq!(TlsVersion::V1_0.to_string(), "TLS 1.0");
527 assert_eq!(TlsVersion::V1_1.to_string(), "TLS 1.1");
528 assert_eq!(TlsVersion::V1_2.to_string(), "TLS 1.2");
529 assert_eq!(TlsVersion::V1_3.to_string(), "TLS 1.3");
530 }
531
532 #[test]
533 fn test_tls_version_ordering() {
534 assert!(TlsVersion::V1_0 < TlsVersion::V1_1);
535 assert!(TlsVersion::V1_1 < TlsVersion::V1_2);
536 assert!(TlsVersion::V1_2 < TlsVersion::V1_3);
537 assert!(TlsVersion::V1_3 > TlsVersion::V1_2);
538 }
539
540 #[test]
541 fn test_tls_connection_helpers() {
542 let http_conn = TlsConnection::new_http();
543 assert!(!http_conn.is_secure);
544
545 let secure_conn = TlsConnection::new_secure(TlsVersion::V1_3);
546 assert!(secure_conn.is_secure);
547 assert!(!secure_conn.has_client_cert);
548
549 let mtls_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
550 assert!(mtls_conn.is_secure);
551 assert!(mtls_conn.has_client_cert);
552 assert!(mtls_conn.client_cert_valid);
553 }
554
555 #[test]
560 fn test_custom_config_from_individual_settings() {
561 let config = TlsConfig {
562 tls_required: true,
563 mtls_required: false,
564 min_version: TlsVersion::V1_2,
565 };
566
567 let enforcer = TlsEnforcer::from_config(config);
568
569 let http_conn = TlsConnection::new_http();
571 assert!(matches!(
572 enforcer.validate_connection(&http_conn),
573 Err(SecurityError::TlsRequired { .. })
574 ));
575
576 let secure_conn = TlsConnection::new_secure(TlsVersion::V1_2);
578 assert!(enforcer.validate_connection(&secure_conn).is_ok());
579
580 let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
582 assert!(enforcer.validate_connection(&no_cert_conn).is_ok());
583 }
584
585 #[test]
586 fn test_http_with_certificate_info_still_fails_when_tls_required() {
587 let enforcer = TlsEnforcer::standard(); let http_with_cert_info = TlsConnection {
591 is_secure: false, version: TlsVersion::V1_2,
593 has_client_cert: true,
594 client_cert_valid: true,
595 };
596
597 assert!(matches!(
598 enforcer.validate_connection(&http_with_cert_info),
599 Err(SecurityError::TlsRequired { .. })
600 ));
601 }
602}