将fetch pp cache改成使用vcir结构,跑通apnic全量同步
This commit is contained in:
@@ -6,8 +6,8 @@ use rpki::data_model::rc::{
|
||||
};
|
||||
use rpki::data_model::roa::RoaObject;
|
||||
|
||||
use rpki::storage::PackFile;
|
||||
use rpki::storage::RocksStore;
|
||||
use rpki::storage::pack::PackFile;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::time::{Duration, Instant};
|
||||
use rpki::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig};
|
||||
use rpki::fetch::rsync::{RsyncFetchError, RsyncFetcher};
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy, SyncPreference};
|
||||
use rpki::storage::{FetchCachePpKey, RocksStore};
|
||||
use rpki::storage::RocksStore;
|
||||
use rpki::sync::repo::{RepoSyncSource, sync_publication_point};
|
||||
use rpki::sync::rrdp::{Fetcher, parse_notification, sync_from_notification};
|
||||
use rpki::validation::from_tal::discover_root_ca_instance_from_tal_url;
|
||||
@@ -80,13 +80,13 @@ impl Fetcher for CountingDenyUriFetcher {
|
||||
fn live_policy() -> Policy {
|
||||
let mut p = Policy::default();
|
||||
p.sync_preference = SyncPreference::RrdpThenRsync;
|
||||
p.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp;
|
||||
p.ca_failed_fetch_policy = CaFailedFetchPolicy::ReuseCurrentInstanceVcir;
|
||||
p
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "live network: APNIC RRDP snapshot bootstrap into persistent RocksDB"]
|
||||
fn apnic_live_bootstrap_snapshot_and_fetch_cache_pp_pack_to_persistent_db() {
|
||||
fn apnic_live_bootstrap_snapshot_and_persist_root_vcir_to_persistent_db() {
|
||||
let http = live_http_fetcher();
|
||||
let rsync = AlwaysFailRsyncFetcher;
|
||||
|
||||
@@ -120,8 +120,8 @@ fn apnic_live_bootstrap_snapshot_and_fetch_cache_pp_pack_to_persistent_db() {
|
||||
|
||||
assert_eq!(sync.source, RepoSyncSource::Rrdp);
|
||||
|
||||
// Build + persist a fetch_cache_pp pack for the root publication point so later runs can
|
||||
// validate behavior under failed fetch conditions (RFC 9286 §6.6).
|
||||
// Build the root publication point and persist the latest VCIR so later runs can
|
||||
// validate current-instance failed-fetch reuse behavior (RFC 9286 §6.6).
|
||||
let ta_der = discovery.trust_anchor.ta_certificate.raw_der;
|
||||
let pp = process_manifest_publication_point(
|
||||
&store,
|
||||
@@ -136,9 +136,10 @@ fn apnic_live_bootstrap_snapshot_and_fetch_cache_pp_pack_to_persistent_db() {
|
||||
|
||||
assert_eq!(pp.source, PublicationPointSource::Fresh);
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&ca_instance.manifest_rsync_uri);
|
||||
let cached = store.get_fetch_cache_pp(&key).expect("get fetch_cache_pp");
|
||||
assert!(cached.is_some(), "expected fetch_cache_pp to be stored");
|
||||
let cached = store
|
||||
.get_vcir(&ca_instance.manifest_rsync_uri)
|
||||
.expect("get vcir");
|
||||
assert!(cached.is_some(), "expected VCIR to be stored");
|
||||
|
||||
eprintln!(
|
||||
"OK: bootstrap complete; persistent db at: {}",
|
||||
@@ -262,15 +263,15 @@ fn apnic_live_delta_only_from_persistent_db() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "offline/synthetic: after bootstrap, force repo sync failure and assert fetch_cache_pp is used (RFC 9286 §6.6)"]
|
||||
fn apnic_root_repo_sync_failure_uses_fetch_cache_pp_pack() {
|
||||
#[ignore = "offline/synthetic: after bootstrap, force repo sync failure and assert current-instance VCIR is reused (RFC 9286 §6.6)"]
|
||||
fn apnic_root_repo_sync_failure_reuses_current_instance_vcir() {
|
||||
let http = live_http_fetcher();
|
||||
let db_dir = persistent_db_dir();
|
||||
let store = RocksStore::open(&db_dir).expect("open rocksdb (must have been bootstrapped)");
|
||||
|
||||
let mut policy = live_policy();
|
||||
policy.sync_preference = SyncPreference::RrdpThenRsync;
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp;
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::ReuseCurrentInstanceVcir;
|
||||
|
||||
let validation_time = time::OffsetDateTime::now_utc();
|
||||
|
||||
@@ -278,12 +279,13 @@ fn apnic_root_repo_sync_failure_uses_fetch_cache_pp_pack() {
|
||||
.expect("discover root CA instance from APNIC TAL");
|
||||
let ca_instance = discovery.ca_instance;
|
||||
|
||||
// Ensure cache exists (created by bootstrap).
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&ca_instance.manifest_rsync_uri);
|
||||
let cached = store.get_fetch_cache_pp(&key).expect("get fetch_cache_pp");
|
||||
// Ensure current-instance VCIR exists (created by bootstrap).
|
||||
let cached = store
|
||||
.get_vcir(&ca_instance.manifest_rsync_uri)
|
||||
.expect("get vcir");
|
||||
assert!(
|
||||
cached.is_some(),
|
||||
"missing fetch_cache_pp; run bootstrap test first. db_dir={}",
|
||||
"missing VCIR; run bootstrap test first. db_dir={}",
|
||||
db_dir.display()
|
||||
);
|
||||
|
||||
@@ -301,13 +303,13 @@ fn apnic_root_repo_sync_failure_uses_fetch_cache_pp_pack() {
|
||||
false,
|
||||
Some("synthetic repo sync failure"),
|
||||
)
|
||||
.expect("must fall back to fetch_cache_pp");
|
||||
.expect("must reuse current-instance VCIR");
|
||||
|
||||
assert_eq!(pp.source, PublicationPointSource::FetchCachePp);
|
||||
assert_eq!(pp.source, PublicationPointSource::VcirCurrentInstance);
|
||||
assert!(
|
||||
pp.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("using fetch_cache_pp")),
|
||||
"expected cache-use warning"
|
||||
pp.warnings.iter().any(|w| w
|
||||
.message
|
||||
.contains("using latest validated result for current CA instance")),
|
||||
"expected current-instance VCIR reuse warning"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,37 +63,39 @@ impl LiveStats {
|
||||
rpki::validation::manifest::PublicationPointSource::Fresh => {
|
||||
self.publication_points_fresh += 1
|
||||
}
|
||||
rpki::validation::manifest::PublicationPointSource::FetchCachePp => {
|
||||
rpki::validation::manifest::PublicationPointSource::VcirCurrentInstance => {
|
||||
self.publication_points_cached += 1
|
||||
}
|
||||
rpki::validation::manifest::PublicationPointSource::FailedFetchNoCache => {}
|
||||
}
|
||||
|
||||
// Include manifest object URI itself.
|
||||
self.pack_uris_total += 1;
|
||||
self.pack_file_uris_unique
|
||||
.insert(res.pack.manifest_rsync_uri.clone());
|
||||
*self
|
||||
.pack_uris_by_ext_total
|
||||
.entry(ext_of_uri(&res.pack.manifest_rsync_uri))
|
||||
.or_insert(0) += 1;
|
||||
|
||||
for f in &res.pack.files {
|
||||
if let Some(pack) = res.snapshot.as_ref() {
|
||||
self.pack_uris_total += 1;
|
||||
self.pack_file_uris_unique.insert(f.rsync_uri.clone());
|
||||
self.pack_file_uris_unique
|
||||
.insert(pack.manifest_rsync_uri.clone());
|
||||
*self
|
||||
.pack_uris_by_ext_total
|
||||
.entry(ext_of_uri(&f.rsync_uri))
|
||||
.entry(ext_of_uri(&pack.manifest_rsync_uri))
|
||||
.or_insert(0) += 1;
|
||||
|
||||
if f.rsync_uri.ends_with(".crl") {
|
||||
self.crl_total += 1;
|
||||
if RpkixCrl::decode_der(&f.bytes).is_ok() {
|
||||
self.crl_decode_ok += 1;
|
||||
}
|
||||
}
|
||||
for f in &pack.files {
|
||||
self.pack_uris_total += 1;
|
||||
self.pack_file_uris_unique.insert(f.rsync_uri.clone());
|
||||
*self
|
||||
.pack_uris_by_ext_total
|
||||
.entry(ext_of_uri(&f.rsync_uri))
|
||||
.or_insert(0) += 1;
|
||||
|
||||
if f.rsync_uri.ends_with(".cer") {
|
||||
self.child_ca_cert_candidates_total += 1;
|
||||
if f.rsync_uri.ends_with(".crl") {
|
||||
self.crl_total += 1;
|
||||
if RpkixCrl::decode_der(&f.bytes).is_ok() {
|
||||
self.crl_decode_ok += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if f.rsync_uri.ends_with(".cer") {
|
||||
self.child_ca_cert_candidates_total += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +170,6 @@ fn apnic_tree_full_stats_serial() {
|
||||
validation_time,
|
||||
timing: None,
|
||||
download_log: None,
|
||||
revalidate_only: false,
|
||||
rrdp_dedup: true,
|
||||
rrdp_repo_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||
rsync_dedup: true,
|
||||
@@ -181,8 +182,12 @@ fn apnic_tree_full_stats_serial() {
|
||||
stats: &stats,
|
||||
};
|
||||
|
||||
let root: CaInstanceHandle =
|
||||
root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
||||
let root: CaInstanceHandle = root_handle_from_trust_anchor(
|
||||
&discovery.trust_anchor,
|
||||
"test-tal".to_string(),
|
||||
None,
|
||||
&discovery.ca_instance,
|
||||
);
|
||||
|
||||
let max_depth = std::env::var("RPKI_APNIC_MAX_DEPTH")
|
||||
.ok()
|
||||
@@ -197,7 +202,6 @@ fn apnic_tree_full_stats_serial() {
|
||||
&TreeRunConfig {
|
||||
max_depth,
|
||||
max_instances,
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
@@ -213,11 +217,6 @@ fn apnic_tree_full_stats_serial() {
|
||||
}
|
||||
}
|
||||
|
||||
let fetch_cache_pp_total = store
|
||||
.fetch_cache_pp_iter_all()
|
||||
.expect("fetch_cache_pp_iter_all")
|
||||
.count();
|
||||
|
||||
println!("APNIC Stage2 full-tree serial stats");
|
||||
println!("tal_url={APNIC_TAL_URL}");
|
||||
println!(
|
||||
@@ -246,7 +245,6 @@ fn apnic_tree_full_stats_serial() {
|
||||
stats.pack_file_uris_unique.len()
|
||||
);
|
||||
println!("pack_uris_by_ext_total={:?}", stats.pack_uris_by_ext_total);
|
||||
println!("fetch_cache_pp_total={fetch_cache_pp_total}");
|
||||
println!();
|
||||
println!(
|
||||
"crl_total={} crl_decode_ok={}",
|
||||
@@ -273,7 +271,6 @@ fn apnic_tree_full_stats_serial() {
|
||||
"rocksdb_raw_objects_total={} raw_by_ext={:?}",
|
||||
raw_total, raw_by_ext
|
||||
);
|
||||
println!("rocksdb_fetch_cache_pp_total={fetch_cache_pp_total}");
|
||||
|
||||
// Loose sanity assertions (avoid flakiness due to repository churn).
|
||||
//
|
||||
|
||||
@@ -36,7 +36,6 @@ fn apnic_tree_depth1_processes_more_than_root() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(1),
|
||||
max_instances: Some(2),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree from tal");
|
||||
@@ -75,7 +74,6 @@ fn apnic_tree_root_only_processes_root_with_long_timeouts() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run APNIC root-only");
|
||||
|
||||
@@ -467,3 +467,337 @@ fn validate_subordinate_ca_rejects_tampered_crl_signature() {
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::CrlVerify(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_subordinate_ca_rejects_non_ca_child_and_non_ca_issuer() {
|
||||
let generated = generate_chain_and_crl(
|
||||
"keyUsage = critical, keyCertSign, cRLSign
|
||||
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
|
||||
",
|
||||
false,
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
|
||||
let ee_child_der =
|
||||
std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
|
||||
.expect("read roa fixture");
|
||||
let roa = rpki::data_model::roa::RoaObject::decode_der(&ee_child_der).expect("decode roa");
|
||||
let ee_der = roa.signed_object.signed_data.certificates[0]
|
||||
.raw_der
|
||||
.clone();
|
||||
|
||||
let err = validate_subordinate_ca_cert(
|
||||
&ee_der,
|
||||
&generated.issuer_ca_der,
|
||||
&generated.issuer_crl_der,
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::ChildNotCa), "{err}");
|
||||
|
||||
let err = validate_subordinate_ca_cert(
|
||||
&generated.child_ca_der,
|
||||
&ee_der,
|
||||
&generated.issuer_crl_der,
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::IssuerNotCa), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_subordinate_ca_rejects_mismatched_issuer_subject_and_missing_resources() {
|
||||
let with_resources = generate_chain_and_crl(
|
||||
"keyUsage = critical, keyCertSign, cRLSign
|
||||
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
|
||||
sbgp-autonomousSysNum = critical, AS:64496
|
||||
",
|
||||
false,
|
||||
);
|
||||
let issuer =
|
||||
ResourceCertificate::decode_der(&with_resources.issuer_ca_der).expect("decode issuer");
|
||||
let wrong_issuer_der = std::fs::read(
|
||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||
)
|
||||
.expect("read wrong issuer fixture");
|
||||
let wrong_issuer =
|
||||
ResourceCertificate::decode_der(&wrong_issuer_der).expect("decode wrong issuer");
|
||||
|
||||
let err = validate_subordinate_ca_cert(
|
||||
&with_resources.child_ca_der,
|
||||
&wrong_issuer_der,
|
||||
&with_resources.issuer_crl_der,
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
wrong_issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
wrong_issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, CaPathError::IssuerSubjectMismatch { .. }),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
let no_resources = generate_chain_and_crl(
|
||||
"keyUsage = critical, keyCertSign, cRLSign
|
||||
",
|
||||
false,
|
||||
);
|
||||
let err = validate_subordinate_ca_cert(
|
||||
&no_resources.child_ca_der,
|
||||
&no_resources.issuer_ca_der,
|
||||
&no_resources.issuer_crl_der,
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::ResourcesMissing), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_subordinate_ca_with_prevalidated_issuer_covers_success_and_error_paths() {
|
||||
use rpki::data_model::common::BigUnsigned;
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let generated = generate_chain_and_crl(
|
||||
"keyUsage = critical, keyCertSign, cRLSign
|
||||
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
|
||||
sbgp-autonomousSysNum = critical, AS:64496
|
||||
",
|
||||
false,
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
|
||||
let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child");
|
||||
let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl");
|
||||
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
|
||||
.expect("parse issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
|
||||
let validated = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&generated.child_ca_der,
|
||||
child.clone(),
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
now,
|
||||
)
|
||||
.expect("validate subordinate with prevalidated issuer");
|
||||
assert!(validated.effective_ip_resources.is_some());
|
||||
assert!(validated.effective_as_resources.is_some());
|
||||
|
||||
let mut revoked = HashSet::new();
|
||||
revoked.insert(BigUnsigned::from_biguint(&child.tbs.serial_number).bytes_be);
|
||||
let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&generated.child_ca_der,
|
||||
child,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&revoked,
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
now,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::ChildRevoked), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_subordinate_ca_with_prevalidated_issuer_rejects_non_ca_inputs_and_invalid_times() {
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let generated = generate_chain_and_crl(
|
||||
"keyUsage = critical, keyCertSign, cRLSign
|
||||
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
|
||||
sbgp-autonomousSysNum = critical, AS:64496
|
||||
",
|
||||
false,
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
|
||||
let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child");
|
||||
let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl");
|
||||
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
|
||||
.expect("parse issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
|
||||
let roa_der =
|
||||
std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
|
||||
.expect("read roa fixture");
|
||||
let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee_rc =
|
||||
ResourceCertificate::decode_der(&roa.signed_object.signed_data.certificates[0].raw_der)
|
||||
.expect("decode ee rc");
|
||||
let (rem, ee_spki) =
|
||||
SubjectPublicKeyInfo::from_der(&ee_rc.tbs.subject_public_key_info).expect("parse ee spki");
|
||||
assert!(rem.is_empty());
|
||||
|
||||
let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&generated.child_ca_der,
|
||||
child.clone(),
|
||||
&ee_rc,
|
||||
&ee_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::IssuerNotCa), "{err}");
|
||||
|
||||
let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&generated.child_ca_der,
|
||||
ee_rc,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::ChildNotCa), "{err}");
|
||||
|
||||
let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&generated.child_ca_der,
|
||||
child.clone(),
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
child.tbs.validity_not_before - time::Duration::seconds(1),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, CaPathError::CertificateNotValidAtTime),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&generated.child_ca_der,
|
||||
child,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
issuer_crl.next_update.utc + time::Duration::seconds(1),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::CrlNotValidAtTime), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_subordinate_ca_with_prevalidated_issuer_rejects_mismatch_and_missing_resources() {
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let generated = generate_chain_and_crl(
|
||||
"keyUsage = critical, keyCertSign, cRLSign
|
||||
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
|
||||
sbgp-autonomousSysNum = critical, AS:64496
|
||||
",
|
||||
false,
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
|
||||
let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child");
|
||||
let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl");
|
||||
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
|
||||
.expect("parse issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
|
||||
let mut mismatched_child = child.clone();
|
||||
mismatched_child.tbs.issuer_name = mismatched_child.tbs.subject_name.clone();
|
||||
|
||||
let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&generated.child_ca_der,
|
||||
mismatched_child,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, CaPathError::IssuerSubjectMismatch { .. }),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
let no_resources = generate_chain_and_crl(
|
||||
"keyUsage = critical, keyCertSign, cRLSign
|
||||
",
|
||||
false,
|
||||
);
|
||||
let no_resources_child = ResourceCertificate::decode_der(&no_resources.child_ca_der)
|
||||
.expect("decode no resources child");
|
||||
let no_resources_issuer = ResourceCertificate::decode_der(&no_resources.issuer_ca_der)
|
||||
.expect("decode no resources issuer");
|
||||
let no_resources_crl =
|
||||
RpkixCrl::decode_der(&no_resources.issuer_crl_der).expect("decode no resources crl");
|
||||
let (rem, no_resources_spki) =
|
||||
SubjectPublicKeyInfo::from_der(&no_resources_issuer.tbs.subject_public_key_info)
|
||||
.expect("parse no resources issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
&no_resources.child_ca_der,
|
||||
no_resources_child,
|
||||
&no_resources_issuer,
|
||||
&no_resources_spki,
|
||||
&no_resources_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
no_resources_issuer.tbs.extensions.ip_resources.as_ref(),
|
||||
no_resources_issuer.tbs.extensions.as_resources.as_ref(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::ResourcesMissing), "{err}");
|
||||
}
|
||||
|
||||
@@ -244,3 +244,250 @@ fn ee_key_usage_wrong_bits_is_rejected() {
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::KeyUsageInvalidBits), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_with_prevalidated_issuer_covers_success_and_error_paths() {
|
||||
use rpki::data_model::common::BigUnsigned;
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::rc::ResourceCertificate;
|
||||
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let g = generate_issuer_ca_ee_and_crl(
|
||||
"keyUsage = critical, digitalSignature
|
||||
",
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
|
||||
let ee = ResourceCertificate::decode_der(&g.ee_der).expect("decode ee");
|
||||
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
|
||||
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
|
||||
.expect("parse issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
|
||||
validate_ee_cert_path_with_prevalidated_issuer(
|
||||
&g.ee_der,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
Some("rsync://example.test/repo/issuer/issuer.crl"),
|
||||
now,
|
||||
)
|
||||
.expect("prevalidated ee path ok");
|
||||
|
||||
let mut revoked = HashSet::new();
|
||||
revoked.insert(BigUnsigned::from_biguint(&ee.tbs.serial_number).bytes_be);
|
||||
let err = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
&g.ee_der,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&revoked,
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
Some("rsync://example.test/repo/issuer/issuer.crl"),
|
||||
now,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::EeRevoked), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_with_prevalidated_issuer_rejects_non_ee_and_non_ca_issuer() {
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::rc::ResourceCertificate;
|
||||
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let g = generate_issuer_ca_ee_and_crl(
|
||||
"keyUsage = critical, digitalSignature
|
||||
",
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
|
||||
let ee = ResourceCertificate::decode_der(&g.ee_der).expect("decode ee");
|
||||
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
|
||||
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
|
||||
.expect("parse issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
|
||||
let err = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
&g.issuer_ca_der,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
None,
|
||||
None,
|
||||
now,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::EeNotEe), "{err}");
|
||||
|
||||
let (rem, bad_spki) =
|
||||
SubjectPublicKeyInfo::from_der(&ee.tbs.subject_public_key_info).expect("parse ee spki");
|
||||
assert!(rem.is_empty());
|
||||
let err = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
&g.ee_der,
|
||||
&ee,
|
||||
&bad_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
None,
|
||||
None,
|
||||
now,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::IssuerNotCa), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_with_prevalidated_issuer_rejects_mismatched_issuer_subject() {
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::rc::ResourceCertificate;
|
||||
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let g = generate_issuer_ca_ee_and_crl(
|
||||
"keyUsage = critical, digitalSignature
|
||||
",
|
||||
);
|
||||
let wrong_issuer_der = std::fs::read(
|
||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||
)
|
||||
.expect("read wrong issuer fixture");
|
||||
let wrong_issuer =
|
||||
ResourceCertificate::decode_der(&wrong_issuer_der).expect("decode wrong issuer");
|
||||
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
|
||||
let (rem, wrong_spki) =
|
||||
SubjectPublicKeyInfo::from_der(&wrong_issuer.tbs.subject_public_key_info)
|
||||
.expect("parse wrong issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
|
||||
let err = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
&g.ee_der,
|
||||
&wrong_issuer,
|
||||
&wrong_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
None,
|
||||
None,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, CertPathError::IssuerSubjectMismatch { .. }),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_rejects_non_ee_and_non_ca_issuer() {
|
||||
use rpki::data_model::roa::RoaObject;
|
||||
|
||||
let g = generate_issuer_ca_ee_and_crl(
|
||||
"keyUsage = critical, digitalSignature
|
||||
",
|
||||
);
|
||||
let roa_der =
|
||||
std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
|
||||
.expect("read roa fixture");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee_from_roa = roa.signed_object.signed_data.certificates[0]
|
||||
.raw_der
|
||||
.clone();
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
|
||||
let err = validate_ee_cert_path(
|
||||
&g.issuer_ca_der,
|
||||
&g.issuer_ca_der,
|
||||
&g.issuer_crl_der,
|
||||
None,
|
||||
None,
|
||||
now,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::EeNotEe), "{err}");
|
||||
|
||||
let err = validate_ee_cert_path(&g.ee_der, &ee_from_roa, &g.issuer_crl_der, None, None, now)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::IssuerNotCa), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_rejects_stale_crl() {
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
|
||||
let g = generate_issuer_ca_ee_and_crl(
|
||||
"keyUsage = critical, digitalSignature
|
||||
",
|
||||
);
|
||||
let crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
|
||||
let err = validate_ee_cert_path(
|
||||
&g.ee_der,
|
||||
&g.issuer_ca_der,
|
||||
&g.issuer_crl_der,
|
||||
None,
|
||||
None,
|
||||
crl.next_update.utc + time::Duration::seconds(1),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::CrlNotValidAtTime), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_cert_path_with_prevalidated_issuer_rejects_invalid_times() {
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::rc::ResourceCertificate;
|
||||
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
|
||||
use std::collections::HashSet;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
let g = generate_issuer_ca_ee_and_crl(
|
||||
"keyUsage = critical, digitalSignature
|
||||
",
|
||||
);
|
||||
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
|
||||
let ee = ResourceCertificate::decode_der(&g.ee_der).expect("decode ee");
|
||||
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
|
||||
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
|
||||
.expect("parse issuer spki");
|
||||
assert!(rem.is_empty());
|
||||
|
||||
let err = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
&g.ee_der,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
Some("rsync://example.test/repo/issuer/issuer.crl"),
|
||||
ee.tbs.validity_not_before - time::Duration::seconds(1),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, CertPathError::CertificateNotValidAtTime),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
let err = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
&g.ee_der,
|
||||
&issuer,
|
||||
&issuer_spki,
|
||||
&issuer_crl,
|
||||
&HashSet::new(),
|
||||
Some("rsync://example.test/repo/issuer/issuer.cer"),
|
||||
Some("rsync://example.test/repo/issuer/issuer.crl"),
|
||||
issuer_crl.next_update.utc + time::Duration::seconds(1),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CertPathError::CrlNotValidAtTime), "{err}");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use rpki::audit::PublicationPointAudit;
|
||||
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
||||
use rpki::storage::{FetchCachePpPack, PackFile, PackTime};
|
||||
use rpki::storage::{PackFile, PackTime};
|
||||
use rpki::validation::manifest::PublicationPointSource;
|
||||
use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer;
|
||||
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
|
||||
use rpki::validation::publication_point::PublicationPointSnapshot;
|
||||
use rpki::validation::tree::{
|
||||
CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner, TreeRunConfig,
|
||||
run_tree_serial_audit,
|
||||
@@ -13,12 +14,12 @@ fn fixture_bytes(path: &str) -> Vec<u8> {
|
||||
.unwrap_or_else(|e| panic!("read fixture {path}: {e}"))
|
||||
}
|
||||
|
||||
fn dummy_pack(files: Vec<PackFile>) -> FetchCachePpPack {
|
||||
fn dummy_snapshot(files: Vec<PackFile>) -> PublicationPointSnapshot {
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let manifest_rsync_uri =
|
||||
"rsync://rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft";
|
||||
FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
PublicationPointSnapshot {
|
||||
format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
publication_point_rsync_uri: "rsync://rpki.cernet.net/repo/cernet/0/".to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
@@ -34,7 +35,7 @@ fn dummy_pack(files: Vec<PackFile>) -> FetchCachePpPack {
|
||||
|
||||
struct SinglePackRunner {
|
||||
policy: Policy,
|
||||
pack: FetchCachePpPack,
|
||||
snapshot: PublicationPointSnapshot,
|
||||
}
|
||||
|
||||
impl PublicationPointRunner for SinglePackRunner {
|
||||
@@ -42,8 +43,8 @@ impl PublicationPointRunner for SinglePackRunner {
|
||||
&self,
|
||||
ca: &CaInstanceHandle,
|
||||
) -> Result<PublicationPointRunResult, String> {
|
||||
let objects = process_fetch_cache_pp_pack_for_issuer(
|
||||
&self.pack,
|
||||
let objects = process_publication_point_snapshot_for_issuer(
|
||||
&self.snapshot,
|
||||
&self.policy,
|
||||
&ca.ca_certificate_der,
|
||||
ca.ca_certificate_rsync_uri.as_deref(),
|
||||
@@ -55,7 +56,7 @@ impl PublicationPointRunner for SinglePackRunner {
|
||||
|
||||
Ok(PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: self.pack.clone(),
|
||||
snapshot: Some(self.snapshot.clone()),
|
||||
warnings: Vec::new(),
|
||||
objects,
|
||||
audit: PublicationPointAudit::default(),
|
||||
@@ -70,7 +71,7 @@ fn crl_mismatch_drops_publication_point_and_cites_rfc_sections() {
|
||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
|
||||
// Include at least one CRL file but with a URI that does NOT match the EE certificate's CRLDP.
|
||||
let pack = dummy_pack(vec![
|
||||
let pack = dummy_snapshot(vec![
|
||||
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/not-it.crl", vec![0x01]),
|
||||
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/a.roa", roa_bytes),
|
||||
]);
|
||||
@@ -78,10 +79,15 @@ fn crl_mismatch_drops_publication_point_and_cites_rfc_sections() {
|
||||
let mut policy = Policy::default();
|
||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint;
|
||||
|
||||
let runner = SinglePackRunner { policy, pack };
|
||||
let runner = SinglePackRunner {
|
||||
policy,
|
||||
snapshot: pack,
|
||||
};
|
||||
|
||||
let root = CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
// Use a real, parseable CA certificate DER so objects processing can reach CRL selection.
|
||||
// The test only asserts CRLDP/locked-pack error handling, not signature chaining.
|
||||
ca_certificate_der: fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
@@ -102,7 +108,6 @@ fn crl_mismatch_drops_publication_point_and_cites_rfc_sections() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree audit");
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy};
|
||||
use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore};
|
||||
use rpki::validation::manifest::{
|
||||
PublicationPointSource, process_manifest_publication_point,
|
||||
process_manifest_publication_point_after_repo_sync,
|
||||
};
|
||||
|
||||
fn issuer_ca_fixture() -> Vec<u8> {
|
||||
std::fs::read(
|
||||
"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 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 load_cernet_manifest_fixture() -> (std::path::PathBuf, Vec<u8>, ManifestObject) {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
)
|
||||
.to_path_buf();
|
||||
let bytes = std::fs::read(&manifest_path).expect("read manifest fixture");
|
||||
let obj = ManifestObject::decode_der(&bytes).expect("decode manifest fixture");
|
||||
(manifest_path, bytes, obj)
|
||||
}
|
||||
|
||||
fn store_raw_publication_point_files(
|
||||
store: &RocksStore,
|
||||
manifest_path: &Path,
|
||||
manifest_rsync_uri: &str,
|
||||
manifest_bytes: &[u8],
|
||||
manifest: &ManifestObject,
|
||||
publication_point_rsync_uri: &str,
|
||||
) {
|
||||
store
|
||||
.put_raw(manifest_rsync_uri, manifest_bytes)
|
||||
.expect("store manifest raw");
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
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 raw");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_pack_revalidation_rejects_missing_file_referenced_by_manifest() {
|
||||
let (manifest_path, manifest_bytes, manifest) = load_cernet_manifest_fixture();
|
||||
let validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
||||
|
||||
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_raw_publication_point_files(
|
||||
&store,
|
||||
&manifest_path,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
&manifest,
|
||||
&publication_point_rsync_uri,
|
||||
);
|
||||
|
||||
let mut policy = Policy::default();
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp;
|
||||
let issuer_ca_der = issuer_ca_fixture();
|
||||
|
||||
let _fresh = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("fresh run stores fetch_cache_pp");
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack");
|
||||
|
||||
// Remove one file from the pack: pack stays internally consistent, but no longer satisfies
|
||||
// RFC 9286 §6.4 when revalidated against the manifest fileList.
|
||||
pack.files.pop().expect("non-empty pack");
|
||||
let bytes = pack.encode().expect("encode pack");
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &bytes)
|
||||
.expect("overwrite fetch_cache_pp");
|
||||
|
||||
// Force cache path: remove raw manifest so fresh processing fails at §6.2.
|
||||
store
|
||||
.delete_raw(&manifest_rsync_uri)
|
||||
.expect("delete raw manifest");
|
||||
|
||||
let err = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect_err("cache pack missing file must be rejected");
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("cached fetch_cache_pp missing file"), "{msg}");
|
||||
assert!(msg.contains("RFC 9286 §6.4"), "{msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_pack_revalidation_rejects_hash_mismatch_against_manifest_filelist() {
|
||||
let (manifest_path, manifest_bytes, manifest) = load_cernet_manifest_fixture();
|
||||
let validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
||||
|
||||
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_raw_publication_point_files(
|
||||
&store,
|
||||
&manifest_path,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
&manifest,
|
||||
&publication_point_rsync_uri,
|
||||
);
|
||||
|
||||
let mut policy = Policy::default();
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp;
|
||||
let issuer_ca_der = issuer_ca_fixture();
|
||||
|
||||
let _fresh = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("fresh run stores fetch_cache_pp");
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack");
|
||||
|
||||
// Mutate one file but keep pack internally consistent by recomputing its sha256 field.
|
||||
let victim = pack.files.first_mut().expect("non-empty pack");
|
||||
victim.bytes[0] ^= 0xFF;
|
||||
victim.sha256 = victim.compute_sha256();
|
||||
let bytes = pack.encode().expect("encode pack");
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &bytes)
|
||||
.expect("overwrite fetch_cache_pp");
|
||||
|
||||
// Force cache path.
|
||||
store
|
||||
.delete_raw(&manifest_rsync_uri)
|
||||
.expect("delete raw manifest");
|
||||
|
||||
let err = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect_err("cache pack hash mismatch must be rejected");
|
||||
let msg = err.to_string();
|
||||
assert!(
|
||||
msg.contains("cached fetch_cache_pp file hash mismatch"),
|
||||
"{msg}"
|
||||
);
|
||||
assert!(msg.contains("RFC 9286 §6.5"), "{msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_sync_failure_forces_fetch_cache_pp_even_if_raw_objects_are_present() {
|
||||
let (manifest_path, manifest_bytes, manifest) = load_cernet_manifest_fixture();
|
||||
let validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
||||
|
||||
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_raw_publication_point_files(
|
||||
&store,
|
||||
&manifest_path,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
&manifest,
|
||||
&publication_point_rsync_uri,
|
||||
);
|
||||
|
||||
let mut policy = Policy::default();
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp;
|
||||
let issuer_ca_der = issuer_ca_fixture();
|
||||
|
||||
// First run: fresh processing stores fetch_cache_pp.
|
||||
let _fresh = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("fresh run stores fetch_cache_pp");
|
||||
|
||||
// Second run: simulate repo sync failure. Even though raw_objects still contain everything
|
||||
// needed for a fresh pack, failed fetch semantics require using cached objects only.
|
||||
let res = process_manifest_publication_point_after_repo_sync(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
false,
|
||||
Some("synthetic repo sync failure"),
|
||||
)
|
||||
.expect("must fall back to fetch_cache_pp");
|
||||
|
||||
assert_eq!(res.source, PublicationPointSource::FetchCachePp);
|
||||
assert!(
|
||||
res.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("using fetch_cache_pp")),
|
||||
"expected fetch_cache_pp warning"
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use rpki::validation::from_tal::{
|
||||
discover_root_ca_instance_from_tal_and_ta_der, discover_root_ca_instance_from_tal_url,
|
||||
run_root_from_tal_url_once,
|
||||
};
|
||||
use rpki::validation::manifest::PublicationPointSource;
|
||||
use url::Url;
|
||||
|
||||
struct MapFetcher {
|
||||
@@ -42,6 +43,26 @@ impl RsyncFetcher for EmptyRsync {
|
||||
}
|
||||
}
|
||||
|
||||
fn openssl_available() -> bool {
|
||||
std::process::Command::new("openssl")
|
||||
.arg("version")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn run(cmd: &mut std::process::Command) {
|
||||
let out = cmd.output().expect("run command");
|
||||
if !out.status.success() {
|
||||
panic!(
|
||||
"command failed: {:?}\nstdout={}\nstderr={}",
|
||||
cmd,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn apnic_tal_bytes() -> Vec<u8> {
|
||||
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic TAL fixture")
|
||||
}
|
||||
@@ -145,7 +166,7 @@ fn discover_root_from_tal_url_errors_when_tal_fetch_fails() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_root_from_tal_url_once_propagates_run_error_when_repo_is_empty() {
|
||||
fn run_root_from_tal_url_once_returns_failed_fetch_no_cache_when_repo_is_empty() {
|
||||
let tal_bytes = apnic_tal_bytes();
|
||||
let tal = Tal::decode_bytes(&tal_bytes).expect("decode TAL");
|
||||
let ta_uri = tal.ta_uris[0].as_str().to_string();
|
||||
@@ -161,7 +182,7 @@ fn run_root_from_tal_url_once_propagates_run_error_when_repo_is_empty() {
|
||||
let mut policy = Policy::default();
|
||||
policy.sync_preference = SyncPreference::RsyncOnly;
|
||||
|
||||
let err = run_root_from_tal_url_once(
|
||||
let out = run_root_from_tal_url_once(
|
||||
&store,
|
||||
&policy,
|
||||
"https://example.test/apnic.tal",
|
||||
@@ -169,7 +190,105 @@ fn run_root_from_tal_url_once_propagates_run_error_when_repo_is_empty() {
|
||||
&EmptyRsync,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.unwrap_err();
|
||||
.expect("run should return failed-fetch-no-cache output");
|
||||
|
||||
assert!(matches!(err, FromTalError::Run(_)));
|
||||
assert_eq!(
|
||||
out.run.publication_point_source,
|
||||
PublicationPointSource::FailedFetchNoCache
|
||||
);
|
||||
assert!(out.run.objects.vrps.is_empty());
|
||||
assert!(out.run.objects.aspas.is_empty());
|
||||
assert!(
|
||||
out.run
|
||||
.publication_point_warnings
|
||||
.iter()
|
||||
.any(|warning| warning.message.contains("no latest validated result"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discover_root_records_ca_instance_discovery_failure_when_ta_lacks_sia() {
|
||||
use base64::Engine;
|
||||
|
||||
assert!(openssl_available(), "openssl is required for this test");
|
||||
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let dir = temp.path();
|
||||
std::fs::write(
|
||||
dir.join("openssl.cnf"),
|
||||
r#"
|
||||
[ req ]
|
||||
prompt = no
|
||||
distinguished_name = dn
|
||||
x509_extensions = v3_ta
|
||||
|
||||
[ dn ]
|
||||
CN = Test TA Without SIA
|
||||
|
||||
[ v3_ta ]
|
||||
basicConstraints = critical,CA:true
|
||||
keyUsage = critical, keyCertSign, cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
certificatePolicies = critical, 1.3.6.1.5.5.7.14.2
|
||||
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/8
|
||||
sbgp-autonomousSysNum = critical, AS:64496-64511
|
||||
"#,
|
||||
)
|
||||
.expect("write openssl cnf");
|
||||
|
||||
run(std::process::Command::new("openssl")
|
||||
.arg("genrsa")
|
||||
.arg("-out")
|
||||
.arg(dir.join("ta.key"))
|
||||
.arg("2048"));
|
||||
run(std::process::Command::new("openssl")
|
||||
.arg("req")
|
||||
.arg("-new")
|
||||
.arg("-x509")
|
||||
.arg("-sha256")
|
||||
.arg("-days")
|
||||
.arg("365")
|
||||
.arg("-key")
|
||||
.arg(dir.join("ta.key"))
|
||||
.arg("-config")
|
||||
.arg(dir.join("openssl.cnf"))
|
||||
.arg("-extensions")
|
||||
.arg("v3_ta")
|
||||
.arg("-out")
|
||||
.arg(dir.join("ta.pem")));
|
||||
run(std::process::Command::new("openssl")
|
||||
.arg("x509")
|
||||
.arg("-in")
|
||||
.arg(dir.join("ta.pem"))
|
||||
.arg("-outform")
|
||||
.arg("DER")
|
||||
.arg("-out")
|
||||
.arg(dir.join("ta.cer")));
|
||||
run(std::process::Command::new("sh").arg("-c").arg(format!(
|
||||
"openssl x509 -in {} -pubkey -noout | openssl pkey -pubin -outform DER > {}",
|
||||
dir.join("ta.pem").display(),
|
||||
dir.join("spki.der").display(),
|
||||
)));
|
||||
|
||||
let ta_uri = "https://example.test/no-sia-ta.cer";
|
||||
let tal_text = format!(
|
||||
"{ta_uri}\n\n{}\n",
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.encode(std::fs::read(dir.join("spki.der")).expect("read spki der"))
|
||||
);
|
||||
let tal = Tal::decode_bytes(tal_text.as_bytes()).expect("decode generated tal");
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(
|
||||
ta_uri.to_string(),
|
||||
std::fs::read(dir.join("ta.cer")).expect("read ta der"),
|
||||
);
|
||||
let fetcher = MapFetcher::new(map);
|
||||
|
||||
let err = discover_root_ca_instance_from_tal(&fetcher, tal, None).unwrap_err();
|
||||
assert!(matches!(err, FromTalError::TaFetch(_)), "{err}");
|
||||
assert!(
|
||||
err.to_string().contains("CA instance discovery failed"),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use std::path::Path;
|
||||
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy};
|
||||
use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore};
|
||||
use rpki::storage::RocksStore;
|
||||
use rpki::validation::manifest::process_manifest_publication_point;
|
||||
|
||||
fn issuer_ca_fixture() -> Vec<u8> {
|
||||
@@ -16,35 +13,13 @@ fn issuer_ca_rsync_uri() -> &'static str {
|
||||
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_is_not_used_when_missing_and_fresh_manifest_is_missing() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
|
||||
let mut policy = Policy::default();
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp;
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::ReuseCurrentInstanceVcir;
|
||||
let issuer_ca_der = issuer_ca_fixture();
|
||||
|
||||
let err = process_manifest_publication_point(
|
||||
@@ -56,89 +31,15 @@ fn cache_is_not_used_when_missing_and_fresh_manifest_is_missing() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
time::OffsetDateTime::from_unix_timestamp(0).unwrap(),
|
||||
)
|
||||
.expect_err("no raw and no fetch_cache_pp should fail");
|
||||
|
||||
assert!(err.to_string().contains("fetch_cache_pp entry missing"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_pack_publication_point_mismatch_is_rejected() {
|
||||
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 validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
||||
|
||||
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");
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
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 issuer_ca_der = issuer_ca_fixture();
|
||||
let _ = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores fetch_cache_pp pack");
|
||||
|
||||
// Corrupt the cached pack by changing the publication point.
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut pack = FetchCachePpPack::decode(&bytes).expect("decode pack");
|
||||
pack.publication_point_rsync_uri = "rsync://evil.invalid/repo/".to_string();
|
||||
let bytes = pack.encode().expect("re-encode pack");
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &bytes)
|
||||
.expect("overwrite fetch_cache_pp");
|
||||
|
||||
// Remove raw manifest to force cache path.
|
||||
store
|
||||
.delete_raw(&manifest_rsync_uri)
|
||||
.expect("delete raw manifest");
|
||||
|
||||
let err = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect_err("cache pack mismatch should fail");
|
||||
.expect_err("no raw and no current-instance VCIR should fail");
|
||||
|
||||
let msg = err.to_string();
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("publication_point_rsync_uri does not match expected")
|
||||
msg.contains("no reusable current-instance validated result is available"),
|
||||
"{msg}"
|
||||
);
|
||||
assert!(
|
||||
msg.contains("latest current-instance VCIR missing"),
|
||||
"{msg}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use std::path::Path;
|
||||
|
||||
use sha2::Digest;
|
||||
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy};
|
||||
use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore};
|
||||
use rpki::storage::{
|
||||
PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
|
||||
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
|
||||
VcirInstanceGate, VcirRelatedArtifact, VcirSummary,
|
||||
};
|
||||
use rpki::validation::manifest::{PublicationPointSource, process_manifest_publication_point};
|
||||
|
||||
fn issuer_ca_fixture() -> Vec<u8> {
|
||||
@@ -38,8 +44,78 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
fn store_validated_manifest_baseline(
|
||||
store: &RocksStore,
|
||||
manifest_rsync_uri: &str,
|
||||
manifest_bytes: &[u8],
|
||||
manifest_number_be: Vec<u8>,
|
||||
this_update: time::OffsetDateTime,
|
||||
next_update: time::OffsetDateTime,
|
||||
) {
|
||||
let manifest_sha256 = hex::encode(sha2::Sha256::digest(manifest_bytes));
|
||||
let mut manifest_raw =
|
||||
RawByHashEntry::from_bytes(manifest_sha256.clone(), manifest_bytes.to_vec());
|
||||
manifest_raw
|
||||
.origin_uris
|
||||
.push(manifest_rsync_uri.to_string());
|
||||
manifest_raw.object_type = Some("mft".to_string());
|
||||
manifest_raw.encoding = Some("der".to_string());
|
||||
store
|
||||
.put_raw_by_hash_entry(&manifest_raw)
|
||||
.expect("store VCIR manifest raw_by_hash");
|
||||
|
||||
let vcir = ValidatedCaInstanceResult {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
tal_id: "test-tal".to_string(),
|
||||
ca_subject_name: "CN=test".to_string(),
|
||||
ca_ski: "aa".to_string(),
|
||||
issuer_ski: "aa".to_string(),
|
||||
last_successful_validation_time: PackTime::from_utc_offset_datetime(this_update),
|
||||
current_manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
current_crl_rsync_uri: format!("{manifest_rsync_uri}.crl"),
|
||||
validated_manifest_meta: ValidatedManifestMeta {
|
||||
validated_manifest_number: manifest_number_be,
|
||||
validated_manifest_this_update: PackTime::from_utc_offset_datetime(this_update),
|
||||
validated_manifest_next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
},
|
||||
instance_gate: VcirInstanceGate {
|
||||
manifest_next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
current_crl_next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
self_ca_not_after: PackTime::from_utc_offset_datetime(next_update),
|
||||
instance_effective_until: PackTime::from_utc_offset_datetime(next_update),
|
||||
},
|
||||
child_entries: Vec::new(),
|
||||
local_outputs: Vec::new(),
|
||||
related_artifacts: vec![VcirRelatedArtifact {
|
||||
artifact_role: VcirArtifactRole::Manifest,
|
||||
artifact_kind: VcirArtifactKind::Mft,
|
||||
uri: Some(manifest_rsync_uri.to_string()),
|
||||
sha256: manifest_sha256,
|
||||
object_type: Some("mft".to_string()),
|
||||
validation_status: VcirArtifactValidationStatus::Accepted,
|
||||
}],
|
||||
summary: VcirSummary {
|
||||
local_vrp_count: 0,
|
||||
local_aspa_count: 0,
|
||||
child_count: 0,
|
||||
accepted_object_count: 1,
|
||||
rejected_object_count: 0,
|
||||
},
|
||||
audit_summary: VcirAuditSummary {
|
||||
failed_fetch_eligible: true,
|
||||
last_failed_fetch_reason: None,
|
||||
warning_count: 0,
|
||||
audit_flags: Vec::new(),
|
||||
},
|
||||
};
|
||||
store
|
||||
.put_vcir(&vcir)
|
||||
.expect("store validated manifest baseline");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_success_writes_fetch_cache_pp_pack() {
|
||||
fn manifest_success_returns_validated_publication_point_data() {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -87,21 +163,15 @@ fn manifest_success_writes_fetch_cache_pp_pack() {
|
||||
assert_eq!(out.source, PublicationPointSource::Fresh);
|
||||
assert!(out.warnings.is_empty());
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let stored = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp pack exists");
|
||||
let decoded = FetchCachePpPack::decode(&stored).expect("decode stored pack");
|
||||
assert_eq!(decoded.manifest_rsync_uri, manifest_rsync_uri);
|
||||
assert_eq!(out.snapshot.manifest_rsync_uri, manifest_rsync_uri);
|
||||
assert_eq!(
|
||||
decoded.publication_point_rsync_uri,
|
||||
out.snapshot.publication_point_rsync_uri,
|
||||
publication_point_rsync_uri
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_hash_mismatch_falls_back_to_fetch_cache_pp_when_enabled() {
|
||||
fn manifest_hash_mismatch_reuses_current_instance_vcir_when_enabled() {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -144,15 +214,17 @@ fn manifest_hash_mismatch_falls_back_to_fetch_cache_pp_when_enabled() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores fetch_cache_pp pack");
|
||||
.expect("first run returns validated publication point");
|
||||
assert_eq!(first.source, PublicationPointSource::Fresh);
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp pack exists");
|
||||
let cached_pack = FetchCachePpPack::decode(&cached_bytes).expect("decode cached");
|
||||
store_validated_manifest_baseline(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
manifest.manifest.manifest_number.bytes_be.clone(),
|
||||
manifest.manifest.this_update,
|
||||
manifest.manifest.next_update,
|
||||
);
|
||||
|
||||
let entries = manifest
|
||||
.manifest
|
||||
@@ -176,10 +248,14 @@ fn manifest_hash_mismatch_falls_back_to_fetch_cache_pp_when_enabled() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("second run falls back to fetch_cache_pp");
|
||||
assert_eq!(second.source, PublicationPointSource::FetchCachePp);
|
||||
assert!(!second.warnings.is_empty());
|
||||
assert_eq!(second.pack, cached_pack);
|
||||
.expect("second run reuses current-instance VCIR");
|
||||
assert_eq!(second.source, PublicationPointSource::VcirCurrentInstance);
|
||||
assert!(
|
||||
second.warnings.iter().any(|w| w
|
||||
.message
|
||||
.contains("using latest validated result for current CA instance")),
|
||||
"expected current-instance VCIR reuse warning"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -216,7 +292,7 @@ fn manifest_failed_fetch_stop_all_output() {
|
||||
}
|
||||
|
||||
let mut policy = Policy::default();
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::UseFetchCachePp;
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::ReuseCurrentInstanceVcir;
|
||||
let issuer_ca_der = issuer_ca_fixture();
|
||||
let _ = process_manifest_publication_point(
|
||||
&store,
|
||||
@@ -227,7 +303,7 @@ fn manifest_failed_fetch_stop_all_output() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores fetch_cache_pp pack");
|
||||
.expect("first run returns validated publication point");
|
||||
|
||||
let entries = manifest
|
||||
.manifest
|
||||
@@ -252,13 +328,13 @@ fn manifest_failed_fetch_stop_all_output() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect_err("stop_all_output should not use fetch_cache_pp");
|
||||
.expect_err("stop_all_output should not reuse current-instance VCIR");
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("cache use is disabled"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_fallback_pack_is_revalidated_and_rejected_if_stale() {
|
||||
fn manifest_failed_fetch_rejects_stale_current_instance_vcir() {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -302,7 +378,16 @@ fn manifest_fallback_pack_is_revalidated_and_rejected_if_stale() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
ok_time,
|
||||
)
|
||||
.expect("first run stores fetch_cache_pp pack");
|
||||
.expect("first run returns validated publication point");
|
||||
|
||||
store_validated_manifest_baseline(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
manifest.manifest.manifest_number.bytes_be.clone(),
|
||||
manifest.manifest.this_update,
|
||||
manifest.manifest.next_update,
|
||||
);
|
||||
|
||||
store
|
||||
.delete_raw(&manifest_rsync_uri)
|
||||
@@ -317,9 +402,9 @@ fn manifest_fallback_pack_is_revalidated_and_rejected_if_stale() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
stale_time,
|
||||
)
|
||||
.expect_err("stale validation_time must reject fetch_cache_pp pack");
|
||||
.expect_err("stale validation_time must reject current-instance VCIR reuse");
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("not valid at validation_time"));
|
||||
assert!(msg.contains("instance_gate expired"), "{msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -369,9 +454,18 @@ fn manifest_revalidation_with_unchanged_manifest_is_fresh() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
t1,
|
||||
)
|
||||
.expect("first run builds and stores fetch_cache_pp pack");
|
||||
.expect("first run returns validated publication point");
|
||||
assert_eq!(first.source, PublicationPointSource::Fresh);
|
||||
|
||||
store_validated_manifest_baseline(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
first.snapshot.manifest_number_be.clone(),
|
||||
manifest.manifest.this_update,
|
||||
manifest.manifest.next_update,
|
||||
);
|
||||
|
||||
let second = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
@@ -384,16 +478,19 @@ fn manifest_revalidation_with_unchanged_manifest_is_fresh() {
|
||||
.expect("second run should accept revalidation of the same manifest");
|
||||
assert_eq!(second.source, PublicationPointSource::Fresh);
|
||||
assert!(second.warnings.is_empty());
|
||||
assert_eq!(second.pack.manifest_bytes, first.pack.manifest_bytes);
|
||||
assert_eq!(
|
||||
second.pack.manifest_number_be,
|
||||
first.pack.manifest_number_be
|
||||
second.snapshot.manifest_bytes,
|
||||
first.snapshot.manifest_bytes
|
||||
);
|
||||
assert_eq!(second.pack.files, first.pack.files);
|
||||
assert_eq!(
|
||||
second.snapshot.manifest_number_be,
|
||||
first.snapshot.manifest_number_be
|
||||
);
|
||||
assert_eq!(second.snapshot.files, first.snapshot.files);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_rollback_is_treated_as_failed_fetch_and_uses_fetch_cache_pp() {
|
||||
fn manifest_rollback_is_treated_as_failed_fetch_and_reuses_current_instance_vcir() {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -439,16 +536,11 @@ fn manifest_rollback_is_treated_as_failed_fetch_and_uses_fetch_cache_pp() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
t1,
|
||||
)
|
||||
.expect("first run builds and stores fetch_cache_pp pack");
|
||||
.expect("first run returns validated publication point");
|
||||
assert_eq!(first.source, PublicationPointSource::Fresh);
|
||||
|
||||
// Simulate a previously validated manifest with a higher manifestNumber (rollback detection).
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let stored = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp pack exists");
|
||||
let mut bumped = FetchCachePpPack::decode(&stored).expect("decode stored pack");
|
||||
let mut bumped = first.snapshot.clone();
|
||||
// Deterministically bump the cached manifestNumber to be strictly greater than the current one.
|
||||
for i in (0..bumped.manifest_number_be.len()).rev() {
|
||||
let (v, carry) = bumped.manifest_number_be[i].overflowing_add(1);
|
||||
@@ -461,10 +553,14 @@ fn manifest_rollback_is_treated_as_failed_fetch_and_uses_fetch_cache_pp() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let bumped_bytes = bumped.encode().expect("encode bumped pack");
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &bumped_bytes)
|
||||
.expect("store bumped pack");
|
||||
store_validated_manifest_baseline(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
bumped.manifest_number_be.clone(),
|
||||
manifest.manifest.this_update,
|
||||
manifest.manifest.next_update,
|
||||
);
|
||||
|
||||
let second = process_manifest_publication_point(
|
||||
&store,
|
||||
@@ -475,9 +571,8 @@ fn manifest_rollback_is_treated_as_failed_fetch_and_uses_fetch_cache_pp() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
t2,
|
||||
)
|
||||
.expect("second run should treat rollback as failed fetch and use cache");
|
||||
assert_eq!(second.source, PublicationPointSource::FetchCachePp);
|
||||
assert_eq!(second.pack, bumped);
|
||||
.expect("second run should treat rollback as failed fetch and reuse current-instance VCIR");
|
||||
assert_eq!(second.source, PublicationPointSource::VcirCurrentInstance);
|
||||
assert!(
|
||||
second
|
||||
.warnings
|
||||
|
||||
@@ -90,7 +90,7 @@ fn manifest_outside_publication_point_yields_no_usable_cache() {
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
// With no cached pack available for this wrong publication point, we get NoUsableCache.
|
||||
// With no reusable current-instance snapshot/VCIR for this wrong publication point, we get NoUsableCache.
|
||||
assert!(
|
||||
matches!(err, ManifestProcessError::NoUsableCache { .. }),
|
||||
"{err}"
|
||||
@@ -98,7 +98,7 @@ fn manifest_outside_publication_point_yields_no_usable_cache() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_outside_publication_point_detects_cached_pack_pp_mismatch() {
|
||||
fn manifest_outside_publication_point_detects_current_instance_snapshot_pp_mismatch() {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -133,7 +133,7 @@ fn manifest_outside_publication_point_detects_cached_pack_pp_mismatch() {
|
||||
let policy = Policy::default();
|
||||
let issuer_ca_der = issuer_ca_fixture_der();
|
||||
|
||||
// First run creates and stores fetch_cache_pp pack (Fresh).
|
||||
// First run validates a fresh publication point snapshot and stores the latest VCIR.
|
||||
let first = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
@@ -147,7 +147,7 @@ fn manifest_outside_publication_point_detects_cached_pack_pp_mismatch() {
|
||||
assert_eq!(first.source, PublicationPointSource::Fresh);
|
||||
|
||||
// Second run with wrong publication point: fresh fails outside PP; cache load also fails
|
||||
// because the cached pack's publication_point_rsync_uri doesn't match the expected one.
|
||||
// because the reusable current-instance snapshot carries a different publication_point_rsync_uri.
|
||||
let wrong_pp = "rsync://example.test/not-the-pp/";
|
||||
let err = process_manifest_publication_point(
|
||||
&store,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use std::path::Path;
|
||||
|
||||
use sha2::Digest;
|
||||
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy};
|
||||
use rpki::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore};
|
||||
use rpki::storage::{
|
||||
PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
|
||||
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
|
||||
VcirInstanceGate, VcirRelatedArtifact, VcirSummary,
|
||||
};
|
||||
use rpki::validation::manifest::{
|
||||
ManifestProcessError, PublicationPointSource, process_manifest_publication_point,
|
||||
process_manifest_publication_point_after_repo_sync,
|
||||
@@ -41,6 +47,76 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
fn store_validated_manifest_baseline(
|
||||
store: &RocksStore,
|
||||
manifest_rsync_uri: &str,
|
||||
manifest_bytes: &[u8],
|
||||
manifest_number_be: Vec<u8>,
|
||||
this_update: time::OffsetDateTime,
|
||||
next_update: time::OffsetDateTime,
|
||||
) {
|
||||
let manifest_sha256 = hex::encode(sha2::Sha256::digest(manifest_bytes));
|
||||
let mut manifest_raw =
|
||||
RawByHashEntry::from_bytes(manifest_sha256.clone(), manifest_bytes.to_vec());
|
||||
manifest_raw
|
||||
.origin_uris
|
||||
.push(manifest_rsync_uri.to_string());
|
||||
manifest_raw.object_type = Some("mft".to_string());
|
||||
manifest_raw.encoding = Some("der".to_string());
|
||||
store
|
||||
.put_raw_by_hash_entry(&manifest_raw)
|
||||
.expect("store VCIR manifest raw_by_hash");
|
||||
|
||||
let vcir = ValidatedCaInstanceResult {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
tal_id: "test-tal".to_string(),
|
||||
ca_subject_name: "CN=test".to_string(),
|
||||
ca_ski: "aa".to_string(),
|
||||
issuer_ski: "aa".to_string(),
|
||||
last_successful_validation_time: PackTime::from_utc_offset_datetime(this_update),
|
||||
current_manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
current_crl_rsync_uri: format!("{manifest_rsync_uri}.crl"),
|
||||
validated_manifest_meta: ValidatedManifestMeta {
|
||||
validated_manifest_number: manifest_number_be,
|
||||
validated_manifest_this_update: PackTime::from_utc_offset_datetime(this_update),
|
||||
validated_manifest_next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
},
|
||||
instance_gate: VcirInstanceGate {
|
||||
manifest_next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
current_crl_next_update: PackTime::from_utc_offset_datetime(next_update),
|
||||
self_ca_not_after: PackTime::from_utc_offset_datetime(next_update),
|
||||
instance_effective_until: PackTime::from_utc_offset_datetime(next_update),
|
||||
},
|
||||
child_entries: Vec::new(),
|
||||
local_outputs: Vec::new(),
|
||||
related_artifacts: vec![VcirRelatedArtifact {
|
||||
artifact_role: VcirArtifactRole::Manifest,
|
||||
artifact_kind: VcirArtifactKind::Mft,
|
||||
uri: Some(manifest_rsync_uri.to_string()),
|
||||
sha256: manifest_sha256,
|
||||
object_type: Some("mft".to_string()),
|
||||
validation_status: VcirArtifactValidationStatus::Accepted,
|
||||
}],
|
||||
summary: VcirSummary {
|
||||
local_vrp_count: 0,
|
||||
local_aspa_count: 0,
|
||||
child_count: 0,
|
||||
accepted_object_count: 1,
|
||||
rejected_object_count: 0,
|
||||
},
|
||||
audit_summary: VcirAuditSummary {
|
||||
failed_fetch_eligible: true,
|
||||
last_failed_fetch_reason: None,
|
||||
warning_count: 0,
|
||||
audit_flags: Vec::new(),
|
||||
},
|
||||
};
|
||||
store
|
||||
.put_vcir(&vcir)
|
||||
.expect("store validated manifest baseline");
|
||||
}
|
||||
|
||||
fn store_manifest_and_locked_files(
|
||||
store: &RocksStore,
|
||||
manifest_path: &Path,
|
||||
@@ -70,7 +146,7 @@ fn store_manifest_and_locked_files(
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_sync_failed_can_fall_back_to_fetch_cache_pp_when_present() {
|
||||
fn repo_sync_failed_can_reuse_current_instance_vcir_when_present() {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -94,7 +170,7 @@ fn repo_sync_failed_can_fall_back_to_fetch_cache_pp_when_present() {
|
||||
|
||||
let issuer_ca_der = issuer_ca_fixture_der();
|
||||
|
||||
// First run: build and store a valid fetch_cache_pp pack (Fresh).
|
||||
// First run: build a fresh publication point result, then seed a current-instance VCIR baseline.
|
||||
let policy = Policy::default();
|
||||
let first = process_manifest_publication_point(
|
||||
&store,
|
||||
@@ -108,7 +184,16 @@ fn repo_sync_failed_can_fall_back_to_fetch_cache_pp_when_present() {
|
||||
.expect("first run ok");
|
||||
assert_eq!(first.source, PublicationPointSource::Fresh);
|
||||
|
||||
// Second run: simulate RRDP/rsync repo sync failure and ensure we still accept the cached pack.
|
||||
store_validated_manifest_baseline(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
manifest.manifest.manifest_number.bytes_be.clone(),
|
||||
manifest.manifest.this_update,
|
||||
manifest.manifest.next_update,
|
||||
);
|
||||
|
||||
// Seed a latest current-instance VCIR baseline, then simulate RRDP/rsync repo sync failure.
|
||||
let second = process_manifest_publication_point_after_repo_sync(
|
||||
&store,
|
||||
&policy,
|
||||
@@ -120,83 +205,13 @@ fn repo_sync_failed_can_fall_back_to_fetch_cache_pp_when_present() {
|
||||
false,
|
||||
Some("repo sync failed in test"),
|
||||
)
|
||||
.expect("repo sync failure should fall back to fetch_cache_pp");
|
||||
assert_eq!(second.source, PublicationPointSource::FetchCachePp);
|
||||
assert_eq!(second.pack, first.pack);
|
||||
assert!(!second.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_pack_manifest_rsync_uri_mismatch_is_rejected_as_invalid_pack() {
|
||||
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 validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
||||
|
||||
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_manifest_and_locked_files(
|
||||
&store,
|
||||
manifest_path,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&manifest,
|
||||
&manifest_bytes,
|
||||
);
|
||||
|
||||
let issuer_ca_der = issuer_ca_fixture_der();
|
||||
let policy = Policy::default();
|
||||
|
||||
// First run stores a valid pack.
|
||||
let _ = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores pack");
|
||||
|
||||
// Corrupt cached pack metadata: manifest_rsync_uri doesn't match the key.
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack");
|
||||
pack.manifest_rsync_uri = "rsync://example.test/wrong.mft".to_string();
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &pack.encode().expect("encode pack"))
|
||||
.expect("store corrupted pack");
|
||||
|
||||
// Force fresh failure and trigger cache load.
|
||||
let err = process_manifest_publication_point_after_repo_sync(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
false,
|
||||
Some("repo sync failed in test"),
|
||||
)
|
||||
.unwrap_err();
|
||||
.expect("repo sync failure should reuse current-instance VCIR");
|
||||
assert_eq!(second.source, PublicationPointSource::VcirCurrentInstance);
|
||||
assert!(
|
||||
matches!(err, ManifestProcessError::NoUsableCache { .. }),
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("cached pack manifest_rsync_uri does not match key"),
|
||||
"unexpected error: {err}"
|
||||
second.warnings.iter().any(|w| w
|
||||
.message
|
||||
.contains("using latest validated result for current CA instance")),
|
||||
"expected current-instance VCIR reuse warning"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -269,7 +284,7 @@ fn manifest_missing_locked_file_is_treated_as_failed_fetch() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_number_increases_but_this_update_not_increasing_is_failed_fetch() {
|
||||
fn manifest_number_increases_but_this_update_not_increasing_reuses_current_instance_vcir() {
|
||||
let manifest_path = Path::new(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -294,7 +309,7 @@ fn manifest_number_increases_but_this_update_not_increasing_is_failed_fetch() {
|
||||
let issuer_ca_der = issuer_ca_fixture_der();
|
||||
let policy = Policy::default();
|
||||
|
||||
// Build and store a valid pack first.
|
||||
// Build and store a valid publication point snapshot first.
|
||||
let _ = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
@@ -304,23 +319,18 @@ fn manifest_number_increases_but_this_update_not_increasing_is_failed_fetch() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores pack");
|
||||
.expect("first run returns validated publication point");
|
||||
|
||||
// Replace the cached pack with an "older" manifestNumber but a newer thisUpdate to trigger
|
||||
// Seed VCIR with an older manifestNumber but a newer thisUpdate to trigger
|
||||
// RFC 9286 §4.2.1 thisUpdate monotonicity failure on the fresh path.
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut old_pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack");
|
||||
old_pack.manifest_number_be = vec![0];
|
||||
old_pack.this_update = rpki::storage::PackTime::from_utc_offset_datetime(
|
||||
store_validated_manifest_baseline(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&manifest_bytes,
|
||||
vec![0],
|
||||
manifest.manifest.this_update + time::Duration::hours(24),
|
||||
manifest.manifest.next_update,
|
||||
);
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &old_pack.encode().expect("encode pack"))
|
||||
.expect("store adjusted pack");
|
||||
|
||||
let out = process_manifest_publication_point(
|
||||
&store,
|
||||
@@ -331,8 +341,8 @@ fn manifest_number_increases_but_this_update_not_increasing_is_failed_fetch() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("should fall back to fetch_cache_pp");
|
||||
assert_eq!(out.source, PublicationPointSource::FetchCachePp);
|
||||
.expect("should reuse current-instance VCIR");
|
||||
assert_eq!(out.source, PublicationPointSource::VcirCurrentInstance);
|
||||
assert!(
|
||||
out.warnings
|
||||
.iter()
|
||||
@@ -368,7 +378,7 @@ fn manifest_number_equal_but_bytes_differ_is_rejected_without_cache() {
|
||||
let mut policy = Policy::default();
|
||||
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::StopAllOutput;
|
||||
|
||||
// Store a cached pack that has the same manifestNumber but different manifest bytes.
|
||||
// Store a cached snapshot that has the same manifestNumber but different manifest bytes.
|
||||
let _ = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
@@ -378,18 +388,18 @@ fn manifest_number_equal_but_bytes_differ_is_rejected_without_cache() {
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores pack");
|
||||
.expect("first run returns validated publication point");
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack");
|
||||
pack.manifest_bytes[0] ^= 0xFF;
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &pack.encode().expect("encode pack"))
|
||||
.expect("store adjusted pack");
|
||||
let mut mutated_manifest_bytes = manifest_bytes.clone();
|
||||
mutated_manifest_bytes[0] ^= 0xFF;
|
||||
store_validated_manifest_baseline(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&mutated_manifest_bytes,
|
||||
manifest.manifest.manifest_number.bytes_be.clone(),
|
||||
manifest.manifest.this_update,
|
||||
manifest.manifest.next_update,
|
||||
);
|
||||
|
||||
let err = process_manifest_publication_point(
|
||||
&store,
|
||||
@@ -455,154 +465,3 @@ fn manifest_embedded_ee_cert_path_validation_fails_with_wrong_issuer_ca() {
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_pack_missing_file_is_rejected_during_revalidation() {
|
||||
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 validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
||||
|
||||
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_manifest_and_locked_files(
|
||||
&store,
|
||||
manifest_path,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&manifest,
|
||||
&manifest_bytes,
|
||||
);
|
||||
|
||||
let issuer_ca_der = issuer_ca_fixture_der();
|
||||
let policy = Policy::default();
|
||||
|
||||
// Store a valid pack first.
|
||||
let _ = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores pack");
|
||||
|
||||
// Corrupt cached pack by removing one referenced file.
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack");
|
||||
assert!(!pack.files.is_empty(), "fixture should lock some files");
|
||||
pack.files.pop();
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &pack.encode().expect("encode pack"))
|
||||
.expect("store corrupted pack");
|
||||
|
||||
// Force the fresh path to fail and trigger cache revalidation.
|
||||
let err = process_manifest_publication_point_after_repo_sync(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
false,
|
||||
Some("repo sync failed in test"),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, ManifestProcessError::NoUsableCache { .. }),
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("cached fetch_cache_pp missing file"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_pack_hash_mismatch_is_rejected_during_revalidation() {
|
||||
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 validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
||||
|
||||
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_manifest_and_locked_files(
|
||||
&store,
|
||||
manifest_path,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&manifest,
|
||||
&manifest_bytes,
|
||||
);
|
||||
|
||||
let issuer_ca_der = issuer_ca_fixture_der();
|
||||
let policy = Policy::default();
|
||||
|
||||
// Store a valid pack first.
|
||||
let _ = process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("first run stores pack");
|
||||
|
||||
// Corrupt cached pack by changing one file's bytes+sha256 so internal validation passes,
|
||||
// but the manifest fileList binding check fails (RFC 9286 §6.5).
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(&manifest_rsync_uri);
|
||||
let cached_bytes = store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.expect("fetch_cache_pp exists");
|
||||
let mut pack = FetchCachePpPack::decode(&cached_bytes).expect("decode pack");
|
||||
let victim = pack.files.first_mut().expect("non-empty file list");
|
||||
victim.bytes[0] ^= 0xFF;
|
||||
victim.sha256 = victim.compute_sha256();
|
||||
store
|
||||
.put_fetch_cache_pp(&key, &pack.encode().expect("encode pack"))
|
||||
.expect("store corrupted pack");
|
||||
|
||||
let err = process_manifest_publication_point_after_repo_sync(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&issuer_ca_der,
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
false,
|
||||
Some("repo sync failed in test"),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, ManifestProcessError::NoUsableCache { .. }),
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("cached fetch_cache_pp file hash mismatch"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use std::path::Path;
|
||||
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::policy::{CaFailedFetchPolicy, Policy};
|
||||
use rpki::storage::{FetchCachePpKey, RocksStore};
|
||||
use rpki::storage::RocksStore;
|
||||
use rpki::validation::manifest::process_manifest_publication_point;
|
||||
|
||||
fn issuer_ca_fixture() -> Vec<u8> {
|
||||
@@ -70,12 +70,11 @@ fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() {
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("RFC 9286 §6.1"), "{msg}");
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
assert!(
|
||||
store
|
||||
.get_fetch_cache_pp(&key)
|
||||
.expect("get fetch_cache_pp")
|
||||
.get_vcir(manifest_rsync_uri)
|
||||
.expect("get vcir")
|
||||
.is_none(),
|
||||
"must not write fetch_cache_pp on failed fetch"
|
||||
"must not write VCIR on failed fetch"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ 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;
|
||||
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
|
||||
|
||||
fn fixture_to_rsync_uri(path: &Path) -> String {
|
||||
let rel = path
|
||||
@@ -31,7 +31,7 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
|
||||
}
|
||||
|
||||
fn build_cernet_pack_and_validation_time() -> (
|
||||
rpki::storage::FetchCachePpPack,
|
||||
rpki::validation::publication_point::PublicationPointSnapshot,
|
||||
time::OffsetDateTime,
|
||||
Vec<u8>,
|
||||
ResourceCertificate,
|
||||
@@ -85,11 +85,11 @@ fn build_cernet_pack_and_validation_time() -> (
|
||||
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer CA cert");
|
||||
|
||||
let crl_file = out
|
||||
.pack
|
||||
.snapshot
|
||||
.files
|
||||
.iter()
|
||||
.find(|f| f.rsync_uri.ends_with(".crl"))
|
||||
.expect("crl present in pack");
|
||||
.expect("crl present in snapshot");
|
||||
let crl = RpkixCrl::decode_der(&crl_file.bytes).expect("decode crl");
|
||||
|
||||
let mut t = manifest.manifest.this_update;
|
||||
@@ -101,7 +101,7 @@ fn build_cernet_pack_and_validation_time() -> (
|
||||
}
|
||||
t += time::Duration::seconds(1);
|
||||
|
||||
(out.pack, t, issuer_ca_der, issuer_ca)
|
||||
(out.snapshot, t, issuer_ca_der, issuer_ca)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -113,7 +113,7 @@ fn missing_crl_causes_roas_to_be_dropped_under_drop_object_policy() {
|
||||
let mut policy = Policy::default();
|
||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&issuer_ca_der,
|
||||
@@ -139,7 +139,7 @@ fn wrong_issuer_ca_cert_causes_roas_to_be_dropped_under_drop_object_policy() {
|
||||
// Use an unrelated trust anchor certificate as the issuer to force EE cert path validation to fail.
|
||||
let wrong_issuer_ca_der =
|
||||
std::fs::read("tests/fixtures/ta/arin-ta.cer").expect("read wrong issuer ca");
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&wrong_issuer_ca_der,
|
||||
@@ -167,7 +167,7 @@ fn invalid_aspa_object_is_reported_as_warning_under_drop_object_policy() {
|
||||
let mut policy = Policy::default();
|
||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&issuer_ca_der,
|
||||
|
||||
@@ -6,7 +6,7 @@ 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;
|
||||
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
|
||||
|
||||
fn fixture_to_rsync_uri(path: &Path) -> String {
|
||||
let rel = path
|
||||
@@ -31,7 +31,7 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
|
||||
}
|
||||
|
||||
fn build_cernet_pack_and_validation_time() -> (
|
||||
rpki::storage::FetchCachePpPack,
|
||||
rpki::validation::publication_point::PublicationPointSnapshot,
|
||||
time::OffsetDateTime,
|
||||
Vec<u8>,
|
||||
ResourceCertificate,
|
||||
@@ -85,11 +85,11 @@ fn build_cernet_pack_and_validation_time() -> (
|
||||
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer CA cert");
|
||||
|
||||
let crl_file = out
|
||||
.pack
|
||||
.snapshot
|
||||
.files
|
||||
.iter()
|
||||
.find(|f| f.rsync_uri.ends_with(".crl"))
|
||||
.expect("crl present in pack");
|
||||
.expect("crl present in snapshot");
|
||||
let crl = RpkixCrl::decode_der(&crl_file.bytes).expect("decode crl");
|
||||
|
||||
// Choose a validation_time that is within:
|
||||
@@ -106,7 +106,7 @@ fn build_cernet_pack_and_validation_time() -> (
|
||||
}
|
||||
t += time::Duration::seconds(1);
|
||||
|
||||
(out.pack, t, issuer_ca_der, issuer_ca)
|
||||
(out.snapshot, t, issuer_ca_der, issuer_ca)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -119,13 +119,13 @@ fn drop_object_policy_drops_only_failing_object() {
|
||||
.iter()
|
||||
.find(|f| f.rsync_uri.ends_with("AS4538.roa"))
|
||||
.map(|f| f.rsync_uri.clone())
|
||||
.expect("AS4538.roa present in pack");
|
||||
.expect("AS4538.roa present in snapshot");
|
||||
|
||||
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");
|
||||
.expect("another ROA present in snapshot");
|
||||
let victim_uri = pack.files[tamper_idx].rsync_uri.clone();
|
||||
|
||||
let mut tampered = pack.files[tamper_idx].bytes.clone();
|
||||
@@ -136,7 +136,7 @@ fn drop_object_policy_drops_only_failing_object() {
|
||||
let mut policy = Policy::default();
|
||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&issuer_ca_der,
|
||||
@@ -168,7 +168,7 @@ fn drop_publication_point_policy_drops_the_publication_point() {
|
||||
.files
|
||||
.iter()
|
||||
.position(|f| f.rsync_uri.ends_with(".roa"))
|
||||
.expect("a ROA present in pack");
|
||||
.expect("a ROA present in snapshot");
|
||||
let victim_uri = pack.files[tamper_idx].rsync_uri.clone();
|
||||
|
||||
let mut tampered = pack.files[tamper_idx].bytes.clone();
|
||||
@@ -179,7 +179,7 @@ fn drop_publication_point_policy_drops_the_publication_point() {
|
||||
let mut policy = Policy::default();
|
||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint;
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&issuer_ca_der,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use rpki::fetch::rsync::LocalDirRsyncFetcher;
|
||||
use rpki::policy::{Policy, SignedObjectFailurePolicy, SyncPreference};
|
||||
use rpki::storage::{FetchCachePpPack, PackFile, PackTime, RocksStore};
|
||||
use rpki::storage::{PackFile, PackTime, RocksStore};
|
||||
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_fetch_cache_pp_pack_for_issuer;
|
||||
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
|
||||
use rpki::validation::publication_point::PublicationPointSnapshot;
|
||||
|
||||
struct NoopHttpFetcher;
|
||||
impl Fetcher for NoopHttpFetcher {
|
||||
@@ -50,16 +51,16 @@ fn issuer_ca_rsync_uri() -> &'static str {
|
||||
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"
|
||||
}
|
||||
|
||||
fn minimal_pack(
|
||||
fn minimal_snapshot(
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
manifest_bytes: Vec<u8>,
|
||||
files: Vec<PackFile>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> FetchCachePpPack {
|
||||
// Keep times consistent enough to pass internal pack validation.
|
||||
FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
) -> PublicationPointSnapshot {
|
||||
// Keep times consistent enough for a valid snapshot.
|
||||
PublicationPointSnapshot {
|
||||
format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
@@ -71,12 +72,12 @@ fn minimal_pack(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_fetch_cache_pp_from_local_rsync_fixture(
|
||||
fn build_publication_point_snapshot_from_local_rsync_fixture(
|
||||
dir: &std::path::Path,
|
||||
rsync_base_uri: &str,
|
||||
manifest_rsync_uri: &str,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> rpki::storage::FetchCachePpPack {
|
||||
) -> rpki::validation::publication_point::PublicationPointSnapshot {
|
||||
let store_dir = tempfile::tempdir().expect("store dir");
|
||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
||||
let policy = Policy {
|
||||
@@ -107,16 +108,16 @@ fn build_fetch_cache_pp_from_local_rsync_fixture(
|
||||
)
|
||||
.expect("process manifest");
|
||||
|
||||
pp.pack
|
||||
pp.snapshot
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() {
|
||||
fn process_snapshot_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_fetch_cache_pp_from_local_rsync_fixture(
|
||||
let pack = build_publication_point_snapshot_from_local_rsync_fixture(
|
||||
&dir,
|
||||
&rsync_base_uri,
|
||||
&manifest_rsync_uri,
|
||||
@@ -128,7 +129,7 @@ fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() {
|
||||
.expect("decode issuer ca");
|
||||
|
||||
let policy = Policy::default();
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&issuer_ca_der,
|
||||
@@ -153,7 +154,7 @@ fn signed_object_failure_policy_drop_object_drops_only_bad_object() {
|
||||
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_fetch_cache_pp_from_local_rsync_fixture(
|
||||
let mut pack = build_publication_point_snapshot_from_local_rsync_fixture(
|
||||
&dir,
|
||||
&rsync_base_uri,
|
||||
&manifest_rsync_uri,
|
||||
@@ -171,7 +172,7 @@ fn signed_object_failure_policy_drop_object_drops_only_bad_object() {
|
||||
.files
|
||||
.iter()
|
||||
.position(|f| f.rsync_uri.ends_with(".roa"))
|
||||
.expect("pack contains roa");
|
||||
.expect("snapshot 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]);
|
||||
|
||||
@@ -183,7 +184,7 @@ fn signed_object_failure_policy_drop_object_drops_only_bad_object() {
|
||||
signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
|
||||
..Policy::default()
|
||||
};
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&issuer_ca_der,
|
||||
@@ -216,7 +217,7 @@ fn signed_object_failure_policy_drop_publication_point_drops_all_output() {
|
||||
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_fetch_cache_pp_from_local_rsync_fixture(
|
||||
let mut pack = build_publication_point_snapshot_from_local_rsync_fixture(
|
||||
&dir,
|
||||
&rsync_base_uri,
|
||||
&manifest_rsync_uri,
|
||||
@@ -234,7 +235,7 @@ fn signed_object_failure_policy_drop_publication_point_drops_all_output() {
|
||||
.files
|
||||
.iter()
|
||||
.position(|f| f.rsync_uri.ends_with(".roa"))
|
||||
.expect("pack contains roa");
|
||||
.expect("snapshot 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]);
|
||||
|
||||
@@ -246,7 +247,7 @@ fn signed_object_failure_policy_drop_publication_point_drops_all_output() {
|
||||
signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
|
||||
..Policy::default()
|
||||
};
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&issuer_ca_der,
|
||||
@@ -283,13 +284,13 @@ fn signed_object_failure_policy_drop_publication_point_drops_all_output() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_without_crl_drops_publication_point() {
|
||||
fn process_snapshot_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(
|
||||
let pack = minimal_snapshot(
|
||||
&manifest_rsync_uri,
|
||||
&rsync_base_uri,
|
||||
manifest_bytes,
|
||||
@@ -301,7 +302,7 @@ fn process_pack_for_issuer_without_crl_drops_publication_point() {
|
||||
);
|
||||
|
||||
let policy = Policy::default();
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[],
|
||||
@@ -318,7 +319,7 @@ fn process_pack_for_issuer_without_crl_drops_publication_point() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_handles_invalid_aspa_bytes() {
|
||||
fn process_snapshot_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);
|
||||
@@ -326,7 +327,7 @@ fn process_pack_for_issuer_handles_invalid_aspa_bytes() {
|
||||
let crl_bytes =
|
||||
std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl");
|
||||
|
||||
let pack = minimal_pack(
|
||||
let pack = minimal_snapshot(
|
||||
&manifest_rsync_uri,
|
||||
&rsync_base_uri,
|
||||
manifest_bytes,
|
||||
@@ -344,7 +345,7 @@ fn process_pack_for_issuer_handles_invalid_aspa_bytes() {
|
||||
signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
|
||||
..Policy::default()
|
||||
};
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[],
|
||||
@@ -359,7 +360,7 @@ fn process_pack_for_issuer_handles_invalid_aspa_bytes() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
|
||||
fn process_snapshot_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);
|
||||
@@ -367,7 +368,7 @@ fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
|
||||
let crl_bytes =
|
||||
std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl");
|
||||
|
||||
let pack = minimal_pack(
|
||||
let pack = minimal_snapshot(
|
||||
&manifest_rsync_uri,
|
||||
&rsync_base_uri,
|
||||
manifest_bytes,
|
||||
@@ -387,7 +388,7 @@ fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
|
||||
signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
|
||||
..Policy::default()
|
||||
};
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&[],
|
||||
@@ -1,16 +1,17 @@
|
||||
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
||||
use rpki::storage::{FetchCachePpPack, PackFile, PackTime};
|
||||
use rpki::validation::objects::process_fetch_cache_pp_pack_for_issuer;
|
||||
use rpki::storage::{PackFile, PackTime};
|
||||
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
|
||||
use rpki::validation::publication_point::PublicationPointSnapshot;
|
||||
|
||||
fn fixture_bytes(path: &str) -> Vec<u8> {
|
||||
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
|
||||
.unwrap_or_else(|e| panic!("read fixture {path}: {e}"))
|
||||
}
|
||||
|
||||
fn dummy_pack(manifest_bytes: Vec<u8>, files: Vec<PackFile>) -> FetchCachePpPack {
|
||||
fn dummy_snapshot(manifest_bytes: Vec<u8>, files: Vec<PackFile>) -> PublicationPointSnapshot {
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
PublicationPointSnapshot {
|
||||
format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: "rsync://example.test/repo/pp/manifest.mft".to_string(),
|
||||
publication_point_rsync_uri: "rsync://example.test/repo/pp/".to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
@@ -23,7 +24,7 @@ fn dummy_pack(manifest_bytes: Vec<u8>, files: Vec<PackFile>) -> FetchCachePpPack
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() {
|
||||
fn process_snapshot_drop_object_on_wrong_issuer_ca_for_roa() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -43,7 +44,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() {
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||
);
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||
@@ -60,7 +61,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() {
|
||||
};
|
||||
|
||||
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&wrong_issuer_ca_der,
|
||||
@@ -78,7 +79,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
|
||||
fn process_snapshot_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -101,7 +102,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
|
||||
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
||||
);
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||
@@ -123,7 +124,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
|
||||
};
|
||||
|
||||
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&wrong_issuer_ca_der,
|
||||
@@ -138,7 +139,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() {
|
||||
fn process_snapshot_drop_object_on_wrong_issuer_ca_for_aspa() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -159,7 +160,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() {
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||
);
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||
@@ -173,7 +174,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() {
|
||||
};
|
||||
|
||||
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&wrong_issuer_ca_der,
|
||||
@@ -191,7 +192,7 @@ fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest() {
|
||||
fn process_snapshot_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -214,7 +215,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest()
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||
);
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||
@@ -229,7 +230,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest()
|
||||
};
|
||||
|
||||
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&wrong_issuer_ca_der,
|
||||
@@ -243,7 +244,7 @@ fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
|
||||
fn process_snapshot_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -253,7 +254,7 @@ fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
|
||||
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
||||
);
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes),
|
||||
@@ -266,7 +267,7 @@ fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
|
||||
..Policy::default()
|
||||
};
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
@@ -287,12 +288,12 @@ fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_drop_object_records_errors_and_continues() {
|
||||
fn process_snapshot_for_issuer_drop_object_records_errors_and_continues() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
@@ -309,7 +310,7 @@ fn process_pack_for_issuer_drop_object_records_errors_and_continues() {
|
||||
..Policy::default()
|
||||
};
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
@@ -328,12 +329,12 @@ fn process_pack_for_issuer_drop_object_records_errors_and_continues() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_drop_publication_point_records_skips_for_rest() {
|
||||
fn process_snapshot_for_issuer_drop_publication_point_records_skips_for_rest() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
@@ -351,7 +352,7 @@ fn process_pack_for_issuer_drop_publication_point_records_skips_for_rest() {
|
||||
..Policy::default()
|
||||
};
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
@@ -370,7 +371,7 @@ fn process_pack_for_issuer_drop_publication_point_records_skips_for_rest() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
|
||||
fn process_snapshot_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -389,7 +390,7 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
|
||||
|
||||
// Provide a CRL file with the *exact* rsync URI referenced by the embedded EE certificate.
|
||||
// Bytes need not be valid for this test: we just want to cover deterministic selection.
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]),
|
||||
@@ -402,7 +403,7 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
|
||||
..Policy::default()
|
||||
};
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
@@ -420,7 +421,7 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() {
|
||||
fn process_snapshot_for_issuer_rejects_roa_when_crldp_crl_missing() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -428,7 +429,7 @@ fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() {
|
||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
|
||||
// Pack has a CRL, but its URI does not match the embedded EE certificate CRLDP.
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
@@ -444,7 +445,7 @@ fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() {
|
||||
..Policy::default()
|
||||
};
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
@@ -469,7 +470,7 @@ fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_aspa() {
|
||||
fn process_snapshot_for_issuer_selects_crl_by_ee_crldp_uri_aspa() {
|
||||
let manifest_bytes = fixture_bytes(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
);
|
||||
@@ -487,7 +488,7 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_aspa() {
|
||||
.as_str()
|
||||
.to_string();
|
||||
|
||||
let pack = dummy_pack(
|
||||
let pack = dummy_snapshot(
|
||||
manifest_bytes,
|
||||
vec![
|
||||
PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]),
|
||||
@@ -500,7 +501,7 @@ fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_aspa() {
|
||||
..Policy::default()
|
||||
};
|
||||
|
||||
let out = process_fetch_cache_pp_pack_for_issuer(
|
||||
let out = process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&policy,
|
||||
&fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
|
||||
|
||||
@@ -6,7 +6,7 @@ fn policy_defaults_are_correct() {
|
||||
assert_eq!(p.sync_preference, SyncPreference::RrdpThenRsync);
|
||||
assert_eq!(
|
||||
p.ca_failed_fetch_policy,
|
||||
CaFailedFetchPolicy::UseFetchCachePp
|
||||
CaFailedFetchPolicy::ReuseCurrentInstanceVcir
|
||||
);
|
||||
assert_eq!(
|
||||
p.signed_object_failure_policy,
|
||||
@@ -30,6 +30,17 @@ signed_object_failure_policy = "drop_publication_point"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_toml_parsing_accepts_reuse_current_instance_vcir() {
|
||||
let toml = r#"ca_failed_fetch_policy = "reuse_current_instance_vcir"
|
||||
"#;
|
||||
let p = Policy::from_toml_str(toml).expect("parse TOML policy");
|
||||
assert_eq!(
|
||||
p.ca_failed_fetch_policy,
|
||||
CaFailedFetchPolicy::ReuseCurrentInstanceVcir
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_toml_parsing_uses_defaults_when_missing() {
|
||||
let p = Policy::from_toml_str("").expect("parse empty TOML policy");
|
||||
|
||||
@@ -7,7 +7,8 @@ use rpki::fetch::rsync::LocalDirRsyncFetcher;
|
||||
use rpki::policy::{Policy, SyncPreference};
|
||||
use rpki::storage::RocksStore;
|
||||
use rpki::sync::rrdp::Fetcher;
|
||||
use rpki::validation::run::{fetch_cache_pp_exists, run_publication_point_once};
|
||||
use rpki::validation::manifest::PublicationPointSource;
|
||||
use rpki::validation::run::run_publication_point_once;
|
||||
|
||||
fn fixture_to_rsync_uri(path: &Path) -> String {
|
||||
let rel = path
|
||||
@@ -40,7 +41,7 @@ impl Fetcher for NeverHttpFetcher {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e2e_offline_uses_rsync_then_writes_fetch_cache_pp_then_outputs_vrps() {
|
||||
fn e2e_offline_uses_rsync_then_writes_latest_vcir_then_outputs_vrps() {
|
||||
let fixture_dir = Path::new("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0");
|
||||
let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/";
|
||||
let manifest_path = fixture_dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft");
|
||||
@@ -80,14 +81,6 @@ fn e2e_offline_uses_rsync_then_writes_fetch_cache_pp_then_outputs_vrps() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
|
||||
let expected_files = std::fs::read_dir(fixture_dir)
|
||||
.expect("read fixture dir")
|
||||
.filter_map(|e| e.ok())
|
||||
.filter_map(|e| e.metadata().ok().map(|m| (e, m)))
|
||||
.filter(|(_e, m)| m.is_file())
|
||||
.count();
|
||||
assert!(expected_files >= 3, "fixture dir seems incomplete");
|
||||
|
||||
let out = run_publication_point_once(
|
||||
&store,
|
||||
&policy,
|
||||
@@ -105,9 +98,14 @@ fn e2e_offline_uses_rsync_then_writes_fetch_cache_pp_then_outputs_vrps() {
|
||||
)
|
||||
.expect("run publication point once");
|
||||
|
||||
assert!(fetch_cache_pp_exists(&store, &manifest_rsync_uri).expect("exists check"));
|
||||
assert_eq!(out.repo_sync.objects_written, expected_files);
|
||||
|
||||
assert!(
|
||||
store
|
||||
.get_vcir(&manifest_rsync_uri)
|
||||
.expect("get vcir")
|
||||
.is_some()
|
||||
);
|
||||
assert_eq!(out.publication_point_source, PublicationPointSource::Fresh);
|
||||
assert!(out.publication_point_warnings.is_empty());
|
||||
assert!(
|
||||
out.objects.vrps.iter().any(|v| v.asn == 4538),
|
||||
"expected VRPs for AS4538"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use rpki::analysis::timing::{TimingHandle, TimingMeta};
|
||||
use rpki::validation::from_tal::discover_root_ca_instance_from_tal_and_ta_der;
|
||||
use rpki::validation::run_tree_from_tal::root_handle_from_trust_anchor;
|
||||
use rpki::validation::run_tree_from_tal::{
|
||||
run_tree_from_tal_and_ta_der_serial, run_tree_from_tal_and_ta_der_serial_audit,
|
||||
run_tree_from_tal_url_serial, run_tree_from_tal_url_serial_audit,
|
||||
run_tree_from_tal_and_ta_der_serial_audit_with_timing, run_tree_from_tal_url_serial,
|
||||
run_tree_from_tal_url_serial_audit, run_tree_from_tal_url_serial_audit_with_timing,
|
||||
};
|
||||
use rpki::validation::tree::TreeRunConfig;
|
||||
|
||||
@@ -31,6 +33,22 @@ impl rpki::fetch::rsync::RsyncFetcher for EmptyRsyncFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_timing_handle(temp: &tempfile::TempDir) -> TimingHandle {
|
||||
TimingHandle::new(TimingMeta {
|
||||
recorded_at_utc_rfc3339: "2026-03-11T00:00:00Z".to_string(),
|
||||
validation_time_utc_rfc3339: "2026-03-11T00:00:00Z".to_string(),
|
||||
tal_url: Some("mock:apnic.tal".to_string()),
|
||||
db_path: Some(temp.path().display().to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn read_timing_json(temp: &tempfile::TempDir, timing: &TimingHandle) -> serde_json::Value {
|
||||
let path = temp.path().join("timing.json");
|
||||
timing.write_json(&path, 20).expect("write timing json");
|
||||
serde_json::from_slice(&std::fs::read(&path).expect("read timing json"))
|
||||
.expect("parse timing json")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_handle_is_constructible_from_fixture_tal_and_ta() {
|
||||
let tal_bytes = std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
||||
@@ -40,7 +58,12 @@ fn root_handle_is_constructible_from_fixture_tal_and_ta() {
|
||||
let discovery =
|
||||
discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None).expect("discover");
|
||||
|
||||
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
||||
let root = root_handle_from_trust_anchor(
|
||||
&discovery.trust_anchor,
|
||||
"test-tal".to_string(),
|
||||
None,
|
||||
&discovery.ca_instance,
|
||||
);
|
||||
|
||||
assert_eq!(root.depth, 0);
|
||||
assert_eq!(
|
||||
@@ -91,18 +114,18 @@ fn run_tree_from_tal_url_entry_executes_and_records_failure_when_repo_empty() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
assert_eq!(out.tree.instances_processed, 0);
|
||||
assert_eq!(out.tree.instances_failed, 1);
|
||||
assert_eq!(out.tree.instances_processed, 1);
|
||||
assert_eq!(out.tree.instances_failed, 0);
|
||||
assert!(
|
||||
out.tree
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("publication point failed")),
|
||||
"expected failure warning"
|
||||
.any(|w| w.message.contains("manifest failed fetch")
|
||||
|| w.message.contains("no latest validated result")),
|
||||
"expected failed-fetch warning"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,18 +158,18 @@ fn run_tree_from_tal_and_ta_der_entry_executes_and_records_failure_when_repo_emp
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
assert_eq!(out.tree.instances_processed, 0);
|
||||
assert_eq!(out.tree.instances_failed, 1);
|
||||
assert_eq!(out.tree.instances_processed, 1);
|
||||
assert_eq!(out.tree.instances_failed, 0);
|
||||
assert!(
|
||||
out.tree
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("publication point failed")),
|
||||
"expected failure warning"
|
||||
.any(|w| w.message.contains("manifest failed fetch")
|
||||
|| w.message.contains("no latest validated result")),
|
||||
"expected failed-fetch warning"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,14 +210,14 @@ fn run_tree_from_tal_url_audit_entry_collects_no_publication_points_when_repo_em
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree audit");
|
||||
|
||||
assert_eq!(out.tree.instances_processed, 0);
|
||||
assert_eq!(out.tree.instances_failed, 1);
|
||||
assert!(out.publication_points.is_empty());
|
||||
assert_eq!(out.tree.instances_processed, 1);
|
||||
assert_eq!(out.tree.instances_failed, 0);
|
||||
assert_eq!(out.publication_points.len(), 1);
|
||||
assert_eq!(out.publication_points[0].source, "failed_fetch_no_cache");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -227,12 +250,113 @@ fn run_tree_from_tal_and_ta_der_audit_entry_collects_no_publication_points_when_
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree audit");
|
||||
|
||||
assert_eq!(out.tree.instances_processed, 0);
|
||||
assert_eq!(out.tree.instances_failed, 1);
|
||||
assert!(out.publication_points.is_empty());
|
||||
assert_eq!(out.tree.instances_processed, 1);
|
||||
assert_eq!(out.tree.instances_failed, 0);
|
||||
assert_eq!(out.publication_points.len(), 1);
|
||||
assert_eq!(out.publication_points[0].source, "failed_fetch_no_cache");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_tree_from_tal_url_audit_with_timing_records_phases_when_repo_empty() {
|
||||
let tal_url = "mock:apnic.tal";
|
||||
let tal_bytes = std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
||||
.expect("read apnic tal fixture");
|
||||
let ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(tal_url.to_string(), tal_bytes);
|
||||
map.insert(
|
||||
"https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer".to_string(),
|
||||
ta_der.clone(),
|
||||
);
|
||||
map.insert(
|
||||
"rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer".to_string(),
|
||||
ta_der.clone(),
|
||||
);
|
||||
let http = MapHttpFetcher { map };
|
||||
let rsync = EmptyRsyncFetcher;
|
||||
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = rpki::storage::RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
let policy = rpki::policy::Policy {
|
||||
sync_preference: rpki::policy::SyncPreference::RsyncOnly,
|
||||
..rpki::policy::Policy::default()
|
||||
};
|
||||
let timing = test_timing_handle(&temp);
|
||||
|
||||
let out = run_tree_from_tal_url_serial_audit_with_timing(
|
||||
&store,
|
||||
&policy,
|
||||
tal_url,
|
||||
&http,
|
||||
&rsync,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
},
|
||||
&timing,
|
||||
)
|
||||
.expect("run tree audit with timing");
|
||||
|
||||
assert_eq!(out.tree.instances_processed, 1);
|
||||
assert_eq!(out.tree.instances_failed, 0);
|
||||
|
||||
let report = read_timing_json(&temp, &timing);
|
||||
assert_eq!(report["phases"]["tal_bootstrap"]["count"].as_u64(), Some(1));
|
||||
assert_eq!(
|
||||
report["phases"]["tree_run_total"]["count"].as_u64(),
|
||||
Some(1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_tree_from_tal_and_ta_der_audit_with_timing_records_phases_when_repo_empty() {
|
||||
let tal_bytes = std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal")
|
||||
.expect("read apnic tal fixture");
|
||||
let ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
||||
|
||||
let http = MapHttpFetcher {
|
||||
map: HashMap::new(),
|
||||
};
|
||||
let rsync = EmptyRsyncFetcher;
|
||||
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = rpki::storage::RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
let policy = rpki::policy::Policy {
|
||||
sync_preference: rpki::policy::SyncPreference::RsyncOnly,
|
||||
..rpki::policy::Policy::default()
|
||||
};
|
||||
let timing = test_timing_handle(&temp);
|
||||
|
||||
let out = run_tree_from_tal_and_ta_der_serial_audit_with_timing(
|
||||
&store,
|
||||
&policy,
|
||||
&tal_bytes,
|
||||
&ta_der,
|
||||
None,
|
||||
&http,
|
||||
&rsync,
|
||||
time::OffsetDateTime::now_utc(),
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: Some(1),
|
||||
},
|
||||
&timing,
|
||||
)
|
||||
.expect("run tree audit with timing");
|
||||
|
||||
assert_eq!(out.tree.instances_processed, 1);
|
||||
assert_eq!(out.tree.instances_failed, 0);
|
||||
|
||||
let report = read_timing_json(&temp, &timing);
|
||||
assert_eq!(report["phases"]["tal_bootstrap"]["count"].as_u64(), Some(1));
|
||||
assert_eq!(
|
||||
report["phases"]["tree_run_total"]["count"].as_u64(),
|
||||
Some(1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use rpki::storage::{FetchCachePpKey, RocksStore};
|
||||
use rpki::storage::RocksStore;
|
||||
|
||||
#[test]
|
||||
fn storage_iter_all_lists_raw_and_fetch_cache_pp_entries() {
|
||||
fn storage_iter_all_lists_raw_entries() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
|
||||
@@ -12,11 +12,6 @@ fn storage_iter_all_lists_raw_and_fetch_cache_pp_entries() {
|
||||
.put_raw("rsync://example.test/repo/b.roa", b"b")
|
||||
.expect("put_raw b");
|
||||
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri("rsync://example.test/repo/m.mft");
|
||||
store
|
||||
.put_fetch_cache_pp(&key, b"x")
|
||||
.expect("put_fetch_cache_pp");
|
||||
|
||||
let raw_keys = store
|
||||
.raw_iter_all()
|
||||
.expect("raw_iter_all")
|
||||
@@ -25,11 +20,4 @@ fn storage_iter_all_lists_raw_and_fetch_cache_pp_entries() {
|
||||
assert_eq!(raw_keys.len(), 2);
|
||||
assert!(raw_keys.contains(&"rsync://example.test/repo/a.cer".to_string()));
|
||||
assert!(raw_keys.contains(&"rsync://example.test/repo/b.roa".to_string()));
|
||||
|
||||
let keys = store
|
||||
.fetch_cache_pp_iter_all()
|
||||
.expect("fetch_cache_pp_iter_all")
|
||||
.map(|(k, _v)| String::from_utf8(k.to_vec()).expect("utf8 key"))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(keys, vec![key.as_str().to_string()]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rocksdb::WriteBatch;
|
||||
|
||||
use rpki::storage::{FetchCachePpKey, RocksStore};
|
||||
use rpki::storage::RocksStore;
|
||||
|
||||
#[test]
|
||||
fn storage_delete_rrdp_state_works() {
|
||||
@@ -57,15 +57,6 @@ fn storage_raw_iter_prefix_filters_by_prefix() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_fetch_cache_pp_key_format_is_stable() {
|
||||
let k = FetchCachePpKey::from_manifest_rsync_uri("rsync://example.net/repo/manifest.mft");
|
||||
assert_eq!(
|
||||
k.as_str(),
|
||||
"fetch_cache_pp:rsync://example.net/repo/manifest.mft"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_write_batch_accepts_empty_batch() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::Path;
|
||||
|
||||
use rpki::storage::{FetchCachePpKey, RocksStore};
|
||||
use rpki::storage::RocksStore;
|
||||
|
||||
#[test]
|
||||
fn storage_opens_and_creates_column_families() {
|
||||
@@ -24,26 +24,6 @@ fn raw_objects_roundtrip_by_rsync_uri() {
|
||||
assert!(got.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_cache_pp_roundtrip_by_manifest_uri() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(dir.path()).expect("open rocksdb");
|
||||
|
||||
let manifest_uri = "rsync://example.invalid/repo/manifest.mft";
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_uri);
|
||||
assert_eq!(
|
||||
key.as_str(),
|
||||
"fetch_cache_pp:rsync://example.invalid/repo/manifest.mft"
|
||||
);
|
||||
|
||||
let bytes = b"pack";
|
||||
store
|
||||
.put_fetch_cache_pp(&key, bytes)
|
||||
.expect("put fetch_cache_pp");
|
||||
let got = store.get_fetch_cache_pp(&key).expect("get fetch_cache_pp");
|
||||
assert_eq!(got.as_deref(), Some(bytes.as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rrdp_state_roundtrip_by_notification_uri() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
@@ -2,17 +2,18 @@ use std::collections::HashMap;
|
||||
|
||||
use rpki::audit::{DiscoveredFrom, PublicationPointAudit};
|
||||
use rpki::report::Warning;
|
||||
use rpki::storage::{FetchCachePpPack, PackTime};
|
||||
use rpki::storage::PackTime;
|
||||
use rpki::validation::manifest::PublicationPointSource;
|
||||
use rpki::validation::objects::{ObjectsOutput, ObjectsStats};
|
||||
use rpki::validation::publication_point::PublicationPointSnapshot;
|
||||
use rpki::validation::tree::{
|
||||
CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner,
|
||||
TreeRunConfig, run_tree_serial,
|
||||
};
|
||||
|
||||
fn empty_pack(manifest_uri: &str, pp_uri: &str) -> FetchCachePpPack {
|
||||
FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
fn empty_snapshot(manifest_uri: &str, pp_uri: &str) -> PublicationPointSnapshot {
|
||||
PublicationPointSnapshot {
|
||||
format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
|
||||
publication_point_rsync_uri: pp_uri.to_string(),
|
||||
manifest_rsync_uri: manifest_uri.to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
@@ -33,6 +34,8 @@ fn empty_pack(manifest_uri: &str, pp_uri: &str) -> FetchCachePpPack {
|
||||
fn ca_handle(manifest_uri: &str) -> CaInstanceHandle {
|
||||
CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
ca_certificate_der: Vec::new(),
|
||||
ca_certificate_rsync_uri: None,
|
||||
effective_ip_resources: None,
|
||||
@@ -104,7 +107,7 @@ fn tree_continues_when_a_publication_point_fails() {
|
||||
root_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(root_manifest, "rsync://example.test/repo/"),
|
||||
snapshot: Some(empty_snapshot(root_manifest, "rsync://example.test/repo/")),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -125,7 +128,10 @@ fn tree_continues_when_a_publication_point_fails() {
|
||||
ok_child_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(ok_child_manifest, "rsync://example.test/repo/ok-child/"),
|
||||
snapshot: Some(empty_snapshot(
|
||||
ok_child_manifest,
|
||||
"rsync://example.test/repo/ok-child/",
|
||||
)),
|
||||
warnings: vec![Warning::new("ok child warning")],
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
|
||||
@@ -2,9 +2,10 @@ use std::collections::HashMap;
|
||||
|
||||
use rpki::audit::{DiscoveredFrom, PublicationPointAudit};
|
||||
use rpki::report::Warning;
|
||||
use rpki::storage::{FetchCachePpPack, PackFile, PackTime};
|
||||
use rpki::storage::{PackFile, PackTime};
|
||||
use rpki::validation::manifest::PublicationPointSource;
|
||||
use rpki::validation::objects::{ObjectsOutput, ObjectsStats};
|
||||
use rpki::validation::publication_point::PublicationPointSnapshot;
|
||||
use rpki::validation::tree::{
|
||||
CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner,
|
||||
TreeRunConfig, run_tree_serial, run_tree_serial_audit,
|
||||
@@ -43,8 +44,8 @@ impl PublicationPointRunner for MockRunner {
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_pack(manifest_uri: &str, pp_uri: &str) -> FetchCachePpPack {
|
||||
FetchCachePpPack {
|
||||
fn empty_snapshot(manifest_uri: &str, pp_uri: &str) -> PublicationPointSnapshot {
|
||||
PublicationPointSnapshot {
|
||||
format_version: 1,
|
||||
publication_point_rsync_uri: pp_uri.to_string(),
|
||||
manifest_rsync_uri: manifest_uri.to_string(),
|
||||
@@ -66,6 +67,8 @@ fn empty_pack(manifest_uri: &str, pp_uri: &str) -> FetchCachePpPack {
|
||||
fn ca_handle(manifest_uri: &str) -> CaInstanceHandle {
|
||||
CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
ca_certificate_der: Vec::new(),
|
||||
ca_certificate_rsync_uri: None,
|
||||
effective_ip_resources: None,
|
||||
@@ -97,7 +100,7 @@ fn discovered_child(
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_enqueues_children_only_for_fresh_publication_points() {
|
||||
fn tree_enqueues_children_for_fresh_and_current_instance_vcir_results() {
|
||||
let root_manifest = "rsync://example.test/repo/root.mft";
|
||||
let child1_manifest = "rsync://example.test/repo/child1.mft";
|
||||
let child2_manifest = "rsync://example.test/repo/child2.mft";
|
||||
@@ -114,7 +117,7 @@ fn tree_enqueues_children_only_for_fresh_publication_points() {
|
||||
root_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(root_manifest, "rsync://example.test/repo/"),
|
||||
snapshot: Some(empty_snapshot(root_manifest, "rsync://example.test/repo/")),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -130,8 +133,11 @@ fn tree_enqueues_children_only_for_fresh_publication_points() {
|
||||
.with(
|
||||
child1_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::FetchCachePp,
|
||||
pack: empty_pack(child1_manifest, "rsync://example.test/repo/child1/"),
|
||||
source: PublicationPointSource::VcirCurrentInstance,
|
||||
snapshot: Some(empty_snapshot(
|
||||
child1_manifest,
|
||||
"rsync://example.test/repo/child1/",
|
||||
)),
|
||||
warnings: vec![Warning::new("child1 warning")],
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -148,7 +154,30 @@ fn tree_enqueues_children_only_for_fresh_publication_points() {
|
||||
child2_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(child2_manifest, "rsync://example.test/repo/child2/"),
|
||||
snapshot: Some(empty_snapshot(
|
||||
child2_manifest,
|
||||
"rsync://example.test/repo/child2/",
|
||||
)),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
},
|
||||
audit: PublicationPointAudit::default(),
|
||||
discovered_children: Vec::new(),
|
||||
},
|
||||
)
|
||||
.with(
|
||||
grandchild_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
snapshot: Some(empty_snapshot(
|
||||
grandchild_manifest,
|
||||
"rsync://example.test/repo/grandchild/",
|
||||
)),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -165,14 +194,20 @@ fn tree_enqueues_children_only_for_fresh_publication_points() {
|
||||
let out = run_tree_serial(ca_handle(root_manifest), &runner, &TreeRunConfig::default())
|
||||
.expect("run tree");
|
||||
|
||||
// root + child1 + child2. grandchild must NOT be processed because child1 used cache.
|
||||
assert_eq!(out.instances_processed, 3);
|
||||
// root + child1 + child2 + grandchild. child1 uses current-instance VCIR, but its child
|
||||
// entry is still restored and processed fresh-first later in the traversal.
|
||||
assert_eq!(out.instances_processed, 4);
|
||||
assert_eq!(out.instances_failed, 0);
|
||||
|
||||
let called = runner.called();
|
||||
assert_eq!(
|
||||
called,
|
||||
vec![root_manifest, child1_manifest, child2_manifest]
|
||||
vec![
|
||||
root_manifest,
|
||||
child1_manifest,
|
||||
child2_manifest,
|
||||
grandchild_manifest
|
||||
]
|
||||
);
|
||||
|
||||
assert!(
|
||||
@@ -184,8 +219,8 @@ fn tree_enqueues_children_only_for_fresh_publication_points() {
|
||||
assert!(
|
||||
out.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("skipping child CA discovery")),
|
||||
"expected RFC 9286 §6.6 enforcement warning"
|
||||
.all(|w| !w.message.contains("skipping child CA discovery")),
|
||||
"did not expect old skip-child warning"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -199,7 +234,7 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
root_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(root_manifest, "rsync://example.test/repo/"),
|
||||
snapshot: Some(empty_snapshot(root_manifest, "rsync://example.test/repo/")),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -216,7 +251,10 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
child_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(child_manifest, "rsync://example.test/repo/child/"),
|
||||
snapshot: Some(empty_snapshot(
|
||||
child_manifest,
|
||||
"rsync://example.test/repo/child/",
|
||||
)),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -236,7 +274,6 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
&TreeRunConfig {
|
||||
max_depth: Some(0),
|
||||
max_instances: None,
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree depth-limited");
|
||||
@@ -249,7 +286,6 @@ fn tree_respects_max_depth_and_max_instances() {
|
||||
&TreeRunConfig {
|
||||
max_depth: None,
|
||||
max_instances: Some(1),
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree instance-limited");
|
||||
@@ -267,7 +303,7 @@ fn tree_audit_includes_parent_and_discovered_from_for_non_root_nodes() {
|
||||
root_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(root_manifest, "rsync://example.test/repo/"),
|
||||
snapshot: Some(empty_snapshot(root_manifest, "rsync://example.test/repo/")),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -284,7 +320,10 @@ fn tree_audit_includes_parent_and_discovered_from_for_non_root_nodes() {
|
||||
child_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack: empty_pack(child_manifest, "rsync://example.test/repo/child/"),
|
||||
snapshot: Some(empty_snapshot(
|
||||
child_manifest,
|
||||
"rsync://example.test/repo/child/",
|
||||
)),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -320,16 +359,23 @@ fn tree_audit_includes_parent_and_discovered_from_for_non_root_nodes() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_revalidate_only_enqueues_children_from_fetch_cache_pp() {
|
||||
fn tree_prefers_lexicographically_first_discovery_when_duplicate_manifest_is_queued() {
|
||||
let root_manifest = "rsync://example.test/repo/root.mft";
|
||||
let child_manifest = "rsync://example.test/repo/child.mft";
|
||||
let duplicate_manifest = "rsync://example.test/repo/child.mft";
|
||||
|
||||
let mut first = discovered_child(root_manifest, duplicate_manifest);
|
||||
first.discovered_from.child_ca_certificate_rsync_uri =
|
||||
"rsync://example.test/repo/z-child.cer".to_string();
|
||||
let mut second = discovered_child(root_manifest, duplicate_manifest);
|
||||
second.discovered_from.child_ca_certificate_rsync_uri =
|
||||
"rsync://example.test/repo/a-child.cer".to_string();
|
||||
|
||||
let runner = MockRunner::default()
|
||||
.with(
|
||||
root_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::FetchCachePp,
|
||||
pack: empty_pack(root_manifest, "rsync://example.test/repo/"),
|
||||
source: PublicationPointSource::Fresh,
|
||||
snapshot: Some(empty_snapshot(root_manifest, "rsync://example.test/repo/")),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -339,14 +385,17 @@ fn tree_revalidate_only_enqueues_children_from_fetch_cache_pp() {
|
||||
audit: Vec::new(),
|
||||
},
|
||||
audit: PublicationPointAudit::default(),
|
||||
discovered_children: vec![discovered_child(root_manifest, child_manifest)],
|
||||
discovered_children: vec![first, second],
|
||||
},
|
||||
)
|
||||
.with(
|
||||
child_manifest,
|
||||
duplicate_manifest,
|
||||
PublicationPointRunResult {
|
||||
source: PublicationPointSource::FetchCachePp,
|
||||
pack: empty_pack(child_manifest, "rsync://example.test/repo/child/"),
|
||||
source: PublicationPointSource::Fresh,
|
||||
snapshot: Some(empty_snapshot(
|
||||
duplicate_manifest,
|
||||
"rsync://example.test/repo/child/",
|
||||
)),
|
||||
warnings: Vec::new(),
|
||||
objects: ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
@@ -360,27 +409,21 @@ fn tree_revalidate_only_enqueues_children_from_fetch_cache_pp() {
|
||||
},
|
||||
);
|
||||
|
||||
let out = run_tree_serial(
|
||||
ca_handle(root_manifest),
|
||||
&runner,
|
||||
&TreeRunConfig {
|
||||
max_depth: None,
|
||||
max_instances: None,
|
||||
revalidate_only: false,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
assert_eq!(out.instances_processed, 1);
|
||||
let out = run_tree_serial_audit(ca_handle(root_manifest), &runner, &TreeRunConfig::default())
|
||||
.expect("run tree audit");
|
||||
|
||||
let out = run_tree_serial(
|
||||
ca_handle(root_manifest),
|
||||
&runner,
|
||||
&TreeRunConfig {
|
||||
max_depth: None,
|
||||
max_instances: None,
|
||||
revalidate_only: true,
|
||||
},
|
||||
)
|
||||
.expect("run tree");
|
||||
assert_eq!(out.instances_processed, 2);
|
||||
assert_eq!(out.tree.instances_processed, 2);
|
||||
assert_eq!(
|
||||
runner.called(),
|
||||
vec![root_manifest.to_string(), duplicate_manifest.to_string()]
|
||||
);
|
||||
let child_audit = &out.publication_points[1];
|
||||
assert_eq!(
|
||||
child_audit
|
||||
.discovered_from
|
||||
.as_ref()
|
||||
.expect("child discovered_from")
|
||||
.child_ca_certificate_rsync_uri,
|
||||
"rsync://example.test/repo/a-child.cer"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
use rpki::storage::{FetchCachePpPack, PackFile, PackTime};
|
||||
|
||||
fn sample_pack() -> FetchCachePpPack {
|
||||
let this_update =
|
||||
PackTime::from_utc_offset_datetime(time::OffsetDateTime::from_unix_timestamp(0).unwrap());
|
||||
let next_update = PackTime::from_utc_offset_datetime(
|
||||
time::OffsetDateTime::from_unix_timestamp(3600).unwrap(),
|
||||
);
|
||||
let verified_at =
|
||||
PackTime::from_utc_offset_datetime(time::OffsetDateTime::from_unix_timestamp(10).unwrap());
|
||||
|
||||
let file1 = PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.net/repo/CA/1.crl",
|
||||
b"crl-bytes".to_vec(),
|
||||
);
|
||||
let file2 = PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.net/repo/CA/2.cer",
|
||||
b"cer-bytes".to_vec(),
|
||||
);
|
||||
|
||||
FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: "rsync://example.net/repo/CA/manifest.mft".to_string(),
|
||||
publication_point_rsync_uri: "rsync://example.net/repo/CA/".to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
this_update,
|
||||
next_update,
|
||||
verified_at,
|
||||
manifest_bytes: b"manifest-bytes".to_vec(),
|
||||
files: vec![file1, file2],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_encode_decode_roundtrip() {
|
||||
let pack = sample_pack();
|
||||
let bytes = pack.encode().expect("encode");
|
||||
let decoded = FetchCachePpPack::decode(&bytes).expect("decode");
|
||||
assert_eq!(decoded, pack);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_missing_manifest() {
|
||||
let mut pack = sample_pack();
|
||||
pack.manifest_bytes.clear();
|
||||
let bytes = pack.encode().expect("encode");
|
||||
assert!(FetchCachePpPack::decode(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_duplicate_rsync_uri_entries() {
|
||||
let mut pack = sample_pack();
|
||||
let dup =
|
||||
PackFile::from_bytes_compute_sha256("rsync://example.net/repo/CA/1.crl", b"other".to_vec());
|
||||
pack.files.push(dup);
|
||||
let bytes = pack.encode().expect("encode");
|
||||
assert!(FetchCachePpPack::decode(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_includes_this_update_next_update() {
|
||||
let pack = sample_pack();
|
||||
let bytes = pack.encode().expect("encode");
|
||||
let decoded = FetchCachePpPack::decode(&bytes).expect("decode");
|
||||
|
||||
let this_update = decoded.this_update.parse().expect("parse this_update");
|
||||
let next_update = decoded.next_update.parse().expect("parse next_update");
|
||||
assert!(next_update > this_update);
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
use rpki::storage::{FetchCachePpPack, PackFile, PackTime};
|
||||
|
||||
fn base_pack() -> FetchCachePpPack {
|
||||
let this_update =
|
||||
PackTime::from_utc_offset_datetime(time::OffsetDateTime::from_unix_timestamp(0).unwrap());
|
||||
let next_update = PackTime::from_utc_offset_datetime(
|
||||
time::OffsetDateTime::from_unix_timestamp(3600).unwrap(),
|
||||
);
|
||||
let verified_at =
|
||||
PackTime::from_utc_offset_datetime(time::OffsetDateTime::from_unix_timestamp(10).unwrap());
|
||||
|
||||
let file =
|
||||
PackFile::from_bytes_compute_sha256("rsync://example.net/repo/obj.cer", b"x".to_vec());
|
||||
|
||||
FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: "rsync://example.net/repo/manifest.mft".to_string(),
|
||||
publication_point_rsync_uri: "rsync://example.net/repo/".to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
this_update,
|
||||
next_update,
|
||||
verified_at,
|
||||
manifest_bytes: b"manifest".to_vec(),
|
||||
files: vec![file],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_unsupported_format_version() {
|
||||
let mut pack = base_pack();
|
||||
pack.format_version = 999;
|
||||
let bytes = pack.encode().expect("encode");
|
||||
assert!(
|
||||
FetchCachePpPack::decode(&bytes)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("unsupported pack format_version")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_missing_manifest_rsync_uri() {
|
||||
let mut pack = base_pack();
|
||||
pack.manifest_rsync_uri.clear();
|
||||
let bytes = pack.encode().expect("encode");
|
||||
assert!(FetchCachePpPack::decode(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_missing_publication_point_rsync_uri() {
|
||||
let mut pack = base_pack();
|
||||
pack.publication_point_rsync_uri.clear();
|
||||
let bytes = pack.encode().expect("encode");
|
||||
assert!(FetchCachePpPack::decode(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_missing_manifest_number() {
|
||||
let mut pack = base_pack();
|
||||
pack.manifest_number_be.clear();
|
||||
let bytes = pack.encode().expect("encode");
|
||||
let err = FetchCachePpPack::decode(&bytes).unwrap_err();
|
||||
assert!(err.to_string().contains("missing required field"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_manifest_number_too_long() {
|
||||
let mut pack = base_pack();
|
||||
pack.manifest_number_be = vec![1u8; 21];
|
||||
let bytes = pack.encode().expect("encode");
|
||||
let err = FetchCachePpPack::decode(&bytes).unwrap_err();
|
||||
assert!(err.to_string().contains("at most 20 octets"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_manifest_number_with_leading_zeros() {
|
||||
let mut pack = base_pack();
|
||||
pack.manifest_number_be = vec![0u8, 1u8];
|
||||
let bytes = pack.encode().expect("encode");
|
||||
let err = FetchCachePpPack::decode(&bytes).unwrap_err();
|
||||
assert!(err.to_string().contains("leading zeros"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_invalid_time_fields() {
|
||||
let mut pack = base_pack();
|
||||
pack.this_update = PackTime {
|
||||
rfc3339_utc: "not-a-time".to_string(),
|
||||
};
|
||||
let bytes = pack.encode().expect("encode");
|
||||
assert!(FetchCachePpPack::decode(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_empty_file_bytes() {
|
||||
let mut pack = base_pack();
|
||||
let mut sha = [0u8; 32];
|
||||
sha[0] = 1;
|
||||
pack.files = vec![PackFile::new(
|
||||
"rsync://example.net/repo/empty.cer",
|
||||
Vec::new(),
|
||||
sha,
|
||||
)];
|
||||
let bytes = pack.encode().expect("encode");
|
||||
assert!(FetchCachePpPack::decode(&bytes).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_file_hash_mismatch() {
|
||||
let mut pack = base_pack();
|
||||
pack.files = vec![PackFile::new(
|
||||
"rsync://example.net/repo/bad.cer",
|
||||
b"abc".to_vec(),
|
||||
[0u8; 32],
|
||||
)];
|
||||
let bytes = pack.encode().expect("encode");
|
||||
let err = FetchCachePpPack::decode(&bytes).unwrap_err();
|
||||
assert!(err.to_string().contains("file hash mismatch"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_rejects_missing_file_rsync_uri() {
|
||||
let mut pack = base_pack();
|
||||
let file = PackFile::from_bytes_compute_sha256("", b"x".to_vec());
|
||||
pack.files = vec![file];
|
||||
let bytes = pack.encode().expect("encode");
|
||||
let err = FetchCachePpPack::decode(&bytes).unwrap_err();
|
||||
assert!(err.to_string().contains("missing required field"));
|
||||
}
|
||||
Reference in New Issue
Block a user