190 lines
6.5 KiB
Rust
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"
|
|
);
|
|
}
|