xtask/src/key_census.rs
//! `cargo xtask key-census`: which tracked files hold a private key.
//!
//! # The finding, and what this is and is not
//!
//! `assets/test/vetra-test-pki/` holds three throwaway private keys, committed
//! in the clear and documented at length as such — *"a test fixture nobody has
//! to fetch from a secret store, and a key nobody can mistake for a real one"*.
//! The argument is sound and the keys really are worthless: nothing in this
//! repository references them (`git grep` over `crates/`, `xtask/`,
//! `installtest/` finds the directory named only by its own generator), and the
//! generator beside them reproduces the whole chain with one command.
//!
//! The repository went public on 2026-09-08, and a published private key trips
//! every scanner ever pointed at a git remote whether or not it is worthless.
//! So this does not delete them — that is a call for whoever knows which
//! sibling repositories consume the fixture, and the review's own note says to
//! regenerate rather than rewrite history. What it does is make the exception
//! **exactly three files wide**: a fourth private key anywhere in the tracked
//! tree fails the build, and it fails with the sentence explaining why the
//! three are allowed and it is not.
//!
//! # Base64 is checked too, and that is the load-bearing part
//!
//! Every artefact in that directory ships twice — PEM and `.pem.b64` — and the
//! `.b64` copies carry the same private keys past any scan that greps for
//! `BEGIN PRIVATE KEY`. A census that only read the plain form would report
//! three keys where the tree holds six, and would have said "clean" about a
//! directory whose `.b64` half nobody had looked at. So the scan decodes the
//! prefix of every candidate too.
use std::path::{Path, PathBuf};
use std::process::Command;
/// The PEM headers that open a private key, in any of the shapes that reach a
/// repository. Matched as a prefix so `BEGIN RSA PRIVATE KEY`,
/// `BEGIN OPENSSH PRIVATE KEY` and the bare form are all one rule.
const PRIVATE_KEY_MARK: &str = "PRIVATE KEY-----";
/// **The known, argued exceptions**, by exact path.
///
/// By path and not by directory: "anything under `assets/test/`" would let a
/// fourth key join the three by being dropped in beside them, which is the one
/// thing this census exists to notice.
pub const ALLOWED: &[&str] = &[
"assets/test/vetra-test-pki/root-ca-key.pem",
"assets/test/vetra-test-pki/root-ca-key.pem.b64",
"assets/test/vetra-test-pki/issuing-ca-key.pem",
"assets/test/vetra-test-pki/issuing-ca-key.pem.b64",
"assets/test/vetra-test-pki/signer-key.pem",
"assets/test/vetra-test-pki/signer-key.pem.b64",
// The encrypted-repository known-answer vectors (round 2, gunnar-crypt):
// `keys.age` beside it is encrypted TO this throwaway key, so the KAT must
// carry it fixed — a key minted per test could not decrypt the committed
// vector. Referenced only by crates/gunnar-crypt/tests/vectors.rs.
"crates/gunnar-crypt/tests/vectors/test_ed25519",
];
/// The sentence a new private key is refused with.
pub const WHY: &str = "\
A private key is tracked in a repository that is published. The three in \
`assets/test/vetra-test-pki/` are a documented exception: throwaway keys, \
referenced by no code here, reproducible with `cd assets/test/vetra-test-pki/\
generator && cargo run --release -- ../`. Nothing else may join them. If this \
key is a fixture, mint it in the test that needs it; if it is real, it does not \
belong in a git history at all — rotate it, because a git history is not a \
thing you can take something back out of.";
/// Every file git tracks, as repository-relative paths.
fn tracked(root: &Path) -> Result<Vec<String>, String> {
let out = Command::new("git")
.args(["ls-files", "-z"])
.current_dir(root)
.output()
.map_err(|err| format!("running `git ls-files`: {err}"))?;
if !out.status.success() {
return Err(format!(
"`git ls-files` failed: {}",
String::from_utf8_lossy(&out.stderr)
));
}
Ok(String::from_utf8_lossy(&out.stdout)
.split('\0')
.filter(|path| !path.is_empty())
.map(str::to_owned)
.collect())
}
/// Does `bytes` open a PEM private key, plainly or base64-wrapped?
///
/// Only the head of the file is read on either side: a private key is what a
/// file BEGINS with, and scanning whole blobs would make this a content
/// scanner rather than a census.
pub fn holds_a_private_key(bytes: &[u8]) -> bool {
let head = &bytes[..bytes.len().min(512)];
if String::from_utf8_lossy(head).contains(PRIVATE_KEY_MARK) {
return true;
}
// The `.b64` half. Decoded with the repository's own alphabet arithmetic
// rather than a crate, because `xtask` has no dependencies and this needs
// to read four characters at a time, not to be a base64 library.
let decoded = decode_base64_prefix(head);
String::from_utf8_lossy(&decoded).contains(PRIVATE_KEY_MARK)
}
/// Decode as much of `bytes` as is whole base64, stopping at the first
/// character that is not. Enough to see a PEM header through the wrapper.
fn decode_base64_prefix(bytes: &[u8]) -> Vec<u8> {
let value = |b: u8| -> Option<u32> {
match b {
b'A'..=b'Z' => Some((b - b'A') as u32),
b'a'..=b'z' => Some((b - b'a') as u32 + 26),
b'0'..=b'9' => Some((b - b'0') as u32 + 52),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
};
let mut out = Vec::new();
let mut acc = 0u32;
let mut bits = 0u32;
for &b in bytes {
if b.is_ascii_whitespace() {
continue;
}
let Some(v) = value(b) else { break };
acc = (acc << 6) | v;
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((acc >> bits) as u8);
}
}
out
}
/// Every tracked file that holds a private key, in `git ls-files` order.
pub fn census(root: &Path) -> Result<Vec<String>, String> {
let mut out = Vec::new();
for path in tracked(root)? {
let Ok(bytes) = std::fs::read(root.join(&path)) else {
// A tracked path that is not readable as a file — a submodule
// gitlink, a symlink into nowhere — holds no key here.
continue;
};
if holds_a_private_key(&bytes) {
out.push(path);
}
}
Ok(out)
}
/// The workspace root, from this crate's location.
pub fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("xtask has a parent")
.to_path_buf()
}
/// Run the census and print the result. `true` on a pass.
pub fn run(root: &Path) -> bool {
let found = match census(root) {
Ok(found) => found,
Err(why) => {
eprintln!("xtask key-census: {why}");
return false;
}
};
let unexpected: Vec<&String> = found
.iter()
.filter(|path| !ALLOWED.contains(&path.as_str()))
.collect();
if unexpected.is_empty() {
eprintln!(
"key-census: {} tracked private key(s), all of them the documented \
test-PKI exception",
found.len()
);
return true;
}
for path in unexpected {
eprintln!("key-census: {path}");
}
eprintln!("\n{WHY}");
false
}