20260324 完成和routinator对齐snapshot delta replay correctness一致
This commit is contained in:
@@ -8,7 +8,7 @@ use crate::data_model::rc::{
|
||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
||||
|
||||
use crate::validation::x509_name::x509_names_equivalent;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -27,6 +27,44 @@ pub struct ValidatedSubordinateCaLite {
|
||||
pub effective_as_resources: Option<AsResourceSet>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct IssuerEffectiveResourcesIndex {
|
||||
parent_ip_by_afi_items: Option<BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>>,
|
||||
parent_ip_merged_intervals: HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>>,
|
||||
parent_asnum_intervals: Option<Vec<(u32, u32)>>,
|
||||
parent_rdi_intervals: Option<Vec<(u32, u32)>>,
|
||||
}
|
||||
|
||||
impl IssuerEffectiveResourcesIndex {
|
||||
pub fn from_effective_resources(
|
||||
issuer_effective_ip: Option<&IpResourceSet>,
|
||||
issuer_effective_as: Option<&AsResourceSet>,
|
||||
) -> Result<Self, CaPathError> {
|
||||
let parent_ip_by_afi_items = issuer_effective_ip.map(ip_resources_by_afi_items).transpose()?;
|
||||
|
||||
let parent_ip_merged_intervals = issuer_effective_ip
|
||||
.map(ip_resources_to_merged_intervals_by_afi)
|
||||
.unwrap_or_default();
|
||||
|
||||
let parent_asnum_intervals = issuer_effective_as.and_then(|resources| {
|
||||
resources
|
||||
.asnum
|
||||
.as_ref()
|
||||
.map(as_choice_to_merged_intervals)
|
||||
});
|
||||
let parent_rdi_intervals = issuer_effective_as.and_then(|resources| {
|
||||
resources.rdi.as_ref().map(as_choice_to_merged_intervals)
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
parent_ip_by_afi_items,
|
||||
parent_ip_merged_intervals,
|
||||
parent_asnum_intervals,
|
||||
parent_rdi_intervals,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CaPathError {
|
||||
#[error("child CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
|
||||
@@ -231,6 +269,40 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
issuer_effective_ip: Option<&IpResourceSet>,
|
||||
issuer_effective_as: Option<&AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
|
||||
let issuer_resources_index = IssuerEffectiveResourcesIndex::from_effective_resources(
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
)?;
|
||||
validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
||||
child_ca_der,
|
||||
child_ca,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
issuer_crl,
|
||||
issuer_crl_revoked_serials,
|
||||
issuer_ca_rsync_uri,
|
||||
issuer_crl_rsync_uri,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
&issuer_resources_index,
|
||||
validation_time,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
||||
child_ca_der: &[u8],
|
||||
child_ca: ResourceCertificate,
|
||||
issuer_ca: &ResourceCertificate,
|
||||
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
||||
issuer_crl: &RpkixCrl,
|
||||
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
issuer_crl_rsync_uri: &str,
|
||||
issuer_effective_ip: Option<&IpResourceSet>,
|
||||
issuer_effective_as: Option<&AsResourceSet>,
|
||||
issuer_resources_index: &IssuerEffectiveResourcesIndex,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
|
||||
if child_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(CaPathError::ChildNotCa);
|
||||
@@ -277,13 +349,17 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
return Err(CaPathError::ChildRevoked);
|
||||
}
|
||||
|
||||
let effective_ip_resources = resolve_child_ip_resources(
|
||||
let effective_ip_resources = resolve_child_ip_resources_indexed(
|
||||
child_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_effective_ip,
|
||||
issuer_resources_index.parent_ip_by_afi_items.as_ref(),
|
||||
&issuer_resources_index.parent_ip_merged_intervals,
|
||||
)?;
|
||||
let effective_as_resources = resolve_child_as_resources(
|
||||
let effective_as_resources = resolve_child_as_resources_indexed(
|
||||
child_ca.tbs.extensions.as_resources.as_ref(),
|
||||
issuer_effective_as,
|
||||
issuer_resources_index.parent_asnum_intervals.as_deref(),
|
||||
issuer_resources_index.parent_rdi_intervals.as_deref(),
|
||||
)?;
|
||||
if effective_ip_resources.is_none() && effective_as_resources.is_none() {
|
||||
return Err(CaPathError::ResourcesMissing);
|
||||
@@ -435,12 +511,30 @@ fn is_serial_revoked_by_crl(cert: &ResourceCertificate, crl: &RpkixCrl) -> bool
|
||||
fn resolve_child_ip_resources(
|
||||
child_ip: Option<&IpResourceSet>,
|
||||
issuer_effective: Option<&IpResourceSet>,
|
||||
) -> Result<Option<IpResourceSet>, CaPathError> {
|
||||
let precomputed_parent_by_afi = issuer_effective.map(ip_resources_by_afi_items).transpose()?;
|
||||
let precomputed_parent_intervals = issuer_effective
|
||||
.map(ip_resources_to_merged_intervals_by_afi)
|
||||
.unwrap_or_default();
|
||||
resolve_child_ip_resources_indexed(
|
||||
child_ip,
|
||||
issuer_effective,
|
||||
precomputed_parent_by_afi.as_ref(),
|
||||
&precomputed_parent_intervals,
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_child_ip_resources_indexed(
|
||||
child_ip: Option<&IpResourceSet>,
|
||||
issuer_effective: Option<&IpResourceSet>,
|
||||
parent_by_afi: Option<&BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>>,
|
||||
parent_intervals_by_afi: &HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>>,
|
||||
) -> Result<Option<IpResourceSet>, CaPathError> {
|
||||
let Some(child_ip) = child_ip else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(parent) = issuer_effective else {
|
||||
let Some(_parent) = issuer_effective else {
|
||||
if child_ip.has_any_inherit() {
|
||||
return Err(CaPathError::InheritWithoutParentResources);
|
||||
}
|
||||
@@ -449,7 +543,7 @@ fn resolve_child_ip_resources(
|
||||
};
|
||||
|
||||
// Resolve per-AFI inherit, producing an effective set with no inherit.
|
||||
let parent_by_afi = ip_resources_by_afi_items(parent)?;
|
||||
let parent_by_afi = parent_by_afi.ok_or(CaPathError::InheritWithoutParentResources)?;
|
||||
let mut out_families: Vec<crate::data_model::rc::IpAddressFamily> = Vec::new();
|
||||
|
||||
for fam in &child_ip.families {
|
||||
@@ -465,9 +559,11 @@ fn resolve_child_ip_resources(
|
||||
}
|
||||
IpAddressChoice::AddressesOrRanges(items) => {
|
||||
// Subset check against parent union for that AFI.
|
||||
let parent_set =
|
||||
ip_resources_single_afi(parent, fam.afi, parent_by_afi.get(&fam.afi));
|
||||
if !ip_family_items_subset(items, &parent_set) {
|
||||
let parent_intervals = parent_intervals_by_afi
|
||||
.get(&fam.afi)
|
||||
.map(Vec::as_slice)
|
||||
.unwrap_or(&[]);
|
||||
if !ip_family_items_subset_with_parent_intervals(items, parent_intervals) {
|
||||
return Err(CaPathError::ResourcesNotSubset);
|
||||
}
|
||||
out_families.push(crate::data_model::rc::IpAddressFamily {
|
||||
@@ -486,6 +582,29 @@ fn resolve_child_ip_resources(
|
||||
fn resolve_child_as_resources(
|
||||
child_as: Option<&AsResourceSet>,
|
||||
issuer_effective: Option<&AsResourceSet>,
|
||||
) -> Result<Option<AsResourceSet>, CaPathError> {
|
||||
let precomputed_asnum = issuer_effective.and_then(|resources| {
|
||||
resources
|
||||
.asnum
|
||||
.as_ref()
|
||||
.map(as_choice_to_merged_intervals)
|
||||
});
|
||||
let precomputed_rdi = issuer_effective.and_then(|resources| {
|
||||
resources.rdi.as_ref().map(as_choice_to_merged_intervals)
|
||||
});
|
||||
resolve_child_as_resources_indexed(
|
||||
child_as,
|
||||
issuer_effective,
|
||||
precomputed_asnum.as_deref(),
|
||||
precomputed_rdi.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_child_as_resources_indexed(
|
||||
child_as: Option<&AsResourceSet>,
|
||||
issuer_effective: Option<&AsResourceSet>,
|
||||
parent_asnum_intervals: Option<&[(u32, u32)]>,
|
||||
parent_rdi_intervals: Option<&[(u32, u32)]>,
|
||||
) -> Result<Option<AsResourceSet>, CaPathError> {
|
||||
let Some(child_as) = child_as else {
|
||||
return Ok(None);
|
||||
@@ -507,7 +626,11 @@ fn resolve_child_as_resources(
|
||||
.ok_or(CaPathError::InheritWithoutParentResources)
|
||||
.map(Some)?,
|
||||
Some(_) => {
|
||||
if !as_choice_subset(child_as.asnum.as_ref(), parent.asnum.as_ref()) {
|
||||
if !as_choice_subset_with_parent_intervals(
|
||||
child_as.asnum.as_ref(),
|
||||
parent.asnum.as_ref(),
|
||||
parent_asnum_intervals,
|
||||
) {
|
||||
return Err(CaPathError::ResourcesNotSubset);
|
||||
}
|
||||
child_as.asnum.clone()
|
||||
@@ -522,7 +645,11 @@ fn resolve_child_as_resources(
|
||||
.ok_or(CaPathError::InheritWithoutParentResources)
|
||||
.map(Some)?,
|
||||
Some(_) => {
|
||||
if !as_choice_subset(child_as.rdi.as_ref(), parent.rdi.as_ref()) {
|
||||
if !as_choice_subset_with_parent_intervals(
|
||||
child_as.rdi.as_ref(),
|
||||
parent.rdi.as_ref(),
|
||||
parent_rdi_intervals,
|
||||
) {
|
||||
return Err(CaPathError::ResourcesNotSubset);
|
||||
}
|
||||
child_as.rdi.clone()
|
||||
@@ -535,6 +662,14 @@ fn resolve_child_as_resources(
|
||||
fn as_choice_subset(
|
||||
child: Option<&AsIdentifierChoice>,
|
||||
parent: Option<&AsIdentifierChoice>,
|
||||
) -> bool {
|
||||
as_choice_subset_with_parent_intervals(child, parent, None)
|
||||
}
|
||||
|
||||
fn as_choice_subset_with_parent_intervals(
|
||||
child: Option<&AsIdentifierChoice>,
|
||||
parent: Option<&AsIdentifierChoice>,
|
||||
parent_intervals_hint: Option<&[(u32, u32)]>,
|
||||
) -> bool {
|
||||
let Some(child) = child else {
|
||||
return true;
|
||||
@@ -552,9 +687,16 @@ fn as_choice_subset(
|
||||
}
|
||||
|
||||
let child_intervals = as_choice_to_merged_intervals(child);
|
||||
let parent_intervals = as_choice_to_merged_intervals(parent);
|
||||
let owned_parent_intervals;
|
||||
let parent_intervals = match parent_intervals_hint {
|
||||
Some(intervals) => intervals,
|
||||
None => {
|
||||
owned_parent_intervals = as_choice_to_merged_intervals(parent);
|
||||
owned_parent_intervals.as_slice()
|
||||
}
|
||||
};
|
||||
for (cmin, cmax) in &child_intervals {
|
||||
if !as_interval_is_covered(&parent_intervals, *cmin, *cmax) {
|
||||
if !as_interval_is_covered(parent_intervals, *cmin, *cmax) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -692,21 +834,33 @@ fn ip_family_items_subset(
|
||||
fn ip_resources_to_merged_intervals(
|
||||
set: &IpResourceSet,
|
||||
) -> std::collections::HashMap<AfiKey, Vec<(Vec<u8>, Vec<u8>)>> {
|
||||
let mut m: std::collections::HashMap<AfiKey, Vec<(Vec<u8>, Vec<u8>)>> =
|
||||
std::collections::HashMap::new();
|
||||
let m = ip_resources_to_merged_intervals_by_afi(set);
|
||||
m.into_iter()
|
||||
.map(|(afi, ranges)| {
|
||||
(
|
||||
match afi {
|
||||
crate::data_model::rc::Afi::Ipv4 => AfiKey::V4,
|
||||
crate::data_model::rc::Afi::Ipv6 => AfiKey::V6,
|
||||
},
|
||||
ranges,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn ip_resources_to_merged_intervals_by_afi(
|
||||
set: &IpResourceSet,
|
||||
) -> HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>> {
|
||||
let mut m: HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>> = HashMap::new();
|
||||
|
||||
for fam in &set.families {
|
||||
let afi = match fam.afi {
|
||||
crate::data_model::rc::Afi::Ipv4 => AfiKey::V4,
|
||||
crate::data_model::rc::Afi::Ipv6 => AfiKey::V6,
|
||||
};
|
||||
match &fam.choice {
|
||||
IpAddressChoice::Inherit => {
|
||||
// When used in subset checks, treat inherit as "all" by leaving it absent.
|
||||
// Resolution should have happened earlier.
|
||||
}
|
||||
IpAddressChoice::AddressesOrRanges(items) => {
|
||||
let ent = m.entry(afi).or_default();
|
||||
let ent = m.entry(fam.afi).or_default();
|
||||
for item in items {
|
||||
match item {
|
||||
crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
|
||||
@@ -730,6 +884,36 @@ fn ip_resources_to_merged_intervals(
|
||||
m
|
||||
}
|
||||
|
||||
fn ip_family_items_subset_with_parent_intervals(
|
||||
child_items: &[crate::data_model::rc::IpAddressOrRange],
|
||||
parent_intervals: &[(Vec<u8>, Vec<u8>)],
|
||||
) -> bool {
|
||||
if parent_intervals.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut child_intervals: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
|
||||
for item in child_items {
|
||||
match item {
|
||||
crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
|
||||
child_intervals.push(prefix_to_range(p))
|
||||
}
|
||||
crate::data_model::rc::IpAddressOrRange::Range(r) => {
|
||||
child_intervals.push((r.min.clone(), r.max.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
let child_intervals = merge_ip_intervals(&child_intervals);
|
||||
|
||||
for (cmin, cmax) in &child_intervals {
|
||||
if !interval_is_covered(parent_intervals, cmin, cmax) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_ip_intervals(v: &[(Vec<u8>, Vec<u8>)]) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
let mut out: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
|
||||
for (min, max) in v {
|
||||
@@ -790,7 +974,8 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::data_model::common::X509NameDer;
|
||||
use crate::data_model::rc::{
|
||||
Afi, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, IpResourceSet,
|
||||
Afi, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
||||
IpAddressOrRange, IpResourceSet,
|
||||
};
|
||||
use crate::data_model::rc::{
|
||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||
@@ -1268,6 +1453,105 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issuer_effective_resources_index_and_indexed_resolvers_cover_success_and_failure_paths() {
|
||||
use crate::data_model::rc::{AsIdOrRange, IpPrefix};
|
||||
|
||||
let parent_ip = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 8,
|
||||
addr: vec![10, 0, 0, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let parent_as = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
|
||||
min: 64500,
|
||||
max: 64599,
|
||||
}])),
|
||||
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(65000)])),
|
||||
};
|
||||
let idx = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&parent_ip), Some(&parent_as))
|
||||
.expect("index builds");
|
||||
assert_eq!(idx.parent_ip_by_afi_items.as_ref().map(|v| v.len()), Some(1));
|
||||
assert_eq!(idx.parent_ip_merged_intervals.len(), 1);
|
||||
assert_eq!(idx.parent_asnum_intervals.as_ref().map(|v| v.len()), Some(1));
|
||||
assert_eq!(idx.parent_rdi_intervals.as_ref().map(|v| v.len()), Some(1));
|
||||
|
||||
let child_ip_subset = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 16,
|
||||
addr: vec![10, 1, 0, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
assert!(resolve_child_ip_resources_indexed(
|
||||
Some(&child_ip_subset),
|
||||
Some(&parent_ip),
|
||||
idx.parent_ip_by_afi_items.as_ref(),
|
||||
&idx.parent_ip_merged_intervals,
|
||||
)
|
||||
.expect("subset should resolve")
|
||||
.is_some());
|
||||
|
||||
let child_ip_bad = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 16,
|
||||
addr: vec![11, 0, 0, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let err = resolve_child_ip_resources_indexed(
|
||||
Some(&child_ip_bad),
|
||||
Some(&parent_ip),
|
||||
idx.parent_ip_by_afi_items.as_ref(),
|
||||
&idx.parent_ip_merged_intervals,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::ResourcesNotSubset));
|
||||
|
||||
let child_as_subset = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64542)])),
|
||||
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(65000)])),
|
||||
};
|
||||
assert!(resolve_child_as_resources_indexed(
|
||||
Some(&child_as_subset),
|
||||
Some(&parent_as),
|
||||
idx.parent_asnum_intervals.as_deref(),
|
||||
idx.parent_rdi_intervals.as_deref(),
|
||||
)
|
||||
.expect("subset as resolves")
|
||||
.is_some());
|
||||
|
||||
let child_as_bad = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(65123)])),
|
||||
rdi: None,
|
||||
};
|
||||
let err = resolve_child_as_resources_indexed(
|
||||
Some(&child_as_bad),
|
||||
Some(&parent_as),
|
||||
idx.parent_asnum_intervals.as_deref(),
|
||||
idx.parent_rdi_intervals.as_deref(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CaPathError::ResourcesNotSubset));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_child_ip_and_as_resources_success_paths() {
|
||||
use crate::data_model::rc::{AsIdOrRange, IpAddressOrRange, IpPrefix};
|
||||
|
||||
@@ -193,6 +193,59 @@ pub fn validate_ee_cert_path_with_prevalidated_issuer(
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ResourceCertificate, CertPathError> {
|
||||
let ee = ResourceCertificate::decode_der(ee_cert_der)?;
|
||||
validate_ee_cert_path_components(
|
||||
&ee,
|
||||
ee_cert_der,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
issuer_crl,
|
||||
issuer_crl_revoked_serials,
|
||||
issuer_ca_rsync_uri,
|
||||
issuer_crl_rsync_uri,
|
||||
validation_time,
|
||||
)?;
|
||||
Ok(ee)
|
||||
}
|
||||
|
||||
/// Validate the EE certificate path using a pre-decoded EE certificate and a pre-validated issuer.
|
||||
///
|
||||
/// This avoids re-decoding the embedded EE certificate when the caller already parsed it while
|
||||
/// decoding a signed object (e.g. ROA / ASPA).
|
||||
pub fn validate_ee_cert_path_with_predecoded_ee(
|
||||
ee: &ResourceCertificate,
|
||||
ee_cert_der: &[u8],
|
||||
issuer_ca: &ResourceCertificate,
|
||||
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
||||
issuer_crl: &RpkixCrl,
|
||||
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
issuer_crl_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<(), CertPathError> {
|
||||
validate_ee_cert_path_components(
|
||||
ee,
|
||||
ee_cert_der,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
issuer_crl,
|
||||
issuer_crl_revoked_serials,
|
||||
issuer_ca_rsync_uri,
|
||||
issuer_crl_rsync_uri,
|
||||
validation_time,
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_ee_cert_path_components(
|
||||
ee: &ResourceCertificate,
|
||||
ee_cert_der: &[u8],
|
||||
issuer_ca: &ResourceCertificate,
|
||||
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
||||
issuer_crl: &RpkixCrl,
|
||||
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
issuer_crl_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<(), CertPathError> {
|
||||
if ee.kind != ResourceCertKind::Ee {
|
||||
return Err(CertPathError::EeNotEe);
|
||||
}
|
||||
@@ -207,12 +260,12 @@ pub fn validate_ee_cert_path_with_prevalidated_issuer(
|
||||
});
|
||||
}
|
||||
|
||||
validate_ee_aki_matches_issuer_ski(&ee, issuer_ca)?;
|
||||
validate_ee_aki_matches_issuer_ski(ee, issuer_ca)?;
|
||||
if let Some(expected_issuer_uri) = issuer_ca_rsync_uri {
|
||||
validate_ee_aia_points_to_issuer_uri(&ee, expected_issuer_uri)?;
|
||||
validate_ee_aia_points_to_issuer_uri(ee, expected_issuer_uri)?;
|
||||
}
|
||||
if let Some(expected_crl_uri) = issuer_crl_rsync_uri {
|
||||
validate_ee_crldp_contains_issuer_crl_uri(&ee, expected_crl_uri)?;
|
||||
validate_ee_crldp_contains_issuer_crl_uri(ee, expected_crl_uri)?;
|
||||
}
|
||||
|
||||
if !time_within_validity(
|
||||
@@ -240,7 +293,7 @@ pub fn validate_ee_cert_path_with_prevalidated_issuer(
|
||||
return Err(CertPathError::EeRevoked);
|
||||
}
|
||||
|
||||
Ok(ee)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_subject_pki_from_der(der: &[u8]) -> Result<SubjectPublicKeyInfo<'_>, CertPathError> {
|
||||
|
||||
@@ -10,8 +10,10 @@ use crate::data_model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaVal
|
||||
use crate::data_model::signed_object::SignedObjectVerifyError;
|
||||
use crate::policy::{Policy, SignedObjectFailurePolicy};
|
||||
use crate::report::{RfcRef, Warning};
|
||||
use crate::storage::PackFile;
|
||||
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_prevalidated_issuer};
|
||||
use crate::storage::{PackFile, PackTime, VcirLocalOutput, VcirOutputType};
|
||||
use crate::validation::cert_path::{
|
||||
CertPathError, validate_ee_cert_path_with_predecoded_ee,
|
||||
};
|
||||
use crate::validation::manifest::PublicationPointData;
|
||||
use crate::validation::publication_point::PublicationPointSnapshot;
|
||||
use x509_parser::prelude::FromDer;
|
||||
@@ -67,6 +69,7 @@ pub struct AspaAttestation {
|
||||
pub struct ObjectsOutput {
|
||||
pub vrps: Vec<Vrp>,
|
||||
pub aspas: Vec<AspaAttestation>,
|
||||
pub local_outputs_cache: Vec<VcirLocalOutput>,
|
||||
pub warnings: Vec<Warning>,
|
||||
pub stats: ObjectsStats,
|
||||
pub audit: Vec<ObjectAuditEntry>,
|
||||
@@ -148,6 +151,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
@@ -171,6 +175,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
@@ -188,6 +193,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
@@ -246,6 +252,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
@@ -254,12 +261,14 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
|
||||
let mut vrps: Vec<Vrp> = Vec::new();
|
||||
let mut aspas: Vec<AspaAttestation> = Vec::new();
|
||||
let mut local_outputs_cache: Vec<VcirLocalOutput> = Vec::new();
|
||||
|
||||
for (idx, file) in locked_files.iter().enumerate() {
|
||||
if file.rsync_uri.ends_with(".roa") {
|
||||
let _t = timing.as_ref().map(|t| t.span_phase("objects_roa_total"));
|
||||
match process_roa_with_issuer(
|
||||
file,
|
||||
manifest_rsync_uri,
|
||||
issuer_ca_der,
|
||||
&issuer_ca,
|
||||
&issuer_spki,
|
||||
@@ -271,9 +280,10 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
validation_time,
|
||||
timing,
|
||||
) {
|
||||
Ok(mut out) => {
|
||||
Ok((mut out, local_outputs)) => {
|
||||
stats.roa_ok += 1;
|
||||
vrps.append(&mut out);
|
||||
local_outputs_cache.extend(local_outputs);
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
@@ -346,6 +356,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
@@ -357,6 +368,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
let _t = timing.as_ref().map(|t| t.span_phase("objects_aspa_total"));
|
||||
match process_aspa_with_issuer(
|
||||
file,
|
||||
manifest_rsync_uri,
|
||||
issuer_ca_der,
|
||||
&issuer_ca,
|
||||
&issuer_spki,
|
||||
@@ -368,9 +380,10 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
validation_time,
|
||||
timing,
|
||||
) {
|
||||
Ok(att) => {
|
||||
Ok((att, local_output)) => {
|
||||
stats.aspa_ok += 1;
|
||||
aspas.push(att);
|
||||
local_outputs_cache.push(local_output);
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
@@ -443,6 +456,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
@@ -456,6 +470,7 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
ObjectsOutput {
|
||||
vrps,
|
||||
aspas,
|
||||
local_outputs_cache,
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
@@ -537,6 +552,7 @@ enum ObjectValidateError {
|
||||
|
||||
fn process_roa_with_issuer(
|
||||
file: &PackFile,
|
||||
manifest_rsync_uri: &str,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca: &ResourceCertificate,
|
||||
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
||||
@@ -547,7 +563,7 @@ fn process_roa_with_issuer(
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
timing: Option<&TimingHandle>,
|
||||
) -> Result<Vec<Vrp>, ObjectValidateError> {
|
||||
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>), ObjectValidateError> {
|
||||
let _decode = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||
@@ -566,9 +582,9 @@ fn process_roa_with_issuer(
|
||||
roa.signed_object.verify()?;
|
||||
drop(_verify);
|
||||
|
||||
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
||||
let ee_der = &roa.signed_object.signed_data.certificates[0].raw_der;
|
||||
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||
.resource_cert
|
||||
let ee_crldp_uris = ee
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
@@ -579,7 +595,8 @@ fn process_roa_with_issuer(
|
||||
let _cert_path = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_roa_validate_ee_cert_path_total"));
|
||||
let ee = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
validate_ee_cert_path_with_predecoded_ee(
|
||||
ee,
|
||||
ee_der,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
@@ -602,11 +619,52 @@ fn process_roa_with_issuer(
|
||||
)?;
|
||||
drop(_subset);
|
||||
|
||||
Ok(roa_to_vrps(&roa))
|
||||
let vrps = roa_to_vrps(&roa);
|
||||
let source_object_hash = sha256_hex_from_32(&file.sha256);
|
||||
let source_ee_cert_hash = crate::audit::sha256_hex(ee.raw_der.as_slice());
|
||||
let item_effective_until = PackTime::from_utc_offset_datetime(ee.tbs.validity_not_after);
|
||||
let local_outputs = vrps
|
||||
.iter()
|
||||
.map(|vrp| {
|
||||
let prefix = vrp_prefix_to_string(vrp);
|
||||
let payload_json = serde_json::json!({
|
||||
"asn": vrp.asn,
|
||||
"prefix": prefix,
|
||||
"max_length": vrp.max_length,
|
||||
})
|
||||
.to_string();
|
||||
let rule_hash = crate::audit::sha256_hex(
|
||||
format!(
|
||||
"roa-rule:{}:{}:{}:{}",
|
||||
source_object_hash, vrp.asn, prefix, vrp.max_length
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
VcirLocalOutput {
|
||||
output_id: rule_hash.clone(),
|
||||
output_type: VcirOutputType::Vrp,
|
||||
item_effective_until: item_effective_until.clone(),
|
||||
source_object_uri: file.rsync_uri.clone(),
|
||||
source_object_type: "roa".to_string(),
|
||||
source_object_hash: source_object_hash.clone(),
|
||||
source_ee_cert_hash: source_ee_cert_hash.clone(),
|
||||
payload_json,
|
||||
rule_hash,
|
||||
validation_path_hint: vec![
|
||||
manifest_rsync_uri.to_string(),
|
||||
file.rsync_uri.clone(),
|
||||
source_object_hash.clone(),
|
||||
],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((vrps, local_outputs))
|
||||
}
|
||||
|
||||
fn process_aspa_with_issuer(
|
||||
file: &PackFile,
|
||||
manifest_rsync_uri: &str,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca: &ResourceCertificate,
|
||||
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
||||
@@ -617,7 +675,7 @@ fn process_aspa_with_issuer(
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
timing: Option<&TimingHandle>,
|
||||
) -> Result<AspaAttestation, ObjectValidateError> {
|
||||
) -> Result<(AspaAttestation, VcirLocalOutput), ObjectValidateError> {
|
||||
let _decode = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_aspa_decode_and_validate_total"));
|
||||
@@ -636,9 +694,9 @@ fn process_aspa_with_issuer(
|
||||
aspa.signed_object.verify()?;
|
||||
drop(_verify);
|
||||
|
||||
let ee = &aspa.signed_object.signed_data.certificates[0].resource_cert;
|
||||
let ee_der = &aspa.signed_object.signed_data.certificates[0].raw_der;
|
||||
let ee_crldp_uris = aspa.signed_object.signed_data.certificates[0]
|
||||
.resource_cert
|
||||
let ee_crldp_uris = ee
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
@@ -649,7 +707,8 @@ fn process_aspa_with_issuer(
|
||||
let _cert_path = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_aspa_validate_ee_cert_path_total"));
|
||||
let ee = validate_ee_cert_path_with_prevalidated_issuer(
|
||||
validate_ee_cert_path_with_predecoded_ee(
|
||||
ee,
|
||||
ee_der,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
@@ -672,10 +731,70 @@ fn process_aspa_with_issuer(
|
||||
)?;
|
||||
drop(_subset);
|
||||
|
||||
Ok(AspaAttestation {
|
||||
let attestation = AspaAttestation {
|
||||
customer_as_id: aspa.aspa.customer_as_id,
|
||||
provider_as_ids: aspa.aspa.provider_as_ids.clone(),
|
||||
})
|
||||
};
|
||||
let source_object_hash = sha256_hex_from_32(&file.sha256);
|
||||
let source_ee_cert_hash = crate::audit::sha256_hex(ee.raw_der.as_slice());
|
||||
let item_effective_until = PackTime::from_utc_offset_datetime(ee.tbs.validity_not_after);
|
||||
let providers = attestation
|
||||
.provider_as_ids
|
||||
.iter()
|
||||
.map(u32::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let rule_hash = crate::audit::sha256_hex(
|
||||
format!(
|
||||
"aspa-rule:{}:{}:{}",
|
||||
source_object_hash, attestation.customer_as_id, providers
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
let local_output = VcirLocalOutput {
|
||||
output_id: rule_hash.clone(),
|
||||
output_type: VcirOutputType::Aspa,
|
||||
item_effective_until,
|
||||
source_object_uri: file.rsync_uri.clone(),
|
||||
source_object_type: "aspa".to_string(),
|
||||
source_object_hash: source_object_hash.clone(),
|
||||
source_ee_cert_hash,
|
||||
payload_json: serde_json::json!({
|
||||
"customer_as_id": attestation.customer_as_id,
|
||||
"provider_as_ids": attestation.provider_as_ids,
|
||||
})
|
||||
.to_string(),
|
||||
rule_hash,
|
||||
validation_path_hint: vec![
|
||||
manifest_rsync_uri.to_string(),
|
||||
file.rsync_uri.clone(),
|
||||
source_object_hash,
|
||||
],
|
||||
};
|
||||
|
||||
Ok((attestation, local_output))
|
||||
}
|
||||
|
||||
|
||||
fn vrp_prefix_to_string(vrp: &Vrp) -> String {
|
||||
let prefix = &vrp.prefix;
|
||||
match prefix.afi {
|
||||
RoaAfi::Ipv4 => {
|
||||
let addr = std::net::Ipv4Addr::new(
|
||||
prefix.addr[0],
|
||||
prefix.addr[1],
|
||||
prefix.addr[2],
|
||||
prefix.addr[3],
|
||||
);
|
||||
format!("{addr}/{}", prefix.prefix_len)
|
||||
}
|
||||
RoaAfi::Ipv6 => {
|
||||
let mut octets = [0u8; 16];
|
||||
octets.copy_from_slice(&prefix.addr[..16]);
|
||||
let addr = std::net::Ipv6Addr::from(octets);
|
||||
format!("{addr}/{}", prefix.prefix_len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_crl_uri_for_certificate<'a>(
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::replay::archive::ReplayArchiveIndex;
|
||||
use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
|
||||
use crate::report::{RfcRef, Warning};
|
||||
use crate::storage::{
|
||||
AuditRuleIndexEntry, AuditRuleKind, PackFile, PackTime, RawByHashEntry, RocksStore,
|
||||
PackFile, PackTime, RawByHashEntry, RocksStore,
|
||||
ValidatedCaInstanceResult, VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus,
|
||||
VcirAuditSummary, VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirOutputType,
|
||||
VcirRelatedArtifact, VcirSummary,
|
||||
@@ -26,7 +26,8 @@ use crate::sync::repo::{
|
||||
use crate::sync::rrdp::Fetcher;
|
||||
use crate::validation::ca_instance::ca_instance_uris_from_ca_certificate;
|
||||
use crate::validation::ca_path::{
|
||||
CaPathError, ValidatedSubordinateCaLite, validate_subordinate_ca_cert_with_prevalidated_issuer,
|
||||
CaPathError, IssuerEffectiveResourcesIndex, ValidatedSubordinateCaLite,
|
||||
validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources,
|
||||
};
|
||||
use crate::validation::manifest::{
|
||||
ManifestFreshError, PublicationPointData, PublicationPointSource,
|
||||
@@ -483,6 +484,11 @@ fn discover_children_from_fresh_snapshot_with_audit<P: PublicationPointData>(
|
||||
|
||||
let mut out: Vec<DiscoveredChildCaInstance> = Vec::new();
|
||||
let mut audits: Vec<ObjectAuditEntry> = Vec::new();
|
||||
let issuer_resources_index = IssuerEffectiveResourcesIndex::from_effective_resources(
|
||||
issuer.effective_ip_resources.as_ref(),
|
||||
issuer.effective_as_resources.as_ref(),
|
||||
)
|
||||
.map_err(|e| format!("build issuer effective resources index failed: {e}"))?;
|
||||
|
||||
let mut cer_seen: u64 = 0;
|
||||
let mut ca_skipped_not_ca: u64 = 0;
|
||||
@@ -628,6 +634,7 @@ fn discover_children_from_fresh_snapshot_with_audit<P: PublicationPointData>(
|
||||
issuer.ca_certificate_rsync_uri.as_deref(),
|
||||
issuer.effective_ip_resources.as_ref(),
|
||||
issuer.effective_as_resources.as_ref(),
|
||||
&issuer_resources_index,
|
||||
validation_time,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
@@ -839,11 +846,12 @@ fn validate_subordinate_ca_cert_with_cached_issuer(
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
issuer_resources_index: &IssuerEffectiveResourcesIndex,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
|
||||
let verified_crl = ensure_issuer_crl_verified(issuer_crl_rsync_uri, crl_cache, issuer_ca_der)?;
|
||||
|
||||
validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
||||
child_ca_der,
|
||||
child_ca,
|
||||
issuer_ca,
|
||||
@@ -854,6 +862,7 @@ fn validate_subordinate_ca_cert_with_cached_issuer(
|
||||
issuer_crl_rsync_uri,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
validation_time,
|
||||
)
|
||||
}
|
||||
@@ -1172,6 +1181,7 @@ fn empty_objects_output() -> crate::validation::objects::ObjectsOutput {
|
||||
crate::validation::objects::ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: crate::validation::objects::ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -1669,36 +1679,8 @@ fn persist_vcir_for_fresh_result(
|
||||
.map_err(|e| format!("load existing VCIR failed: {e}"))?;
|
||||
|
||||
store
|
||||
.put_vcir(&vcir)
|
||||
.map_err(|e| format!("store VCIR failed: {e}"))?;
|
||||
|
||||
if let Some(previous) = previous.as_ref() {
|
||||
for output in &previous.local_outputs {
|
||||
if let Some(kind) = audit_rule_kind_for_output(output.output_type) {
|
||||
store
|
||||
.delete_audit_rule_index_entry(kind, &output.rule_hash)
|
||||
.map_err(|e| format!("delete stale audit rule index failed: {e}"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for output in &vcir.local_outputs {
|
||||
let Some(kind) = audit_rule_kind_for_output(output.output_type) else {
|
||||
continue;
|
||||
};
|
||||
let entry = AuditRuleIndexEntry {
|
||||
kind,
|
||||
rule_hash: output.rule_hash.clone(),
|
||||
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
|
||||
source_object_uri: output.source_object_uri.clone(),
|
||||
source_object_hash: output.source_object_hash.clone(),
|
||||
output_id: output.output_id.clone(),
|
||||
item_effective_until: output.item_effective_until.clone(),
|
||||
};
|
||||
store
|
||||
.put_audit_rule_index_entry(&entry)
|
||||
.map_err(|e| format!("store audit rule index failed: {e}"))?;
|
||||
}
|
||||
.replace_vcir_and_audit_rule_indexes(previous.as_ref(), &vcir)
|
||||
.map_err(|e| format!("store VCIR and audit rule index failed: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1847,6 +1829,10 @@ fn build_vcir_local_outputs(
|
||||
pack: &PublicationPointSnapshot,
|
||||
objects: &crate::validation::objects::ObjectsOutput,
|
||||
) -> Result<Vec<VcirLocalOutput>, String> {
|
||||
if !objects.local_outputs_cache.is_empty() {
|
||||
return Ok(objects.local_outputs_cache.clone());
|
||||
}
|
||||
|
||||
let accepted_roa_uris: HashSet<&str> = objects
|
||||
.audit
|
||||
.iter()
|
||||
@@ -2250,12 +2236,6 @@ fn audit_result_to_vcir_status(result: &AuditObjectResult) -> VcirArtifactValida
|
||||
}
|
||||
}
|
||||
|
||||
fn audit_rule_kind_for_output(output_type: VcirOutputType) -> Option<AuditRuleKind> {
|
||||
match output_type {
|
||||
VcirOutputType::Vrp => Some(AuditRuleKind::Roa),
|
||||
VcirOutputType::Aspa => Some(AuditRuleKind::Aspa),
|
||||
}
|
||||
}
|
||||
|
||||
fn roa_to_vrps_for_vcir(roa: &RoaObject) -> Vec<Vrp> {
|
||||
let asn = roa.roa.as_id;
|
||||
@@ -2298,7 +2278,7 @@ mod tests {
|
||||
use crate::fetch::rsync::LocalDirRsyncFetcher;
|
||||
use crate::fetch::rsync::{RsyncFetchError, RsyncFetcher};
|
||||
use crate::storage::{
|
||||
PackFile, PackTime, RawByHashEntry, ValidatedCaInstanceResult, ValidatedManifestMeta,
|
||||
PackFile, PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
|
||||
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
|
||||
VcirChildEntry, VcirInstanceGate, VcirLocalOutput, VcirOutputType, VcirRelatedArtifact,
|
||||
VcirSummary,
|
||||
@@ -2529,6 +2509,64 @@ authorityKeyIdentifier = keyid:always
|
||||
}
|
||||
}
|
||||
|
||||
fn cernet_publication_point_snapshot_for_vcir_tests(
|
||||
) -> (PublicationPointSnapshot, Vec<u8>, time::OffsetDateTime) {
|
||||
let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0");
|
||||
let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/";
|
||||
let manifest_file = "05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft";
|
||||
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
||||
let manifest_bytes = std::fs::read(dir.join(manifest_file)).expect("read manifest fixture");
|
||||
let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest fixture");
|
||||
let candidate = manifest.manifest.this_update + time::Duration::seconds(60);
|
||||
let validation_time = if candidate < manifest.manifest.next_update {
|
||||
candidate
|
||||
} else {
|
||||
manifest.manifest.this_update
|
||||
};
|
||||
|
||||
let issuer_ca_der = std::fs::read(
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
|
||||
),
|
||||
)
|
||||
.expect("read issuer ca fixture");
|
||||
|
||||
let store_dir = tempfile::tempdir().expect("store dir");
|
||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
||||
let policy = Policy {
|
||||
sync_preference: crate::policy::SyncPreference::RsyncOnly,
|
||||
..Policy::default()
|
||||
};
|
||||
|
||||
sync_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
None,
|
||||
rsync_base_uri,
|
||||
&NeverHttpFetcher,
|
||||
&LocalDirRsyncFetcher::new(&dir),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("sync cernet fixture");
|
||||
|
||||
let pp = crate::validation::manifest::process_manifest_publication_point(
|
||||
&store,
|
||||
&policy,
|
||||
&manifest_rsync_uri,
|
||||
rsync_base_uri,
|
||||
issuer_ca_der.as_slice(),
|
||||
Some(
|
||||
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
|
||||
),
|
||||
validation_time,
|
||||
)
|
||||
.expect("process manifest publication point");
|
||||
|
||||
(pp.snapshot, issuer_ca_der, validation_time)
|
||||
}
|
||||
|
||||
fn sample_vcir_for_projection(
|
||||
now: time::OffsetDateTime,
|
||||
child_cert_hash: &str,
|
||||
@@ -2693,6 +2731,327 @@ authorityKeyIdentifier = keyid:always
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_vcir_local_outputs_prefers_cached_outputs() {
|
||||
let pack = dummy_pack_with_files(vec![]);
|
||||
let ca = CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
ca_certificate_der: vec![1],
|
||||
ca_certificate_rsync_uri: None,
|
||||
effective_ip_resources: None,
|
||||
effective_as_resources: None,
|
||||
rsync_base_uri: pack.publication_point_rsync_uri.clone(),
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
|
||||
rrdp_notification_uri: None,
|
||||
};
|
||||
let cached = vec![VcirLocalOutput {
|
||||
output_id: "cached-output".to_string(),
|
||||
output_type: VcirOutputType::Vrp,
|
||||
item_effective_until: pack.next_update.clone(),
|
||||
source_object_uri: "rsync://example.test/repo/issuer/a.roa".to_string(),
|
||||
source_object_type: "roa".to_string(),
|
||||
source_object_hash: sha256_hex(b"cached-roa"),
|
||||
source_ee_cert_hash: sha256_hex(b"cached-ee"),
|
||||
payload_json: "{\"asn\":64500}".to_string(),
|
||||
rule_hash: sha256_hex(b"cached-rule"),
|
||||
validation_path_hint: vec![pack.manifest_rsync_uri.clone()],
|
||||
}];
|
||||
let outputs = build_vcir_local_outputs(
|
||||
&ca,
|
||||
&pack,
|
||||
&crate::validation::objects::ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: cached.clone(),
|
||||
warnings: Vec::new(),
|
||||
stats: crate::validation::objects::ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
},
|
||||
)
|
||||
.expect("reuse cached outputs");
|
||||
assert_eq!(outputs, cached);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_and_persist_vcir_embedded_evidence_for_real_signed_objects() {
|
||||
let (pack, issuer_ca_der, validation_time) = cernet_publication_point_snapshot_for_vcir_tests();
|
||||
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer ca");
|
||||
let objects = crate::validation::objects::process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&Policy::default(),
|
||||
issuer_ca_der.as_slice(),
|
||||
Some("rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"),
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
assert!(!objects.local_outputs_cache.is_empty(), "expected local outputs from signed objects");
|
||||
|
||||
let evidence = collect_vcir_embedded_evidence(&pack, &objects).expect("collect embedded evidence");
|
||||
assert!(evidence.len() >= 2, "expected manifest EE and signed-object EE evidence");
|
||||
|
||||
let store_dir = tempfile::tempdir().expect("store dir");
|
||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
||||
let ca = CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
ca_certificate_der: issuer_ca_der.clone(),
|
||||
ca_certificate_rsync_uri: Some(
|
||||
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer".to_string(),
|
||||
),
|
||||
effective_ip_resources: issuer_ca.tbs.extensions.ip_resources.clone(),
|
||||
effective_as_resources: issuer_ca.tbs.extensions.as_resources.clone(),
|
||||
rsync_base_uri: pack.publication_point_rsync_uri.clone(),
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
|
||||
rrdp_notification_uri: None,
|
||||
};
|
||||
persist_vcir_non_repository_evidence(&store, &ca, &evidence)
|
||||
.expect("persist embedded evidence");
|
||||
|
||||
let issuer_hash = sha256_hex(&issuer_ca_der);
|
||||
let issuer_entry = store
|
||||
.get_raw_by_hash_entry(&issuer_hash)
|
||||
.expect("load issuer raw entry")
|
||||
.expect("issuer raw entry present");
|
||||
assert!(issuer_entry
|
||||
.origin_uris
|
||||
.iter()
|
||||
.any(|uri| uri.ends_with("BfycW4hQb3wNP4YsiJW-1n6fjro.cer")));
|
||||
for entry in &evidence {
|
||||
assert!(store
|
||||
.get_raw_by_hash_entry(&entry.raw_entry.sha256_hex)
|
||||
.expect("load evidence raw entry")
|
||||
.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_vcir_local_outputs_falls_back_to_decoding_accepted_objects_when_cache_is_empty() {
|
||||
let (pack, issuer_ca_der, validation_time) = cernet_publication_point_snapshot_for_vcir_tests();
|
||||
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer ca");
|
||||
let objects = crate::validation::objects::process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&Policy::default(),
|
||||
issuer_ca_der.as_slice(),
|
||||
Some("rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"),
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
let mut objects_without_cache = objects.clone();
|
||||
objects_without_cache.local_outputs_cache.clear();
|
||||
let ca = CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
ca_certificate_der: issuer_ca_der,
|
||||
ca_certificate_rsync_uri: Some(
|
||||
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer".to_string(),
|
||||
),
|
||||
effective_ip_resources: issuer_ca.tbs.extensions.ip_resources.clone(),
|
||||
effective_as_resources: issuer_ca.tbs.extensions.as_resources.clone(),
|
||||
rsync_base_uri: pack.publication_point_rsync_uri.clone(),
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
|
||||
rrdp_notification_uri: None,
|
||||
};
|
||||
let local_outputs = build_vcir_local_outputs(&ca, &pack, &objects_without_cache)
|
||||
.expect("rebuild vcir local outputs");
|
||||
assert!(!local_outputs.is_empty());
|
||||
assert_eq!(local_outputs.len(), objects.vrps.len());
|
||||
assert!(local_outputs
|
||||
.iter()
|
||||
.all(|output| output.output_type == VcirOutputType::Vrp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persist_vcir_for_fresh_result_stores_vcir_and_audit_indexes_for_real_snapshot() {
|
||||
let (pack, issuer_ca_der, validation_time) = cernet_publication_point_snapshot_for_vcir_tests();
|
||||
let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer ca");
|
||||
let objects = crate::validation::objects::process_publication_point_snapshot_for_issuer(
|
||||
&pack,
|
||||
&Policy::default(),
|
||||
issuer_ca_der.as_slice(),
|
||||
Some("rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"),
|
||||
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||
validation_time,
|
||||
None,
|
||||
);
|
||||
let store_dir = tempfile::tempdir().expect("store dir");
|
||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
||||
let ca = CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
ca_certificate_der: issuer_ca_der.clone(),
|
||||
ca_certificate_rsync_uri: Some(
|
||||
"rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer".to_string(),
|
||||
),
|
||||
effective_ip_resources: issuer_ca.tbs.extensions.ip_resources.clone(),
|
||||
effective_as_resources: issuer_ca.tbs.extensions.as_resources.clone(),
|
||||
rsync_base_uri: pack.publication_point_rsync_uri.clone(),
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
|
||||
rrdp_notification_uri: None,
|
||||
};
|
||||
|
||||
persist_vcir_for_fresh_result(
|
||||
&store,
|
||||
&ca,
|
||||
&pack,
|
||||
&objects,
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
validation_time,
|
||||
)
|
||||
.expect("persist vcir for fresh result");
|
||||
|
||||
let vcir = store
|
||||
.get_vcir(&pack.manifest_rsync_uri)
|
||||
.expect("get vcir")
|
||||
.expect("vcir exists");
|
||||
assert_eq!(vcir.manifest_rsync_uri, pack.manifest_rsync_uri);
|
||||
assert_eq!(vcir.summary.local_vrp_count as usize, objects.vrps.len());
|
||||
let first_output = vcir.local_outputs.first().expect("local outputs stored");
|
||||
assert!(store
|
||||
.get_audit_rule_index_entry(crate::storage::AuditRuleKind::Roa, &first_output.rule_hash)
|
||||
.expect("get audit rule index entry")
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_vcir_related_artifacts_classifies_snapshot_files_and_audit_statuses() {
|
||||
let manifest_bytes = std::fs::read(
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||
),
|
||||
)
|
||||
.expect("read manifest fixture");
|
||||
let crl_bytes = std::fs::read(
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||
),
|
||||
)
|
||||
.expect("read crl fixture");
|
||||
let pack = PublicationPointSnapshot {
|
||||
format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: "rsync://example.test/repo/issuer/issuer.mft".to_string(),
|
||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
this_update: PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()),
|
||||
next_update: PackTime::from_utc_offset_datetime(
|
||||
time::OffsetDateTime::now_utc() + time::Duration::hours(1),
|
||||
),
|
||||
verified_at: PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()),
|
||||
manifest_bytes,
|
||||
files: vec![
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
crl_bytes,
|
||||
),
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.test/repo/issuer/child.cer",
|
||||
vec![1u8, 2],
|
||||
),
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.test/repo/issuer/a.roa",
|
||||
vec![3u8, 4],
|
||||
),
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.test/repo/issuer/a.asa",
|
||||
vec![5u8, 6],
|
||||
),
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.test/repo/issuer/a.gbr",
|
||||
vec![7u8, 8],
|
||||
),
|
||||
PackFile::from_bytes_compute_sha256(
|
||||
"rsync://example.test/repo/issuer/extra.bin",
|
||||
vec![9u8],
|
||||
),
|
||||
],
|
||||
};
|
||||
let ca = CaInstanceHandle {
|
||||
depth: 0,
|
||||
tal_id: "test-tal".to_string(),
|
||||
parent_manifest_rsync_uri: None,
|
||||
ca_certificate_der: vec![0x11, 0x22],
|
||||
ca_certificate_rsync_uri: Some("rsync://example.test/repo/issuer/issuer.cer".to_string()),
|
||||
effective_ip_resources: None,
|
||||
effective_as_resources: None,
|
||||
rsync_base_uri: pack.publication_point_rsync_uri.clone(),
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
|
||||
rrdp_notification_uri: None,
|
||||
};
|
||||
let objects = crate::validation::objects::ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: crate::validation::objects::ObjectsStats::default(),
|
||||
audit: vec![
|
||||
ObjectAuditEntry {
|
||||
rsync_uri: "rsync://example.test/repo/issuer/a.roa".to_string(),
|
||||
sha256_hex: sha256_hex_from_32(&pack.files[2].sha256),
|
||||
kind: AuditObjectKind::Roa,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some("bad roa".to_string()),
|
||||
},
|
||||
ObjectAuditEntry {
|
||||
rsync_uri: "rsync://example.test/repo/issuer/a.asa".to_string(),
|
||||
sha256_hex: sha256_hex_from_32(&pack.files[3].sha256),
|
||||
kind: AuditObjectKind::Aspa,
|
||||
result: AuditObjectResult::Skipped,
|
||||
detail: Some("skipped aspa".to_string()),
|
||||
},
|
||||
],
|
||||
};
|
||||
let embedded = vec![VcirEmbeddedEvidence {
|
||||
artifact: VcirRelatedArtifact {
|
||||
artifact_role: VcirArtifactRole::EeCert,
|
||||
artifact_kind: VcirArtifactKind::Cer,
|
||||
uri: None,
|
||||
sha256: sha256_hex(b"embedded-ee"),
|
||||
object_type: Some("cer".to_string()),
|
||||
validation_status: VcirArtifactValidationStatus::Accepted,
|
||||
},
|
||||
raw_entry: embedded_raw_entry(sha256_hex(b"embedded-ee"), vec![1u8, 2, 3]),
|
||||
}];
|
||||
let artifacts = build_vcir_related_artifacts(
|
||||
&ca,
|
||||
&pack,
|
||||
"rsync://example.test/repo/issuer/issuer.crl",
|
||||
&objects,
|
||||
&[],
|
||||
&embedded,
|
||||
);
|
||||
assert!(artifacts.iter().any(|artifact| artifact.artifact_role == VcirArtifactRole::Manifest));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.artifact_role == VcirArtifactRole::TrustAnchorCert));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/issuer.crl")
|
||||
&& artifact.artifact_role == VcirArtifactRole::CurrentCrl));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/child.cer")
|
||||
&& artifact.artifact_role == VcirArtifactRole::ChildCaCert));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/a.roa")
|
||||
&& artifact.validation_status == VcirArtifactValidationStatus::Rejected));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/a.asa")
|
||||
&& artifact.validation_status == VcirArtifactValidationStatus::WarningOnly));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/a.gbr")
|
||||
&& artifact.artifact_kind == VcirArtifactKind::Gbr));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/extra.bin")
|
||||
&& artifact.artifact_kind == VcirArtifactKind::Other));
|
||||
assert!(artifacts.iter().any(|artifact| artifact.uri.is_none() && artifact.sha256 == sha256_hex(b"embedded-ee")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_issuer_crl_from_snapshot_reports_missing_crldp_for_self_signed_cert() {
|
||||
let ta_der = std::fs::read(
|
||||
@@ -3207,6 +3566,7 @@ authorityKeyIdentifier = keyid:always
|
||||
let objects = crate::validation::objects::ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: crate::validation::objects::ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
@@ -3267,6 +3627,7 @@ authorityKeyIdentifier = keyid:always
|
||||
let objects = crate::validation::objects::ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
stats: crate::validation::objects::ObjectsStats::default(),
|
||||
audit: vec![ObjectAuditEntry {
|
||||
@@ -3872,6 +4233,7 @@ authorityKeyIdentifier = keyid:always
|
||||
let objects = crate::validation::objects::ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: vec![Warning::new("objects warning")],
|
||||
stats: crate::validation::objects::ObjectsStats::default(),
|
||||
audit: vec![ObjectAuditEntry {
|
||||
@@ -3956,6 +4318,7 @@ authorityKeyIdentifier = keyid:always
|
||||
&crate::validation::objects::ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
local_outputs_cache: Vec::new(),
|
||||
warnings: vec![Warning::new("object warning")],
|
||||
stats: crate::validation::objects::ObjectsStats::default(),
|
||||
audit: Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user