360 lines
14 KiB
Rust
360 lines
14 KiB
Rust
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::{PublicationPointSource, process_manifest_publication_point};
|
|
|
|
fn issuer_ca_fixture() -> Vec<u8> {
|
|
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
|
|
}
|
|
|
|
#[test]
|
|
fn manifest_success_writes_fetch_cache_pp_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 issuer_ca_der = issuer_ca_fixture();
|
|
let out = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
validation_time,
|
|
)
|
|
.expect("process manifest publication point");
|
|
assert_eq!(out.source, PublicationPointSource::Fresh);
|
|
assert!(out.warnings.is_empty());
|
|
|
|
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
|
let stored = store
|
|
.get_fetch_cache_pp(&key)
|
|
.expect("get fetch_cache_pp")
|
|
.expect("fetch_cache_pp pack exists");
|
|
let decoded = FetchCachePpPack::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_fetch_cache_pp_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 issuer_ca_der = issuer_ca_fixture();
|
|
let first = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
validation_time,
|
|
)
|
|
.expect("first run stores fetch_cache_pp pack");
|
|
assert_eq!(first.source, PublicationPointSource::Fresh);
|
|
|
|
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 pack exists");
|
|
let cached_pack = FetchCachePpPack::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,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
validation_time,
|
|
)
|
|
.expect("second run falls back to fetch_cache_pp");
|
|
assert_eq!(second.source, PublicationPointSource::FetchCachePp);
|
|
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::UseFetchCachePp;
|
|
let issuer_ca_der = issuer_ca_fixture();
|
|
let _ = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
validation_time,
|
|
)
|
|
.expect("first run stores fetch_cache_pp 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,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
validation_time,
|
|
)
|
|
.expect_err("stop_all_output should not use fetch_cache_pp");
|
|
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 issuer_ca_der = issuer_ca_fixture();
|
|
let _ = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
ok_time,
|
|
)
|
|
.expect("first run stores fetch_cache_pp 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,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
stale_time,
|
|
)
|
|
.expect_err("stale validation_time must reject fetch_cache_pp pack");
|
|
let msg = err.to_string();
|
|
assert!(msg.contains("not valid at validation_time"));
|
|
}
|
|
|
|
#[test]
|
|
fn manifest_replay_is_treated_as_failed_fetch_and_uses_fetch_cache_pp() {
|
|
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 t1 = manifest.manifest.this_update + time::Duration::seconds(1);
|
|
let t2 = manifest.manifest.this_update + time::Duration::seconds(2);
|
|
|
|
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 issuer_ca_der = issuer_ca_fixture();
|
|
|
|
let first = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
t1,
|
|
)
|
|
.expect("first run builds and stores fetch_cache_pp pack");
|
|
assert_eq!(first.source, PublicationPointSource::Fresh);
|
|
|
|
let second = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
t2,
|
|
)
|
|
.expect("second run should treat replay as failed fetch and use cache");
|
|
assert_eq!(second.source, PublicationPointSource::FetchCachePp);
|
|
assert_eq!(second.pack, first.pack);
|
|
assert!(
|
|
second
|
|
.warnings
|
|
.iter()
|
|
.any(|w| w.message.contains("manifestNumber not higher")),
|
|
"expected warning mentioning manifestNumber monotonicity"
|
|
);
|
|
}
|