use std::path::Path; use rpki::data_model::crl::RpkixCrl; use rpki::data_model::manifest::ManifestObject; use rpki::data_model::rc::ResourceCertificate; use rpki::policy::{Policy, SignedObjectFailurePolicy}; use rpki::storage::{PackFile, RocksStore}; use rpki::validation::manifest::process_manifest_publication_point; use rpki::validation::objects::process_verified_publication_point_pack_for_issuer; fn fixture_to_rsync_uri(path: &Path) -> String { let rel = path .strip_prefix("tests/fixtures/repository") .expect("path under tests/fixtures/repository"); let mut it = rel.components(); let host = it .next() .expect("host component") .as_os_str() .to_string_lossy(); let rest = it.as_path().to_string_lossy(); format!("rsync://{host}/{rest}") } fn fixture_dir_to_rsync_uri(dir: &Path) -> String { let mut s = fixture_to_rsync_uri(dir); if !s.ends_with('/') { s.push('/'); } s } fn build_cernet_pack_and_validation_time() -> ( rpki::storage::VerifiedPublicationPointPack, time::OffsetDateTime, Vec, ResourceCertificate, ) { let manifest_path = Path::new( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let manifest_bytes = std::fs::read(manifest_path).expect("read manifest fixture"); let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest fixture"); let manifest_rsync_uri = fixture_to_rsync_uri(manifest_path); let publication_point_rsync_uri = fixture_dir_to_rsync_uri(manifest_path.parent().unwrap()); let temp = tempfile::tempdir().expect("tempdir"); let store = RocksStore::open(temp.path()).expect("open rocksdb"); store .put_raw(&manifest_rsync_uri, &manifest_bytes) .expect("store manifest"); for entry in &manifest.manifest.files { let file_path = manifest_path.parent().unwrap().join(&entry.file_name); let bytes = std::fs::read(&file_path) .unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}")); let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name); store.put_raw(&rsync_uri, &bytes).expect("store file"); } let policy = Policy::default(); let out = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, manifest.manifest.this_update + time::Duration::seconds(1), ) .expect("process manifest publication point"); let issuer_ca_der = std::fs::read( "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", ) .expect("read issuer CA cert fixture"); let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer CA cert"); let crl_file = out .pack .files .iter() .find(|f| f.rsync_uri.ends_with(".crl")) .expect("crl present in pack"); let crl = RpkixCrl::decode_der(&crl_file.bytes).expect("decode crl"); // Choose a validation_time that is within: // - manifest thisUpdate..nextUpdate (RFC 9286 ยง6.3) // - issuer CA validity // - CRL thisUpdate..nextUpdate // - and should be within EE validity for most objects. let mut t = manifest.manifest.this_update; if issuer_ca.tbs.validity_not_before > t { t = issuer_ca.tbs.validity_not_before; } if crl.this_update.utc > t { t = crl.this_update.utc; } t += time::Duration::seconds(1); (out.pack, t, issuer_ca_der, issuer_ca) } #[test] fn drop_object_policy_drops_only_failing_object() { let (mut pack, validation_time, issuer_ca_der, issuer_ca) = build_cernet_pack_and_validation_time(); let valid_roa_uri = pack .files .iter() .find(|f| f.rsync_uri.ends_with("AS4538.roa")) .map(|f| f.rsync_uri.clone()) .expect("AS4538.roa present in pack"); let tamper_idx = pack .files .iter() .position(|f| f.rsync_uri.ends_with(".roa") && f.rsync_uri != valid_roa_uri) .expect("another ROA present in pack"); let victim_uri = pack.files[tamper_idx].rsync_uri.clone(); let mut tampered = pack.files[tamper_idx].bytes.clone(); let last = tampered.len() - 1; tampered[last] ^= 0xFF; pack.files[tamper_idx] = PackFile::from_bytes_compute_sha256(victim_uri.clone(), tampered); let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject; let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &issuer_ca_der, None, issuer_ca.tbs.extensions.ip_resources.as_ref(), issuer_ca.tbs.extensions.as_resources.as_ref(), validation_time, ); assert!( out.vrps.iter().any(|v| v.asn == 4538), "expected at least one VRP for AS4538" ); assert!( out.warnings .iter() .any(|w| w.context.as_deref() == Some(&victim_uri)), "expected a warning for the tampered object" ); } #[test] fn drop_publication_point_policy_drops_the_publication_point() { let (mut pack, validation_time, issuer_ca_der, issuer_ca) = build_cernet_pack_and_validation_time(); let tamper_idx = pack .files .iter() .position(|f| f.rsync_uri.ends_with(".roa")) .expect("a ROA present in pack"); let victim_uri = pack.files[tamper_idx].rsync_uri.clone(); let mut tampered = pack.files[tamper_idx].bytes.clone(); let last = tampered.len() - 1; tampered[last] ^= 0xFF; pack.files[tamper_idx] = PackFile::from_bytes_compute_sha256(victim_uri.clone(), tampered); let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint; let out = process_verified_publication_point_pack_for_issuer( &pack, &policy, &issuer_ca_der, None, issuer_ca.tbs.extensions.ip_resources.as_ref(), issuer_ca.tbs.extensions.as_resources.as_ref(), validation_time, ); assert!(out.stats.publication_point_dropped); assert!(out.vrps.is_empty(), "expected publication point dropped"); assert!( out.audit.iter().any(|a| a.rsync_uri == victim_uri), "expected audit entry for victim object" ); }