use std::fmt;
use serde::{Deserialize, Serialize};
use crate::security::errors::{Result, SecurityError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TlsVersion {
V1_0,
V1_1,
V1_2,
V1_3,
}
impl fmt::Display for TlsVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::V1_0 => write!(f, "TLS 1.0"),
Self::V1_1 => write!(f, "TLS 1.1"),
Self::V1_2 => write!(f, "TLS 1.2"),
Self::V1_3 => write!(f, "TLS 1.3"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TlsConnection {
pub is_secure: bool,
pub version: TlsVersion,
pub has_client_cert: bool,
pub client_cert_valid: bool,
}
impl TlsConnection {
#[must_use]
pub const fn new_http() -> Self {
Self {
is_secure: false,
version: TlsVersion::V1_2, has_client_cert: false,
client_cert_valid: false,
}
}
#[must_use]
pub const fn new_secure(version: TlsVersion) -> Self {
Self {
is_secure: true,
version,
has_client_cert: false,
client_cert_valid: false,
}
}
#[must_use]
pub const fn new_secure_with_client_cert(version: TlsVersion) -> Self {
Self {
is_secure: true,
version,
has_client_cert: true,
client_cert_valid: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TlsConfig {
pub tls_required: bool,
pub mtls_required: bool,
pub min_version: TlsVersion,
}
impl TlsConfig {
#[must_use]
pub const fn permissive() -> Self {
Self {
tls_required: false,
mtls_required: false,
min_version: TlsVersion::V1_2,
}
}
#[must_use]
pub const fn standard() -> Self {
Self {
tls_required: true,
mtls_required: false,
min_version: TlsVersion::V1_2,
}
}
#[must_use]
pub const fn strict() -> Self {
Self {
tls_required: true,
mtls_required: true,
min_version: TlsVersion::V1_3,
}
}
}
#[derive(Debug, Clone)]
pub struct TlsEnforcer {
config: TlsConfig,
}
impl TlsEnforcer {
#[must_use]
pub const fn from_config(config: TlsConfig) -> Self {
Self { config }
}
#[must_use]
pub const fn permissive() -> Self {
Self::from_config(TlsConfig::permissive())
}
#[must_use]
pub const fn standard() -> Self {
Self::from_config(TlsConfig::standard())
}
#[must_use]
pub const fn strict() -> Self {
Self::from_config(TlsConfig::strict())
}
pub fn validate_connection(&self, conn: &TlsConnection) -> Result<()> {
if self.config.tls_required && !conn.is_secure {
return Err(SecurityError::TlsRequired {
detail: "HTTPS required, but connection is HTTP".to_string(),
});
}
if conn.is_secure && conn.version < self.config.min_version {
return Err(SecurityError::TlsVersionTooOld {
current: conn.version,
required: self.config.min_version,
});
}
if self.config.mtls_required && !conn.has_client_cert {
return Err(SecurityError::MtlsRequired {
detail: "Client certificate required, but none provided".to_string(),
});
}
if conn.has_client_cert && !conn.client_cert_valid {
return Err(SecurityError::InvalidClientCert {
detail: "Client certificate provided but validation failed".to_string(),
});
}
Ok(())
}
#[must_use]
pub const fn config(&self) -> &TlsConfig {
&self.config
}
}