#[path = "build_support/mod.rs"]
mod build_support;
use build_support::{curation, der, pem, subject_hash};
use std::fmt::Write as _;
use std::path::Path;
struct Entry {
subject_hash: u32,
der: Vec<u8>,
subject: Vec<u8>,
label: String,
}
fn main() {
let manifest = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR");
let roots = Path::new(&manifest).join("roots");
println!("cargo:rerun-if-changed=roots");
println!("cargo:rerun-if-changed=build_support");
println!("cargo:rerun-if-changed=build.rs");
let now = curation::now_yyyymmddhhmmss();
let mut entries = Vec::new();
let mut pems = Vec::new();
collect_pems(&roots, &mut pems);
pems.sort();
for path in &pems {
println!("cargo:rerun-if-changed={}", path.display());
let file = path.file_name().unwrap().to_string_lossy().into_owned();
let text = std::fs::read_to_string(path).unwrap_or_else(|e| panic!("{file}: {e}"));
let der = pem::read_one_certificate(&text).unwrap_or_else(|e| panic!("{file}: {e}"));
let hash = subject_hash(&der).unwrap_or_else(|e| panic!("{file}: subject hash: {e}"));
let tbs = der::tbs_certificate(&der).unwrap_or_else(|e| panic!("{file}: {e}"));
let subject = der::tbs_field(tbs, der::TbsField::Subject)
.unwrap_or_else(|e| panic!("{file}: subject: {e}"))
.to_vec();
if let Err(reason) = curation::check_cert(&der, now) {
println!("cargo:warning=roots/{file}: {reason}");
}
let label = parse_label(&text).unwrap_or_else(|| stem(&file));
entries.push(Entry {
subject_hash: hash,
der,
subject,
label,
});
}
assert!(!entries.is_empty(), "no roots found in {}", roots.display());
entries.sort_by(|a, b| {
(a.subject_hash, &a.subject, &a.der).cmp(&(b.subject_hash, &b.subject, &b.der))
});
let mut seqs = vec![0u8; entries.len()];
for i in 0..entries.len() {
if i > 0 && entries[i].subject_hash == entries[i - 1].subject_hash {
seqs[i] = seqs[i - 1] + 1;
}
}
let der_dir = Path::new(&out_dir).join("der");
std::fs::create_dir_all(&der_dir).expect("create der dir");
let mut src = String::new();
src.push_str("// @generated by build.rs from roots/*.pem — do not edit.\n");
writeln!(src, "pub(crate) static CERTS: &[Cert] = &[").unwrap();
for (i, e) in entries.iter().enumerate() {
let der_path = der_dir.join(format!("{i:04}.der"));
std::fs::write(&der_path, &e.der).expect("write der blob");
writeln!(
src,
" // {} ({:08x}.{})",
e.label, e.subject_hash, seqs[i]
)
.unwrap();
writeln!(
src,
" Cert::new(0x{:08x}, {}, include_bytes!(concat!(env!(\"OUT_DIR\"), \"/der/{:04}.der\")), &{:?}, {:?}),",
e.subject_hash, seqs[i], i, e.subject, e.label
)
.unwrap();
}
writeln!(src, "];").unwrap();
std::fs::write(Path::new(&out_dir).join("certs.rs"), src).expect("write certs.rs");
}
fn collect_pems(dir: &Path, out: &mut Vec<std::path::PathBuf>) {
println!("cargo:rerun-if-changed={}", dir.display());
let entries =
std::fs::read_dir(dir).unwrap_or_else(|e| panic!("cannot read {}: {e}", dir.display()));
for ent in entries {
let path = ent.expect("dir entry").path();
if path.is_dir() {
collect_pems(&path, out);
} else if path.extension().and_then(|s| s.to_str()) == Some("pem") {
out.push(path);
}
}
}
fn parse_label(text: &str) -> Option<String> {
text.lines()
.find_map(|l| l.trim_start().strip_prefix("# Label:"))
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
fn stem(file: &str) -> String {
file.strip_suffix(".pem").unwrap_or(file).to_string()
}