run tree from tal pass
This commit is contained in:
@@ -1,18 +1,29 @@
|
||||
use crate::audit::{AuditObjectKind, AuditObjectResult, ObjectAuditEntry, sha256_hex_from_32};
|
||||
use crate::data_model::aspa::{AspaDecodeError, AspaObject, AspaValidateError};
|
||||
use crate::data_model::manifest::ManifestObject;
|
||||
use crate::data_model::rc::{
|
||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressOrRange, IpPrefix as RcIpPrefix,
|
||||
ResourceCertKind, ResourceCertificate,
|
||||
ResourceCertificate,
|
||||
};
|
||||
use crate::data_model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaValidateError};
|
||||
use crate::data_model::signed_object::SignedObjectVerifyError;
|
||||
use crate::audit::{AuditObjectKind, AuditObjectResult, ObjectAuditEntry, sha256_hex_from_32};
|
||||
use crate::policy::{Policy, SignedObjectFailurePolicy};
|
||||
use crate::report::{RfcRef, Warning};
|
||||
use crate::storage::{PackFile, VerifiedPublicationPointPack};
|
||||
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path};
|
||||
|
||||
use std::collections::HashMap;
|
||||
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")];
|
||||
|
||||
fn extra_rfc_refs_for_crl_selection(e: &ObjectValidateError) -> &'static [RfcRef] {
|
||||
match e {
|
||||
ObjectValidateError::MissingCrlDpUris => RFC_CRLDP,
|
||||
ObjectValidateError::CrlNotFound(_) => RFC_CRLDP_AND_LOCKED_PACK,
|
||||
_ => RFC_NONE,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Vrp {
|
||||
@@ -60,8 +71,16 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
) -> ObjectsOutput {
|
||||
let mut warnings: Vec<Warning> = Vec::new();
|
||||
let mut stats = ObjectsStats::default();
|
||||
stats.roa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count();
|
||||
stats.aspa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".asa")).count();
|
||||
stats.roa_total = pack
|
||||
.files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".roa"))
|
||||
.count();
|
||||
stats.aspa_total = pack
|
||||
.files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".asa"))
|
||||
.count();
|
||||
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
|
||||
|
||||
// Enforce that `manifest_bytes` is actually a manifest object.
|
||||
@@ -75,43 +94,42 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
.map(|f| (f.rsync_uri.clone(), f.bytes.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (issuer_crl_uri, issuer_crl_der) = match choose_crl_for_issuer(issuer_ca_der, &crl_files) {
|
||||
Ok((uri, der)) => (uri, der),
|
||||
Err(e) => {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(
|
||||
Warning::new(format!("dropping publication point: {e}"))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §5")])
|
||||
.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 due to missing issuer CRL".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 due to missing issuer CRL".to_string()),
|
||||
});
|
||||
}
|
||||
// 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) {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(
|
||||
Warning::new("dropping publication point: no CRL files in verified pack")
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")])
|
||||
.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 due to missing CRL files in verified pack".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 due to missing CRL files in verified pack".to_string()),
|
||||
});
|
||||
}
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
};
|
||||
}
|
||||
};
|
||||
return ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
};
|
||||
}
|
||||
|
||||
let mut vrps: Vec<Vrp> = Vec::new();
|
||||
let mut aspas: Vec<AspaAttestation> = Vec::new();
|
||||
@@ -121,9 +139,8 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
match process_roa_with_issuer(
|
||||
file,
|
||||
issuer_ca_der,
|
||||
&issuer_crl_der,
|
||||
issuer_ca_rsync_uri,
|
||||
Some(issuer_crl_uri.as_str()),
|
||||
&crl_files,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
validation_time,
|
||||
@@ -148,9 +165,11 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(e.to_string()),
|
||||
});
|
||||
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
|
||||
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||
warnings.push(
|
||||
Warning::new(format!("dropping invalid ROA: {}: {e}", file.rsync_uri))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")])
|
||||
.with_rfc_refs(&refs)
|
||||
.with_context(&file.rsync_uri),
|
||||
)
|
||||
}
|
||||
@@ -188,12 +207,14 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
});
|
||||
}
|
||||
}
|
||||
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
|
||||
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point due to invalid ROA: {}: {e}",
|
||||
file.rsync_uri
|
||||
))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")])
|
||||
.with_rfc_refs(&refs)
|
||||
.with_context(&pack.manifest_rsync_uri),
|
||||
);
|
||||
return ObjectsOutput {
|
||||
@@ -210,9 +231,8 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
match process_aspa_with_issuer(
|
||||
file,
|
||||
issuer_ca_der,
|
||||
&issuer_crl_der,
|
||||
issuer_ca_rsync_uri,
|
||||
Some(issuer_crl_uri.as_str()),
|
||||
&crl_files,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
validation_time,
|
||||
@@ -237,9 +257,11 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(e.to_string()),
|
||||
});
|
||||
let mut refs = vec![RfcRef("RFC 6488 §3")];
|
||||
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||
warnings.push(
|
||||
Warning::new(format!("dropping invalid ASPA: {}: {e}", file.rsync_uri))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3")])
|
||||
.with_rfc_refs(&refs)
|
||||
.with_context(&file.rsync_uri),
|
||||
)
|
||||
}
|
||||
@@ -277,12 +299,14 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
});
|
||||
}
|
||||
}
|
||||
let mut refs = vec![RfcRef("RFC 6488 §3")];
|
||||
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point due to invalid ASPA: {}: {e}",
|
||||
file.rsync_uri
|
||||
))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3")])
|
||||
.with_rfc_refs(&refs)
|
||||
.with_context(&pack.manifest_rsync_uri),
|
||||
);
|
||||
return ObjectsOutput {
|
||||
@@ -307,221 +331,6 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ObjectsProcessError {
|
||||
#[error(
|
||||
"publication point dropped due to invalid signed object: {rsync_uri}: {detail} (policy=signed_object_failure_policy=drop_publication_point)"
|
||||
)]
|
||||
PublicationPointDropped { rsync_uri: String, detail: String },
|
||||
}
|
||||
|
||||
pub trait IssuerCaCertificateResolver {
|
||||
fn resolve_by_subject_dn(&self, subject_dn: &str) -> Option<Vec<u8>>;
|
||||
}
|
||||
|
||||
pub fn process_verified_publication_point_pack(
|
||||
pack: &VerifiedPublicationPointPack,
|
||||
policy: &Policy,
|
||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ObjectsOutput, ObjectsProcessError> {
|
||||
let mut warnings = Vec::new();
|
||||
let mut stats = ObjectsStats::default();
|
||||
stats.roa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count();
|
||||
stats.aspa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".asa")).count();
|
||||
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
|
||||
|
||||
// Parse manifest once (primarily to enforce that `manifest_bytes` really is a manifest object).
|
||||
let _manifest =
|
||||
ManifestObject::decode_der(&pack.manifest_bytes).expect("verified pack manifest decodes");
|
||||
|
||||
// Index CA certs found in the pack by subject DN (best-effort; packs may be incomplete).
|
||||
let mut ca_certs_by_subject: HashMap<String, Vec<u8>> = HashMap::new();
|
||||
for f in &pack.files {
|
||||
if !f.rsync_uri.ends_with(".cer") {
|
||||
continue;
|
||||
}
|
||||
let Ok(cert) = ResourceCertificate::decode_der(&f.bytes) else {
|
||||
continue;
|
||||
};
|
||||
if cert.kind != ResourceCertKind::Ca {
|
||||
continue;
|
||||
}
|
||||
ca_certs_by_subject.insert(cert.tbs.subject_dn.clone(), f.bytes.clone());
|
||||
}
|
||||
|
||||
// Decode CRLs present in the pack (may be none in synthetic tests).
|
||||
let crl_files = pack
|
||||
.files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".crl"))
|
||||
.map(|f| (f.rsync_uri.clone(), f.bytes.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut vrps = Vec::new();
|
||||
let mut aspas = Vec::new();
|
||||
|
||||
for (idx, file) in pack.files.iter().enumerate() {
|
||||
if file.rsync_uri.ends_with(".roa") {
|
||||
match process_roa(
|
||||
file,
|
||||
&ca_certs_by_subject,
|
||||
issuer_resolver,
|
||||
&crl_files,
|
||||
validation_time,
|
||||
) {
|
||||
Ok(mut out) => {
|
||||
stats.roa_ok += 1;
|
||||
vrps.append(&mut out);
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Roa,
|
||||
result: AuditObjectResult::Ok,
|
||||
detail: None,
|
||||
});
|
||||
}
|
||||
Err(e) => match policy.signed_object_failure_policy {
|
||||
SignedObjectFailurePolicy::DropObject => {
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Roa,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(e.to_string()),
|
||||
});
|
||||
warnings.push(
|
||||
Warning::new(format!("dropping invalid ROA: {}: {e}", file.rsync_uri))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")])
|
||||
.with_context(&file.rsync_uri),
|
||||
)
|
||||
}
|
||||
SignedObjectFailurePolicy::DropPublicationPoint => {
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Roa,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(e.to_string()),
|
||||
});
|
||||
for f in pack.files.iter().skip(idx + 1) {
|
||||
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 due to policy=signed_object_failure_policy=drop_publication_point"
|
||||
.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 due to policy=signed_object_failure_policy=drop_publication_point"
|
||||
.to_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
return Err(ObjectsProcessError::PublicationPointDropped {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
detail: e.to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
} else if file.rsync_uri.ends_with(".asa") {
|
||||
match process_aspa(
|
||||
file,
|
||||
&ca_certs_by_subject,
|
||||
issuer_resolver,
|
||||
&crl_files,
|
||||
validation_time,
|
||||
) {
|
||||
Ok(att) => {
|
||||
stats.aspa_ok += 1;
|
||||
aspas.push(att);
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Aspa,
|
||||
result: AuditObjectResult::Ok,
|
||||
detail: None,
|
||||
});
|
||||
}
|
||||
Err(e) => match policy.signed_object_failure_policy {
|
||||
SignedObjectFailurePolicy::DropObject => {
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Aspa,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(e.to_string()),
|
||||
});
|
||||
warnings.push(
|
||||
Warning::new(format!("dropping invalid ASPA: {}: {e}", file.rsync_uri))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3")])
|
||||
.with_context(&file.rsync_uri),
|
||||
)
|
||||
}
|
||||
SignedObjectFailurePolicy::DropPublicationPoint => {
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Aspa,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(e.to_string()),
|
||||
});
|
||||
for f in pack.files.iter().skip(idx + 1) {
|
||||
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 due to policy=signed_object_failure_policy=drop_publication_point"
|
||||
.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 due to policy=signed_object_failure_policy=drop_publication_point"
|
||||
.to_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
return Err(ObjectsProcessError::PublicationPointDropped {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
detail: e.to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ObjectsOutput {
|
||||
vrps,
|
||||
aspas,
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ObjectValidateError {
|
||||
#[error("ROA decode failed: {0}")]
|
||||
@@ -542,11 +351,20 @@ enum ObjectValidateError {
|
||||
#[error("EE certificate path validation failed: {0}")]
|
||||
CertPath(#[from] CertPathError),
|
||||
|
||||
#[error("missing issuer CA certificate for subject DN: {0}")]
|
||||
MissingIssuerCaCert(String),
|
||||
#[error(
|
||||
"certificate CRLDistributionPoints URIs missing (cannot select issuer CRL) (RFC 6487 §4.8.6)"
|
||||
)]
|
||||
MissingCrlDpUris,
|
||||
|
||||
#[error("no CRL available for issuer CA")]
|
||||
MissingCrl,
|
||||
#[error(
|
||||
"no CRL available in verified pack (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)"
|
||||
)]
|
||||
MissingCrlInPack,
|
||||
|
||||
#[error(
|
||||
"CRL referenced by CRLDistributionPoints not found in verified pack: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)"
|
||||
)]
|
||||
CrlNotFound(String),
|
||||
|
||||
#[error(
|
||||
"issuer effective IP resources missing (cannot validate EE IP resources subset) (RFC 6487 §7.2; RFC 3779 §2.3)"
|
||||
@@ -564,49 +382,11 @@ enum ObjectValidateError {
|
||||
EeResourcesNotSubset,
|
||||
}
|
||||
|
||||
fn process_roa(
|
||||
file: &PackFile,
|
||||
ca_certs_by_subject: &HashMap<String, Vec<u8>>,
|
||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
||||
crl_files: &[(String, Vec<u8>)],
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<Vec<Vrp>, ObjectValidateError> {
|
||||
let roa = RoaObject::decode_der(&file.bytes)?;
|
||||
roa.validate_embedded_ee_cert()?;
|
||||
roa.signed_object.verify()?;
|
||||
|
||||
let ee_der = &roa.signed_object.signed_data.certificates[0].raw_der;
|
||||
let ee_issuer_dn = roa.signed_object.signed_data.certificates[0]
|
||||
.resource_cert
|
||||
.tbs
|
||||
.issuer_dn
|
||||
.clone();
|
||||
|
||||
let issuer_ca_der = ca_certs_by_subject
|
||||
.get(&ee_issuer_dn)
|
||||
.cloned()
|
||||
.or_else(|| issuer_resolver.resolve_by_subject_dn(&ee_issuer_dn))
|
||||
.ok_or_else(|| ObjectValidateError::MissingIssuerCaCert(ee_issuer_dn.clone()))?;
|
||||
|
||||
let (crl_uri, crl_der) = choose_crl_for_issuer(&issuer_ca_der, crl_files)?;
|
||||
validate_ee_cert_path(
|
||||
ee_der,
|
||||
&issuer_ca_der,
|
||||
&crl_der,
|
||||
None,
|
||||
Some(crl_uri.as_str()),
|
||||
validation_time,
|
||||
)?;
|
||||
|
||||
Ok(roa_to_vrps(&roa))
|
||||
}
|
||||
|
||||
fn process_roa_with_issuer(
|
||||
file: &PackFile,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_crl_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
issuer_crl_rsync_uri: Option<&str>,
|
||||
crl_files: &[(String, Vec<u8>)],
|
||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
@@ -616,70 +396,33 @@ fn process_roa_with_issuer(
|
||||
roa.signed_object.verify()?;
|
||||
|
||||
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
|
||||
.tbs
|
||||
.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(
|
||||
ee_der,
|
||||
issuer_ca_der,
|
||||
issuer_crl_der,
|
||||
&issuer_crl_der,
|
||||
issuer_ca_rsync_uri,
|
||||
issuer_crl_rsync_uri,
|
||||
Some(issuer_crl_rsync_uri.as_str()),
|
||||
validation_time,
|
||||
)?;
|
||||
|
||||
validate_ee_resources_subset(
|
||||
&validated.ee,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
)?;
|
||||
validate_ee_resources_subset(&validated.ee, issuer_effective_ip, issuer_effective_as)?;
|
||||
|
||||
Ok(roa_to_vrps(&roa))
|
||||
}
|
||||
|
||||
fn process_aspa(
|
||||
file: &PackFile,
|
||||
ca_certs_by_subject: &HashMap<String, Vec<u8>>,
|
||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
||||
crl_files: &[(String, Vec<u8>)],
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<AspaAttestation, ObjectValidateError> {
|
||||
let aspa = AspaObject::decode_der(&file.bytes)?;
|
||||
aspa.validate_embedded_ee_cert()?;
|
||||
aspa.signed_object.verify()?;
|
||||
|
||||
let ee_der = &aspa.signed_object.signed_data.certificates[0].raw_der;
|
||||
let ee_issuer_dn = aspa.signed_object.signed_data.certificates[0]
|
||||
.resource_cert
|
||||
.tbs
|
||||
.issuer_dn
|
||||
.clone();
|
||||
|
||||
let issuer_ca_der = ca_certs_by_subject
|
||||
.get(&ee_issuer_dn)
|
||||
.cloned()
|
||||
.or_else(|| issuer_resolver.resolve_by_subject_dn(&ee_issuer_dn))
|
||||
.ok_or_else(|| ObjectValidateError::MissingIssuerCaCert(ee_issuer_dn.clone()))?;
|
||||
|
||||
let (crl_uri, crl_der) = choose_crl_for_issuer(&issuer_ca_der, crl_files)?;
|
||||
validate_ee_cert_path(
|
||||
ee_der,
|
||||
&issuer_ca_der,
|
||||
&crl_der,
|
||||
None,
|
||||
Some(crl_uri.as_str()),
|
||||
validation_time,
|
||||
)?;
|
||||
|
||||
Ok(AspaAttestation {
|
||||
customer_as_id: aspa.aspa.customer_as_id,
|
||||
provider_as_ids: aspa.aspa.provider_as_ids.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn process_aspa_with_issuer(
|
||||
file: &PackFile,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_crl_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
issuer_crl_rsync_uri: Option<&str>,
|
||||
crl_files: &[(String, Vec<u8>)],
|
||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
@@ -689,20 +432,24 @@ fn process_aspa_with_issuer(
|
||||
aspa.signed_object.verify()?;
|
||||
|
||||
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
|
||||
.tbs
|
||||
.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(
|
||||
ee_der,
|
||||
issuer_ca_der,
|
||||
issuer_crl_der,
|
||||
&issuer_crl_der,
|
||||
issuer_ca_rsync_uri,
|
||||
issuer_crl_rsync_uri,
|
||||
Some(issuer_crl_rsync_uri.as_str()),
|
||||
validation_time,
|
||||
)?;
|
||||
|
||||
validate_ee_resources_subset(
|
||||
&validated.ee,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
)?;
|
||||
validate_ee_resources_subset(&validated.ee, issuer_effective_ip, issuer_effective_as)?;
|
||||
|
||||
Ok(AspaAttestation {
|
||||
customer_as_id: aspa.aspa.customer_as_id,
|
||||
@@ -710,42 +457,31 @@ fn process_aspa_with_issuer(
|
||||
})
|
||||
}
|
||||
|
||||
fn choose_crl_for_issuer(
|
||||
issuer_ca_der: &[u8],
|
||||
fn choose_crl_for_certificate(
|
||||
crldp_uris: Option<&Vec<url::Url>>,
|
||||
crl_files: &[(String, Vec<u8>)],
|
||||
) -> Result<(String, Vec<u8>), ObjectValidateError> {
|
||||
if crl_files.is_empty() {
|
||||
return Err(ObjectValidateError::MissingCrl);
|
||||
return Err(ObjectValidateError::MissingCrlInPack);
|
||||
}
|
||||
|
||||
let issuer_tbs = ResourceCertificate::decode_der(issuer_ca_der)
|
||||
.ok()
|
||||
.map(|c| c.tbs);
|
||||
let Some(issuer_tbs) = issuer_tbs else {
|
||||
return Ok(crl_files[0].clone());
|
||||
let Some(crldp_uris) = crldp_uris else {
|
||||
return Err(ObjectValidateError::MissingCrlDpUris);
|
||||
};
|
||||
|
||||
if let Some(uris) = issuer_tbs.extensions.crl_distribution_points_uris.as_ref() {
|
||||
for u in 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uri, bytes) in crl_files {
|
||||
let Ok(crl) = crate::data_model::crl::RpkixCrl::decode_der(bytes) else {
|
||||
continue;
|
||||
};
|
||||
if crl.issuer_dn == issuer_tbs.subject_dn {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to the first CRL when the pack is incomplete or uses a different DN string
|
||||
// representation. Signature binding is still validated in `validate_ee_cert_path`.
|
||||
Ok(crl_files[0].clone())
|
||||
Err(ObjectValidateError::CrlNotFound(
|
||||
crldp_uris
|
||||
.iter()
|
||||
.map(|u| u.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
))
|
||||
}
|
||||
|
||||
fn validate_ee_resources_subset(
|
||||
@@ -1039,7 +775,10 @@ fn roa_afi_to_string(afi: RoaAfi) -> &'static str {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::data_model::rc::{Afi, AsIdOrRange, AsIdentifierChoice, IpAddressFamily, IpAddressOrRange, IpAddressRange, IpPrefix, IpResourceSet};
|
||||
use crate::data_model::rc::{
|
||||
Afi, AsIdOrRange, AsIdentifierChoice, IpAddressFamily, IpAddressOrRange, IpAddressRange,
|
||||
IpPrefix, IpResourceSet,
|
||||
};
|
||||
|
||||
fn fixture_bytes(path: &str) -> Vec<u8> {
|
||||
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
|
||||
@@ -1056,10 +795,9 @@ mod tests {
|
||||
#[test]
|
||||
fn as_choice_subset_rejects_inherit() {
|
||||
let child = Some(&AsIdentifierChoice::Inherit);
|
||||
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
|
||||
min: 1,
|
||||
max: 10,
|
||||
}]));
|
||||
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range { min: 1, max: 10 },
|
||||
]));
|
||||
assert!(!as_choice_subset(child, parent));
|
||||
}
|
||||
|
||||
@@ -1069,10 +807,9 @@ mod tests {
|
||||
AsIdOrRange::Id(5),
|
||||
AsIdOrRange::Range { min: 7, max: 9 },
|
||||
]));
|
||||
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
|
||||
min: 1,
|
||||
max: 10,
|
||||
}]));
|
||||
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range { min: 1, max: 10 },
|
||||
]));
|
||||
assert!(as_choice_subset(child, parent));
|
||||
}
|
||||
|
||||
@@ -1153,82 +890,84 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose_crl_for_issuer_reports_missing_crl() {
|
||||
let issuer_ca_der = fixture_bytes(
|
||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||
);
|
||||
let err = choose_crl_for_issuer(&issuer_ca_der, &[]).unwrap_err();
|
||||
assert!(matches!(err, ObjectValidateError::MissingCrl));
|
||||
fn choose_crl_for_certificate_reports_missing_crl_in_pack() {
|
||||
let roa_der =
|
||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||
.resource_cert
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
.as_ref();
|
||||
let err = choose_crl_for_certificate(ee_crldp_uris, &[]).unwrap_err();
|
||||
assert!(matches!(err, ObjectValidateError::MissingCrlInPack));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose_crl_for_issuer_falls_back_to_first_when_issuer_ca_is_not_decodable() {
|
||||
let invalid_issuer_ca_der = vec![0x01, 0x02, 0x03];
|
||||
fn choose_crl_for_certificate_reports_missing_crldp_uris() {
|
||||
let crl_a = ("rsync://example.test/a.crl".to_string(), vec![0x01]);
|
||||
let crl_b = ("rsync://example.test/b.crl".to_string(), vec![0x02]);
|
||||
let (uri, bytes) =
|
||||
choose_crl_for_issuer(&invalid_issuer_ca_der, &[crl_a.clone(), crl_b]).unwrap();
|
||||
assert_eq!(uri, crl_a.0);
|
||||
assert_eq!(bytes, crl_a.1);
|
||||
let err = choose_crl_for_certificate(None, &[crl_a]).unwrap_err();
|
||||
assert!(matches!(err, ObjectValidateError::MissingCrlDpUris));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose_crl_for_issuer_prefers_matching_crldp_uri() {
|
||||
let issuer_ca_der = fixture_bytes(
|
||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||
);
|
||||
let matching_crl_der = fixture_bytes(
|
||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl",
|
||||
);
|
||||
fn choose_crl_for_certificate_prefers_matching_crldp_uri_in_order() {
|
||||
let roa_der =
|
||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||
.resource_cert
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
.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_issuer(
|
||||
&issuer_ca_der,
|
||||
let (uri, bytes) = choose_crl_for_certificate(
|
||||
Some(ee_crldp_uris),
|
||||
&[
|
||||
(
|
||||
"rsync://example.test/other.crl".to_string(),
|
||||
other_crl_der,
|
||||
),
|
||||
(
|
||||
"rsync://ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl".to_string(),
|
||||
matching_crl_der.clone(),
|
||||
),
|
||||
("rsync://example.test/other.crl".to_string(), other_crl_der),
|
||||
(matching_uri.clone(), matching_crl_der.clone()),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
uri,
|
||||
"rsync://ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl"
|
||||
);
|
||||
assert_eq!(uri, matching_uri);
|
||||
assert_eq!(bytes, matching_crl_der);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose_crl_for_issuer_falls_back_to_first_when_no_dn_match() {
|
||||
let issuer_ca_der = fixture_bytes(
|
||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||
);
|
||||
fn choose_crl_for_certificate_reports_not_found_when_crldp_does_not_match_pack() {
|
||||
let roa_der =
|
||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||
.resource_cert
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
.as_ref();
|
||||
|
||||
let (uri, bytes) = choose_crl_for_issuer(
|
||||
&issuer_ca_der,
|
||||
&[
|
||||
("rsync://example.test/a.crl".to_string(), vec![0x01]),
|
||||
("rsync://example.test/b.crl".to_string(), vec![0x02]),
|
||||
],
|
||||
let err = choose_crl_for_certificate(
|
||||
ee_crldp_uris,
|
||||
&[("rsync://example.test/other.crl".to_string(), vec![0x01])],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(uri, "rsync://example.test/a.crl");
|
||||
assert_eq!(bytes, vec![0x01]);
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, ObjectValidateError::CrlNotFound(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_ee_resources_subset_reports_missing_issuer_effective_ip() {
|
||||
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
|
||||
);
|
||||
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
let roa_der = std::fs::read(roa_path).expect("read roa");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
||||
@@ -1265,9 +1004,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn validate_ee_resources_subset_reports_not_subset() {
|
||||
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
|
||||
);
|
||||
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
let roa_der = std::fs::read(roa_path).expect("read roa");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
||||
@@ -1286,8 +1024,7 @@ mod tests {
|
||||
}],
|
||||
};
|
||||
|
||||
let err =
|
||||
validate_ee_resources_subset(ee, Some(&issuer_ip), None).unwrap_err();
|
||||
let err = validate_ee_resources_subset(ee, Some(&issuer_ip), None).unwrap_err();
|
||||
assert!(matches!(err, ObjectValidateError::EeResourcesNotSubset));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user