20260701 add validation-update-03 VRS mode
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user