use rpki::policy::{Policy, SignedObjectFailurePolicy}; use rpki::storage::{FetchCachePpPack, PackFile, PackTime}; use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer; fn fixture_bytes(path: &str) -> Vec { std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path)) .unwrap_or_else(|e| panic!("read fixture {path}: {e}")) } fn dummy_pack(manifest_bytes: Vec, files: Vec) -> FetchCachePpPack { let now = time::OffsetDateTime::now_utc(); FetchCachePpPack { format_version: FetchCachePpPack::FORMAT_VERSION_V1, manifest_rsync_uri: "rsync://example.test/repo/pp/manifest.mft".to_string(), publication_point_rsync_uri: "rsync://example.test/repo/pp/".to_string(), manifest_number_be: vec![1], this_update: PackTime::from_utc_offset_datetime(now), next_update: PackTime::from_utc_offset_datetime(now + time::Duration::hours(1)), verified_at: PackTime::from_utc_offset_datetime(now), manifest_bytes, files, } } #[test] fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let roa_bytes = fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa"); let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa"); let ee_crldp = roa.signed_object.signed_data.certificates[0] .resource_cert .tbs .extensions .crl_distribution_points_uris .as_ref() .expect("ee crldp")[0] .as_str() .to_string(); let crl_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl", ); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes), PackFile::from_bytes_compute_sha256( "rsync://rpki.cernet.net/repo/cernet/0/AS4538.roa", roa_bytes, ), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, None, None, None, time::OffsetDateTime::now_utc(), ); assert_eq!(out.stats.roa_total, 1); assert_eq!(out.stats.roa_ok, 0); assert_eq!(out.audit.len(), 1); assert_eq!(out.warnings.len(), 1); } #[test] fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let roa_bytes = fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa"); let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa"); let ee_crldp = roa.signed_object.signed_data.certificates[0] .resource_cert .tbs .extensions .crl_distribution_points_uris .as_ref() .expect("ee crldp")[0] .as_str() .to_string(); let crl_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl", ); let aspa_bytes = fixture_bytes( "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa", ); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes), PackFile::from_bytes_compute_sha256( "rsync://example.test/repo/pp/first.roa", roa_bytes.clone(), ), PackFile::from_bytes_compute_sha256( "rsync://example.test/repo/pp/second.roa", roa_bytes, ), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint, ..Policy::default() }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, None, None, None, time::OffsetDateTime::now_utc(), ); assert!(out.stats.publication_point_dropped); assert_eq!(out.warnings.len(), 1); } #[test] fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let aspa_bytes = fixture_bytes( "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa", ); let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa"); let ee_crldp = aspa.signed_object.signed_data.certificates[0] .resource_cert .tbs .extensions .crl_distribution_points_uris .as_ref() .expect("ee crldp")[0] .as_str() .to_string(); let crl_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl", ); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, None, None, None, time::OffsetDateTime::now_utc(), ); assert_eq!(out.stats.aspa_total, 1); assert_eq!(out.stats.aspa_ok, 0); assert_eq!(out.audit.len(), 1); assert_eq!(out.warnings.len(), 1); } #[test] fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let roa_bytes = fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa"); let aspa_bytes = fixture_bytes( "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa", ); let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa"); let ee_crldp = aspa.signed_object.signed_data.certificates[0] .resource_cert .tbs .extensions .crl_distribution_points_uris .as_ref() .expect("ee crldp")[0] .as_str() .to_string(); let crl_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl", ); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/y.roa", roa_bytes), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint, ..Policy::default() }; let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer"); let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &wrong_issuer_ca_der, None, None, None, time::OffsetDateTime::now_utc(), ); assert!(out.stats.publication_point_dropped); } #[test] fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let roa_bytes = fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa"); let aspa_bytes = fixture_bytes( "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa", ); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.asa", aspa_bytes), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], None, None, None, time::OffsetDateTime::now_utc(), ); assert!(out.stats.publication_point_dropped); assert_eq!(out.stats.roa_total, 1); assert_eq!(out.stats.aspa_total, 1); assert_eq!(out.vrps.len(), 0); assert_eq!(out.aspas.len(), 0); assert_eq!(out.audit.len(), 2); assert_eq!(out.warnings.len(), 1); } #[test] fn process_pack_for_issuer_drop_object_records_errors_and_continues() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256( "rsync://example.test/repo/pp/issuer.crl", vec![0x01], ), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/b.asa", vec![0x00]), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], None, None, None, time::OffsetDateTime::now_utc(), ); assert!(!out.stats.publication_point_dropped); assert_eq!(out.stats.roa_total, 1); assert_eq!(out.stats.aspa_total, 1); assert_eq!(out.audit.len(), 2); assert_eq!(out.warnings.len(), 2); } #[test] fn process_pack_for_issuer_drop_publication_point_records_skips_for_rest() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256( "rsync://example.test/repo/pp/issuer.crl", vec![0x01], ), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/b.roa", vec![0x00]), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/c.asa", vec![0x00]), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint, ..Policy::default() }; let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], None, None, None, time::OffsetDateTime::now_utc(), ); assert!(out.stats.publication_point_dropped); assert_eq!(out.stats.roa_total, 2); assert_eq!(out.stats.aspa_total, 1); assert!(out.audit.len() >= 3, "expected error + skipped entries"); assert_eq!(out.warnings.len(), 1); } #[test] fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let roa_bytes = fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa"); let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa"); let ee_crldp = roa.signed_object.signed_data.certificates[0] .resource_cert .tbs .extensions .crl_distribution_points_uris .as_ref() .expect("ee crldp")[0] .as_str() .to_string(); // Provide a CRL file with the *exact* rsync URI referenced by the embedded EE certificate. // Bytes need not be valid for this test: we just want to cover deterministic selection. let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], None, None, None, time::OffsetDateTime::now_utc(), ); assert_eq!(out.stats.roa_total, 1); assert_eq!(out.stats.roa_ok, 0, "expected validation to fail later"); assert_eq!(out.audit.len(), 1); assert_eq!(out.warnings.len(), 1); } #[test] fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let roa_bytes = fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa"); // Pack has a CRL, but its URI does not match the embedded EE certificate CRLDP. let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256( "rsync://example.test/repo/pp/not-it.crl", vec![0x01], ), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], None, None, None, time::OffsetDateTime::now_utc(), ); assert_eq!(out.stats.roa_total, 1); assert_eq!(out.stats.roa_ok, 0); assert_eq!(out.audit.len(), 1); assert_eq!(out.warnings.len(), 1); assert!( out.warnings[0].message.contains("dropping invalid ROA") || out.warnings[0] .message .contains("dropping publication point"), "expected deterministic CRL selection failure to surface as an invalid ROA warning" ); } #[test] fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_aspa() { let manifest_bytes = fixture_bytes( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let aspa_bytes = fixture_bytes( "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa", ); let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa"); let ee_crldp = aspa.signed_object.signed_data.certificates[0] .resource_cert .tbs .extensions .crl_distribution_points_uris .as_ref() .expect("ee crldp")[0] .as_str() .to_string(); let pack = dummy_pack( manifest_bytes, vec![ PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]), PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.asa", aspa_bytes), ], ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let out = process_fetch_cache_pp_pack_for_issuer( &pack, &policy, &[0x01, 0x02, 0x03], None, None, None, time::OffsetDateTime::now_utc(), ); assert_eq!(out.stats.aspa_total, 1); assert_eq!(out.stats.aspa_ok, 0); assert_eq!(out.audit.len(), 1); assert_eq!(out.warnings.len(), 1); }