/// Collects statistics about the SKS packet dump using the openpgp /// crate, Sequoia's low-level API. /// /// Note that to achieve reasonable performance, you need to compile /// Sequoia and this program with optimizations: /// /// % cargo run --example dsa-statistics --release \ /// -- <packet-dump> [<packet-dump>..] use std::{ env, fs, sync::mpsc::sync_channel, thread, time::Duration, }; use anyhow::Context; use sequoia_openpgp as openpgp; use openpgp::{ cert::amalgamation::ValidAmalgamation, types::*, policy::NullPolicy, }; use sequoia_openpgp_mt::keyring; fn main() -> openpgp::Result<()> { let args: Vec<String> = env::args().collect(); if args.len() < 2 { return Err(anyhow::anyhow!("Collects statistics about OpenPGP packet dumps.\n\n\ Usage: {} <packet-dump> [<packet-dump>...]\n", args[0])); } let (sender, receiver) = sync_channel(4); // For each input file, create a parser. let n_threads = num_cpus::get() / 2; let chunk_size = args[1..].len() / n_threads; for inputs in args[1..].chunks(chunk_size) { let inputs = inputs.to_vec(); let sender = sender.clone(); thread::spawn(move || { for input in inputs { eprintln!("Parsing {:?}...", input); let f = fs::File::open(&input) .context(format!("Failed to parse {:?}", input)) .unwrap(); sender.send(keyring::parse(f) .context(format!("Failed to parse {:?}", input)) .unwrap()).unwrap(); } }); } drop(sender); const P: &NullPolicy = unsafe { &NullPolicy::new() }; let five_years = Duration::new(5 * 365 * 24 * 60 * 60, 0); let mut all_certs_including_parse_errors = 0; let mut all_certs = 0; let mut gp_certs = 0; let mut valid_all_certs = 0; let mut valid_gp_certs = 0; let mut valid_gp_dsa_elg_certs = 0; let mut valid_recent_all_certs = 0; let mut valid_recent_gp_certs = 0; let mut valid_recent_gp_dsa_elg_certs = 0; fn is_revoked(s: RevocationStatus) -> bool { match s { RevocationStatus::Revoked(_) => true, _ => false, } } while let Ok(certs) = receiver.recv() { all_certs_including_parse_errors += certs.len(); for cert in certs.into_iter().filter_map(|c| c.ok()) { all_certs += 1; // Canonicalize at creation time to see if it once had an // encryption (sub)key. let creation_time = cert.primary_key().key().creation_time(); if let Ok(vcert0) = cert.with_policy(P, creation_time) { if vcert0.keys() .for_transport_encryption() .for_storage_encryption() .count() > 0 { gp_certs += 1; } } // Canonicalize for now. if let Ok(vcert) = cert.with_policy(P, None) { if ! vcert.primary_key().alive().is_ok() || ! is_revoked(vcert.primary_key().revocation_status()) { // Primary not alive or revoked. continue; } let recent = if cert.primary_key().key().creation_time().elapsed().unwrap() <= five_years { 1 } else { 0 }; valid_all_certs += 1; valid_recent_all_certs += recent; // General purpose now? if vcert.keys() .for_transport_encryption() .for_storage_encryption() .alive() .revoked(false) .count() > 0 { valid_gp_certs += 1; valid_recent_gp_certs += recent; } #[allow(deprecated)] if vcert.primary_key().key().pk_algo() == PublicKeyAlgorithm::DSA && vcert.keys() .for_transport_encryption() .for_storage_encryption() .alive() .revoked(false) .filter(|vka| vka.key().pk_algo() == PublicKeyAlgorithm::ElGamalEncrypt) .count() > 0 { valid_gp_dsa_elg_certs += 1; valid_recent_gp_dsa_elg_certs += recent; } } } } println!("All (including parse errors): {}", all_certs_including_parse_errors); println!("All certs: {} ({:.2}% parsed ok)", all_certs, all_certs as f32 / all_certs_including_parse_errors as f32 * 100.); println!("General purpose certs (GP): {} ({:.2}% of all certs)", gp_certs, gp_certs as f32 / all_certs as f32 * 100.); println!("Valid now certs: {} ({:.2}% of all certs)", valid_all_certs, valid_all_certs as f32 / all_certs as f32 * 100.); println!("Valid now GP certs: {} ({:.2}% of all valid now certs)", valid_gp_certs, valid_gp_certs as f32 / valid_all_certs as f32 * 100.); println!("Valid now GP DSA/ELG certs: {} ({:.2}% of all valid now GP certs)", valid_gp_dsa_elg_certs, valid_gp_dsa_elg_certs as f32 / valid_gp_certs as f32 * 100.); println!("Valid now recent certs: {} ({:.2}% of all valid now certs)", valid_recent_all_certs, valid_recent_all_certs as f32 / valid_all_certs as f32 * 100.); println!("Valid now recent GP certs: {} ({:.2}% of all valid now recent certs)", valid_recent_gp_certs, valid_recent_gp_certs as f32 / valid_recent_all_certs as f32 * 100.); println!("Valid now recent GP DSA/ELG certs: {} ({:.2}% of all valid now recent GP certs)", valid_recent_gp_dsa_elg_certs, valid_recent_gp_dsa_elg_certs as f32 / valid_recent_gp_certs as f32 * 100.); Ok(()) }