use rpki::fetch::rsync::LocalDirRsyncFetcher; use rpki::policy::{Policy, SignedObjectFailurePolicy, SyncPreference}; use rpki::storage::{PackFile, PackTime, RocksStore, VerifiedPublicationPointPack}; use rpki::sync::repo::sync_publication_point; use rpki::sync::rrdp::Fetcher; use rpki::validation::manifest::process_manifest_publication_point; use rpki::validation::objects::process_verified_publication_point_pack_for_issuer; struct NoopHttpFetcher; impl Fetcher for NoopHttpFetcher { fn fetch(&self, _uri: &str) -> Result, String> { Err("http disabled in test".to_string()) } } fn cernet_fixture() -> (std::path::PathBuf, String, String) { let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0"); let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/".to_string(); let manifest_file = "05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft".to_string(); (dir, rsync_base_uri, manifest_file) } fn validation_time_from_manifest_fixture( dir: &std::path::Path, manifest_file: &str, ) -> time::OffsetDateTime { let bytes = std::fs::read(dir.join(manifest_file)).expect("read manifest fixture"); let mft = rpki::data_model::manifest::ManifestObject::decode_der(&bytes).expect("decode mft"); let this_update = mft.manifest.this_update; let next_update = mft.manifest.next_update; let candidate = this_update + time::Duration::seconds(60); if candidate < next_update { candidate } else { this_update } } fn issuer_ca_fixture() -> Vec { std::fs::read( std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", ), ) .expect("read issuer ca fixture") } fn issuer_ca_rsync_uri() -> &'static str { "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer" } fn minimal_pack( manifest_rsync_uri: &str, publication_point_rsync_uri: &str, manifest_bytes: Vec, files: Vec, validation_time: time::OffsetDateTime, ) -> VerifiedPublicationPointPack { // Keep times consistent enough to pass internal pack validation. VerifiedPublicationPointPack { format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, manifest_rsync_uri: manifest_rsync_uri.to_string(), publication_point_rsync_uri: publication_point_rsync_uri.to_string(), this_update: PackTime::from_utc_offset_datetime(validation_time), next_update: PackTime::from_utc_offset_datetime(validation_time + time::Duration::hours(1)), verified_at: PackTime::from_utc_offset_datetime(validation_time), manifest_bytes, files, } } fn build_verified_pack_from_local_rsync_fixture( dir: &std::path::Path, rsync_base_uri: &str, manifest_rsync_uri: &str, validation_time: time::OffsetDateTime, ) -> rpki::storage::VerifiedPublicationPointPack { let store_dir = tempfile::tempdir().expect("store dir"); let store = RocksStore::open(store_dir.path()).expect("open rocksdb"); let policy = Policy { sync_preference: SyncPreference::RsyncOnly, ..Policy::default() }; sync_publication_point( &store, &policy, None, rsync_base_uri, &NoopHttpFetcher, &LocalDirRsyncFetcher::new(dir), ) .expect("sync into raw_objects"); let pp = process_manifest_publication_point( &store, &policy, manifest_rsync_uri, rsync_base_uri, validation_time, ) .expect("process manifest"); pp.pack } #[test] fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() { let (dir, rsync_base_uri, manifest_file) = cernet_fixture(); let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); let pack = build_verified_pack_from_local_rsync_fixture( &dir, &rsync_base_uri, &manifest_rsync_uri, validation_time, ); let issuer_ca_der = issuer_ca_fixture(); let issuer_ca = rpki::data_model::rc::ResourceCertificate::decode_der(&issuer_ca_der) .expect("decode issuer ca"); let policy = Policy::default(); let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &issuer_ca_der, Some(issuer_ca_rsync_uri()), issuer_ca.tbs.extensions.ip_resources.as_ref(), issuer_ca.tbs.extensions.as_resources.as_ref(), validation_time, ); assert!( out.vrps.len() > 10, "expected many VRPs, got {}", out.vrps.len() ); assert!(out.aspas.is_empty()); } #[test] fn signed_object_failure_policy_drop_object_drops_only_bad_object() { let (dir, rsync_base_uri, manifest_file) = cernet_fixture(); let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); let mut pack = build_verified_pack_from_local_rsync_fixture( &dir, &rsync_base_uri, &manifest_rsync_uri, validation_time, ); let roa_uris = pack .files .iter() .filter(|f| f.rsync_uri.ends_with(".roa")) .map(|f| f.rsync_uri.clone()) .collect::>(); let bad_idx = pack .files .iter() .position(|f| f.rsync_uri.ends_with(".roa")) .expect("pack contains roa"); let bad_uri = pack.files[bad_idx].rsync_uri.clone(); pack.files[bad_idx] = PackFile::from_bytes_compute_sha256(bad_uri, vec![0u8]); let issuer_ca_der = issuer_ca_fixture(); let issuer_ca = rpki::data_model::rc::ResourceCertificate::decode_der(&issuer_ca_der) .expect("decode issuer ca"); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &issuer_ca_der, Some(issuer_ca_rsync_uri()), issuer_ca.tbs.extensions.ip_resources.as_ref(), issuer_ca.tbs.extensions.as_resources.as_ref(), validation_time, ); assert!(out.vrps.len() > 0); assert!(!out.warnings.is_empty()); assert_eq!( out.audit.len(), roa_uris.len(), "expected one audit entry per ROA" ); assert!( out.audit .iter() .any(|e| e.rsync_uri == pack.files[bad_idx].rsync_uri && matches!(e.result, rpki::audit::AuditObjectResult::Error)), "expected audit error for the corrupted ROA" ); } #[test] fn signed_object_failure_policy_drop_publication_point_drops_all_output() { let (dir, rsync_base_uri, manifest_file) = cernet_fixture(); let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); let mut pack = build_verified_pack_from_local_rsync_fixture( &dir, &rsync_base_uri, &manifest_rsync_uri, validation_time, ); let roa_uris = pack .files .iter() .filter(|f| f.rsync_uri.ends_with(".roa")) .map(|f| f.rsync_uri.clone()) .collect::>(); let bad_idx = pack .files .iter() .position(|f| f.rsync_uri.ends_with(".roa")) .expect("pack contains roa"); let bad_uri = pack.files[bad_idx].rsync_uri.clone(); pack.files[bad_idx] = PackFile::from_bytes_compute_sha256(bad_uri, vec![0u8]); let issuer_ca_der = issuer_ca_fixture(); let issuer_ca = rpki::data_model::rc::ResourceCertificate::decode_der(&issuer_ca_der) .expect("decode issuer ca"); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint, ..Policy::default() }; let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &issuer_ca_der, Some(issuer_ca_rsync_uri()), issuer_ca.tbs.extensions.ip_resources.as_ref(), issuer_ca.tbs.extensions.as_resources.as_ref(), validation_time, ); assert!(out.vrps.is_empty()); assert!(out.aspas.is_empty()); assert!(!out.warnings.is_empty()); assert_eq!( out.audit.len(), roa_uris.len(), "expected audit entries for all ROAs (error + skipped due to policy)" ); let bad_entry = out .audit .iter() .find(|e| e.rsync_uri == pack.files[bad_idx].rsync_uri) .expect("bad roa audit entry"); assert!( matches!(bad_entry.result, rpki::audit::AuditObjectResult::Error), "expected error for corrupted ROA" ); assert!( out.audit .iter() .any(|e| matches!(e.result, rpki::audit::AuditObjectResult::Skipped)), "expected at least one skipped ROA due to drop_publication_point policy" ); } #[test] fn process_pack_for_issuer_without_crl_drops_publication_point() { let (dir, rsync_base_uri, manifest_file) = cernet_fixture(); let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft"); let pack = minimal_pack( &manifest_rsync_uri, &rsync_base_uri, manifest_bytes, vec![ // include a ROA-looking file; it won't be reached because CRL selection fails first PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}dummy.roa"), vec![1]), ], validation_time, ); let policy = Policy::default(); let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &[], None, None, None, validation_time, ); assert!(out.vrps.is_empty()); assert!(out.aspas.is_empty()); assert!(!out.warnings.is_empty()); } #[test] fn process_pack_for_issuer_handles_invalid_aspa_bytes() { let (dir, rsync_base_uri, manifest_file) = cernet_fixture(); let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft"); let crl_bytes = std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl"); let pack = minimal_pack( &manifest_rsync_uri, &rsync_base_uri, manifest_bytes, vec![ PackFile::from_bytes_compute_sha256( format!("{rsync_base_uri}05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl"), crl_bytes, ), PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}bad.asa"), vec![0u8]), ], validation_time, ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropObject, ..Policy::default() }; let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &[], None, None, None, validation_time, ); assert!(out.aspas.is_empty()); assert!(!out.warnings.is_empty()); } #[test] fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() { let (dir, rsync_base_uri, manifest_file) = cernet_fixture(); let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}"); let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file); let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft"); let crl_bytes = std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl"); let pack = minimal_pack( &manifest_rsync_uri, &rsync_base_uri, manifest_bytes, vec![ PackFile::from_bytes_compute_sha256( format!("{rsync_base_uri}05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl"), crl_bytes, ), PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}bad.asa"), vec![0u8]), // A ROA-like file: we should not reach it due to policy. PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}dummy.roa"), vec![1u8]), ], validation_time, ); let policy = Policy { signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint, ..Policy::default() }; let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &[], None, None, None, validation_time, ); assert!(out.vrps.is_empty()); assert!(out.aspas.is_empty()); assert!(!out.warnings.is_empty()); } // NOTE: DN-based issuer resolution and pack-local CA indexing have been removed for determinism.