rpki/tests/test_objects_process_pack_for_issuer.rs
2026-02-10 12:09:59 +08:00

395 lines
13 KiB
Rust

use rpki::fetch::rsync::LocalDirRsyncFetcher;
use rpki::policy::{Policy, SignedObjectFailurePolicy, SyncPreference};
use rpki::storage::{PackFile, PackTime, RocksStore, VerifiedPublicationPointPack};
use rpki::sync::repo::sync_publication_point;
use rpki::sync::rrdp::Fetcher;
use rpki::validation::manifest::process_manifest_publication_point;
use rpki::validation::objects::process_verified_publication_point_pack_for_issuer;
struct NoopHttpFetcher;
impl Fetcher for NoopHttpFetcher {
fn fetch(&self, _uri: &str) -> Result<Vec<u8>, String> {
Err("http disabled in test".to_string())
}
}
fn cernet_fixture() -> (std::path::PathBuf, String, String) {
let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0");
let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/".to_string();
let manifest_file = "05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft".to_string();
(dir, rsync_base_uri, manifest_file)
}
fn validation_time_from_manifest_fixture(
dir: &std::path::Path,
manifest_file: &str,
) -> time::OffsetDateTime {
let bytes = std::fs::read(dir.join(manifest_file)).expect("read manifest fixture");
let mft = rpki::data_model::manifest::ManifestObject::decode_der(&bytes).expect("decode mft");
let this_update = mft.manifest.this_update;
let next_update = mft.manifest.next_update;
let candidate = this_update + time::Duration::seconds(60);
if candidate < next_update {
candidate
} else {
this_update
}
}
fn issuer_ca_fixture() -> Vec<u8> {
std::fs::read(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"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 minimal_pack(
manifest_rsync_uri: &str,
publication_point_rsync_uri: &str,
manifest_bytes: Vec<u8>,
files: Vec<PackFile>,
validation_time: time::OffsetDateTime,
) -> VerifiedPublicationPointPack {
// Keep times consistent enough to pass internal pack validation.
VerifiedPublicationPointPack {
format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1,
manifest_rsync_uri: manifest_rsync_uri.to_string(),
publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
this_update: PackTime::from_utc_offset_datetime(validation_time),
next_update: PackTime::from_utc_offset_datetime(validation_time + time::Duration::hours(1)),
verified_at: PackTime::from_utc_offset_datetime(validation_time),
manifest_bytes,
files,
}
}
fn build_verified_pack_from_local_rsync_fixture(
dir: &std::path::Path,
rsync_base_uri: &str,
manifest_rsync_uri: &str,
validation_time: time::OffsetDateTime,
) -> rpki::storage::VerifiedPublicationPointPack {
let store_dir = tempfile::tempdir().expect("store dir");
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
let policy = Policy {
sync_preference: SyncPreference::RsyncOnly,
..Policy::default()
};
sync_publication_point(
&store,
&policy,
None,
rsync_base_uri,
&NoopHttpFetcher,
&LocalDirRsyncFetcher::new(dir),
)
.expect("sync into raw_objects");
let pp = process_manifest_publication_point(
&store,
&policy,
manifest_rsync_uri,
rsync_base_uri,
validation_time,
)
.expect("process manifest");
pp.pack
}
#[test]
fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() {
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
let pack = build_verified_pack_from_local_rsync_fixture(
&dir,
&rsync_base_uri,
&manifest_rsync_uri,
validation_time,
);
let issuer_ca_der = issuer_ca_fixture();
let issuer_ca = rpki::data_model::rc::ResourceCertificate::decode_der(&issuer_ca_der)
.expect("decode issuer ca");
let policy = Policy::default();
let out = process_verified_publication_point_pack_for_issuer(
&pack,
&policy,
&issuer_ca_der,
Some(issuer_ca_rsync_uri()),
issuer_ca.tbs.extensions.ip_resources.as_ref(),
issuer_ca.tbs.extensions.as_resources.as_ref(),
validation_time,
);
assert!(
out.vrps.len() > 10,
"expected many VRPs, got {}",
out.vrps.len()
);
assert!(out.aspas.is_empty());
}
#[test]
fn signed_object_failure_policy_drop_object_drops_only_bad_object() {
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
let mut pack = build_verified_pack_from_local_rsync_fixture(
&dir,
&rsync_base_uri,
&manifest_rsync_uri,
validation_time,
);
let roa_uris = pack
.files
.iter()
.filter(|f| f.rsync_uri.ends_with(".roa"))
.map(|f| f.rsync_uri.clone())
.collect::<Vec<_>>();
let bad_idx = pack
.files
.iter()
.position(|f| f.rsync_uri.ends_with(".roa"))
.expect("pack contains roa");
let bad_uri = pack.files[bad_idx].rsync_uri.clone();
pack.files[bad_idx] = PackFile::from_bytes_compute_sha256(bad_uri, vec![0u8]);
let issuer_ca_der = issuer_ca_fixture();
let issuer_ca = rpki::data_model::rc::ResourceCertificate::decode_der(&issuer_ca_der)
.expect("decode issuer ca");
let policy = Policy {
signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
..Policy::default()
};
let out = process_verified_publication_point_pack_for_issuer(
&pack,
&policy,
&issuer_ca_der,
Some(issuer_ca_rsync_uri()),
issuer_ca.tbs.extensions.ip_resources.as_ref(),
issuer_ca.tbs.extensions.as_resources.as_ref(),
validation_time,
);
assert!(out.vrps.len() > 0);
assert!(!out.warnings.is_empty());
assert_eq!(
out.audit.len(),
roa_uris.len(),
"expected one audit entry per ROA"
);
assert!(
out.audit
.iter()
.any(|e| e.rsync_uri == pack.files[bad_idx].rsync_uri
&& matches!(e.result, rpki::audit::AuditObjectResult::Error)),
"expected audit error for the corrupted ROA"
);
}
#[test]
fn signed_object_failure_policy_drop_publication_point_drops_all_output() {
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
let mut pack = build_verified_pack_from_local_rsync_fixture(
&dir,
&rsync_base_uri,
&manifest_rsync_uri,
validation_time,
);
let roa_uris = pack
.files
.iter()
.filter(|f| f.rsync_uri.ends_with(".roa"))
.map(|f| f.rsync_uri.clone())
.collect::<Vec<_>>();
let bad_idx = pack
.files
.iter()
.position(|f| f.rsync_uri.ends_with(".roa"))
.expect("pack contains roa");
let bad_uri = pack.files[bad_idx].rsync_uri.clone();
pack.files[bad_idx] = PackFile::from_bytes_compute_sha256(bad_uri, vec![0u8]);
let issuer_ca_der = issuer_ca_fixture();
let issuer_ca = rpki::data_model::rc::ResourceCertificate::decode_der(&issuer_ca_der)
.expect("decode issuer ca");
let policy = Policy {
signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
..Policy::default()
};
let out = process_verified_publication_point_pack_for_issuer(
&pack,
&policy,
&issuer_ca_der,
Some(issuer_ca_rsync_uri()),
issuer_ca.tbs.extensions.ip_resources.as_ref(),
issuer_ca.tbs.extensions.as_resources.as_ref(),
validation_time,
);
assert!(out.vrps.is_empty());
assert!(out.aspas.is_empty());
assert!(!out.warnings.is_empty());
assert_eq!(
out.audit.len(),
roa_uris.len(),
"expected audit entries for all ROAs (error + skipped due to policy)"
);
let bad_entry = out
.audit
.iter()
.find(|e| e.rsync_uri == pack.files[bad_idx].rsync_uri)
.expect("bad roa audit entry");
assert!(
matches!(bad_entry.result, rpki::audit::AuditObjectResult::Error),
"expected error for corrupted ROA"
);
assert!(
out.audit
.iter()
.any(|e| matches!(e.result, rpki::audit::AuditObjectResult::Skipped)),
"expected at least one skipped ROA due to drop_publication_point policy"
);
}
#[test]
fn process_pack_for_issuer_without_crl_drops_publication_point() {
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
let pack = minimal_pack(
&manifest_rsync_uri,
&rsync_base_uri,
manifest_bytes,
vec![
// include a ROA-looking file; it won't be reached because CRL selection fails first
PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}dummy.roa"), vec![1]),
],
validation_time,
);
let policy = Policy::default();
let out = process_verified_publication_point_pack_for_issuer(
&pack,
&policy,
&[],
None,
None,
None,
validation_time,
);
assert!(out.vrps.is_empty());
assert!(out.aspas.is_empty());
assert!(!out.warnings.is_empty());
}
#[test]
fn process_pack_for_issuer_handles_invalid_aspa_bytes() {
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
let crl_bytes =
std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl");
let pack = minimal_pack(
&manifest_rsync_uri,
&rsync_base_uri,
manifest_bytes,
vec![
PackFile::from_bytes_compute_sha256(
format!("{rsync_base_uri}05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl"),
crl_bytes,
),
PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}bad.asa"), vec![0u8]),
],
validation_time,
);
let policy = Policy {
signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
..Policy::default()
};
let out = process_verified_publication_point_pack_for_issuer(
&pack,
&policy,
&[],
None,
None,
None,
validation_time,
);
assert!(out.aspas.is_empty());
assert!(!out.warnings.is_empty());
}
#[test]
fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
let crl_bytes =
std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl");
let pack = minimal_pack(
&manifest_rsync_uri,
&rsync_base_uri,
manifest_bytes,
vec![
PackFile::from_bytes_compute_sha256(
format!("{rsync_base_uri}05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl"),
crl_bytes,
),
PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}bad.asa"), vec![0u8]),
// A ROA-like file: we should not reach it due to policy.
PackFile::from_bytes_compute_sha256(format!("{rsync_base_uri}dummy.roa"), vec![1u8]),
],
validation_time,
);
let policy = Policy {
signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
..Policy::default()
};
let out = process_verified_publication_point_pack_for_issuer(
&pack,
&policy,
&[],
None,
None,
None,
validation_time,
);
assert!(out.vrps.is_empty());
assert!(out.aspas.is_empty());
assert!(!out.warnings.is_empty());
}
// NOTE: DN-based issuer resolution and pack-local CA indexing have been removed for determinism.