use super::{ScanContext, ScanPhase};
use crate::ciphers::tester::CipherTester;
use crate::protocols::{Protocol, ProtocolTestResult};
use crate::{Args, Result};
use async_trait::async_trait;
use std::collections::HashMap;
pub struct CipherPhase;
impl CipherPhase {
pub fn new() -> Self {
Self
}
fn configure_tester(&self, context: &ScanContext) -> CipherTester {
let target = context.target();
let mut tester = CipherTester::new(target);
if let Some(timeout_secs) = context.args.connection.connect_timeout {
tester = tester.with_connect_timeout(std::time::Duration::from_secs(timeout_secs));
}
if let Some(sleep_ms) = context.args.connection.sleep {
tester = tester.with_sleep(std::time::Duration::from_millis(sleep_ms));
}
tester = tester.with_max_concurrent_tests(context.args.network.max_concurrent_ciphers);
if let Some(retry_config) = context.args.retry_config() {
tester = tester.with_retry_config(Some(retry_config));
}
if context.args.starttls.rdp {
tester = tester.with_rdp(true);
}
if let Some(starttls_proto) = context.args.starttls_protocol() {
tester = tester.with_starttls(Some(starttls_proto));
}
if context.args.network.test_all_ips {
tester = tester.with_test_all_ips(true);
}
tester
}
async fn test_supported_protocols(
&self,
tester: &CipherTester,
protocol_results: &[ProtocolTestResult],
) -> Result<HashMap<Protocol, crate::ciphers::tester::ProtocolCipherSummary>> {
let mut results = HashMap::new();
for protocol_result in protocol_results {
if !protocol_result.supported || matches!(protocol_result.protocol, Protocol::QUIC) {
continue;
}
let summary = tester
.test_protocol_ciphers(protocol_result.protocol)
.await?;
if !summary.supported_ciphers.is_empty() {
results.insert(protocol_result.protocol, summary);
}
}
Ok(results)
}
}
impl Default for CipherPhase {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ScanPhase for CipherPhase {
fn name(&self) -> &'static str {
"Testing Cipher Suites"
}
fn should_run(&self, args: &Args) -> bool {
!args.scan.no_ciphersuites
&& (args.scan.each_cipher || args.scan.all || args.target.is_some())
}
async fn execute(&self, context: &mut ScanContext) -> Result<()> {
if context.results.protocols.is_empty() {
return Ok(());
}
let tester = self.configure_tester(context);
let cipher_results = self
.test_supported_protocols(&tester, &context.results.protocols)
.await?;
context.results.ciphers = cipher_results;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn test_cipher_phase_should_run() {
let phase = CipherPhase::new();
let mut args = Args::default();
args.scan.each_cipher = true;
assert!(phase.should_run(&args));
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 mut args = Args::default();
args.scan.all = true;
args.scan.no_ciphersuites = true;
assert!(!phase.should_run(&args));
let args = Args::default();
assert!(!phase.should_run(&args));
}
#[test]
fn test_cipher_phase_name() {
let phase = CipherPhase::new();
assert_eq!(phase.name(), "Testing Cipher Suites");
}
}