use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct TlsInspectionResult {
pub detected: bool,
pub description: String,
pub tests: Vec<TlsTest>,
}
#[derive(Debug, Clone, Serialize)]
pub struct TlsTest {
pub host: String,
pub expected_issuer: String,
pub actual_issuer: Option<String>,
pub intercepted: bool,
}
pub async fn collect() -> Option<TlsInspectionResult> {
let mut tests = Vec::new();
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.ok()?;
let intercepted = check_tls_interception(&client).await;
tests.push(TlsTest {
host: "cloudflare.com".to_string(),
expected_issuer: "Cloudflare Inc / DigiCert / Google Trust Services".to_string(),
actual_issuer: None,
intercepted,
});
let description = if intercepted {
"TLS interception detected - a proxy may be inspecting HTTPS traffic".to_string()
} else {
"No TLS interception detected".to_string()
};
Some(TlsInspectionResult {
detected: intercepted,
description,
tests,
})
}
async fn check_tls_interception(client: &reqwest::Client) -> bool {
match client.get("https://1.1.1.1/cdn-cgi/trace").send().await {
Ok(resp) => {
let text = resp.text().await.unwrap_or_default();
if text.contains("fl=") && text.contains("ip=") {
return false;
}
true
}
Err(e) => {
let err_str = format!("{}", e);
err_str.contains("certificate") || err_str.contains("SSL") || err_str.contains("tls")
}
}
}