use super::{ScanContext, ScanPhase};
use crate::certificates::{
parser::{CertificateChain, CertificateParser},
revocation::{RevocationChecker, RevocationResult},
validator::{CertificateValidator, ValidationResult},
};
use crate::{Args, Result};
use async_trait::async_trait;
pub struct CertificatePhase;
impl CertificatePhase {
pub fn new() -> Self {
Self
}
async fn parse_certificate_chain(&self, context: &ScanContext) -> Result<CertificateChain> {
let target = context.target();
let parser = if let Some(ref mtls_config) = context.mtls_config {
CertificateParser::with_mtls(target, mtls_config.clone())
} else {
CertificateParser::new(target)
};
parser.get_certificate_chain().await
}
fn validate_certificate_chain(
&self,
context: &ScanContext,
chain: &CertificateChain,
) -> Result<ValidationResult> {
let hostname = context.target.hostname.clone();
let validator = if context.args.scan.no_check_certificate {
CertificateValidator::with_config(hostname, true, true)?
} else {
CertificateValidator::with_platform_trust(hostname)?
};
validator.validate_chain(chain)
}
async fn check_revocation_status(
&self,
context: &ScanContext,
chain: &CertificateChain,
) -> Option<RevocationResult> {
if !context.args.tls.phone_out {
return None;
}
let leaf_cert = chain.certificates.first()?;
let issuer_cert = if chain.certificates.len() >= 2 {
chain.certificates.get(1)
} else {
None
};
let checker = RevocationChecker::new(true);
checker
.check_revocation_status(leaf_cert, issuer_cert)
.await
.ok()
}
}
impl Default for CertificatePhase {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ScanPhase for CertificatePhase {
fn name(&self) -> &'static str {
"Analyzing Certificate"
}
fn should_run(&self, args: &Args) -> bool {
args.scan.all || args.target.is_some()
}
async fn execute(&self, context: &mut ScanContext) -> Result<()> {
let chain = self.parse_certificate_chain(context).await?;
let validation = self.validate_certificate_chain(context, &chain)?;
let revocation = self.check_revocation_status(context, &chain).await;
context.results.certificate_chain = Some(crate::scanner::CertificateAnalysisResult {
chain,
validation,
revocation,
});
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn test_certificate_phase_should_run() {
let phase = CertificatePhase::new();
let mut args = Args::default();
args.scan.all = true;
assert!(phase.should_run(&args));
let mut args = Args::default();
args.target = Some("example.com".to_string());
assert!(phase.should_run(&args));
let args = Args::default();
assert!(!phase.should_run(&args));
}
#[test]
fn test_certificate_phase_name() {
let phase = CertificatePhase::new();
assert_eq!(phase.name(), "Analyzing Certificate");
}
}