use security_core::severity::SecuritySeverity;
use security_events::event::{EventOutcome, SecurityEvent};
use security_events::kind::EventKind;
use security_events::sink::SecuritySink;
use serde::Serialize;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub enum TlsVersion {
Ssl3,
Tls10,
Tls11,
Tls12,
Tls13,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
pub enum CipherSuite {
Aes128Gcm,
Aes256Gcm,
Chacha20Poly1305,
Aes128Cbc,
Aes256Cbc,
Rc4,
Des,
Null,
Other(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TlsValidationResult {
Allow,
Deny {
reason: TlsDenyReason,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TlsDenyReason {
TlsVersion {
minimum: TlsVersion,
actual: TlsVersion,
},
WeakCipher {
cipher: CipherSuite,
},
}
#[derive(Clone, Debug)]
pub struct TlsPolicy {
min_version: TlsVersion,
allowed_ciphers: Option<Vec<CipherSuite>>,
}
impl TlsPolicy {
#[must_use]
pub fn new(min_version: TlsVersion) -> Self {
Self {
min_version,
allowed_ciphers: None,
}
}
#[must_use]
pub fn with_allowed_ciphers(mut self, ciphers: Vec<CipherSuite>) -> Self {
self.allowed_ciphers = Some(ciphers);
self
}
pub fn validate(&self, version: TlsVersion, cipher: &CipherSuite) -> TlsValidationResult {
if version < self.min_version {
return TlsValidationResult::Deny {
reason: TlsDenyReason::TlsVersion {
minimum: self.min_version,
actual: version,
},
};
}
if Self::is_known_weak(cipher) {
return TlsValidationResult::Deny {
reason: TlsDenyReason::WeakCipher {
cipher: cipher.clone(),
},
};
}
if let Some(ref allowed) = self.allowed_ciphers {
if !allowed.contains(cipher) {
return TlsValidationResult::Deny {
reason: TlsDenyReason::WeakCipher {
cipher: cipher.clone(),
},
};
}
}
TlsValidationResult::Allow
}
pub fn validate_and_emit(
&self,
version: TlsVersion,
cipher: &CipherSuite,
sink: &dyn SecuritySink,
) -> TlsValidationResult {
let result = self.validate(version, cipher);
if let TlsValidationResult::Deny { ref reason } = result {
let mut event = SecurityEvent::new(
EventKind::TlsViolation,
SecuritySeverity::High,
EventOutcome::Blocked,
);
event.reason_code = Some(match reason {
TlsDenyReason::TlsVersion { .. } => "tls_version_too_low",
TlsDenyReason::WeakCipher { .. } => "weak_cipher_suite",
});
sink.write_event(&event);
}
result
}
fn is_known_weak(cipher: &CipherSuite) -> bool {
matches!(
cipher,
CipherSuite::Rc4 | CipherSuite::Des | CipherSuite::Null
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tls_version_ordering() {
assert!(TlsVersion::Ssl3 < TlsVersion::Tls10);
assert!(TlsVersion::Tls10 < TlsVersion::Tls11);
assert!(TlsVersion::Tls11 < TlsVersion::Tls12);
assert!(TlsVersion::Tls12 < TlsVersion::Tls13);
}
}