use std::collections::HashMap; 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::{ IssuerCaCertificateResolver, process_verified_publication_point_pack, }; 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 } struct EmptyResolver; impl IssuerCaCertificateResolver for EmptyResolver { fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option> { None } } struct MapResolver { by_subject_dn: HashMap>, } impl IssuerCaCertificateResolver for MapResolver { fn resolve_by_subject_dn(&self, subject_dn: &str) -> Option> { self.by_subject_dn.get(subject_dn).cloned() } } fn build_cernet_pack_and_validation_time() -> ( rpki::storage::VerifiedPublicationPointPack, time::OffsetDateTime, MapResolver, ) { 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 policy = Policy::default(); let out = process_manifest_publication_point( &store, &policy, &manifest_rsync_uri, &publication_point_rsync_uri, manifest.manifest.this_update + time::Duration::seconds(1), ) .expect("process manifest publication point"); 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 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"); 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); let resolver = MapResolver { by_subject_dn: HashMap::from([(issuer_ca.tbs.subject_dn, issuer_ca_der)]), }; (out.pack, t, resolver) } #[test] fn missing_crl_causes_roas_to_be_dropped_under_drop_object_policy() { let (mut pack, validation_time, resolver) = build_cernet_pack_and_validation_time(); pack.files.retain(|f| !f.rsync_uri.ends_with(".crl")); let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject; let out = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time) .expect("drop_object should not fail the publication point"); assert!(out.vrps.is_empty()); assert!(!out.warnings.is_empty()); } #[test] fn missing_issuer_ca_cert_causes_roas_to_be_dropped_under_drop_object_policy() { let (pack, validation_time, _resolver) = build_cernet_pack_and_validation_time(); let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject; let out = process_verified_publication_point_pack(&pack, &policy, &EmptyResolver, validation_time) .expect("drop_object should not fail the publication point"); assert!(out.vrps.is_empty()); assert!(!out.warnings.is_empty()); } #[test] fn invalid_aspa_object_is_reported_as_warning_under_drop_object_policy() { let (mut pack, validation_time, resolver) = build_cernet_pack_and_validation_time(); let uri = "rsync://rpki.cernet.net/repo/cernet/0/INVALID.asa".to_string(); pack.files.push(PackFile::from_bytes_compute_sha256( uri.clone(), b"\0\0".to_vec(), )); let mut policy = Policy::default(); policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject; let out = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time) .expect("drop_object should not fail"); assert!( out.warnings .iter() .any(|w| w.context.as_deref() == Some(&uri)), "expected warning for invalid ASPA" ); }