584 lines
21 KiB
Rust
584 lines
21 KiB
Rust
use std::path::Path;
|
|
|
|
use sha2::Digest;
|
|
|
|
use rpki::data_model::manifest::ManifestObject;
|
|
use rpki::policy::{CaFailedFetchPolicy, Policy};
|
|
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> {
|
|
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 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_returns_validated_publication_point_data() {
|
|
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 out = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
validation_time,
|
|
)
|
|
.expect("process manifest publication point");
|
|
assert_eq!(out.source, PublicationPointSource::Fresh);
|
|
assert!(out.warnings.is_empty());
|
|
|
|
assert_eq!(out.snapshot.manifest_rsync_uri, manifest_rsync_uri);
|
|
assert_eq!(
|
|
out.snapshot.publication_point_rsync_uri,
|
|
publication_point_rsync_uri
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
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",
|
|
);
|
|
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 first = 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 returns validated publication point");
|
|
assert_eq!(first.source, PublicationPointSource::Fresh);
|
|
|
|
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
|
|
.parse_files()
|
|
.expect("parse validated manifest fileList");
|
|
let victim = entries.first().expect("non-empty file list");
|
|
let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name);
|
|
let mut tampered = store
|
|
.get_raw(&victim_uri)
|
|
.expect("get victim raw")
|
|
.expect("victim raw exists");
|
|
tampered[0] ^= 0xFF;
|
|
store.put_raw(&victim_uri, &tampered).expect("tamper raw");
|
|
|
|
let second = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
validation_time,
|
|
)
|
|
.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]
|
|
fn manifest_failed_fetch_stop_all_output() {
|
|
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 mut policy = Policy::default();
|
|
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::ReuseCurrentInstanceVcir;
|
|
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 returns validated publication point");
|
|
|
|
let entries = manifest
|
|
.manifest
|
|
.parse_files()
|
|
.expect("parse validated manifest fileList");
|
|
let victim = entries.first().expect("non-empty file list");
|
|
let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name);
|
|
let mut tampered = store
|
|
.get_raw(&victim_uri)
|
|
.expect("get victim raw")
|
|
.expect("victim raw exists");
|
|
tampered[0] ^= 0xFF;
|
|
store.put_raw(&victim_uri, &tampered).expect("tamper raw");
|
|
|
|
policy.ca_failed_fetch_policy = CaFailedFetchPolicy::StopAllOutput;
|
|
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("stop_all_output should not reuse current-instance VCIR");
|
|
let msg = err.to_string();
|
|
assert!(msg.contains("cache use is disabled"));
|
|
}
|
|
|
|
#[test]
|
|
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",
|
|
);
|
|
let manifest_bytes = std::fs::read(manifest_path).expect("read manifest fixture");
|
|
let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest fixture");
|
|
|
|
let ok_time = manifest.manifest.this_update + time::Duration::seconds(1);
|
|
let stale_time = manifest.manifest.next_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()),
|
|
ok_time,
|
|
)
|
|
.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)
|
|
.expect("delete manifest raw to force fallback");
|
|
|
|
let err = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
stale_time,
|
|
)
|
|
.expect_err("stale validation_time must reject current-instance VCIR reuse");
|
|
let msg = err.to_string();
|
|
assert!(msg.contains("instance_gate expired"), "{msg}");
|
|
}
|
|
|
|
#[test]
|
|
fn manifest_revalidation_with_unchanged_manifest_is_fresh() {
|
|
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 t1 = manifest.manifest.this_update + time::Duration::seconds(1);
|
|
let t2 = manifest.manifest.this_update + time::Duration::seconds(2);
|
|
|
|
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 first = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
t1,
|
|
)
|
|
.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,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
t2,
|
|
)
|
|
.expect("second run should accept revalidation of the same manifest");
|
|
assert_eq!(second.source, PublicationPointSource::Fresh);
|
|
assert!(second.warnings.is_empty());
|
|
assert_eq!(
|
|
second.snapshot.manifest_bytes,
|
|
first.snapshot.manifest_bytes
|
|
);
|
|
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_reuses_current_instance_vcir() {
|
|
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 t1 = manifest.manifest.this_update + time::Duration::seconds(1);
|
|
let t2 = manifest.manifest.this_update + time::Duration::seconds(2);
|
|
|
|
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 first = process_manifest_publication_point(
|
|
&store,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
t1,
|
|
)
|
|
.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 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);
|
|
bumped.manifest_number_be[i] = v;
|
|
if !carry {
|
|
break;
|
|
}
|
|
if i == 0 {
|
|
bumped.manifest_number_be.insert(0, 1);
|
|
break;
|
|
}
|
|
}
|
|
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,
|
|
&policy,
|
|
&manifest_rsync_uri,
|
|
&publication_point_rsync_uri,
|
|
&issuer_ca_der,
|
|
Some(issuer_ca_rsync_uri()),
|
|
t2,
|
|
)
|
|
.expect("second run should treat rollback as failed fetch and reuse current-instance VCIR");
|
|
assert_eq!(second.source, PublicationPointSource::VcirCurrentInstance);
|
|
assert!(
|
|
second
|
|
.warnings
|
|
.iter()
|
|
.any(|w| w.message.contains("manifestNumber not higher")),
|
|
"expected warning mentioning manifestNumber monotonicity"
|
|
);
|
|
}
|