464 lines
15 KiB
Rust
464 lines
15 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::{
|
|
IssuerCaCertificateResolver, process_verified_publication_point_pack,
|
|
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());
|
|
}
|
|
|
|
struct NoIssuerResolver;
|
|
impl IssuerCaCertificateResolver for NoIssuerResolver {
|
|
fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option<Vec<u8>> {
|
|
None
|
|
}
|
|
}
|
|
|
|
struct AlwaysIssuerResolver {
|
|
issuer_ca_der: Vec<u8>,
|
|
}
|
|
|
|
impl IssuerCaCertificateResolver for AlwaysIssuerResolver {
|
|
fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option<Vec<u8>> {
|
|
Some(self.issuer_ca_der.clone())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn process_verified_pack_indexes_ca_certs_by_subject() {
|
|
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");
|
|
|
|
// Add a real CA certificate to exercise CA indexing logic.
|
|
let ca_der = issuer_ca_fixture();
|
|
|
|
let pack = minimal_pack(
|
|
&manifest_rsync_uri,
|
|
&rsync_base_uri,
|
|
manifest_bytes,
|
|
vec![PackFile::from_bytes_compute_sha256(
|
|
format!("{rsync_base_uri}some-ca.cer"),
|
|
ca_der,
|
|
)],
|
|
validation_time,
|
|
);
|
|
|
|
let policy = Policy::default();
|
|
let out = process_verified_publication_point_pack(
|
|
&pack,
|
|
&policy,
|
|
&NoIssuerResolver,
|
|
validation_time,
|
|
)
|
|
.expect("process pack");
|
|
assert!(out.vrps.is_empty());
|
|
assert!(out.aspas.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn process_pack_with_resolver_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 policy = Policy::default();
|
|
let issuer_ca_der = issuer_ca_fixture();
|
|
let resolver = AlwaysIssuerResolver {
|
|
issuer_ca_der: issuer_ca_der.clone(),
|
|
};
|
|
|
|
let out = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time)
|
|
.expect("process pack");
|
|
assert!(out.vrps.len() > 10, "expected many VRPs, got {}", out.vrps.len());
|
|
assert!(
|
|
out.audit.len() >= pack.files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count(),
|
|
"expected ROA audit entries"
|
|
);
|
|
}
|