172 lines
5.5 KiB
Rust
172 lines
5.5 KiB
Rust
use std::path::PathBuf;
|
|
|
|
fn usage() -> &'static str {
|
|
"Usage: verification_ccr_compare --source-ccr <path> --verification-ccr <path> --out-json <path> [--out-md <path>]"
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
struct Args {
|
|
source_ccr: PathBuf,
|
|
verification_ccr: PathBuf,
|
|
out_json: PathBuf,
|
|
out_md: Option<PathBuf>,
|
|
}
|
|
|
|
fn parse_args(argv: &[String]) -> Result<Args, String> {
|
|
let mut source_ccr = None;
|
|
let mut verification_ccr = None;
|
|
let mut out_json = None;
|
|
let mut out_md = None;
|
|
let mut index = 1usize;
|
|
while index < argv.len() {
|
|
let target = match argv[index].as_str() {
|
|
"--source-ccr" => &mut source_ccr,
|
|
"--verification-ccr" => &mut verification_ccr,
|
|
"--out-json" => &mut out_json,
|
|
"--out-md" => &mut out_md,
|
|
"-h" | "--help" => return Err(usage().to_string()),
|
|
other => return Err(format!("unknown argument: {other}\n{}", usage())),
|
|
};
|
|
index += 1;
|
|
*target =
|
|
Some(PathBuf::from(argv.get(index).ok_or_else(|| {
|
|
format!("{} requires a value", argv[index - 1])
|
|
})?));
|
|
index += 1;
|
|
}
|
|
let source_ccr = source_ccr.ok_or_else(|| format!("--source-ccr is required\n{}", usage()))?;
|
|
let verification_ccr =
|
|
verification_ccr.ok_or_else(|| format!("--verification-ccr is required\n{}", usage()))?;
|
|
let out_json = out_json.ok_or_else(|| format!("--out-json is required\n{}", usage()))?;
|
|
Ok(Args {
|
|
source_ccr,
|
|
verification_ccr,
|
|
out_json,
|
|
out_md,
|
|
})
|
|
}
|
|
|
|
fn run(args: &Args) -> Result<(), String> {
|
|
let comparison =
|
|
rpki::verification_only::compare_ccr_files(&args.source_ccr, &args.verification_ccr)?;
|
|
rpki::verification_only::write_ccr_comparison(
|
|
&args.out_json,
|
|
args.out_md.as_deref(),
|
|
&comparison,
|
|
)?;
|
|
if !comparison.state_digest_match {
|
|
return Err(format!(
|
|
"CCR state digest mismatch: {}",
|
|
comparison.mismatched_states.join(",")
|
|
));
|
|
}
|
|
println!("CCR state digests match: {}", args.out_json.display());
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> Result<(), String> {
|
|
let argv = std::env::args().collect::<Vec<_>>();
|
|
run(&parse_args(&argv)?)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use rpki::ccr::{
|
|
CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation,
|
|
build_roa_payload_state, encode_content_info,
|
|
};
|
|
use rpki::data_model::roa::{IpPrefix, RoaAfi};
|
|
use rpki::validation::objects::Vrp;
|
|
use std::fs;
|
|
|
|
fn argv(values: &[&str]) -> Vec<String> {
|
|
std::iter::once("verification_ccr_compare")
|
|
.chain(values.iter().copied())
|
|
.map(str::to_string)
|
|
.collect()
|
|
}
|
|
|
|
fn sample_ccr(asn: u32) -> Vec<u8> {
|
|
let vrps = build_roa_payload_state(&[Vrp {
|
|
asn,
|
|
prefix: IpPrefix {
|
|
afi: RoaAfi::Ipv4,
|
|
prefix_len: 24,
|
|
addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
},
|
|
max_length: 24,
|
|
}])
|
|
.expect("build VRP state");
|
|
encode_content_info(&CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
|
|
version: 0,
|
|
hash_alg: CcrDigestAlgorithm::Sha256,
|
|
produced_at: time::OffsetDateTime::UNIX_EPOCH,
|
|
mfts: None,
|
|
vrps: Some(vrps),
|
|
vaps: None,
|
|
tas: None,
|
|
rks: None,
|
|
}))
|
|
.expect("encode CCR")
|
|
}
|
|
|
|
#[test]
|
|
fn parse_args_accepts_complete_input_and_rejects_bad_forms() {
|
|
let parsed = parse_args(&argv(&[
|
|
"--source-ccr",
|
|
"source.ccr",
|
|
"--verification-ccr",
|
|
"verification.ccr",
|
|
"--out-json",
|
|
"result.json",
|
|
"--out-md",
|
|
"result.md",
|
|
]))
|
|
.expect("parse");
|
|
assert_eq!(parsed.source_ccr, PathBuf::from("source.ccr"));
|
|
assert_eq!(parsed.out_md, Some(PathBuf::from("result.md")));
|
|
assert!(parse_args(&argv(&[])).unwrap_err().contains("source-ccr"));
|
|
assert!(
|
|
parse_args(&argv(&["--source-ccr"]))
|
|
.unwrap_err()
|
|
.contains("requires a value")
|
|
);
|
|
assert!(
|
|
parse_args(&argv(&["--unknown"]))
|
|
.unwrap_err()
|
|
.contains("unknown argument")
|
|
);
|
|
assert!(parse_args(&argv(&["--help"])).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn run_writes_matching_outputs_and_rejects_mismatch() {
|
|
let temp = tempfile::tempdir().expect("tempdir");
|
|
let source = temp.path().join("source.ccr");
|
|
let verification = temp.path().join("verification.ccr");
|
|
let out_json = temp.path().join("out/result.json");
|
|
let out_md = temp.path().join("out/result.md");
|
|
fs::write(&source, sample_ccr(64496)).expect("source");
|
|
fs::write(&verification, sample_ccr(64496)).expect("verification");
|
|
let args = Args {
|
|
source_ccr: source.clone(),
|
|
verification_ccr: verification.clone(),
|
|
out_json: out_json.clone(),
|
|
out_md: Some(out_md.clone()),
|
|
};
|
|
run(&args).expect("matching run");
|
|
assert!(out_json.is_file());
|
|
assert!(out_md.is_file());
|
|
|
|
fs::write(&verification, sample_ccr(64497)).expect("mismatch");
|
|
let error = run(&args).unwrap_err();
|
|
assert!(error.contains("vrps"));
|
|
assert!(
|
|
fs::read_to_string(out_json)
|
|
.expect("comparison JSON")
|
|
.contains("mismatchedStates")
|
|
);
|
|
}
|
|
}
|