use openssl::hash::{MessageDigest, hash};
use openssl::pkey::PKey;
use openssl::x509::X509;
pub struct DebianKeyDetector {
weak_key_hashes: Vec<String>,
}
impl DebianKeyDetector {
pub fn new() -> Self {
Self {
weak_key_hashes: Self::load_sample_weak_keys(),
}
}
pub fn with_hashes(hashes: Vec<String>) -> Self {
Self {
weak_key_hashes: hashes,
}
}
pub fn is_weak_key(&self, cert: &X509) -> Result<bool, String> {
let public_key = cert
.public_key()
.map_err(|e| format!("Failed to extract public key: {}", e))?;
self.is_weak_public_key(&public_key)
}
pub fn is_weak_public_key(
&self,
public_key: &PKey<impl openssl::pkey::HasPublic>,
) -> Result<bool, String> {
let der_bytes = public_key
.public_key_to_der()
.map_err(|e| format!("Failed to export public key to DER: {}", e))?;
let fingerprint = hash(MessageDigest::sha256(), &der_bytes)
.map_err(|e| format!("Failed to compute SHA256 hash: {}", e))?;
let fingerprint_hex = fingerprint
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>();
Ok(self.weak_key_hashes.contains(&fingerprint_hex))
}
fn load_sample_weak_keys() -> Vec<String> {
vec![
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592".to_string(),
"e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683".to_string(),
]
}
pub fn blacklist_size(&self) -> usize {
self.weak_key_hashes.len()
}
pub fn add_weak_key(&mut self, hash: String) {
if !self.weak_key_hashes.contains(&hash) {
self.weak_key_hashes.push(hash);
}
}
pub fn clear_blacklist(&mut self) {
self.weak_key_hashes.clear();
}
pub fn load_from_hashes<I>(&mut self, hashes: I)
where
I: IntoIterator<Item = String>,
{
for hash in hashes {
self.add_weak_key(hash);
}
}
}
impl Default for DebianKeyDetector {
fn default() -> Self {
Self::new()
}
}
pub fn is_debian_weak_key(cert: &X509) -> Result<bool, String> {
let detector = DebianKeyDetector::new();
detector.is_weak_key(cert)
}
#[cfg(test)]
mod tests {
use super::*;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
#[test]
fn test_detector_creation() {
let detector = DebianKeyDetector::new();
assert!(
detector.blacklist_size() > 0,
"Detector should have sample weak keys"
);
}
#[test]
fn test_detector_with_custom_hashes() {
let custom_hashes = vec![
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
];
let detector = DebianKeyDetector::with_hashes(custom_hashes.clone());
assert_eq!(detector.blacklist_size(), 2);
}
#[test]
fn test_add_weak_key() {
let mut detector = DebianKeyDetector::new();
let initial_size = detector.blacklist_size();
let new_hash =
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc".to_string();
detector.add_weak_key(new_hash.clone());
assert_eq!(detector.blacklist_size(), initial_size + 1);
detector.add_weak_key(new_hash);
assert_eq!(detector.blacklist_size(), initial_size + 1);
}
#[test]
fn test_clear_blacklist() {
let mut detector = DebianKeyDetector::new();
detector.clear_blacklist();
assert_eq!(detector.blacklist_size(), 0);
}
#[test]
fn test_load_from_hashes() {
let mut detector = DebianKeyDetector::new();
detector.clear_blacklist();
let hashes = vec![
"1111111111111111111111111111111111111111111111111111111111111111".to_string(),
"2222222222222222222222222222222222222222222222222222222222222222".to_string(),
"3333333333333333333333333333333333333333333333333333333333333333".to_string(),
];
detector.load_from_hashes(hashes.clone());
assert_eq!(detector.blacklist_size(), 3);
}
#[test]
fn test_normal_key_not_weak() {
let rsa = Rsa::generate(2048).expect("test assertion should succeed");
let pkey = PKey::from_rsa(rsa).expect("test assertion should succeed");
let detector = DebianKeyDetector::new();
let is_weak = detector
.is_weak_public_key(&pkey)
.expect("test assertion should succeed");
assert!(
!is_weak,
"Normal generated key should not be detected as weak"
);
}
#[test]
fn test_default_implementation() {
let detector = DebianKeyDetector::default();
assert!(detector.blacklist_size() > 0);
}
}