use super::revocation::{RevocationChecker, RevocationResult, RevocationStatus};
use crate::Result;
use crate::certificates::parser::CertificateInfo;
use serde::{Deserialize, Serialize};
pub struct StrictRevocationChecker {
base_checker: RevocationChecker,
hard_fail_mode: bool,
}
impl StrictRevocationChecker {
pub fn new(phone_out_enabled: bool, hard_fail_mode: bool) -> Self {
Self {
base_checker: RevocationChecker::new(phone_out_enabled),
hard_fail_mode,
}
}
pub async fn check_revocation_with_hardfail(
&self,
cert: &CertificateInfo,
issuer: Option<&CertificateInfo>,
) -> Result<StrictRevocationResult> {
match self
.base_checker
.check_revocation_status(cert, issuer)
.await
{
Ok(result) => {
if self.hard_fail_mode && result.status == RevocationStatus::Error {
return Err(crate::TlsError::InvalidHandshake {
details: format!(
"Hard fail mode: revocation check failed with error: {}",
result.details
),
});
}
Ok(StrictRevocationResult {
base_result: result,
hard_fail_applied: false,
error_details: None,
})
}
Err(e) => {
if self.hard_fail_mode {
Err(crate::TlsError::InvalidHandshake {
details: format!("Hard fail mode: revocation check error: {}", e),
})
} else {
Ok(StrictRevocationResult {
base_result: RevocationResult {
status: RevocationStatus::Unknown,
method: super::revocation::RevocationMethod::None,
details: format!("Revocation check error (soft fail): {}", e),
ocsp_stapling: false,
must_staple: false,
},
hard_fail_applied: false,
error_details: Some(e.to_string()),
})
}
}
}
}
pub async fn check_revocation_chain(
&self,
certificates: &[CertificateInfo],
) -> Result<Vec<StrictRevocationResult>> {
let mut results = Vec::new();
for (i, cert) in certificates.iter().enumerate() {
let issuer = certificates.get(i + 1);
match self.check_revocation_with_hardfail(cert, issuer).await {
Ok(result) => results.push(result),
Err(e) => {
if self.hard_fail_mode {
return Err(e);
}
results.push(StrictRevocationResult {
base_result: RevocationResult {
status: RevocationStatus::Unknown,
method: super::revocation::RevocationMethod::None,
details: format!("Chain check error (soft fail): {}", e),
ocsp_stapling: false,
must_staple: false,
},
hard_fail_applied: false,
error_details: Some(e.to_string()),
});
}
}
}
Ok(results)
}
pub fn is_hard_fail_enabled(&self) -> bool {
self.hard_fail_mode
}
pub fn is_phone_out_enabled(&self) -> bool {
self.base_checker.is_phone_out_enabled()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrictRevocationResult {
pub base_result: RevocationResult,
pub hard_fail_applied: bool,
pub error_details: Option<String>,
}
impl StrictRevocationResult {
pub fn status(&self) -> RevocationStatus {
self.base_result.status
}
pub fn method(&self) -> super::revocation::RevocationMethod {
self.base_result.method
}
pub fn details(&self) -> &str {
&self.base_result.details
}
pub fn is_good(&self) -> bool {
self.base_result.status == RevocationStatus::Good
}
pub fn is_revoked(&self) -> bool {
self.base_result.status == RevocationStatus::Revoked
}
pub fn is_unknown(&self) -> bool {
self.base_result.status == RevocationStatus::Unknown
}
pub fn is_error(&self) -> bool {
self.base_result.status == RevocationStatus::Error
}
}
pub struct StrictRevocationCheckerBuilder {
phone_out_enabled: bool,
hard_fail_mode: bool,
}
impl Default for StrictRevocationCheckerBuilder {
fn default() -> Self {
Self::new()
}
}
impl StrictRevocationCheckerBuilder {
pub fn new() -> Self {
Self {
phone_out_enabled: false,
hard_fail_mode: false,
}
}
pub fn with_phone_out(mut self, enabled: bool) -> Self {
self.phone_out_enabled = enabled;
self
}
pub fn with_hard_fail(mut self, enabled: bool) -> Self {
self.hard_fail_mode = enabled;
self
}
pub fn build(self) -> StrictRevocationChecker {
StrictRevocationChecker::new(self.phone_out_enabled, self.hard_fail_mode)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_defaults() {
let checker = StrictRevocationCheckerBuilder::new().build();
assert!(!checker.is_hard_fail_enabled());
}
#[test]
fn test_builder_with_hard_fail() {
let checker = StrictRevocationCheckerBuilder::new()
.with_hard_fail(true)
.build();
assert!(checker.is_hard_fail_enabled());
}
#[test]
fn test_strict_result_status_checks() {
let result = StrictRevocationResult {
base_result: RevocationResult {
status: RevocationStatus::Good,
method: super::super::revocation::RevocationMethod::OCSP,
details: "Test".to_string(),
ocsp_stapling: false,
must_staple: false,
},
hard_fail_applied: false,
error_details: None,
};
assert!(result.is_good());
assert!(!result.is_revoked());
assert!(!result.is_unknown());
assert!(!result.is_error());
}
#[test]
fn test_strict_result_revoked() {
let result = StrictRevocationResult {
base_result: RevocationResult {
status: RevocationStatus::Revoked,
method: super::super::revocation::RevocationMethod::OCSP,
details: "Certificate is revoked".to_string(),
ocsp_stapling: false,
must_staple: false,
},
hard_fail_applied: false,
error_details: None,
};
assert!(!result.is_good());
assert!(result.is_revoked());
assert!(!result.is_unknown());
assert!(!result.is_error());
}
}