use serde::{Deserialize, Serialize};
use crate::error::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DomainTrustScoreRequest {
pub domain: String,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpTrustScoreRequest {
pub ip: String,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
}
fn default_timeout() -> u64 { 30 }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustScore {
pub target: String,
pub target_type: String, pub overall_score: u8, pub trust_level: String, pub dimension_scores: DimensionScores,
pub risk_factors: Vec<String>,
pub trust_factors: Vec<String>,
pub recommendation: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DimensionScores {
pub dns_trust: u8, pub tls_trust: u8, pub threat_reputation: u8, pub registration_age: u8, pub infrastructure_maturity: u8, }
impl DimensionScores {
pub fn weighted_score(&self) -> u8 {
let weighted = (self.dns_trust as u32 * 20
+ self.tls_trust as u32 * 25
+ self.threat_reputation as u32 * 25
+ self.registration_age as u32 * 15
+ self.infrastructure_maturity as u32 * 15) / 100;
std::cmp::min(100, weighted as u8)
}
}
pub async fn check_domain_trust_score(req: &DomainTrustScoreRequest) -> Result<TrustScore> {
let mut dimensions = DimensionScores {
dns_trust: 50,
tls_trust: 50,
threat_reputation: 50,
registration_age: 50,
infrastructure_maturity: 50,
};
let mut risk_factors = Vec::new();
let mut trust_factors = Vec::new();
if let Ok(email_result) = crate::api::check_email_security(&crate::api::EmailSecurityRequest {
domain: req.domain.clone(),
dkim_selectors: vec!["default".to_string(), "google".to_string()],
timeout_secs: req.timeout_secs,
}).await {
let has_dmarc = email_result.dmarc.raw.is_some();
let has_spf = email_result.spf.raw.is_some();
if has_dmarc && has_spf {
dimensions.dns_trust = 85;
trust_factors.push("Complete email security (SPF+DMARC)".to_string());
} else if has_spf {
dimensions.dns_trust = 60;
trust_factors.push("SPF record configured".to_string());
} else {
dimensions.dns_trust = 30;
risk_factors.push("Missing email security records".to_string());
}
}
if let Ok(tls_result) = crate::api::check_tls_chain(&crate::api::TlsCheckRequest {
hostname: req.domain.clone(),
port: 443,
check_dane: false,
timeout_secs: req.timeout_secs,
}).await {
if tls_result.valid {
let days_until_expiry = tls_result.days_until_expiry.unwrap_or(0);
if days_until_expiry > 180 {
dimensions.tls_trust = 95;
trust_factors.push("Valid TLS with 6+ months validity".to_string());
} else if days_until_expiry > 30 {
dimensions.tls_trust = 70;
trust_factors.push("Valid TLS but approaching expiry".to_string());
} else {
dimensions.tls_trust = 20;
risk_factors.push("TLS certificate expiring soon".to_string());
}
if let Some(version) = &tls_result.tls_version {
if version.contains("1.3") {
trust_factors.push("TLS 1.3".to_string());
}
}
} else {
dimensions.tls_trust = 10;
risk_factors.push("Invalid or missing TLS".to_string());
}
}
if let Ok(threat_result) = crate::api::check_threat_intel_aggregate(&crate::api::ThreatIntelRequest {
target: req.domain.clone(),
include_sources: None,
timeout_secs: req.timeout_secs,
}).await {
dimensions.threat_reputation = std::cmp::max(0, 100 - threat_result.risk_score);
if threat_result.overall_verdict == "clean" {
trust_factors.push("Clean threat reputation".to_string());
} else {
risk_factors.push(format!("Threat flags: {}", threat_result.overall_verdict));
}
}
if let Ok(whois_result) = crate::api::check_whois(&crate::api::WhoisCheckRequest {
domain: req.domain.clone(),
timeout_secs: req.timeout_secs,
}).await {
if let Some(created) = whois_result.created_date {
if created.contains("2024") {
dimensions.registration_age = 40;
risk_factors.push("Recently registered".to_string());
} else if created.contains("2023") {
dimensions.registration_age = 70;
trust_factors.push("Established domain (1-2 years)".to_string());
} else {
dimensions.registration_age = 85;
trust_factors.push("Legacy domain".to_string());
}
}
}
if let Ok(caa_result) = crate::api::check_caa(&crate::api::CaaCheckRequest {
domain: req.domain.clone(),
issued_by_ca: None,
timeout_secs: req.timeout_secs,
}).await {
if !caa_result.records.is_empty() {
dimensions.infrastructure_maturity = 85;
trust_factors.push("CAA records configured".to_string());
} else {
dimensions.infrastructure_maturity = 45;
risk_factors.push("No CAA records".to_string());
}
}
let overall_score = dimensions.weighted_score();
let trust_level = match overall_score {
80..=100 => "high_trust".to_string(),
60..=79 => "medium_trust".to_string(),
40..=59 => "low_trust".to_string(),
_ => "untrusted".to_string(),
};
let recommendation = match overall_score {
90..=100 => "Verified safe — proceed with confidence".to_string(),
75..=89 => "Generally trustworthy — monitor".to_string(),
60..=74 => "Acceptable — verify before engagement".to_string(),
40..=59 => "Elevated risk — exercise caution".to_string(),
_ => "High risk — avoid until verified".to_string(),
};
Ok(TrustScore {
target: req.domain.clone(),
target_type: "domain".to_string(),
overall_score,
trust_level,
dimension_scores: dimensions,
risk_factors,
trust_factors,
recommendation,
})
}
pub async fn check_ip_trust_score(req: &IpTrustScoreRequest) -> Result<TrustScore> {
let mut dimensions = DimensionScores {
dns_trust: 50,
tls_trust: 50,
threat_reputation: 50,
registration_age: 50,
infrastructure_maturity: 50,
};
let mut risk_factors = Vec::new();
let mut trust_factors = Vec::new();
if let Ok(rdns_result) = crate::api::check_rdns(&crate::api::RdnsCheckRequest {
ip: req.ip.clone(),
timeout_secs: req.timeout_secs,
}).await {
if let Some(ptr) = &rdns_result.ptr_record {
if !ptr.is_empty() {
dimensions.dns_trust = 80;
trust_factors.push("Reverse DNS configured".to_string());
} else {
dimensions.dns_trust = 30;
risk_factors.push("No reverse DNS".to_string());
}
} else {
dimensions.dns_trust = 30;
risk_factors.push("No reverse DNS".to_string());
}
}
dimensions.tls_trust = 50;
if let Ok(threat_result) = crate::api::check_threat_intel_aggregate(&crate::api::ThreatIntelRequest {
target: req.ip.clone(),
include_sources: None,
timeout_secs: req.timeout_secs,
}).await {
dimensions.threat_reputation = std::cmp::max(0, 100 - threat_result.risk_score);
if threat_result.overall_verdict != "clean" {
risk_factors.push(format!("Threat: {}", threat_result.overall_verdict));
} else {
trust_factors.push("Clean threat reputation".to_string());
}
}
if let Ok(bgp_result) = crate::api::check_bgp_route(&crate::api::BgpRouteRequest {
ip: req.ip.clone(),
timeout_secs: req.timeout_secs,
}).await {
if let Some(asn) = bgp_result.asn {
dimensions.registration_age = 75;
trust_factors.push(format!("BGP announced (ASN{})", asn));
} else {
dimensions.registration_age = 30;
risk_factors.push("Not in BGP routing".to_string());
}
}
if let Ok(rpki_result) = crate::api::check_rpki(&crate::api::RpkiCheckRequest {
ip: req.ip.clone(),
}).await {
if rpki_result.roa_valid {
dimensions.infrastructure_maturity = 95;
trust_factors.push("RPKI ROA valid".to_string());
} else if rpki_result.roa_state == "invalid" {
dimensions.infrastructure_maturity = 15;
risk_factors.push("RPKI ROA invalid".to_string());
} else {
dimensions.infrastructure_maturity = 50;
}
}
let overall_score = dimensions.weighted_score();
let trust_level = match overall_score {
80..=100 => "high_trust".to_string(),
60..=79 => "medium_trust".to_string(),
40..=59 => "low_trust".to_string(),
_ => "untrusted".to_string(),
};
let recommendation = match overall_score {
90..=100 => "Well-configured infrastructure — high trust".to_string(),
75..=89 => "Legitimate infrastructure — safe".to_string(),
60..=74 => "Standard infrastructure — acceptable".to_string(),
40..=59 => "Suspicious indicators — verify".to_string(),
_ => "High-risk IP — block or quarantine".to_string(),
};
Ok(TrustScore {
target: req.ip.clone(),
target_type: "ip".to_string(),
overall_score,
trust_level,
dimension_scores: dimensions,
risk_factors,
trust_factors,
recommendation,
})
}