use crate::sign::{minisign_verify, parse_minisign_pubkey};
use serde::Deserialize;
use std::path::Path;
pub const COMPILED_IN_ROOT_KEYS: &[&str] = &[
"untrusted comment: vanta registry root key\nRWTKdzWEeVXHgj5NxXCdfaJwJYJ5rdpNJ+MJ4IINh2RlSVBgOOt7QbKL",
];
#[derive(Debug, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct RootsFile {
keys: Vec<String>,
}
pub fn load_root_keys(trust_dir: &Path) -> Vec<String> {
let mut roots: Vec<String> = COMPILED_IN_ROOT_KEYS
.iter()
.map(|s| s.to_string())
.collect();
let path = trust_dir.join("roots.toml");
if let Ok(src) = std::fs::read_to_string(&path) {
if let Ok(parsed) = toml::from_str::<RootsFile>(&src) {
roots.extend(parsed.keys);
}
}
roots
}
pub fn index_signed_by_root(index_bytes: &[u8], signature: &str, roots: &[String]) -> bool {
roots.iter().any(|root| match parse_minisign_pubkey(root) {
Ok(key) => minisign_verify(index_bytes, signature, &key).is_ok(),
Err(_) => false,
})
}
pub fn artifact_key_is_trusted(artifact_key: &str, index_verified: bool, roots: &[String]) -> bool {
index_verified || key_in_roots(artifact_key, roots)
}
fn key_in_roots(key: &str, roots: &[String]) -> bool {
match payload_line(key) {
Some(target) => roots.iter().any(|r| payload_line(r) == Some(target)),
None => false,
}
}
fn payload_line(text: &str) -> Option<&str> {
text.lines()
.map(str::trim)
.rfind(|l| !l.is_empty() && !l.starts_with("untrusted comment:"))
}
#[cfg(test)]
mod tests {
use super::*;
fn sign(seed: [u8; 32], data: &[u8]) -> (String, String) {
use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signer, SigningKey};
let key_id = [1u8, 2, 3, 4, 5, 6, 7, 8];
let sk = SigningKey::from_bytes(&seed);
let pk = sk.verifying_key().to_bytes();
let sig = sk.sign(data).to_bytes();
let mut pk_raw = b"Ed".to_vec();
pk_raw.extend_from_slice(&key_id);
pk_raw.extend_from_slice(&pk);
let pubkey = format!("untrusted comment: test\n{}", STANDARD.encode(&pk_raw));
let mut sig_raw = b"Ed".to_vec();
sig_raw.extend_from_slice(&key_id);
sig_raw.extend_from_slice(&sig);
let sig_file = format!(
"untrusted comment: test sig\n{}\ntrusted comment: t\n{}",
STANDARD.encode(&sig_raw),
STANDARD.encode([0u8; 64])
);
(pubkey, sig_file)
}
#[test]
fn index_verified_only_against_pinned_root() {
let index = b"[tools.node]\n# the registry index bytes";
let (root_pub, sig) = sign([7u8; 32], index);
let roots = [root_pub];
assert!(index_signed_by_root(index, &sig, &roots));
assert!(!index_signed_by_root(index, &sig, &[]));
let (attacker_pub, _) = sign([9u8; 32], b"unrelated");
assert!(!index_signed_by_root(index, &sig, &[attacker_pub]));
assert!(!index_signed_by_root(b"tampered", &sig, &roots));
}
#[test]
fn artifact_key_trust_rules() {
let (attacker_pub, _) = sign([3u8; 32], b"x");
let (root_pub, _) = sign([4u8; 32], b"y");
let roots = [root_pub.clone()];
assert!(!artifact_key_is_trusted(&attacker_pub, false, &roots));
assert!(artifact_key_is_trusted(&attacker_pub, true, &roots));
assert!(artifact_key_is_trusted(&root_pub, false, &roots));
}
}