20260701 add validation-update-03 VRS mode

This commit is contained in:
2026-07-01 20:28:25 +08:00
parent a501cfb709
commit a5b194ce35
14 changed files with 1430 additions and 24 deletions

View File

@@ -5,6 +5,7 @@ use crate::data_model::rc::{
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
ResourceCertificate, ResourceCertificateDecodeError,
};
use crate::policy::ResourceValidationMode;
use x509_parser::prelude::{FromDer, X509Certificate};
use crate::validation::x509_name::x509_names_equivalent;
@@ -18,6 +19,7 @@ pub struct ValidatedSubordinateCa {
pub issuer_crl: RpkixCrl,
pub effective_ip_resources: Option<IpResourceSet>,
pub effective_as_resources: Option<AsResourceSet>,
pub resource_warnings: ResourceValidationWarnings,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -25,6 +27,41 @@ pub struct ValidatedSubordinateCaLite {
pub child_ca: ResourceCertificate,
pub effective_ip_resources: Option<IpResourceSet>,
pub effective_as_resources: Option<AsResourceSet>,
pub resource_warnings: ResourceValidationWarnings,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ResourceValidationWarnings {
pub ip_reduced_by_vrs: bool,
pub as_reduced_by_vrs: bool,
pub ip_vrs_empty: bool,
pub as_vrs_empty: bool,
}
impl ResourceValidationWarnings {
pub fn is_empty(&self) -> bool {
!self.ip_reduced_by_vrs
&& !self.as_reduced_by_vrs
&& !self.ip_vrs_empty
&& !self.as_vrs_empty
}
pub fn summary(&self) -> String {
let mut parts = Vec::new();
if self.ip_reduced_by_vrs {
parts.push("ip_reduced_by_vrs");
}
if self.as_reduced_by_vrs {
parts.push("as_reduced_by_vrs");
}
if self.ip_vrs_empty {
parts.push("ip_vrs_empty");
}
if self.as_vrs_empty {
parts.push("as_vrs_empty");
}
parts.join(",")
}
}
#[derive(Clone, Debug, Default)]
@@ -178,6 +215,30 @@ pub fn validate_subordinate_ca_cert(
issuer_effective_ip: Option<&IpResourceSet>,
issuer_effective_as: Option<&AsResourceSet>,
validation_time: time::OffsetDateTime,
) -> Result<ValidatedSubordinateCa, CaPathError> {
validate_subordinate_ca_cert_with_resource_validation_mode(
child_ca_der,
issuer_ca_der,
issuer_crl_der,
issuer_ca_rsync_uri,
issuer_crl_rsync_uri,
issuer_effective_ip,
issuer_effective_as,
validation_time,
ResourceValidationMode::Rfc6487,
)
}
pub fn validate_subordinate_ca_cert_with_resource_validation_mode(
child_ca_der: &[u8],
issuer_ca_der: &[u8],
issuer_crl_der: &[u8],
issuer_ca_rsync_uri: Option<&str>,
issuer_crl_rsync_uri: &str,
issuer_effective_ip: Option<&IpResourceSet>,
issuer_effective_as: Option<&AsResourceSet>,
validation_time: time::OffsetDateTime,
resource_validation_mode: ResourceValidationMode,
) -> Result<ValidatedSubordinateCa, CaPathError> {
let child_ca = ResourceCertificate::decode_der(child_ca_der)?;
if child_ca.kind != ResourceCertKind::Ca {
@@ -230,13 +291,20 @@ pub fn validate_subordinate_ca_cert(
return Err(CaPathError::ChildRevoked);
}
let effective_ip_resources = resolve_child_ip_resources(
let ResourceResolution {
effective_ip_resources,
effective_as_resources,
warnings: resource_warnings,
} = resolve_child_resources(
child_ca.tbs.extensions.ip_resources.as_ref(),
issuer_effective_ip,
)?;
let effective_as_resources = resolve_child_as_resources(
child_ca.tbs.extensions.as_resources.as_ref(),
issuer_effective_as,
&IssuerEffectiveResourcesIndex::from_effective_resources(
issuer_effective_ip,
issuer_effective_as,
)?,
resource_validation_mode,
)?;
if effective_ip_resources.is_none() && effective_as_resources.is_none() {
return Err(CaPathError::ResourcesMissing);
@@ -248,6 +316,7 @@ pub fn validate_subordinate_ca_cert(
issuer_crl,
effective_ip_resources,
effective_as_resources,
resource_warnings,
})
}
@@ -285,6 +354,7 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer(
issuer_effective_as,
&issuer_resources_index,
validation_time,
ResourceValidationMode::Rfc6487,
)
}
@@ -301,6 +371,7 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
issuer_effective_as: Option<&AsResourceSet>,
issuer_resources_index: &IssuerEffectiveResourcesIndex,
validation_time: time::OffsetDateTime,
resource_validation_mode: ResourceValidationMode,
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
if child_ca.kind != ResourceCertKind::Ca {
return Err(CaPathError::ChildNotCa);
@@ -347,17 +418,17 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
return Err(CaPathError::ChildRevoked);
}
let effective_ip_resources = resolve_child_ip_resources_indexed(
let ResourceResolution {
effective_ip_resources,
effective_as_resources,
warnings: resource_warnings,
} = resolve_child_resources(
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_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(),
issuer_resources_index,
resource_validation_mode,
)?;
if effective_ip_resources.is_none() && effective_as_resources.is_none() {
return Err(CaPathError::ResourcesMissing);
@@ -367,6 +438,7 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
child_ca,
effective_ip_resources,
effective_as_resources,
resource_warnings,
})
}
@@ -506,6 +578,66 @@ fn is_serial_revoked_by_crl(cert: &ResourceCertificate, crl: &RpkixCrl) -> bool
.any(|rc| rc.serial_number == serial)
}
#[derive(Clone, Debug)]
struct ResourceResolution {
effective_ip_resources: Option<IpResourceSet>,
effective_as_resources: Option<AsResourceSet>,
warnings: ResourceValidationWarnings,
}
fn resolve_child_resources(
child_ip: Option<&IpResourceSet>,
issuer_effective_ip: Option<&IpResourceSet>,
child_as: Option<&AsResourceSet>,
issuer_effective_as: Option<&AsResourceSet>,
issuer_resources_index: &IssuerEffectiveResourcesIndex,
mode: ResourceValidationMode,
) -> Result<ResourceResolution, CaPathError> {
match mode {
ResourceValidationMode::Rfc6487 => Ok(ResourceResolution {
effective_ip_resources: resolve_child_ip_resources_indexed(
child_ip,
issuer_effective_ip,
issuer_resources_index.parent_ip_by_afi_items.as_ref(),
&issuer_resources_index.parent_ip_merged_intervals,
)?,
effective_as_resources: resolve_child_as_resources_indexed(
child_as,
issuer_effective_as,
issuer_resources_index.parent_asnum_intervals.as_deref(),
issuer_resources_index.parent_rdi_intervals.as_deref(),
)?,
warnings: ResourceValidationWarnings::default(),
}),
ResourceValidationMode::ValidationUpdate03 => {
let (effective_ip_resources, ip_reduced_by_vrs, ip_vrs_empty) =
resolve_child_ip_resources_vrs(
child_ip,
issuer_effective_ip,
issuer_resources_index.parent_ip_by_afi_items.as_ref(),
&issuer_resources_index.parent_ip_merged_intervals,
)?;
let (effective_as_resources, as_reduced_by_vrs, as_vrs_empty) =
resolve_child_as_resources_vrs(
child_as,
issuer_effective_as,
issuer_resources_index.parent_asnum_intervals.as_deref(),
issuer_resources_index.parent_rdi_intervals.as_deref(),
)?;
Ok(ResourceResolution {
effective_ip_resources,
effective_as_resources,
warnings: ResourceValidationWarnings {
ip_reduced_by_vrs,
as_reduced_by_vrs,
ip_vrs_empty,
as_vrs_empty,
},
})
}
}
}
fn resolve_child_ip_resources(
child_ip: Option<&IpResourceSet>,
issuer_effective: Option<&IpResourceSet>,
@@ -581,6 +713,77 @@ fn resolve_child_ip_resources_indexed(
}))
}
fn resolve_child_ip_resources_vrs(
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>, bool, bool), CaPathError> {
let Some(child_ip) = child_ip else {
return Ok((None, false, false));
};
if child_ip.has_any_inherit() && issuer_effective.is_none() {
return Err(CaPathError::InheritWithoutParentResources);
}
let parent_by_afi = parent_by_afi.unwrap_or_else(|| {
static EMPTY: std::sync::OnceLock<
BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>,
> = std::sync::OnceLock::new();
EMPTY.get_or_init(BTreeMap::new)
});
let mut out_families = Vec::new();
let mut reduced = false;
let mut saw_declared = false;
for fam in &child_ip.families {
match &fam.choice {
IpAddressChoice::Inherit => {
let items = parent_by_afi
.get(&fam.afi)
.ok_or(CaPathError::InheritWithoutParentResources)?;
out_families.push(crate::data_model::rc::IpAddressFamily {
afi: fam.afi,
choice: IpAddressChoice::AddressesOrRanges(items.clone()),
});
}
IpAddressChoice::AddressesOrRanges(items) => {
saw_declared = saw_declared || !items.is_empty();
let parent_intervals = parent_intervals_by_afi
.get(&fam.afi)
.map(Vec::as_slice)
.unwrap_or(&[]);
let intersections =
intersect_ip_items_with_parent_intervals(items, parent_intervals);
let child_intervals = ip_items_to_merged_intervals(items);
if intervals_changed(&child_intervals, &intersections) {
reduced = true;
}
if !intersections.is_empty() {
out_families.push(crate::data_model::rc::IpAddressFamily {
afi: fam.afi,
choice: IpAddressChoice::AddressesOrRanges(ip_intervals_to_ranges(
fam.afi,
&intersections,
)),
});
}
}
}
}
let empty = saw_declared && out_families.is_empty();
Ok((
Some(IpResourceSet {
families: out_families,
}),
reduced,
empty,
))
}
fn resolve_child_as_resources(
child_as: Option<&AsResourceSet>,
issuer_effective: Option<&AsResourceSet>,
@@ -656,6 +859,80 @@ fn resolve_child_as_resources_indexed(
Ok(Some(AsResourceSet { asnum, rdi }))
}
fn resolve_child_as_resources_vrs(
child_as: Option<&AsResourceSet>,
issuer_effective: Option<&AsResourceSet>,
parent_asnum_intervals: Option<&[(u32, u32)]>,
parent_rdi_intervals: Option<&[(u32, u32)]>,
) -> Result<(Option<AsResourceSet>, bool, bool), CaPathError> {
let Some(child_as) = child_as else {
return Ok((None, false, false));
};
if issuer_effective.is_none()
&& (matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit))
|| matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit)))
{
return Err(CaPathError::InheritWithoutParentResources);
}
let (asnum, asnum_reduced, asnum_empty) = resolve_as_choice_vrs(
child_as.asnum.as_ref(),
issuer_effective.and_then(|p| p.asnum.as_ref()),
parent_asnum_intervals,
)?;
let (rdi, rdi_reduced, rdi_empty) = resolve_as_choice_vrs(
child_as.rdi.as_ref(),
issuer_effective.and_then(|p| p.rdi.as_ref()),
parent_rdi_intervals,
)?;
Ok((
Some(AsResourceSet { asnum, rdi }),
asnum_reduced || rdi_reduced,
asnum_empty || rdi_empty,
))
}
fn resolve_as_choice_vrs(
child: Option<&AsIdentifierChoice>,
parent: Option<&AsIdentifierChoice>,
parent_intervals_hint: Option<&[(u32, u32)]>,
) -> Result<(Option<AsIdentifierChoice>, bool, bool), CaPathError> {
let Some(child) = child else {
return Ok((None, false, false));
};
match child {
AsIdentifierChoice::Inherit => {
let parent = parent
.cloned()
.ok_or(CaPathError::InheritWithoutParentResources)?;
Ok((Some(parent), false, false))
}
AsIdentifierChoice::AsIdsOrRanges(_) => {
let child_intervals = as_choice_to_merged_intervals(child);
let parent_intervals;
let parent_intervals = match parent_intervals_hint {
Some(v) => v,
None => {
parent_intervals = parent
.map(as_choice_to_merged_intervals)
.unwrap_or_default();
parent_intervals.as_slice()
}
};
let intersections = intersect_as_intervals(&child_intervals, parent_intervals);
let reduced = child_intervals != intersections;
let empty = !child_intervals.is_empty() && intersections.is_empty();
Ok((
Some(AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items(
&intersections,
))),
reduced,
empty,
))
}
}
}
fn as_choice_subset(
child: Option<&AsIdentifierChoice>,
parent: Option<&AsIdentifierChoice>,
@@ -911,6 +1188,134 @@ fn ip_family_items_subset_with_parent_intervals(
true
}
fn ip_items_to_merged_intervals(
items: &[crate::data_model::rc::IpAddressOrRange],
) -> Vec<(Vec<u8>, Vec<u8>)> {
let mut intervals = Vec::new();
for item in items {
match item {
crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
intervals.push(prefix_to_range(p))
}
crate::data_model::rc::IpAddressOrRange::Range(r) => {
intervals.push((r.min.clone(), r.max.clone()))
}
}
}
intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
merge_ip_intervals(&intervals)
}
fn intersect_ip_items_with_parent_intervals(
items: &[crate::data_model::rc::IpAddressOrRange],
parent_intervals: &[(Vec<u8>, Vec<u8>)],
) -> Vec<(Vec<u8>, Vec<u8>)> {
let child_intervals = ip_items_to_merged_intervals(items);
intersect_ip_intervals(&child_intervals, parent_intervals)
}
fn intersect_ip_intervals(
child_intervals: &[(Vec<u8>, Vec<u8>)],
parent_intervals: &[(Vec<u8>, Vec<u8>)],
) -> Vec<(Vec<u8>, Vec<u8>)> {
let mut out = Vec::new();
let mut parent_index = 0usize;
for (child_min, child_max) in child_intervals {
while parent_index < parent_intervals.len()
&& parent_intervals[parent_index].1.as_slice() < child_min.as_slice()
{
parent_index += 1;
}
let mut scan = parent_index;
while scan < parent_intervals.len()
&& parent_intervals[scan].0.as_slice() <= child_max.as_slice()
{
let (parent_min, parent_max) = &parent_intervals[scan];
let min = if bytes_leq(child_min, parent_min) {
parent_min.clone()
} else {
child_min.clone()
};
let max = if bytes_leq(child_max, parent_max) {
child_max.clone()
} else {
parent_max.clone()
};
if bytes_leq(&min, &max) {
out.push((min, max));
}
scan += 1;
}
}
merge_ip_intervals(&out)
}
fn ip_intervals_to_ranges(
afi: crate::data_model::rc::Afi,
intervals: &[(Vec<u8>, Vec<u8>)],
) -> Vec<crate::data_model::rc::IpAddressOrRange> {
intervals
.iter()
.map(|(min, max)| {
crate::data_model::rc::IpAddressOrRange::Range(crate::data_model::rc::IpAddressRange {
min: normalize_ip_bytes(afi, min),
max: normalize_ip_bytes(afi, max),
})
})
.collect()
}
fn normalize_ip_bytes(afi: crate::data_model::rc::Afi, bytes: &[u8]) -> Vec<u8> {
let target_len = afi.octets_len();
if bytes.len() == target_len {
return bytes.to_vec();
}
let mut out = vec![0u8; target_len];
let copy_len = bytes.len().min(target_len);
out[..copy_len].copy_from_slice(&bytes[..copy_len]);
out
}
fn intervals_changed<T: PartialEq>(left: &[T], right: &[T]) -> bool {
left != right
}
fn intersect_as_intervals(child: &[(u32, u32)], parent: &[(u32, u32)]) -> Vec<(u32, u32)> {
let mut out = Vec::new();
let mut parent_index = 0usize;
for (child_min, child_max) in child {
while parent_index < parent.len() && parent[parent_index].1 < *child_min {
parent_index += 1;
}
let mut scan = parent_index;
while scan < parent.len() && parent[scan].0 <= *child_max {
let min = (*child_min).max(parent[scan].0);
let max = (*child_max).min(parent[scan].1);
if min <= max {
out.push((min, max));
}
scan += 1;
}
}
merge_as_intervals(&out)
}
fn as_intervals_to_items(intervals: &[(u32, u32)]) -> Vec<crate::data_model::rc::AsIdOrRange> {
intervals
.iter()
.map(|(min, max)| {
if min == max {
crate::data_model::rc::AsIdOrRange::Id(*min)
} else {
crate::data_model::rc::AsIdOrRange::Range {
min: *min,
max: *max,
}
}
})
.collect()
}
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 {
@@ -971,8 +1376,8 @@ mod tests {
use super::*;
use crate::data_model::common::X509NameDer;
use crate::data_model::rc::{
Afi, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange,
IpResourceSet,
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
IpAddressOrRange, IpPrefix, IpResourceSet,
};
use crate::data_model::rc::{
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
@@ -1225,6 +1630,233 @@ mod tests {
assert!(matches!(err, CaPathError::InheritWithoutParentResources));
}
#[test]
fn validation_update_03_intersects_overclaiming_child_ip_resources() {
let issuer = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![10, 0, 0, 0],
},
)]),
}],
};
let child = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 23,
addr: vec![10, 0, 0, 0],
},
)]),
}],
};
let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None)
.expect("index");
let resolved = resolve_child_resources(
Some(&child),
Some(&issuer),
None,
None,
&index,
ResourceValidationMode::ValidationUpdate03,
)
.expect("vrs resolution");
assert!(resolved.warnings.ip_reduced_by_vrs);
assert!(!resolved.warnings.ip_vrs_empty);
let effective = resolved.effective_ip_resources.expect("effective ip");
assert!(effective.families[0].contains_prefix(&IpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![10, 0, 0, 0],
}));
assert!(!effective.families[0].contains_prefix(&IpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![10, 0, 1, 0],
}));
}
#[test]
fn validation_update_03_keeps_empty_vrs_instead_of_rejecting_child_ca() {
let issuer = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![10, 0, 0, 0],
},
)]),
}],
};
let child = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![192, 0, 2, 0],
},
)]),
}],
};
let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None)
.expect("index");
let strict_err = resolve_child_resources(
Some(&child),
Some(&issuer),
None,
None,
&index,
ResourceValidationMode::Rfc6487,
)
.unwrap_err();
assert!(matches!(strict_err, CaPathError::ResourcesNotSubset));
let resolved = resolve_child_resources(
Some(&child),
Some(&issuer),
None,
None,
&index,
ResourceValidationMode::ValidationUpdate03,
)
.expect("vrs resolution");
assert!(resolved.warnings.ip_reduced_by_vrs);
assert!(resolved.warnings.ip_vrs_empty);
assert!(
resolved
.effective_ip_resources
.expect("effective ip")
.families
.is_empty()
);
}
#[test]
fn validation_update_03_intersects_overclaiming_child_as_resources() {
let issuer = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64500,
max: 64510,
},
])),
rdi: None,
};
let child = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64505,
max: 64520,
},
])),
rdi: None,
};
let index = IssuerEffectiveResourcesIndex::from_effective_resources(None, Some(&issuer))
.expect("index");
let resolved = resolve_child_resources(
None,
None,
Some(&child),
Some(&issuer),
&index,
ResourceValidationMode::ValidationUpdate03,
)
.expect("vrs resolution");
assert!(resolved.warnings.as_reduced_by_vrs);
assert!(!resolved.warnings.as_vrs_empty);
let effective = resolved.effective_as_resources.expect("effective as");
assert_eq!(
effective.asnum,
Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64505,
max: 64510,
},
]))
);
}
#[test]
fn validation_update_03_records_all_resource_warning_summary_parts() {
let issuer_ip = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![10, 0, 0, 0],
},
)]),
}],
};
let child_ip = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 24,
addr: vec![192, 0, 2, 0],
},
)]),
}],
};
let issuer_as = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64500,
max: 64510,
},
])),
rdi: None,
};
let child_as = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64520,
max: 64530,
},
])),
rdi: None,
};
let index = IssuerEffectiveResourcesIndex::from_effective_resources(
Some(&issuer_ip),
Some(&issuer_as),
)
.expect("index");
let resolved = resolve_child_resources(
Some(&child_ip),
Some(&issuer_ip),
Some(&child_as),
Some(&issuer_as),
&index,
ResourceValidationMode::ValidationUpdate03,
)
.expect("vrs resolution");
assert!(resolved.warnings.ip_reduced_by_vrs);
assert!(resolved.warnings.ip_vrs_empty);
assert!(resolved.warnings.as_reduced_by_vrs);
assert!(resolved.warnings.as_vrs_empty);
assert_eq!(
resolved.warnings.summary(),
"ip_reduced_by_vrs,as_reduced_by_vrs,ip_vrs_empty,as_vrs_empty"
);
assert!(!resolved.warnings.is_empty());
}
#[test]
fn child_aki_mismatch_is_rejected() {
let issuer = dummy_cert(