增加rc,完成所有模型的解析,优化error code 的RFC引用

This commit is contained in:
2026-02-03 16:50:52 +08:00
parent 56ae2ca4fc
commit cc9f3f21de
47 changed files with 3837 additions and 1516 deletions

View File

@@ -1,4 +1,5 @@
use crate::data_model::oid::OID_CT_ASPA;
use crate::data_model::rc::ResourceCertificate;
use crate::data_model::signed_object::{RpkiSignedObject, SignedObjectDecodeError};
use der_parser::ber::{Class};
use der_parser::der::{parse_der, DerObject, Tag};
@@ -19,73 +20,64 @@ pub struct AspaEContent {
#[derive(Debug, thiserror::Error)]
pub enum AspaDecodeError {
#[error("SignedObject decode error: {0}")]
#[error("SignedObject decode error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")]
SignedObjectDecode(#[from] SignedObjectDecodeError),
#[error("ASPA eContentType must be {OID_CT_ASPA}, got {0}")]
#[error("ASPA eContentType must be {OID_CT_ASPA}, got {0} (draft-ietf-sidrops-aspa-profile-21 §2)")]
InvalidEContentType(String),
#[error("ASPA parse error: {0}")]
#[error("ASPA parse error: {0} (draft-ietf-sidrops-aspa-profile-21 §3; DER)")]
Parse(String),
#[error("ASPA trailing bytes: {0} bytes")]
#[error("ASPA trailing bytes: {0} bytes (draft-ietf-sidrops-aspa-profile-21 §3; DER)")]
TrailingBytes(usize),
#[error("ASProviderAttestation must be a SEQUENCE of 3 elements")]
#[error("ASProviderAttestation must be a SEQUENCE of 3 elements (draft-ietf-sidrops-aspa-profile-21 §3)")]
InvalidAttestationSequence,
#[error("ASPA version must be 1 and MUST be explicitly encoded")]
#[error("ASPA version must be 1 and MUST be explicitly encoded (draft-ietf-sidrops-aspa-profile-21 §3.1)")]
VersionMustBeExplicitOne,
#[error("ASPA customerASID out of range (0..=4294967295), got {0}")]
#[error("ASPA customerASID out of range (0..=4294967295), got {0} (draft-ietf-sidrops-aspa-profile-21 §3.2)")]
CustomerAsIdOutOfRange(u64),
#[error("ASPA providers must contain at least one ASID")]
#[error("ASPA providers must contain at least one ASID (draft-ietf-sidrops-aspa-profile-21 §3.3)")]
EmptyProviders,
#[error("ASPA provider ASID out of range (0..=4294967295), got {0}")]
#[error("ASPA provider ASID out of range (0..=4294967295), got {0} (draft-ietf-sidrops-aspa-profile-21 §3.3)")]
ProviderAsIdOutOfRange(u64),
#[error("ASPA providers must be in strictly increasing order")]
#[error("ASPA providers must be in strictly increasing order (draft-ietf-sidrops-aspa-profile-21 §3.3)")]
ProvidersNotStrictlyIncreasing,
#[error("ASPA providers contains the customerASID ({0}) which is not allowed")]
#[error("ASPA providers contains the customerASID ({0}) which is not allowed (draft-ietf-sidrops-aspa-profile-21 §3.3)")]
ProvidersContainCustomer(u32),
}
#[derive(Debug, thiserror::Error)]
pub enum AspaValidateError {
#[error("ASPA EE certificate must contain AS resources extension (RFC 3779)")]
#[error("ASPA EE certificate must contain AS resources extension (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2)")]
EeAsResourcesMissing,
#[error("ASPA EE certificate AS resources must not use inherit")]
#[error("ASPA EE certificate AS resources must not use inherit (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.3)")]
EeAsResourcesInherit,
#[error("ASPA EE certificate AS resources must not include ranges")]
#[error("ASPA EE certificate AS resources must not include ranges (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.6-§3.2.3.7)")]
EeAsResourcesRangePresent,
#[error("ASPA customerASID ({customer_as_id}) does not match EE AS resources ({ee_as_id})")]
#[error("ASPA EE certificate AS resources must not include RDI (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.5; RFC 6487 §4.8.11)")]
EeAsResourcesRdiPresent,
#[error("ASPA EE certificate AS resources must contain exactly one ASID (id element) (draft-ietf-sidrops-aspa-profile-21 §4)")]
EeAsResourcesNotSingleId,
#[error("ASPA customerASID ({customer_as_id}) does not match EE AS resources ({ee_as_id}) (draft-ietf-sidrops-aspa-profile-21 §4)")]
CustomerAsIdMismatch { customer_as_id: u32, ee_as_id: u32 },
#[error("ASPA EE certificate must not contain IP resources extension (RFC 3779)")]
#[error("ASPA EE certificate must not contain IP resources extension (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §2.2)")]
EeIpResourcesPresent,
}
/// Minimal EE resource information required to validate ASPA payload semantics.
///
/// The caller is responsible for extracting this from the EE certificate (RFC 3779).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EeAspaResources {
/// The single ASID carried in EE's AS resources "id" element (profile requires exactly one).
pub as_id: Option<u32>,
/// Whether EE's AS resources uses inherit (must be false).
pub as_resources_inherit: bool,
/// Whether EE's AS resources includes any range elements (must be false).
pub as_resources_range_present: bool,
/// Whether EE certificate contains IP resources extension (must be false).
pub ip_resources_present: bool,
}
impl AspaObject {
pub fn decode_der(der: &[u8]) -> Result<Self, AspaDecodeError> {
let signed_object = RpkiSignedObject::decode_der(der)?;
@@ -109,6 +101,12 @@ impl AspaObject {
econtent_type: OID_CT_ASPA.to_string(),
})
}
/// Validate this ASPA's embedded EE certificate resources.
pub fn validate_embedded_ee_cert(&self) -> Result<(), AspaValidateError> {
let ee = &self.signed_object.signed_data.certificates[0].resource_cert;
self.aspa.validate_against_ee_cert(ee)
}
}
impl AspaEContent {
@@ -165,27 +163,45 @@ impl AspaEContent {
})
}
/// Validate ASPA payload against EE resources extracted by the caller.
/// Validate ASPA payload against the embedded EE resource certificate.
///
/// This implements the EE/payload semantic checks described in
/// `draft-ietf-sidrops-aspa-profile-21` §4 (as summarized in `rpki/specs/08_aspa.md`).
pub fn validate_against_ee_resources(&self, ee: &EeAspaResources) -> Result<(), AspaValidateError> {
if ee.ip_resources_present {
pub fn validate_against_ee_cert(
&self,
ee: &ResourceCertificate,
) -> Result<(), AspaValidateError> {
if ee.tbs.extensions.ip_resources.is_some() {
return Err(AspaValidateError::EeIpResourcesPresent);
}
if ee.as_resources_inherit {
let asn = ee
.tbs
.extensions
.as_resources
.as_ref()
.ok_or(AspaValidateError::EeAsResourcesMissing)?;
if asn.rdi.is_some() {
return Err(AspaValidateError::EeAsResourcesRdiPresent);
}
if asn.is_asnum_inherit() {
return Err(AspaValidateError::EeAsResourcesInherit);
}
if ee.as_resources_range_present {
if asn.has_any_range() {
return Err(AspaValidateError::EeAsResourcesRangePresent);
}
let ee_as_id = ee.as_id.ok_or(AspaValidateError::EeAsResourcesMissing)?;
let ee_as_id = asn
.asnum_single_id()
.ok_or(AspaValidateError::EeAsResourcesNotSingleId)?;
if ee_as_id != self.customer_as_id {
return Err(AspaValidateError::CustomerAsIdMismatch {
customer_as_id: self.customer_as_id,
ee_as_id,
});
}
Ok(())
}
}