rpki/tests/test_ccr_m7.rs

203 lines
8.9 KiB
Rust

use rpki::ccr::{
CcrContentInfo, CcrDigestAlgorithm, ManifestInstance, ManifestState, RoaPayloadSet,
RoaPayloadState, RouterKey, RouterKeySet, RouterKeyState, TrustAnchorState,
compute_state_hash, decode_content_info, dump_content_info_json_value,
encode::{
encode_aspa_payload_state_payload_der, encode_content_info,
encode_manifest_state_payload_der, encode_roa_payload_state_payload_der,
encode_router_key_state_payload_der, encode_trust_anchor_state_payload_der,
},
verify::{verify_against_report_json_path, verify_against_vcir_store, verify_content_info_bytes},
};
use rpki::data_model::common::BigUnsigned;
use rpki::storage::{
PackTime, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
VcirChildEntry, VcirInstanceGate, VcirRelatedArtifact, VcirSummary,
};
fn sample_time() -> time::OffsetDateTime {
time::OffsetDateTime::parse(
"2026-03-24T00:00:00Z",
&time::format_description::well_known::Rfc3339,
)
.expect("valid rfc3339")
}
fn sample_manifest_state() -> ManifestState {
let mis = vec![ManifestInstance {
hash: vec![0x10; 32],
size: 2048,
aki: vec![0x20; 20],
manifest_number: BigUnsigned { bytes_be: vec![1] },
this_update: sample_time(),
locations: vec![vec![0x30, 0x00]],
subordinates: vec![vec![0x30; 20]],
}];
let der = encode_manifest_state_payload_der(&mis).expect("encode mis");
ManifestState {
mis,
most_recent_update: sample_time(),
hash: compute_state_hash(&der),
}
}
fn sample_roa_state() -> RoaPayloadState {
let rps = vec![RoaPayloadSet {
as_id: 64496,
ip_addr_blocks: vec![vec![0x30, 0x08, 0x04, 0x02, 0x00, 0x01, 0x30, 0x02, 0x03, 0x00]],
}];
let der = encode_roa_payload_state_payload_der(&rps).expect("encode rps");
RoaPayloadState { rps, hash: compute_state_hash(&der) }
}
fn sample_aspa_state() -> rpki::ccr::AspaPayloadState {
let aps = vec![rpki::ccr::AspaPayloadSet { customer_as_id: 64496, providers: vec![64497] }];
let der = encode_aspa_payload_state_payload_der(&aps).expect("encode aps");
rpki::ccr::AspaPayloadState { aps, hash: compute_state_hash(&der) }
}
fn sample_ta_state() -> TrustAnchorState {
let skis = vec![vec![0x11; 20]];
let der = encode_trust_anchor_state_payload_der(&skis).expect("encode skis");
TrustAnchorState { skis, hash: compute_state_hash(&der) }
}
fn sample_rks() -> RouterKeyState {
let rksets = vec![RouterKeySet { as_id: 64496, router_keys: vec![RouterKey { ski: vec![0x22; 20], spki_der: vec![0x30, 0x00] }] }];
let der = encode_router_key_state_payload_der(&rksets).expect("encode rk");
RouterKeyState { rksets, hash: compute_state_hash(&der) }
}
fn sample_ccr() -> Vec<u8> {
let ccr = rpki::ccr::RpkiCanonicalCacheRepresentation {
version: 0,
hash_alg: CcrDigestAlgorithm::Sha256,
produced_at: sample_time(),
mfts: Some(sample_manifest_state()),
vrps: Some(sample_roa_state()),
vaps: Some(sample_aspa_state()),
tas: Some(sample_ta_state()),
rks: Some(sample_rks()),
};
encode_content_info(&CcrContentInfo::new(ccr)).expect("encode ccr")
}
#[test]
fn verify_content_info_bytes_succeeds_for_valid_ccr() {
let der = sample_ccr();
let summary = verify_content_info_bytes(&der).expect("verify ccr");
assert!(summary.state_hashes_ok);
assert_eq!(summary.manifest_instances, 1);
assert_eq!(summary.roa_payload_sets, 1);
assert_eq!(summary.aspa_payload_sets, 1);
assert_eq!(summary.trust_anchor_ski_count, 1);
assert_eq!(summary.router_key_sets, 1);
assert_eq!(summary.router_key_count, 1);
}
#[test]
fn verify_content_info_bytes_rejects_tampered_manifest_hash() {
let mut content_info = decode_content_info(&sample_ccr()).expect("decode ccr");
content_info.content.mfts.as_mut().unwrap().hash[0] ^= 0x01;
let der = encode_content_info(&content_info).expect("encode tampered ccr");
let err = verify_content_info_bytes(&der).expect_err("tampered hash must fail");
assert!(err.to_string().contains("ManifestState hash mismatch"), "{err}");
}
#[test]
fn dump_content_info_json_value_contains_expected_counts() {
let json = dump_content_info_json_value(&sample_ccr()).expect("dump ccr");
assert_eq!(json["version"], 0);
assert_eq!(json["state_aspects"]["mfts"]["manifest_instances"], 1);
assert_eq!(json["state_aspects"]["vrps"]["payload_sets"], 1);
assert_eq!(json["state_aspects"]["vaps"]["payload_sets"], 1);
assert_eq!(json["state_aspects"]["tas"]["ski_count"], 1);
assert_eq!(json["state_aspects"]["rks"]["router_key_sets"], 1);
}
#[test]
fn verify_against_report_json_path_rejects_mismatching_report() {
let td = tempfile::tempdir().expect("tempdir");
let report = serde_json::json!({
"vrps": [{"asn": 64496, "prefix": "0.0.0.0/0", "max_length": 0}],
"aspas": [{"customer_as_id": 64496, "provider_as_ids": [64497]}]
});
let report_path = td.path().join("report.json");
std::fs::write(&report_path, serde_json::to_vec(&report).unwrap()).expect("write report");
let mut ci = decode_content_info(&sample_ccr()).expect("decode ccr");
ci.content.vrps = Some(RoaPayloadState {
rps: vec![RoaPayloadSet { as_id: 64496, ip_addr_blocks: vec![vec![0x30, 0x08, 0x04, 0x02, 0x00, 0x01, 0x30, 0x02, 0x03, 0x00]] }],
hash: compute_state_hash(&encode_roa_payload_state_payload_der(&[RoaPayloadSet { as_id: 64496, ip_addr_blocks: vec![vec![0x30, 0x08, 0x04, 0x02, 0x00, 0x01, 0x30, 0x02, 0x03, 0x00]] }]).unwrap()),
});
verify_against_report_json_path(&ci, &report_path).expect_err("report mismatch expected");
}
#[test]
fn verify_against_vcir_store_matches_manifest_hashes() {
let td = tempfile::tempdir().expect("tempdir");
let store = RocksStore::open(td.path()).expect("open rocksdb");
let manifest_hash = hex::encode(vec![0x10; 32]);
let vcir = ValidatedCaInstanceResult {
manifest_rsync_uri: "rsync://example.test/current.mft".to_string(),
parent_manifest_rsync_uri: None,
tal_id: "apnic".to_string(),
ca_subject_name: "CN=test".to_string(),
ca_ski: "11".repeat(20),
issuer_ski: "22".repeat(20),
last_successful_validation_time: PackTime::from_utc_offset_datetime(sample_time()),
current_manifest_rsync_uri: "rsync://example.test/current.mft".to_string(),
current_crl_rsync_uri: "rsync://example.test/current.crl".to_string(),
validated_manifest_meta: ValidatedManifestMeta {
validated_manifest_number: vec![1],
validated_manifest_this_update: PackTime::from_utc_offset_datetime(sample_time()),
validated_manifest_next_update: PackTime::from_utc_offset_datetime(sample_time()),
},
instance_gate: VcirInstanceGate {
manifest_next_update: PackTime::from_utc_offset_datetime(sample_time()),
current_crl_next_update: PackTime::from_utc_offset_datetime(sample_time()),
self_ca_not_after: PackTime::from_utc_offset_datetime(sample_time()),
instance_effective_until: PackTime::from_utc_offset_datetime(sample_time()),
},
child_entries: vec![VcirChildEntry {
child_manifest_rsync_uri: "rsync://example.test/child.mft".to_string(),
child_cert_rsync_uri: "rsync://example.test/child.cer".to_string(),
child_cert_hash: "aa".repeat(32),
child_ski: "33".repeat(20),
child_rsync_base_uri: "rsync://example.test/".to_string(),
child_publication_point_rsync_uri: "rsync://example.test/".to_string(),
child_rrdp_notification_uri: None,
child_effective_ip_resources: None,
child_effective_as_resources: None,
accepted_at_validation_time: PackTime::from_utc_offset_datetime(sample_time()),
}],
local_outputs: Vec::new(),
related_artifacts: vec![VcirRelatedArtifact {
artifact_role: VcirArtifactRole::Manifest,
artifact_kind: VcirArtifactKind::Mft,
uri: Some("rsync://example.test/current.mft".to_string()),
sha256: manifest_hash.clone(),
object_type: Some("mft".to_string()),
validation_status: VcirArtifactValidationStatus::Accepted,
}],
summary: VcirSummary {
local_vrp_count: 0,
local_aspa_count: 0,
local_router_key_count: 0,
child_count: 1,
accepted_object_count: 1,
rejected_object_count: 0,
},
audit_summary: VcirAuditSummary {
failed_fetch_eligible: true,
last_failed_fetch_reason: None,
warning_count: 0,
audit_flags: Vec::new(),
},
};
store.put_vcir(&vcir).expect("put vcir");
let ci = decode_content_info(&sample_ccr()).expect("decode ccr");
verify_against_vcir_store(&ci, &store).expect("vcir cross-check");
}