手动执行全量同步

This commit is contained in:
2026-03-04 11:12:53 +08:00
parent 0f3d65254e
commit 6276d13814
54 changed files with 3879 additions and 670 deletions

View File

@@ -1,3 +1,4 @@
use crate::analysis::timing::TimingHandle;
use crate::audit::{AuditObjectKind, AuditObjectResult, ObjectAuditEntry, sha256_hex_from_32};
use crate::data_model::aspa::{AspaDecodeError, AspaObject, AspaValidateError};
use crate::data_model::manifest::ManifestObject;
@@ -10,13 +11,35 @@ use crate::data_model::signed_object::SignedObjectVerifyError;
use crate::policy::{Policy, SignedObjectFailurePolicy};
use crate::report::{RfcRef, Warning};
use crate::storage::{FetchCachePpPack, PackFile};
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path};
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_prevalidated_issuer};
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
const RFC_NONE: &[RfcRef] = &[];
const RFC_CRLDP: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6")];
const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] =
&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")];
#[derive(Clone, Debug)]
struct VerifiedIssuerCrl {
crl: crate::data_model::crl::RpkixCrl,
revoked_serials: std::collections::HashSet<Vec<u8>>,
}
#[derive(Clone, Debug)]
enum CachedIssuerCrl {
Pending(Vec<u8>),
Ok(VerifiedIssuerCrl),
}
#[derive(Clone, Debug, Default)]
struct IssuerResourcesIndex {
ip_v4: Option<Vec<(Vec<u8>, Vec<u8>)>>,
ip_v6: Option<Vec<(Vec<u8>, Vec<u8>)>>,
asnum: Option<Vec<(u32, u32)>>,
rdi: Option<Vec<(u32, u32)>>,
}
fn extra_rfc_refs_for_crl_selection(e: &ObjectValidateError) -> &'static [RfcRef] {
match e {
ObjectValidateError::MissingCrlDpUris => RFC_CRLDP,
@@ -68,6 +91,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
validation_time: time::OffsetDateTime,
timing: Option<&TimingHandle>,
) -> ObjectsOutput {
let mut warnings: Vec<Warning> = Vec::new();
let mut stats = ObjectsStats::default();
@@ -87,16 +111,105 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
let _manifest =
ManifestObject::decode_der(&pack.manifest_bytes).expect("fetch_cache_pp manifest decodes");
let crl_files = pack
// Decode issuer CA once; if it fails we cannot validate ROA/ASPA EE certificates.
let issuer_ca = match ResourceCertificate::decode_der(issuer_ca_der) {
Ok(v) => v,
Err(e) => {
stats.publication_point_dropped = true;
warnings.push(
Warning::new(format!(
"dropping publication point: issuer CA decode failed: {e}"
))
.with_rfc_refs(&[RfcRef("RFC 6487 §7.2"), RfcRef("RFC 5280 §6.1")])
.with_context(&pack.manifest_rsync_uri),
);
for f in &pack.files {
if f.rsync_uri.ends_with(".roa") {
audit.push(ObjectAuditEntry {
rsync_uri: f.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&f.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Skipped,
detail: Some("skipped: issuer CA decode failed".to_string()),
});
} else if f.rsync_uri.ends_with(".asa") {
audit.push(ObjectAuditEntry {
rsync_uri: f.rsync_uri.clone(),
sha256_hex: sha256_hex_from_32(&f.sha256),
kind: AuditObjectKind::Aspa,
result: AuditObjectResult::Skipped,
detail: Some("skipped: issuer CA decode failed".to_string()),
});
}
}
return ObjectsOutput {
vrps: Vec::new(),
aspas: Vec::new(),
warnings,
stats,
audit,
};
}
};
// Parse issuer SubjectPublicKeyInfo once and reuse for all EE certificate signature checks.
let issuer_spki = match SubjectPublicKeyInfo::from_der(&issuer_ca.tbs.subject_public_key_info) {
Ok((rem, spki)) if rem.is_empty() => spki,
Ok((rem, _)) => {
stats.publication_point_dropped = true;
warnings.push(
Warning::new(format!(
"dropping publication point: trailing bytes after issuer SPKI DER: {} bytes",
rem.len()
))
.with_rfc_refs(&[RfcRef("RFC 5280 §4.1.2.7")])
.with_context(&pack.manifest_rsync_uri),
);
return ObjectsOutput {
vrps: Vec::new(),
aspas: Vec::new(),
warnings,
stats,
audit,
};
}
Err(e) => {
stats.publication_point_dropped = true;
warnings.push(
Warning::new(format!(
"dropping publication point: issuer SPKI parse failed: {e}"
))
.with_rfc_refs(&[RfcRef("RFC 5280 §4.1.2.7")])
.with_context(&pack.manifest_rsync_uri),
);
return ObjectsOutput {
vrps: Vec::new(),
aspas: Vec::new(),
warnings,
stats,
audit,
};
}
};
let mut crl_cache: std::collections::HashMap<String, CachedIssuerCrl> = pack
.files
.iter()
.filter(|f| f.rsync_uri.ends_with(".crl"))
.map(|f| (f.rsync_uri.clone(), f.bytes.clone()))
.collect::<Vec<_>>();
.map(|f| {
(
f.rsync_uri.clone(),
CachedIssuerCrl::Pending(f.bytes.clone()),
)
})
.collect();
let issuer_resources_index =
build_issuer_resources_index(issuer_effective_ip, issuer_effective_as);
// If the pack has signed objects but no CRLs at all, we cannot validate any embedded EE
// certificate paths deterministically (EE CRLDP must reference an rsync URI in the pack).
if crl_files.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) {
if crl_cache.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) {
stats.publication_point_dropped = true;
warnings.push(
Warning::new("dropping publication point: no CRL files in fetch_cache_pp")
@@ -110,9 +223,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
sha256_hex: sha256_hex_from_32(&f.sha256),
kind: AuditObjectKind::Roa,
result: AuditObjectResult::Skipped,
detail: Some(
"skipped due to missing CRL files in fetch_cache_pp".to_string(),
),
detail: Some("skipped due to missing CRL files in fetch_cache_pp".to_string()),
});
} else if f.rsync_uri.ends_with(".asa") {
audit.push(ObjectAuditEntry {
@@ -120,9 +231,7 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
sha256_hex: sha256_hex_from_32(&f.sha256),
kind: AuditObjectKind::Aspa,
result: AuditObjectResult::Skipped,
detail: Some(
"skipped due to missing CRL files in fetch_cache_pp".to_string(),
),
detail: Some("skipped due to missing CRL files in fetch_cache_pp".to_string()),
});
}
}
@@ -140,14 +249,19 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
for (idx, file) in pack.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,
issuer_ca_der,
&issuer_ca,
&issuer_spki,
issuer_ca_rsync_uri,
&crl_files,
&mut crl_cache,
&issuer_resources_index,
issuer_effective_ip,
issuer_effective_as,
validation_time,
timing,
) {
Ok(mut out) => {
stats.roa_ok += 1;
@@ -232,14 +346,19 @@ pub fn process_fetch_cache_pp_pack_for_issuer(
},
}
} else if file.rsync_uri.ends_with(".asa") {
let _t = timing.as_ref().map(|t| t.span_phase("objects_aspa_total"));
match process_aspa_with_issuer(
file,
issuer_ca_der,
&issuer_ca,
&issuer_spki,
issuer_ca_rsync_uri,
&crl_files,
&mut crl_cache,
&issuer_resources_index,
issuer_effective_ip,
issuer_effective_as,
validation_time,
timing,
) {
Ok(att) => {
stats.aspa_ok += 1;
@@ -389,15 +508,33 @@ enum ObjectValidateError {
fn process_roa_with_issuer(
file: &PackFile,
issuer_ca_der: &[u8],
issuer_ca: &ResourceCertificate,
issuer_spki: &SubjectPublicKeyInfo<'_>,
issuer_ca_rsync_uri: Option<&str>,
crl_files: &[(String, Vec<u8>)],
crl_cache: &mut std::collections::HashMap<String, CachedIssuerCrl>,
issuer_resources_index: &IssuerResourcesIndex,
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
validation_time: time::OffsetDateTime,
timing: Option<&TimingHandle>,
) -> Result<Vec<Vrp>, ObjectValidateError> {
let _decode = timing
.as_ref()
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
let roa = RoaObject::decode_der(&file.bytes)?;
drop(_decode);
let _ee_profile = timing
.as_ref()
.map(|t| t.span_phase("objects_roa_validate_embedded_ee_total"));
roa.validate_embedded_ee_cert()?;
drop(_ee_profile);
let _verify = timing
.as_ref()
.map(|t| t.span_phase("objects_roa_verify_signature_total"));
roa.signed_object.verify()?;
drop(_verify);
let ee_der = &roa.signed_object.signed_data.certificates[0].raw_der;
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
@@ -406,18 +543,34 @@ fn process_roa_with_issuer(
.extensions
.crl_distribution_points_uris
.as_ref();
let (issuer_crl_rsync_uri, issuer_crl_der) =
choose_crl_for_certificate(ee_crldp_uris, crl_files)?;
let validated = validate_ee_cert_path(
let issuer_crl_rsync_uri = choose_crl_uri_for_certificate(ee_crldp_uris, crl_cache)?;
let verified_crl = ensure_issuer_crl_verified(issuer_crl_rsync_uri, crl_cache, issuer_ca_der)?;
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(
ee_der,
issuer_ca_der,
&issuer_crl_der,
issuer_ca,
issuer_spki,
&verified_crl.crl,
&verified_crl.revoked_serials,
issuer_ca_rsync_uri,
Some(issuer_crl_rsync_uri.as_str()),
Some(issuer_crl_rsync_uri),
validation_time,
)?;
drop(_cert_path);
validate_ee_resources_subset(&validated.ee, issuer_effective_ip, issuer_effective_as)?;
let _subset = timing
.as_ref()
.map(|t| t.span_phase("objects_roa_validate_ee_resources_subset_total"));
validate_ee_resources_subset(
&ee,
issuer_effective_ip,
issuer_effective_as,
issuer_resources_index,
)?;
drop(_subset);
Ok(roa_to_vrps(&roa))
}
@@ -425,15 +578,33 @@ fn process_roa_with_issuer(
fn process_aspa_with_issuer(
file: &PackFile,
issuer_ca_der: &[u8],
issuer_ca: &ResourceCertificate,
issuer_spki: &SubjectPublicKeyInfo<'_>,
issuer_ca_rsync_uri: Option<&str>,
crl_files: &[(String, Vec<u8>)],
crl_cache: &mut std::collections::HashMap<String, CachedIssuerCrl>,
issuer_resources_index: &IssuerResourcesIndex,
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
validation_time: time::OffsetDateTime,
timing: Option<&TimingHandle>,
) -> Result<AspaAttestation, ObjectValidateError> {
let _decode = timing
.as_ref()
.map(|t| t.span_phase("objects_aspa_decode_and_validate_total"));
let aspa = AspaObject::decode_der(&file.bytes)?;
drop(_decode);
let _ee_profile = timing
.as_ref()
.map(|t| t.span_phase("objects_aspa_validate_embedded_ee_total"));
aspa.validate_embedded_ee_cert()?;
drop(_ee_profile);
let _verify = timing
.as_ref()
.map(|t| t.span_phase("objects_aspa_verify_signature_total"));
aspa.signed_object.verify()?;
drop(_verify);
let ee_der = &aspa.signed_object.signed_data.certificates[0].raw_der;
let ee_crldp_uris = aspa.signed_object.signed_data.certificates[0]
@@ -442,18 +613,34 @@ fn process_aspa_with_issuer(
.extensions
.crl_distribution_points_uris
.as_ref();
let (issuer_crl_rsync_uri, issuer_crl_der) =
choose_crl_for_certificate(ee_crldp_uris, crl_files)?;
let validated = validate_ee_cert_path(
let issuer_crl_rsync_uri = choose_crl_uri_for_certificate(ee_crldp_uris, crl_cache)?;
let verified_crl = ensure_issuer_crl_verified(issuer_crl_rsync_uri, crl_cache, issuer_ca_der)?;
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(
ee_der,
issuer_ca_der,
&issuer_crl_der,
issuer_ca,
issuer_spki,
&verified_crl.crl,
&verified_crl.revoked_serials,
issuer_ca_rsync_uri,
Some(issuer_crl_rsync_uri.as_str()),
Some(issuer_crl_rsync_uri),
validation_time,
)?;
drop(_cert_path);
validate_ee_resources_subset(&validated.ee, issuer_effective_ip, issuer_effective_as)?;
let _subset = timing
.as_ref()
.map(|t| t.span_phase("objects_aspa_validate_ee_resources_subset_total"));
validate_ee_resources_subset(
&ee,
issuer_effective_ip,
issuer_effective_as,
issuer_resources_index,
)?;
drop(_subset);
Ok(AspaAttestation {
customer_as_id: aspa.aspa.customer_as_id,
@@ -461,11 +648,11 @@ fn process_aspa_with_issuer(
})
}
fn choose_crl_for_certificate(
crldp_uris: Option<&Vec<String>>,
crl_files: &[(String, Vec<u8>)],
) -> Result<(String, Vec<u8>), ObjectValidateError> {
if crl_files.is_empty() {
fn choose_crl_uri_for_certificate<'a>(
crldp_uris: Option<&'a Vec<String>>,
crl_cache: &std::collections::HashMap<String, CachedIssuerCrl>,
) -> Result<&'a str, ObjectValidateError> {
if crl_cache.is_empty() {
return Err(ObjectValidateError::MissingCrlInPack);
}
@@ -475,8 +662,8 @@ fn choose_crl_for_certificate(
for u in crldp_uris {
let s = u.as_str();
if let Some((uri, bytes)) = crl_files.iter().find(|(uri, _)| uri.as_str() == s) {
return Ok((uri.clone(), bytes.clone()));
if crl_cache.contains_key(s) {
return Ok(s);
}
}
Err(ObjectValidateError::CrlNotFound(
@@ -488,16 +675,52 @@ fn choose_crl_for_certificate(
))
}
fn ensure_issuer_crl_verified<'a>(
crl_rsync_uri: &str,
crl_cache: &'a mut std::collections::HashMap<String, CachedIssuerCrl>,
issuer_ca_der: &[u8],
) -> Result<&'a VerifiedIssuerCrl, CertPathError> {
let entry = crl_cache
.get_mut(crl_rsync_uri)
.expect("CRL must exist in cache");
match entry {
CachedIssuerCrl::Ok(v) => Ok(v),
CachedIssuerCrl::Pending(bytes) => {
let der = std::mem::take(bytes);
let crl = crate::data_model::crl::RpkixCrl::decode_der(&der)
.map_err(CertPathError::CrlDecode)?;
crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)
.map_err(CertPathError::CrlVerify)?;
let mut revoked_serials: std::collections::HashSet<Vec<u8>> =
std::collections::HashSet::with_capacity(crl.revoked_certs.len());
for rc in &crl.revoked_certs {
revoked_serials.insert(rc.serial_number.bytes_be.clone());
}
*entry = CachedIssuerCrl::Ok(VerifiedIssuerCrl {
crl,
revoked_serials,
});
match entry {
CachedIssuerCrl::Ok(v) => Ok(v),
_ => unreachable!(),
}
}
}
}
fn validate_ee_resources_subset(
ee: &ResourceCertificate,
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
issuer_resources_index: &IssuerResourcesIndex,
) -> Result<(), ObjectValidateError> {
if let Some(child_ip) = ee.tbs.extensions.ip_resources.as_ref() {
let Some(parent_ip) = issuer_effective_ip else {
return Err(ObjectValidateError::MissingIssuerEffectiveIp);
};
if !ip_resources_is_subset(child_ip, parent_ip) {
if !ip_resources_is_subset_indexed(child_ip, parent_ip, issuer_resources_index) {
return Err(ObjectValidateError::EeResourcesNotSubset);
}
}
@@ -506,7 +729,7 @@ fn validate_ee_resources_subset(
let Some(parent_as) = issuer_effective_as else {
return Err(ObjectValidateError::MissingIssuerEffectiveAs);
};
if !as_resources_is_subset(child_as, parent_as) {
if !as_resources_is_subset_indexed(child_as, parent_as, issuer_resources_index) {
return Err(ObjectValidateError::EeResourcesNotSubset);
}
}
@@ -519,6 +742,40 @@ fn as_resources_is_subset(child: &AsResourceSet, parent: &AsResourceSet) -> bool
&& as_choice_subset(child.rdi.as_ref(), parent.rdi.as_ref())
}
fn as_resources_is_subset_indexed(
child: &AsResourceSet,
parent: &AsResourceSet,
idx: &IssuerResourcesIndex,
) -> bool {
let _ = parent;
as_choice_subset_indexed(child.asnum.as_ref(), idx.asnum.as_deref())
&& as_choice_subset_indexed(child.rdi.as_ref(), idx.rdi.as_deref())
}
fn as_choice_subset_indexed(
child: Option<&AsIdentifierChoice>,
parent_intervals: Option<&[(u32, u32)]>,
) -> bool {
let Some(child) = child else {
return true;
};
let Some(parent_intervals) = parent_intervals else {
return false;
};
if matches!(child, AsIdentifierChoice::Inherit) {
return false;
}
let child_intervals = as_choice_to_merged_intervals(child);
for (cmin, cmax) in &child_intervals {
if !as_interval_is_covered(parent_intervals, *cmin, *cmax) {
return false;
}
}
true
}
fn as_choice_subset(
child: Option<&AsIdentifierChoice>,
parent: Option<&AsIdentifierChoice>,
@@ -614,6 +871,45 @@ fn ip_resources_is_subset(
true
}
fn ip_resources_is_subset_indexed(
child: &crate::data_model::rc::IpResourceSet,
parent: &crate::data_model::rc::IpResourceSet,
idx: &IssuerResourcesIndex,
) -> bool {
let _ = parent;
for fam in &child.families {
let parent_intervals = match fam.afi {
crate::data_model::rc::Afi::Ipv4 => idx.ip_v4.as_deref(),
crate::data_model::rc::Afi::Ipv6 => idx.ip_v6.as_deref(),
};
let Some(parent_intervals) = parent_intervals else {
return false;
};
let items = match &fam.choice {
IpAddressChoice::Inherit => return false,
IpAddressChoice::AddressesOrRanges(items) => items,
};
let mut child_intervals: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
for item in items {
match item {
IpAddressOrRange::Prefix(p) => child_intervals.push(prefix_to_range(p)),
IpAddressOrRange::Range(r) => child_intervals.push((r.min.clone(), r.max.clone())),
}
}
if child_intervals.is_empty() {
continue;
}
child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
merge_ip_intervals_in_place(&mut child_intervals);
if !intervals_are_covered(parent_intervals, &child_intervals) {
return false;
}
}
true
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum AfiKey {
V4,
@@ -650,7 +946,7 @@ fn ip_resources_to_merged_intervals(
for (_afi, v) in m.iter_mut() {
v.sort_by(|(a, _), (b, _)| a.cmp(b));
*v = merge_ip_intervals(v);
merge_ip_intervals_in_place(v);
}
m
@@ -683,28 +979,31 @@ fn ip_resources_to_merged_intervals_strict(
for (_afi, v) in m.iter_mut() {
v.sort_by(|(a, _), (b, _)| a.cmp(b));
*v = merge_ip_intervals(v);
merge_ip_intervals_in_place(v);
}
Ok(m)
}
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 {
fn merge_ip_intervals_in_place(v: &mut Vec<(Vec<u8>, Vec<u8>)>) {
if v.is_empty() {
return;
}
let mut out: Vec<(Vec<u8>, Vec<u8>)> = Vec::with_capacity(v.len());
for (min, max) in v.drain(..) {
let Some(last) = out.last_mut() else {
out.push((min.clone(), max.clone()));
out.push((min, max));
continue;
};
if bytes_leq(min, &increment_bytes(&last.1)) {
if bytes_leq(&last.1, max) {
last.1 = max.clone();
if bytes_leq(&min, &last.1) || bytes_is_next(&min, &last.1) {
if bytes_leq(&last.1, &max) {
last.1 = max;
}
continue;
}
out.push((min.clone(), max.clone()));
out.push((min, max));
}
out
*v = out;
}
fn interval_is_covered(parent: &[(Vec<u8>, Vec<u8>)], min: &[u8], max: &[u8]) -> bool {
@@ -719,6 +1018,23 @@ fn interval_is_covered(parent: &[(Vec<u8>, Vec<u8>)], min: &[u8], max: &[u8]) ->
false
}
fn intervals_are_covered(parent: &[(Vec<u8>, Vec<u8>)], child: &[(Vec<u8>, Vec<u8>)]) -> bool {
let mut i = 0usize;
for (cmin, cmax) in child {
while i < parent.len() && parent[i].1.as_slice() < cmin.as_slice() {
i += 1;
}
if i >= parent.len() {
return false;
}
let (pmin, pmax) = &parent[i];
if !bytes_leq(pmin, cmin) || !bytes_leq(cmax, pmax) {
return false;
}
}
true
}
fn prefix_to_range(prefix: &RcIpPrefix) -> (Vec<u8>, Vec<u8>) {
let mut min = prefix.addr.clone();
let mut max = prefix.addr.clone();
@@ -752,6 +1068,78 @@ fn increment_bytes(v: &[u8]) -> Vec<u8> {
vec![0u8; out.len()]
}
fn bytes_is_next(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut carry: u16 = 1;
for i in (0..b.len()).rev() {
let sum = (b[i] as u16) + carry;
let expected = (sum & 0xFF) as u8;
carry = sum >> 8;
if a[i] != expected {
return false;
}
}
true
}
fn build_issuer_resources_index(
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
) -> IssuerResourcesIndex {
let mut idx = IssuerResourcesIndex::default();
if let Some(ip) = issuer_effective_ip {
let mut v4: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let mut v6: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
for fam in &ip.families {
let ent = match fam.afi {
crate::data_model::rc::Afi::Ipv4 => &mut v4,
crate::data_model::rc::Afi::Ipv6 => &mut v6,
};
match &fam.choice {
IpAddressChoice::Inherit => {
// Effective resources should not contain inherit; leave empty so subset fails.
}
IpAddressChoice::AddressesOrRanges(items) => {
for item in items {
match item {
IpAddressOrRange::Prefix(p) => ent.push(prefix_to_range(p)),
IpAddressOrRange::Range(r) => ent.push((r.min.clone(), r.max.clone())),
}
}
}
}
}
if !v4.is_empty() {
v4.sort_by(|(a, _), (b, _)| a.cmp(b));
merge_ip_intervals_in_place(&mut v4);
idx.ip_v4 = Some(v4);
}
if !v6.is_empty() {
v6.sort_by(|(a, _), (b, _)| a.cmp(b));
merge_ip_intervals_in_place(&mut v6);
idx.ip_v6 = Some(v6);
}
}
if let Some(asr) = issuer_effective_as {
if let Some(choice) = asr.asnum.as_ref() {
if !matches!(choice, AsIdentifierChoice::Inherit) {
idx.asnum = Some(as_choice_to_merged_intervals(choice));
}
}
if let Some(choice) = asr.rdi.as_ref() {
if !matches!(choice, AsIdentifierChoice::Inherit) {
idx.rdi = Some(as_choice_to_merged_intervals(choice));
}
}
}
idx
}
fn roa_to_vrps(roa: &RoaObject) -> Vec<Vrp> {
let asn = roa.roa.as_id;
let mut out = Vec::new();
@@ -783,6 +1171,7 @@ mod tests {
Afi, AsIdOrRange, AsIdentifierChoice, IpAddressFamily, IpAddressOrRange, IpAddressRange,
IpPrefix, IpResourceSet,
};
use std::collections::HashMap;
fn fixture_bytes(path: &str) -> Vec<u8> {
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
@@ -885,12 +1274,12 @@ mod tests {
#[test]
fn merge_ip_intervals_merges_contiguous() {
let v = vec![
let mut v = vec![
(vec![0, 0, 0, 0], vec![0, 0, 0, 10]),
(vec![0, 0, 0, 11], vec![0, 0, 0, 20]),
];
let merged = merge_ip_intervals(&v);
assert_eq!(merged, vec![(vec![0, 0, 0, 0], vec![0, 0, 0, 20])]);
merge_ip_intervals_in_place(&mut v);
assert_eq!(v, vec![(vec![0, 0, 0, 0], vec![0, 0, 0, 20])]);
}
#[test]
@@ -904,14 +1293,19 @@ mod tests {
.extensions
.crl_distribution_points_uris
.as_ref();
let err = choose_crl_for_certificate(ee_crldp_uris, &[]).unwrap_err();
let crl_cache: HashMap<String, CachedIssuerCrl> = HashMap::new();
let err = choose_crl_uri_for_certificate(ee_crldp_uris, &crl_cache).unwrap_err();
assert!(matches!(err, ObjectValidateError::MissingCrlInPack));
}
#[test]
fn choose_crl_for_certificate_reports_missing_crldp_uris() {
let crl_a = ("rsync://example.test/a.crl".to_string(), vec![0x01]);
let err = choose_crl_for_certificate(None, &[crl_a]).unwrap_err();
let mut crl_cache: HashMap<String, CachedIssuerCrl> = HashMap::new();
crl_cache.insert(
"rsync://example.test/a.crl".to_string(),
CachedIssuerCrl::Pending(vec![0x01]),
);
let err = choose_crl_uri_for_certificate(None, &crl_cache).unwrap_err();
assert!(matches!(err, ObjectValidateError::MissingCrlDpUris));
}
@@ -928,24 +1322,16 @@ mod tests {
.as_ref()
.expect("fixture ee has crldp");
// Use two CRLs, only one matches the first CRLDP URI.
let other_crl_der = fixture_bytes(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
);
let matching_uri = ee_crldp_uris[0].as_str().to_string();
let matching_crl_der = vec![0x01, 0x02, 0x03];
let (uri, bytes) = choose_crl_for_certificate(
Some(ee_crldp_uris),
&[
("rsync://example.test/other.crl".to_string(), other_crl_der),
(matching_uri.clone(), matching_crl_der.clone()),
],
)
.unwrap();
let mut crl_cache: HashMap<String, CachedIssuerCrl> = HashMap::new();
crl_cache.insert(
"rsync://example.test/other.crl".to_string(),
CachedIssuerCrl::Pending(vec![0x00]),
);
crl_cache.insert(matching_uri.clone(), CachedIssuerCrl::Pending(vec![0x01]));
let uri = choose_crl_uri_for_certificate(Some(ee_crldp_uris), &crl_cache).unwrap();
assert_eq!(uri, matching_uri);
assert_eq!(bytes, matching_crl_der);
}
#[test]
@@ -960,11 +1346,12 @@ mod tests {
.crl_distribution_points_uris
.as_ref();
let err = choose_crl_for_certificate(
ee_crldp_uris,
&[("rsync://example.test/other.crl".to_string(), vec![0x01])],
)
.unwrap_err();
let mut crl_cache: HashMap<String, CachedIssuerCrl> = HashMap::new();
crl_cache.insert(
"rsync://example.test/other.crl".to_string(),
CachedIssuerCrl::Pending(vec![0x01]),
);
let err = choose_crl_uri_for_certificate(ee_crldp_uris, &crl_cache).unwrap_err();
assert!(matches!(err, ObjectValidateError::CrlNotFound(_)));
}
@@ -976,7 +1363,8 @@ mod tests {
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
let err = validate_ee_resources_subset(ee, None, None).unwrap_err();
let idx = IssuerResourcesIndex::default();
let err = validate_ee_resources_subset(ee, None, None, &idx).unwrap_err();
assert!(matches!(err, ObjectValidateError::MissingIssuerEffectiveIp));
}
@@ -1002,7 +1390,8 @@ mod tests {
}],
};
let err = validate_ee_resources_subset(ee, Some(&issuer_ip), None).unwrap_err();
let idx = build_issuer_resources_index(Some(&issuer_ip), None);
let err = validate_ee_resources_subset(ee, Some(&issuer_ip), None, &idx).unwrap_err();
assert!(matches!(err, ObjectValidateError::MissingIssuerEffectiveAs));
}
@@ -1028,7 +1417,8 @@ mod tests {
}],
};
let err = validate_ee_resources_subset(ee, Some(&issuer_ip), None).unwrap_err();
let idx = build_issuer_resources_index(Some(&issuer_ip), None);
let err = validate_ee_resources_subset(ee, Some(&issuer_ip), None, &idx).unwrap_err();
assert!(matches!(err, ObjectValidateError::EeResourcesNotSubset));
}
}