use std::path::Path; use rpki::data_model::manifest::ManifestObject; use rpki::policy::{CaFailedFetchPolicy, Policy}; use rpki::storage::{RocksStore, VerifiedKey, VerifiedPublicationPointPack}; use rpki::validation::manifest::{PublicationPointSource, process_manifest_publication_point}; 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 } #[test] fn manifest_success_writes_verified_pack() { 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 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 .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, validation_time, ) .expect("process manifest publication point"); assert_eq!(out.source, PublicationPointSource::Fresh); assert!(out.warnings.is_empty()); let key = VerifiedKey::from_manifest_rsync_uri(&manifest_rsync_uri); let stored = store .get_verified(&key) .expect("get verified") .expect("verified pack exists"); let decoded = VerifiedPublicationPointPack::decode(&stored).expect("decode stored pack"); assert_eq!(decoded.manifest_rsync_uri, manifest_rsync_uri); assert_eq!( decoded.publication_point_rsync_uri, publication_point_rsync_uri ); } #[test] fn manifest_hash_mismatch_falls_back_to_verified_cache_when_enabled() { 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 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 .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 first = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, validation_time, ) .expect("first run stores verified pack"); assert_eq!(first.source, PublicationPointSource::Fresh); let key = VerifiedKey::from_manifest_rsync_uri(&manifest_rsync_uri); let cached_bytes = store .get_verified(&key) .expect("get verified") .expect("verified pack exists"); let cached_pack = VerifiedPublicationPointPack::decode(&cached_bytes).expect("decode cached"); let victim = manifest .manifest .files .first() .expect("non-empty file list"); let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name); let mut tampered = store .get_raw(&victim_uri) .expect("get victim raw") .expect("victim raw exists"); tampered[0] ^= 0xFF; store.put_raw(&victim_uri, &tampered).expect("tamper raw"); let second = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, validation_time, ) .expect("second run falls back to verified cache"); assert_eq!(second.source, PublicationPointSource::VerifiedCache); assert!(!second.warnings.is_empty()); assert_eq!(second.pack, cached_pack); } #[test] fn manifest_failed_fetch_stop_all_output() { 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 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 .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 mut policy = Policy::default(); policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseVerifiedCache; let _ = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, validation_time, ) .expect("first run stores verified pack"); let victim = manifest .manifest .files .first() .expect("non-empty file list"); let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name); let mut tampered = store .get_raw(&victim_uri) .expect("get victim raw") .expect("victim raw exists"); tampered[0] ^= 0xFF; store.put_raw(&victim_uri, &tampered).expect("tamper raw"); policy.ca_failed_fetch_policy = CaFailedFetchPolicy::StopAllOutput; let err = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, validation_time, ) .expect_err("stop_all_output should not use verified cache"); let msg = err.to_string(); assert!(msg.contains("cache use is disabled")); } #[test] fn manifest_fallback_pack_is_revalidated_and_rejected_if_stale() { 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 ok_time = manifest.manifest.this_update + time::Duration::seconds(1); let stale_time = manifest.manifest.next_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 .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 _ = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, ok_time, ) .expect("first run stores verified pack"); store .delete_raw(&manifest_rsync_uri) .expect("delete manifest raw to force fallback"); let err = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, stale_time, ) .expect_err("stale validation_time must reject verified cache pack"); let msg = err.to_string(); assert!(msg.contains("not valid at validation_time")); }