20260511 CIR V3 trust anchors self-contained

This commit is contained in:
2026-05-11 18:12:41 +08:00
parent 51e483d924
commit f2fbb20a29
25 changed files with 742 additions and 522 deletions

View File

@@ -57,7 +57,7 @@ fn real_main() -> Result<(), String> {
let cir = rpki::cir::decode_cir(&bytes).map_err(|e| format!("decode cir failed: {e}"))?;
println!("object_count={}", cir.objects.len());
println!("tal_count={}", cir.tals.len());
println!("trust_anchor_count={}", cir.trust_anchors.len());
for (index, item) in cir.objects.iter().take(args.limit).enumerate() {
println!(
"{:04} object={} sha256={}",

View File

@@ -49,17 +49,23 @@ fn run(argv: Vec<String>) -> Result<(), String> {
.map_err(|e| format!("read CIR failed: {}: {e}", cir_path.display()))?;
let cir = rpki::cir::decode_cir(&bytes).map_err(|e| e.to_string())?;
std::fs::create_dir_all(&tals_dir)
.map_err(|e| format!("create tals dir failed: {}: {e}", tals_dir.display()))?;
std::fs::create_dir_all(&tals_dir).map_err(|e| {
format!(
"create trust_anchors dir failed: {}: {e}",
tals_dir.display()
)
})?;
let mut tal_files = Vec::new();
for (idx, tal) in cir.tals.iter().enumerate() {
for (idx, tal) in cir.trust_anchors.iter().enumerate() {
let filename = format!("tal-{:03}.tal", idx + 1);
let path = tals_dir.join(filename);
std::fs::write(&path, &tal.tal_bytes)
.map_err(|e| format!("write TAL failed: {}: {e}", path.display()))?;
tal_files.push(serde_json::json!({
"talUri": tal.tal_uri,
"taRsyncUri": tal.ta_rsync_uri,
"taCertificateSha256": hex::encode(&tal.ta_certificate_sha256),
"path": path,
}));
}
@@ -70,6 +76,7 @@ fn run(argv: Vec<String>) -> Result<(), String> {
.map_err(|e| format!("format validationTime failed: {e}"))?;
let meta = serde_json::json!({
"validationTime": validation_time,
"trustAnchorCount": cir.trust_anchors.len(),
"talFiles": tal_files,
});
if let Some(parent) = meta_json.parent() {

View File

@@ -59,9 +59,11 @@ fn run(argv: Vec<String>) -> Result<(), String> {
match result {
Ok(summary) => {
eprintln!(
"materialized CIR: mirror={} objects={} linked={} copied={} keep_db={}",
"materialized CIR: mirror={} objects={} trust_anchors={} materialized_files={} linked={} copied={} keep_db={}",
mirror_root.display(),
summary.object_count,
summary.trust_anchor_count,
summary.materialized_file_count,
summary.linked_files,
summary.copied_files,
keep_db

View File

@@ -443,8 +443,8 @@ fn uri_extension(uri: &str) -> String {
mod tests {
use super::*;
use rpki::cir::{
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
CirRejectedObject, CirTal, compute_reject_list_sha256, encode_cir,
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir,
};
#[test]
@@ -664,20 +664,28 @@ mod tests {
.collect::<Vec<_>>();
objects.sort_by(|left, right| left.rsync_uri.cmp(&right.rsync_uri));
CanonicalInputRepresentation {
version: CIR_VERSION_V2,
version: CIR_VERSION_V3,
hash_alg: CirHashAlgorithm::Sha256,
validation_time: time::OffsetDateTime::UNIX_EPOCH,
objects,
tals: vec![CirTal {
tal_uri: "https://tal.example.test/apnic.tal".to_string(),
tal_bytes: b"https://tal.example.test/apnic.tal\nrsync://example.test/ta.cer\nMIIB"
.to_vec(),
}],
trust_anchors: vec![sample_trust_anchor()],
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
rejected_objects: Vec::<CirRejectedObject>::new(),
}
}
fn sample_trust_anchor() -> CirTrustAnchor {
let ta_rsync_uri = "rsync://example.test/ta.cer";
let ta_certificate_der = b"ta-der".to_vec();
CirTrustAnchor {
ta_rsync_uri: ta_rsync_uri.to_string(),
tal_uri: "https://tal.example.test/apnic.tal".to_string(),
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
ta_certificate_sha256: Sha256::digest(&ta_certificate_der).to_vec(),
ta_certificate_der,
}
}
fn write_cir(path: &Path, cir: &CanonicalInputRepresentation) {
std::fs::write(path, encode_cir(cir).expect("encode cir")).expect("write cir");
}

View File

@@ -114,40 +114,56 @@ fn run(args: Args) -> Result<(), String> {
.iter()
.map(|item| item.object_uri.clone())
.collect::<BTreeSet<_>>();
let ours_tals = ours
.tals
let ours_trust_anchors = ours
.trust_anchors
.iter()
.map(|item| item.tal_uri.clone())
.collect::<BTreeSet<_>>();
let peer_tals = peer
.tals
.map(|item| {
(
item.ta_rsync_uri.clone(),
hex::encode(&item.ta_certificate_sha256),
)
})
.collect::<BTreeMap<_, _>>();
let peer_trust_anchors = peer
.trust_anchors
.iter()
.map(|item| item.tal_uri.clone())
.collect::<BTreeSet<_>>();
.map(|item| {
(
item.ta_rsync_uri.clone(),
hex::encode(&item.ta_certificate_sha256),
)
})
.collect::<BTreeMap<_, _>>();
let object_summary = compare_object_maps(&ours_objects, &peer_objects, args.sample_limit);
let reject_summary = compare_sets(&ours_rejects, &peer_rejects, args.sample_limit);
let tal_summary = compare_sets(&ours_tals, &peer_tals, args.sample_limit);
let trust_anchor_summary =
compare_object_maps(&ours_trust_anchors, &peer_trust_anchors, args.sample_limit);
let reject_hash_match = ours.reject_list_sha256 == peer.reject_list_sha256;
let all_match =
object_summary.match_ && reject_summary.match_ && tal_summary.match_ && reject_hash_match;
let all_match = object_summary.match_
&& reject_summary.match_
&& trust_anchor_summary.match_
&& reject_hash_match;
let summary = json!({
"allMatch": all_match,
"objects": object_summary.to_json(),
"rejects": reject_summary.to_json(),
"tals": tal_summary.to_json(),
"trustAnchors": trust_anchor_summary.to_json(),
"trust_anchors": trust_anchor_summary.to_json(),
"rejectListSha256Match": reject_hash_match,
"ours": {
"objectCount": ours.objects.len(),
"talCount": ours.tals.len(),
"talCount": ours.trust_anchors.len(),
"trustAnchorCount": ours.trust_anchors.len(),
"rejectCount": ours.rejected_objects.len(),
"rejectListSha256": hex::encode(&ours.reject_list_sha256),
"validationTime": ours.validation_time.to_string(),
},
"rpkiClient": {
"objectCount": peer.objects.len(),
"talCount": peer.tals.len(),
"talCount": peer.trust_anchors.len(),
"trustAnchorCount": peer.trust_anchors.len(),
"rejectCount": peer.rejected_objects.len(),
"rejectListSha256": hex::encode(&peer.reject_list_sha256),
"validationTime": peer.validation_time.to_string(),
@@ -200,23 +216,25 @@ fn write_markdown(path: &Path, summary: &serde_json::Value) -> Result<(), String
summary["rejects"]["match"].as_bool().unwrap_or(false)
),
format!(
"- `talsMatch`: `{}`",
summary["tals"]["match"].as_bool().unwrap_or(false)
"- `trustAnchorsMatch`: `{}`",
summary["trustAnchors"]["match"].as_bool().unwrap_or(false)
),
format!(
"- `rejectListSha256Match`: `{}`",
summary["rejectListSha256Match"].as_bool().unwrap_or(false)
),
format!(
"- `ours`: objects `{}`, tals `{}`, rejects `{}`",
"- `ours`: objects `{}`, trustAnchors `{}`, rejects `{}`",
summary["ours"]["objectCount"].as_u64().unwrap_or(0),
summary["ours"]["talCount"].as_u64().unwrap_or(0),
summary["ours"]["trustAnchorCount"].as_u64().unwrap_or(0),
summary["ours"]["rejectCount"].as_u64().unwrap_or(0)
),
format!(
"- `rpkiClient`: objects `{}`, tals `{}`, rejects `{}`",
"- `rpkiClient`: objects `{}`, trustAnchors `{}`, rejects `{}`",
summary["rpkiClient"]["objectCount"].as_u64().unwrap_or(0),
summary["rpkiClient"]["talCount"].as_u64().unwrap_or(0),
summary["rpkiClient"]["trustAnchorCount"]
.as_u64()
.unwrap_or(0),
summary["rpkiClient"]["rejectCount"].as_u64().unwrap_or(0)
),
];
@@ -410,8 +428,8 @@ fn uri_extension(uri: &str) -> String {
mod tests {
use super::*;
use rpki::cir::{
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
CirRejectedObject, CirTal, compute_reject_list_sha256, encode_cir,
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir, sha256,
};
#[test]
@@ -513,7 +531,7 @@ mod tests {
assert_eq!(summary["allMatch"], true);
assert_eq!(summary["objects"]["match"], true);
assert_eq!(summary["rejects"]["match"], true);
assert_eq!(summary["tals"]["match"], true);
assert_eq!(summary["trustAnchors"]["match"], true);
assert_eq!(summary["rejectListSha256Match"], true);
assert_eq!(summary["ours"]["objectCount"], 2);
assert!(
@@ -524,7 +542,7 @@ mod tests {
}
#[test]
fn run_reports_object_reject_tal_differences_with_samples() {
fn run_reports_object_reject_trust_anchor_differences_with_samples() {
let temp = tempfile::tempdir().expect("tempdir");
let ours_cir = sample_cir(
&[
@@ -581,7 +599,7 @@ mod tests {
1
);
assert_eq!(summary["rejects"]["match"], false);
assert_eq!(summary["tals"]["match"], false);
assert_eq!(summary["trustAnchors"]["match"], false);
assert_eq!(summary["rejectListSha256Match"], false);
}
@@ -700,7 +718,7 @@ mod tests {
fn sample_cir(
objects: &[(&str, u8)],
tals: &[&str],
trust_anchors: &[&str],
rejected_objects: &[(&str, Option<&str>)],
) -> CanonicalInputRepresentation {
let rejected_objects = rejected_objects
@@ -711,7 +729,7 @@ mod tests {
})
.collect::<Vec<_>>();
CanonicalInputRepresentation {
version: CIR_VERSION_V2,
version: CIR_VERSION_V3,
hash_alg: CirHashAlgorithm::Sha256,
validation_time: sample_time(),
objects: objects
@@ -721,12 +739,23 @@ mod tests {
sha256: vec![*fill; 32],
})
.collect(),
tals: tals
trust_anchors: trust_anchors
.iter()
.map(|tal_uri| CirTal {
tal_uri: (*tal_uri).to_string(),
tal_bytes: format!("{tal_uri}\nrsync://example.net/repo/ta.cer\nMIIB")
.into_bytes(),
.map(|tal_uri| {
let name = tal_uri
.rsplit('/')
.next()
.unwrap_or("root.tal")
.trim_end_matches(".tal");
let ta_rsync_uri = format!("rsync://example.net/repo/{name}.cer");
let ta_certificate_der = format!("ta-der-{name}").into_bytes();
CirTrustAnchor {
ta_rsync_uri: ta_rsync_uri.clone(),
tal_uri: (*tal_uri).to_string(),
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
ta_certificate_sha256: sha256(&ta_certificate_der),
ta_certificate_der,
}
})
.collect(),
reject_list_sha256: compute_reject_list_sha256(

View File

@@ -1,11 +1,9 @@
use std::path::PathBuf;
use rpki::blob_store::ExternalRepoBytesDb;
use rpki::cir::{
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
compute_reject_list_sha256, encode_cir,
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
compute_reject_list_sha256, encode_cir, sha256,
};
use sha2::Digest;
const USAGE: &str = "Usage: cir_ta_only_fixture --tal-path <path> --ta-path <path> --tal-uri <url> --validation-time <rfc3339> --cir-out <path> --repo-bytes-db <path>";
@@ -88,6 +86,7 @@ fn parse_args(
fn main() -> Result<(), String> {
let argv: Vec<String> = std::env::args().collect();
let (tal_path, ta_path, tal_uri, validation_time, cir_out, repo_bytes_db) = parse_args(&argv)?;
let _ = repo_bytes_db;
let tal_bytes = std::fs::read(&tal_path).map_err(|e| format!("read tal failed: {e}"))?;
let ta_bytes = std::fs::read(&ta_path).map_err(|e| format!("read ta failed: {e}"))?;
@@ -101,22 +100,20 @@ fn main() -> Result<(), String> {
.as_str()
.to_string();
let sha = sha2::Sha256::digest(&ta_bytes);
let hash_hex = hex::encode(sha);
ExternalRepoBytesDb::open(&repo_bytes_db)
.map_err(|e| format!("open repo bytes db failed: {e}"))?
.put_blob_bytes_batch(&[(hash_hex, ta_bytes.clone())])
.map_err(|e| format!("write repo bytes db failed: {e}"))?;
let ta_certificate_sha256 = sha256(&ta_bytes);
let cir = CanonicalInputRepresentation {
version: CIR_VERSION_V2,
version: CIR_VERSION_V3,
hash_alg: CirHashAlgorithm::Sha256,
validation_time,
objects: vec![CirObject {
rsync_uri: ta_rsync_uri,
sha256: sha.to_vec(),
objects: Vec::new(),
trust_anchors: vec![CirTrustAnchor {
ta_rsync_uri,
tal_uri,
tal_bytes,
ta_certificate_der: ta_bytes,
ta_certificate_sha256,
}],
tals: vec![CirTal { tal_uri, tal_bytes }],
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
rejected_objects: Vec::new(),
};