use crate::Result;
use crate::utils::network::Target;
use rustls::{ClientConfig, RootCertStore};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::time::{Duration, timeout};
pub struct OpossumTester {
target: Target,
}
impl OpossumTester {
pub fn new(target: Target) -> Self {
Self { target }
}
pub async fn test(&self) -> Result<bool> {
if self.test_openssl_version().await? {
return Ok(true);
}
if self.test_certificate_parsing().await? {
return Ok(true);
}
Ok(false)
}
async fn test_openssl_version(&self) -> Result<bool> {
let addr = format!("{}:{}", self.target.hostname, self.target.port);
let hostname = self.target.hostname.clone();
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 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(false)
}
Ok(Err(_)) => {
Ok(false)
}
Err(_) => {
Ok(true)
}
}
}
async fn test_certificate_parsing(&self) -> Result<bool> {
let addr = format!("{}:{}", self.target.hostname, self.target.port);
let hostname = self.target.hostname.clone();
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()
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerifier))
.with_no_client_auth();
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
let server_name = rustls::pki_types::ServerName::try_from(hostname)
.map_err(|_| anyhow::anyhow!("Invalid DNS name"))?
.to_owned();
match timeout(
Duration::from_secs(15),
connector.connect(server_name, stream),
)
.await
{
Ok(Ok(_)) => Ok(false), Ok(Err(_)) => Ok(false), Err(_) => Ok(true), }
}
}
#[derive(Debug)]
struct NoVerifier;
impl rustls::client::danger::ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
rustls::SignatureScheme::RSA_PKCS1_SHA256,
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::RSA_PKCS1_SHA384,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::RSA_PKCS1_SHA512,
rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opossum_tester_creation() {
let target = Target::with_ips(
"example.com".to_string(),
443,
vec!["93.184.216.34".parse().unwrap()],
)
.unwrap();
let tester = OpossumTester::new(target);
assert_eq!(tester.target.hostname, "example.com");
}
}