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 { 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"); }