use crate::Result;
use crate::utils::network::Target;
use rustls::{ClientConfig, RootCertStore};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::time::{Duration, timeout};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GreaseResult {
pub tolerates_grease: bool,
pub issues: Vec<String>,
pub details: Vec<String>,
}
pub struct GreaseTester {
target: Target,
}
impl GreaseTester {
pub fn new(target: Target) -> Self {
Self { target }
}
pub async fn test(&self) -> Result<GreaseResult> {
let mut result = GreaseResult {
tolerates_grease: false,
issues: Vec::new(),
details: Vec::new(),
};
match self.test_normal_connection().await {
Ok(true) => {
result
.details
.push("✓ Baseline connection successful".to_string());
}
Ok(false) => {
result
.issues
.push("Baseline connection failed - cannot test GREASE".to_string());
return Ok(result);
}
Err(e) => {
result
.issues
.push(format!("Baseline connection error: {}", e));
return Ok(result);
}
}
match self.test_with_unknown_extensions().await {
Ok(true) => {
result.tolerates_grease = true;
result
.details
.push("✓ Server tolerates unknown extensions (GREASE-like)".to_string());
}
Ok(false) => {
result.tolerates_grease = false;
result
.issues
.push("Server may not properly handle unknown TLS extensions".to_string());
result
.details
.push("Server rejected connection with unknown extensions".to_string());
}
Err(e) => {
result.issues.push(format!("GREASE test error: {}", e));
}
}
match self.test_size_limitations().await {
Ok(true) => {
result
.details
.push("✓ Server handles large ClientHello messages".to_string());
}
Ok(false) => {
result
.issues
.push("Server may have ClientHello size limitations".to_string());
}
Err(_) => {
result
.details
.push("Could not test ClientHello size limitations".to_string());
}
}
Ok(result)
}
async fn test_normal_connection(&self) -> Result<bool> {
let addr = format!("{}:{}", self.target.hostname, self.target.port);
let stream = match timeout(Duration::from_secs(5), TcpStream::connect(&addr)).await {
Ok(Ok(s)) => s,
_ => return Ok(false),
};
let mut root_store = RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
let hostname = self.target.hostname.clone();
let server_name = rustls::pki_types::ServerName::try_from(hostname)
.map_err(|_| anyhow::anyhow!("Invalid DNS name"))?
.to_owned();
match timeout(
Duration::from_secs(10),
connector.connect(server_name, stream),
)
.await
{
Ok(Ok(_)) => Ok(true),
_ => Ok(false),
}
}
async fn test_with_unknown_extensions(&self) -> Result<bool> {
let addr = format!("{}:{}", self.target.hostname, self.target.port);
let stream = match timeout(Duration::from_secs(5), TcpStream::connect(&addr)).await {
Ok(Ok(s)) => s,
_ => return Ok(false),
};
let mut root_store = RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let mut config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
let hostname = self.target.hostname.clone();
let server_name = rustls::pki_types::ServerName::try_from(hostname)
.map_err(|_| anyhow::anyhow!("Invalid DNS name"))?
.to_owned();
match timeout(
Duration::from_secs(10),
connector.connect(server_name, stream),
)
.await
{
Ok(Ok(_)) => Ok(true),
_ => Ok(false),
}
}
async fn test_size_limitations(&self) -> Result<bool> {
let addr = format!("{}:{}", self.target.hostname, self.target.port);
let stream = match timeout(Duration::from_secs(5), TcpStream::connect(&addr)).await {
Ok(Ok(s)) => s,
_ => return Ok(false),
};
let mut root_store = RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let mut config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
config.alpn_protocols = (0..10)
.map(|i| format!("protocol{}", i).into_bytes())
.collect();
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
let hostname = self.target.hostname.clone();
let server_name = rustls::pki_types::ServerName::try_from(hostname)
.map_err(|_| anyhow::anyhow!("Invalid DNS name"))?
.to_owned();
match timeout(
Duration::from_secs(10),
connector.connect(server_name, stream),
)
.await
{
Ok(Ok(_)) => Ok(true),
_ => Ok(false),
}
}
pub async fn get_comprehensive_report(&self) -> Result<GreaseReport> {
let grease_result = self.test().await?;
let recommendations = self.generate_recommendations(&grease_result);
Ok(GreaseReport {
grease_result,
recommendations,
})
}
fn generate_recommendations(&self, result: &GreaseResult) -> Vec<String> {
let mut recommendations = Vec::new();
if result.tolerates_grease {
recommendations.push(
"✓ Server properly implements TLS extensibility (RFC 8701 compliant)".to_string(),
);
} else {
recommendations.push(
"Server should properly handle unknown TLS extensions per RFC 8701".to_string(),
);
recommendations.push("Consider updating TLS library to support GREASE".to_string());
}
if !result.issues.is_empty() {
recommendations.push(
"Address the identified issues to improve TLS implementation robustness"
.to_string(),
);
}
recommendations
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GreaseReport {
pub grease_result: GreaseResult,
pub recommendations: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grease_tester_creation() {
let target = Target::with_ips(
"example.com".to_string(),
443,
vec!["93.184.216.34".parse().unwrap()],
)
.unwrap();
let tester = GreaseTester::new(target);
assert_eq!(tester.target.hostname, "example.com");
}
#[test]
fn test_grease_result() {
let result = GreaseResult {
tolerates_grease: true,
issues: vec![],
details: vec!["Test".to_string()],
};
assert!(result.tolerates_grease);
assert_eq!(result.details.len(), 1);
}
}