rpki/tests/test_objects_policy_m8.rs
2026-02-11 10:07:24 +08:00

190 lines
6.5 KiB
Rust

use std::path::Path;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::manifest::ManifestObject;
use rpki::data_model::rc::ResourceCertificate;
use rpki::policy::{Policy, SignedObjectFailurePolicy};
use rpki::storage::{PackFile, RocksStore};
use rpki::validation::manifest::process_manifest_publication_point;
use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer;
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 build_cernet_pack_and_validation_time() -> (
rpki::storage::FetchCachePpPack,
time::OffsetDateTime,
Vec<u8>,
ResourceCertificate,
) {
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 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 issuer_ca_der = std::fs::read(
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
)
.expect("read issuer CA cert fixture");
let policy = Policy::default();
let out = process_manifest_publication_point(
&store,
&policy,
&manifest_rsync_uri,
&publication_point_rsync_uri,
&issuer_ca_der,
Some("rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"),
manifest.manifest.this_update + time::Duration::seconds(1),
)
.expect("process manifest publication point");
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer CA cert");
let crl_file = out
.pack
.files
.iter()
.find(|f| f.rsync_uri.ends_with(".crl"))
.expect("crl present in pack");
let crl = RpkixCrl::decode_der(&crl_file.bytes).expect("decode crl");
// Choose a validation_time that is within:
// - manifest thisUpdate..nextUpdate (RFC 9286 §6.3)
// - issuer CA validity
// - CRL thisUpdate..nextUpdate
// - and should be within EE validity for most objects.
let mut t = manifest.manifest.this_update;
if issuer_ca.tbs.validity_not_before > t {
t = issuer_ca.tbs.validity_not_before;
}
if crl.this_update.utc > t {
t = crl.this_update.utc;
}
t += time::Duration::seconds(1);
(out.pack, t, issuer_ca_der, issuer_ca)
}
#[test]
fn drop_object_policy_drops_only_failing_object() {
let (mut pack, validation_time, issuer_ca_der, issuer_ca) =
build_cernet_pack_and_validation_time();
let valid_roa_uri = pack
.files
.iter()
.find(|f| f.rsync_uri.ends_with("AS4538.roa"))
.map(|f| f.rsync_uri.clone())
.expect("AS4538.roa present in pack");
let tamper_idx = pack
.files
.iter()
.position(|f| f.rsync_uri.ends_with(".roa") && f.rsync_uri != valid_roa_uri)
.expect("another ROA present in pack");
let victim_uri = pack.files[tamper_idx].rsync_uri.clone();
let mut tampered = pack.files[tamper_idx].bytes.clone();
let last = tampered.len() - 1;
tampered[last] ^= 0xFF;
pack.files[tamper_idx] = PackFile::from_bytes_compute_sha256(victim_uri.clone(), tampered);
let mut policy = Policy::default();
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
let out = process_fetch_cache_pp_pack_for_issuer(
&pack,
&policy,
&issuer_ca_der,
None,
issuer_ca.tbs.extensions.ip_resources.as_ref(),
issuer_ca.tbs.extensions.as_resources.as_ref(),
validation_time,
);
assert!(
out.vrps.iter().any(|v| v.asn == 4538),
"expected at least one VRP for AS4538"
);
assert!(
out.warnings
.iter()
.any(|w| w.context.as_deref() == Some(&victim_uri)),
"expected a warning for the tampered object"
);
}
#[test]
fn drop_publication_point_policy_drops_the_publication_point() {
let (mut pack, validation_time, issuer_ca_der, issuer_ca) =
build_cernet_pack_and_validation_time();
let tamper_idx = pack
.files
.iter()
.position(|f| f.rsync_uri.ends_with(".roa"))
.expect("a ROA present in pack");
let victim_uri = pack.files[tamper_idx].rsync_uri.clone();
let mut tampered = pack.files[tamper_idx].bytes.clone();
let last = tampered.len() - 1;
tampered[last] ^= 0xFF;
pack.files[tamper_idx] = PackFile::from_bytes_compute_sha256(victim_uri.clone(), tampered);
let mut policy = Policy::default();
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint;
let out = process_fetch_cache_pp_pack_for_issuer(
&pack,
&policy,
&issuer_ca_der,
None,
issuer_ca.tbs.extensions.ip_resources.as_ref(),
issuer_ca.tbs.extensions.as_resources.as_ref(),
validation_time,
);
assert!(out.stats.publication_point_dropped);
assert!(out.vrps.is_empty(), "expected publication point dropped");
assert!(
out.audit.iter().any(|a| a.rsync_uri == victim_uri),
"expected audit entry for victim object"
);
}