use std::path::Path; use sha2::Digest; use rpki::data_model::manifest::ManifestObject; use rpki::policy::{CaFailedFetchPolicy, Policy}; use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore}; use rpki::validation::manifest::process_manifest_publication_point; fn issuer_ca_fixture() -> Vec { std::fs::read( "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 put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec, object_type: &str) { let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes)); let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes); raw_entry.origin_uris.push(rsync_uri.to_string()); raw_entry.object_type = Some(object_type.to_string()); raw_entry.encoding = Some("der".to_string()); store .put_raw_by_hash_entry(&raw_entry) .expect("store raw_by_hash"); store .put_repository_view_entry(&RepositoryViewEntry { rsync_uri: rsync_uri.to_string(), current_hash: Some(sha256_hex), repository_source: Some("https://example.test/notification.xml".to_string()), object_type: Some(object_type.to_string()), state: RepositoryViewState::Present, }) .expect("store repository view"); } #[test] fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() { let fixture_manifest_path = Path::new( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ); let fixture_dir = fixture_manifest_path.parent().expect("fixture dir"); let manifest_bytes = std::fs::read(fixture_manifest_path).expect("read manifest fixture"); let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest fixture"); let validation_time = manifest.manifest.this_update + time::Duration::seconds(1); // Intentionally mismatch: manifest is NOT under the publication point URI. let manifest_rsync_uri = "rsync://example.test/a/manifest.mft"; let publication_point_rsync_uri = "rsync://example.test/b/"; let temp = tempfile::tempdir().expect("tempdir"); let store = RocksStore::open(temp.path()).expect("open rocksdb"); // Store the manifest at its rsync URI. put_current_object(&store, manifest_rsync_uri, manifest_bytes.clone(), "mft"); // Store all referenced files under the (different) publication point so that §6.4/§6.5 // would otherwise succeed if §6.1 was not enforced. let entries = manifest .manifest .parse_files() .expect("parse validated manifest fileList"); for entry in &entries { let file_path = fixture_dir.join(entry.file_name.as_str()); 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); let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin"); put_current_object(&store, &rsync_uri, bytes, object_type); } let mut policy = Policy::default(); policy.ca_failed_fetch_policy = CaFailedFetchPolicy::StopAllOutput; let issuer_ca_der = issuer_ca_fixture(); let err = process_manifest_publication_point( &store, &policy, manifest_rsync_uri, publication_point_rsync_uri, &issuer_ca_der, Some(issuer_ca_rsync_uri()), validation_time, ) .expect_err("§6.1 mismatch must be treated as failed fetch"); let msg = err.to_string(); assert!(msg.contains("RFC 9286 §6.1"), "{msg}"); assert!( store .get_vcir(manifest_rsync_uri) .expect("get vcir") .is_none(), "must not write VCIR on failed fetch" ); }