增加aspa对象解析
This commit is contained in:
223
src/data_model/aspa.rs
Normal file
223
src/data_model/aspa.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
use crate::data_model::oid::OID_CT_ASPA;
|
||||
use crate::data_model::signed_object::{RpkiSignedObject, SignedObjectDecodeError};
|
||||
use der_parser::ber::{Class};
|
||||
use der_parser::der::{parse_der, DerObject, Tag};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AspaObject {
|
||||
pub signed_object: RpkiSignedObject,
|
||||
pub econtent_type: String,
|
||||
pub aspa: AspaEContent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AspaEContent {
|
||||
pub version: u32,
|
||||
pub customer_as_id: u32,
|
||||
pub provider_as_ids: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AspaDecodeError {
|
||||
#[error("SignedObject decode error: {0}")]
|
||||
SignedObjectDecode(#[from] SignedObjectDecodeError),
|
||||
|
||||
#[error("ASPA eContentType must be {OID_CT_ASPA}, got {0}")]
|
||||
InvalidEContentType(String),
|
||||
|
||||
#[error("ASPA parse error: {0}")]
|
||||
Parse(String),
|
||||
|
||||
#[error("ASPA trailing bytes: {0} bytes")]
|
||||
TrailingBytes(usize),
|
||||
|
||||
#[error("ASProviderAttestation must be a SEQUENCE of 3 elements")]
|
||||
InvalidAttestationSequence,
|
||||
|
||||
#[error("ASPA version must be 1 and MUST be explicitly encoded")]
|
||||
VersionMustBeExplicitOne,
|
||||
|
||||
#[error("ASPA customerASID out of range (0..=4294967295), got {0}")]
|
||||
CustomerAsIdOutOfRange(u64),
|
||||
|
||||
#[error("ASPA providers must contain at least one ASID")]
|
||||
EmptyProviders,
|
||||
|
||||
#[error("ASPA provider ASID out of range (0..=4294967295), got {0}")]
|
||||
ProviderAsIdOutOfRange(u64),
|
||||
|
||||
#[error("ASPA providers must be in strictly increasing order")]
|
||||
ProvidersNotStrictlyIncreasing,
|
||||
|
||||
#[error("ASPA providers contains the customerASID ({0}) which is not allowed")]
|
||||
ProvidersContainCustomer(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AspaValidateError {
|
||||
#[error("ASPA EE certificate must contain AS resources extension (RFC 3779)")]
|
||||
EeAsResourcesMissing,
|
||||
|
||||
#[error("ASPA EE certificate AS resources must not use inherit")]
|
||||
EeAsResourcesInherit,
|
||||
|
||||
#[error("ASPA EE certificate AS resources must not include ranges")]
|
||||
EeAsResourcesRangePresent,
|
||||
|
||||
#[error("ASPA customerASID ({customer_as_id}) does not match EE AS resources ({ee_as_id})")]
|
||||
CustomerAsIdMismatch { customer_as_id: u32, ee_as_id: u32 },
|
||||
|
||||
#[error("ASPA EE certificate must not contain IP resources extension (RFC 3779)")]
|
||||
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)?;
|
||||
Self::from_signed_object(signed_object)
|
||||
}
|
||||
|
||||
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, AspaDecodeError> {
|
||||
let econtent_type = signed_object
|
||||
.signed_data
|
||||
.encap_content_info
|
||||
.econtent_type
|
||||
.clone();
|
||||
if econtent_type != OID_CT_ASPA {
|
||||
return Err(AspaDecodeError::InvalidEContentType(econtent_type));
|
||||
}
|
||||
|
||||
let aspa = AspaEContent::decode_der(&signed_object.signed_data.encap_content_info.econtent)?;
|
||||
Ok(Self {
|
||||
aspa,
|
||||
signed_object,
|
||||
econtent_type: OID_CT_ASPA.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AspaEContent {
|
||||
/// Decode the DER-encoded ASProviderAttestation defined in draft-ietf-sidrops-aspa-profile-21 §3.
|
||||
pub fn decode_der(der: &[u8]) -> Result<Self, AspaDecodeError> {
|
||||
let (rem, obj) = parse_der(der).map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(AspaDecodeError::TrailingBytes(rem.len()));
|
||||
}
|
||||
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
if seq.len() != 3 {
|
||||
return Err(AspaDecodeError::InvalidAttestationSequence);
|
||||
}
|
||||
|
||||
// version [0] EXPLICIT INTEGER MUST be present and MUST be 1.
|
||||
let v_obj = &seq[0];
|
||||
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
|
||||
return Err(AspaDecodeError::VersionMustBeExplicitOne);
|
||||
}
|
||||
let inner_der = v_obj
|
||||
.as_slice()
|
||||
.map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
let (rem, inner) =
|
||||
parse_der(inner_der).map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(AspaDecodeError::Parse(
|
||||
"trailing bytes inside ASProviderAttestation.version".into(),
|
||||
));
|
||||
}
|
||||
let v = inner
|
||||
.as_u64()
|
||||
.map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
if v != 1 {
|
||||
return Err(AspaDecodeError::VersionMustBeExplicitOne);
|
||||
}
|
||||
|
||||
let customer_u64 = seq[1]
|
||||
.as_u64()
|
||||
.map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
if customer_u64 > u32::MAX as u64 {
|
||||
return Err(AspaDecodeError::CustomerAsIdOutOfRange(customer_u64));
|
||||
}
|
||||
let customer_as_id = customer_u64 as u32;
|
||||
|
||||
let providers = parse_providers(&seq[2], customer_as_id)?;
|
||||
|
||||
Ok(Self {
|
||||
version: 1,
|
||||
customer_as_id,
|
||||
provider_as_ids: providers,
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate ASPA payload against EE resources extracted by the caller.
|
||||
///
|
||||
/// 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 {
|
||||
return Err(AspaValidateError::EeIpResourcesPresent);
|
||||
}
|
||||
if ee.as_resources_inherit {
|
||||
return Err(AspaValidateError::EeAsResourcesInherit);
|
||||
}
|
||||
if ee.as_resources_range_present {
|
||||
return Err(AspaValidateError::EeAsResourcesRangePresent);
|
||||
}
|
||||
let ee_as_id = ee.as_id.ok_or(AspaValidateError::EeAsResourcesMissing)?;
|
||||
if ee_as_id != self.customer_as_id {
|
||||
return Err(AspaValidateError::CustomerAsIdMismatch {
|
||||
customer_as_id: self.customer_as_id,
|
||||
ee_as_id,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_providers(obj: &DerObject<'_>, customer_as_id: u32) -> Result<Vec<u32>, AspaDecodeError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
if seq.is_empty() {
|
||||
return Err(AspaDecodeError::EmptyProviders);
|
||||
}
|
||||
|
||||
let mut out: Vec<u32> = Vec::with_capacity(seq.len());
|
||||
let mut prev: Option<u32> = None;
|
||||
for item in seq {
|
||||
let v = item
|
||||
.as_u64()
|
||||
.map_err(|e| AspaDecodeError::Parse(e.to_string()))?;
|
||||
if v > u32::MAX as u64 {
|
||||
return Err(AspaDecodeError::ProviderAsIdOutOfRange(v));
|
||||
}
|
||||
let asn = v as u32;
|
||||
if asn == customer_as_id {
|
||||
return Err(AspaDecodeError::ProvidersContainCustomer(customer_as_id));
|
||||
}
|
||||
if let Some(p) = prev {
|
||||
if asn <= p {
|
||||
return Err(AspaDecodeError::ProvidersNotStrictlyIncreasing);
|
||||
}
|
||||
}
|
||||
prev = Some(asn);
|
||||
out.push(asn);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
Reference in New Issue
Block a user