use crate::Result;
use crate::protocols::Protocol;
use crate::protocols::handshake::ClientHelloBuilder;
use crate::utils::network::Target;
use crate::utils::{VulnSslConfig, test_vuln_ssl_connection};
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::timeout;
pub struct CrimeTester<'a> {
target: &'a Target,
}
impl<'a> CrimeTester<'a> {
pub fn new(target: &'a Target) -> Self {
Self { target }
}
pub async fn test(&self) -> Result<CrimeTestResult> {
let tls_compression = self.test_tls_compression().await?;
let spdy_compression = self.test_spdy_compression().await?;
let vulnerable = tls_compression || spdy_compression;
let details = if vulnerable {
let mut parts = Vec::new();
if tls_compression {
parts.push("TLS compression enabled");
}
if spdy_compression {
parts.push("SPDY compression enabled");
}
format!("Vulnerable to CRIME (CVE-2012-4929): {}", parts.join(", "))
} else {
"Not vulnerable - TLS/SPDY compression disabled".to_string()
};
Ok(CrimeTestResult {
vulnerable,
tls_compression_enabled: tls_compression,
spdy_compression_enabled: spdy_compression,
details,
})
}
async fn test_tls_compression(&self) -> Result<bool> {
let connected = test_vuln_ssl_connection(self.target, VulnSslConfig::default())
.await
.map_err(crate::TlsError::from)?;
if connected {
Ok(false)
} else {
Ok(false)
}
}
async fn test_spdy_compression(&self) -> Result<bool> {
let addr = self.target.socket_addrs()[0];
match timeout(Duration::from_secs(5), TcpStream::connect(addr)).await {
Ok(Ok(mut stream)) => {
let client_hello = self.build_client_hello_with_npn();
stream.write_all(&client_hello).await?;
let mut buffer = vec![0u8; 8192];
match timeout(Duration::from_secs(3), stream.read(&mut buffer)).await {
Ok(Ok(n)) if n > 0 => {
let spdy_selected = buffer[..n]
.windows(4)
.any(|w| w == b"spdy" || w == b"h2-1" || w == b"h2c-");
Ok(spdy_selected)
}
_ => Ok(false),
}
}
_ => Ok(false),
}
}
fn build_client_hello_with_npn(&self) -> Vec<u8> {
let mut builder = ClientHelloBuilder::new(Protocol::TLS12);
builder
.for_rsa_key_exchange()
.with_compression(true) .add_npn(); builder.build().unwrap_or_else(|_| Vec::new())
}
}
#[derive(Debug, Clone)]
pub struct CrimeTestResult {
pub vulnerable: bool,
pub tls_compression_enabled: bool,
pub spdy_compression_enabled: bool,
pub details: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crime_result_creation() {
let result = CrimeTestResult {
vulnerable: true,
tls_compression_enabled: true,
spdy_compression_enabled: false,
details: "Test".to_string(),
};
assert!(result.vulnerable);
assert!(result.tls_compression_enabled);
assert!(!result.spdy_compression_enabled);
}
#[test]
fn test_client_hello_with_npn() {
let target = Target::with_ips(
"example.com".to_string(),
443,
vec!["93.184.216.34".parse().unwrap()],
)
.unwrap();
let tester = CrimeTester::new(&target);
let hello = tester.build_client_hello_with_npn();
assert!(hello.len() > 50);
assert_eq!(hello[0], 0x16); assert_eq!(hello[5], 0x01);
let has_deflate = hello.windows(2).any(|w| w == [0x02, 0x01]);
assert!(has_deflate);
}
}