use std::path::Path; use rpki::data_model::manifest::ManifestObject; use rpki::policy::{CaFailedFetchPolicy, Policy}; use rpki::storage::{FetchCachePpKey, FetchCachePpPack, 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 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 load_cernet_manifest_fixture() -> (std::path::PathBuf, Vec, ManifestObject) { let manifest_path = Path::new( "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft", ) .to_path_buf(); let bytes = std::fs::read(&manifest_path).expect("read manifest fixture"); let obj = ManifestObject::decode_der(&bytes).expect("decode manifest fixture"); (manifest_path, bytes, obj) } fn store_raw_publication_point_files( store: &RocksStore, manifest_path: &Path, manifest_rsync_uri: &str, manifest_bytes: &[u8], manifest: &ManifestObject, publication_point_rsync_uri: &str, ) { store .put_raw(manifest_rsync_uri, manifest_bytes) .expect("store manifest raw"); 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 raw"); } } #[test] fn cached_pack_revalidation_rejects_missing_file_referenced_by_manifest() { let (manifest_path, manifest_bytes, manifest) = load_cernet_manifest_fixture(); let validation_time = manifest.manifest.this_update + time::Duration::seconds(1); 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_raw_publication_point_files( &store, &manifest_path, &manifest_rsync_uri, &manifest_bytes, &manifest, &publication_point_rsync_uri, ); let mut policy = Policy::default(); policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp; let issuer_ca_der = issuer_ca_fixture(); let _fresh = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, &issuer_ca_der, Some(issuer_ca_rsync_uri()), validation_time, ) .expect("fresh run stores fetch_cache_pp"); let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri); let cached_bytes = store .get_fetch_cache_pp(&key) .expect("get fetch_cache_pp") .expect("fetch_cache_pp exists"); let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack"); // Remove one file from the pack: pack stays internally consistent, but no longer satisfies // RFC 9286 §6.4 when revalidated against the manifest fileList. pack.files.pop().expect("non-empty pack"); let bytes = pack.encode().expect("encode pack"); store .put_fetch_cache_pp(&key, &bytes) .expect("overwrite fetch_cache_pp"); // Force cache path: remove raw manifest so fresh processing fails at §6.2. store .delete_raw(&manifest_rsync_uri) .expect("delete raw manifest"); 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("cache pack missing file must be rejected"); let msg = err.to_string(); assert!(msg.contains("cached fetch_cache_pp missing file"), "{msg}"); assert!(msg.contains("RFC 9286 §6.4"), "{msg}"); } #[test] fn cached_pack_revalidation_rejects_hash_mismatch_against_manifest_filelist() { let (manifest_path, manifest_bytes, manifest) = load_cernet_manifest_fixture(); let validation_time = manifest.manifest.this_update + time::Duration::seconds(1); 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_raw_publication_point_files( &store, &manifest_path, &manifest_rsync_uri, &manifest_bytes, &manifest, &publication_point_rsync_uri, ); let mut policy = Policy::default(); policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp; let issuer_ca_der = issuer_ca_fixture(); let _fresh = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, &issuer_ca_der, Some(issuer_ca_rsync_uri()), validation_time, ) .expect("fresh run stores fetch_cache_pp"); let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri); let cached_bytes = store .get_fetch_cache_pp(&key) .expect("get fetch_cache_pp") .expect("fetch_cache_pp exists"); let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack"); // Mutate one file but keep pack internally consistent by recomputing its sha256 field. let victim = pack.files.first_mut().expect("non-empty pack"); victim.bytes[0] ^= 0xFF; victim.sha256 = victim.compute_sha256(); let bytes = pack.encode().expect("encode pack"); store .put_fetch_cache_pp(&key, &bytes) .expect("overwrite fetch_cache_pp"); // Force cache path. store .delete_raw(&manifest_rsync_uri) .expect("delete raw manifest"); 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("cache pack hash mismatch must be rejected"); let msg = err.to_string(); assert!(msg.contains("cached fetch_cache_pp file hash mismatch"), "{msg}"); assert!(msg.contains("RFC 9286 §6.5"), "{msg}"); }