use std::{fs, path::Path};
fn main() {
let mut any_secret_context = false;
let mut uses_thread_rng_for_secret = false;
let mut saw_osrng = false;
let mut saw_const_time_eq = false;
let mut saw_zeroize = false;
let mut saw_pqc = false;
let files = collect_rs_files("src");
for f in files {
let Ok(src) = fs::read_to_string(&f) else { continue; };
if src.contains("pqcrypto") || src.contains("oqs") {
saw_pqc = true;
}
for line in src.lines() {
let l = line.trim();
let looks_like_code = !(l.starts_with("//")
|| l.starts_with("/*")
|| l.starts_with('*')
|| l.contains('"'));
if looks_like_code && looks_like_secret_context_line(l) {
any_secret_context = true;
}
if looks_like_code && l.contains("thread_rng()") && looks_like_secret_context_line(l) {
uses_thread_rng_for_secret = true;
}
if looks_like_code
&& (l.contains("rand::rngs::OsRng") || l.contains("rand_core::OsRng"))
{
saw_osrng = true;
}
if looks_like_code
&& (l.contains("subtle::ConstantTimeEq")
|| l.contains(".ct_eq(")
|| l.contains("ct_eq("))
{
saw_const_time_eq = true;
}
if looks_like_code
&& (l.contains("derive(Zeroize") || l.contains("zeroize::Zeroize"))
{
saw_zeroize = true;
}
}
}
let require_crypto_hygiene = saw_pqc || any_secret_context;
if !require_crypto_hygiene {
println!("ok");
return;
}
if uses_thread_rng_for_secret && !saw_osrng {
println!("err: avoid `thread_rng()` for key/nonce; use OsRng");
return;
}
if !saw_osrng {
println!("err: no OsRng usage detected for secrets");
return;
}
if !saw_const_time_eq {
println!("err: no constant-time equality found (use subtle::ConstantTimeEq / ct_eq)");
return;
}
if !saw_zeroize {
println!("err: no `zeroize` usage detected for secret types");
return;
}
println!("ok");
}
fn collect_rs_files(root: &str) -> Vec<String> {
let mut out = Vec::new();
visit(Path::new(root), &mut out);
out
}
fn visit(p: &Path, out: &mut Vec<String>) {
if p.file_name().and_then(|s| s.to_str()) == Some("target") {
return;
}
let Ok(read) = fs::read_dir(p) else { return };
for entry in read.flatten() {
let path = entry.path();
if path.is_dir() {
visit(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
if let Some(s) = path.to_str() {
out.push(s.to_string());
}
}
}
}
fn looks_like_secret_context_line(l: &str) -> bool {
l.contains("keygen")
|| l.contains("generate_key")
|| l.contains("seal")
|| l.contains("sign(")
|| l.contains("nonce")
|| l.contains("secret")
|| l.contains("private_key")
|| l.contains("shared_secret")
}