增加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)
|
||||
}
|
||||
@@ -8,3 +8,5 @@ mod oids;
|
||||
pub mod oid;
|
||||
pub mod signed_object;
|
||||
pub mod manifest;
|
||||
pub mod roa;
|
||||
pub mod aspa;
|
||||
|
||||
@@ -14,6 +14,8 @@ pub const OID_CRL_NUMBER: &str = "2.5.29.20";
|
||||
pub const OID_SUBJECT_KEY_IDENTIFIER: &str = "2.5.29.14";
|
||||
|
||||
pub const OID_CT_RPKI_MANIFEST: &str = "1.2.840.113549.1.9.16.1.26";
|
||||
pub const OID_CT_ROUTE_ORIGIN_AUTHZ: &str = "1.2.840.113549.1.9.16.1.24";
|
||||
pub const OID_CT_ASPA: &str = "1.2.840.113549.1.9.16.1.49";
|
||||
|
||||
// X.509 extensions / access methods (RFC 5280 / RFC 6487)
|
||||
pub const OID_SUBJECT_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.11";
|
||||
|
||||
466
src/data_model/roa.rs
Normal file
466
src/data_model/roa.rs
Normal file
@@ -0,0 +1,466 @@
|
||||
use crate::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ;
|
||||
use crate::data_model::signed_object::{RpkiSignedObject, SignedObjectDecodeError};
|
||||
use der_parser::ber::{BerObjectContent, Class};
|
||||
use der_parser::der::{parse_der, DerObject, Tag};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RoaObject {
|
||||
pub signed_object: RpkiSignedObject,
|
||||
pub econtent_type: String,
|
||||
pub roa: RoaEContent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RoaEContent {
|
||||
pub version: u32,
|
||||
pub as_id: u32,
|
||||
pub ip_addr_blocks: Vec<RoaIpAddressFamily>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RoaDecodeError {
|
||||
#[error("SignedObject decode error: {0}")]
|
||||
SignedObjectDecode(#[from] SignedObjectDecodeError),
|
||||
|
||||
#[error("ROA eContentType must be {OID_CT_ROUTE_ORIGIN_AUTHZ}, got {0}")]
|
||||
InvalidEContentType(String),
|
||||
|
||||
#[error("ROA parse error: {0}")]
|
||||
Parse(String),
|
||||
|
||||
#[error("ROA trailing bytes: {0} bytes")]
|
||||
TrailingBytes(usize),
|
||||
|
||||
#[error("RouteOriginAttestation must be a SEQUENCE of 2 or 3 elements, got {0}")]
|
||||
InvalidAttestationSequenceLen(usize),
|
||||
|
||||
#[error("ROA version must be 0, got {0}")]
|
||||
InvalidVersion(u64),
|
||||
|
||||
#[error("ROA asID out of range (0..=4294967295), got {0}")]
|
||||
AsIdOutOfRange(u64),
|
||||
|
||||
#[error("ROA ipAddrBlocks must have length 1..2, got {0}")]
|
||||
InvalidIpAddrBlocksLen(usize),
|
||||
|
||||
#[error("ROAIPAddressFamily must be a SEQUENCE of 2 elements")]
|
||||
InvalidIpAddressFamily,
|
||||
|
||||
#[error("ROA addressFamily must be an OCTET STRING of 2 bytes")]
|
||||
InvalidAddressFamily,
|
||||
|
||||
#[error("ROA addressFamily AFI not supported: {0:02X?}")]
|
||||
UnsupportedAfi(Vec<u8>),
|
||||
|
||||
#[error("ROA contains duplicate AFI {0:?}")]
|
||||
DuplicateAfi(RoaAfi),
|
||||
|
||||
#[error("ROAAddresses must have at least one entry")]
|
||||
EmptyAddressList,
|
||||
|
||||
#[error("ROAIPAddress must be a SEQUENCE of 1..2 elements")]
|
||||
InvalidRoaIpAddress,
|
||||
|
||||
#[error("ROAIPAddress.address must be a BIT STRING")]
|
||||
InvalidPrefixBitString,
|
||||
|
||||
#[error("ROAIPAddress.address has invalid unused bits encoding")]
|
||||
InvalidPrefixUnusedBits,
|
||||
|
||||
#[error("ROAIPAddress.address prefix length {prefix_len} out of range for {afi:?}")]
|
||||
PrefixLenOutOfRange { afi: RoaAfi, prefix_len: u16 },
|
||||
|
||||
#[error("ROAIPAddress.maxLength out of range for {afi:?}: prefix_len={prefix_len}, max_len={max_len}")]
|
||||
InvalidMaxLength {
|
||||
afi: RoaAfi,
|
||||
prefix_len: u16,
|
||||
max_len: u16,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RoaValidateError {
|
||||
#[error("ROA EE certificate must not contain AS resources extension (RFC 9582 §5)")]
|
||||
EeAsResourcesPresent,
|
||||
|
||||
#[error("ROA EE certificate IP resources must not use inherit (RFC 9582 §5)")]
|
||||
EeIpResourcesInherit,
|
||||
|
||||
#[error("ROA prefix not covered by EE certificate IP resources: {afi:?} {addr:?}/{prefix_len}")]
|
||||
PrefixNotInEeResources {
|
||||
afi: RoaAfi,
|
||||
addr: Vec<u8>,
|
||||
prefix_len: u16,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct IpResourceSet {
|
||||
pub prefixes: Vec<IpPrefix>,
|
||||
}
|
||||
|
||||
impl IpResourceSet {
|
||||
pub fn contains_prefix(&self, p: &IpPrefix) -> bool {
|
||||
self.prefixes
|
||||
.iter()
|
||||
.any(|r| r.afi == p.afi && prefix_covers(r, p))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EeResources {
|
||||
pub ip_resources: IpResourceSet,
|
||||
pub ip_resources_inherit: bool,
|
||||
pub as_resources_present: bool,
|
||||
}
|
||||
|
||||
impl RoaObject {
|
||||
pub fn decode_der(der: &[u8]) -> Result<Self, RoaDecodeError> {
|
||||
let signed_object = RpkiSignedObject::decode_der(der)?;
|
||||
Self::from_signed_object(signed_object)
|
||||
}
|
||||
|
||||
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, RoaDecodeError> {
|
||||
let econtent_type = signed_object
|
||||
.signed_data
|
||||
.encap_content_info
|
||||
.econtent_type
|
||||
.clone();
|
||||
if econtent_type != OID_CT_ROUTE_ORIGIN_AUTHZ {
|
||||
return Err(RoaDecodeError::InvalidEContentType(econtent_type));
|
||||
}
|
||||
|
||||
let roa = RoaEContent::decode_der(&signed_object.signed_data.encap_content_info.econtent)?;
|
||||
Ok(Self {
|
||||
roa,
|
||||
signed_object,
|
||||
econtent_type: OID_CT_ROUTE_ORIGIN_AUTHZ.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum RoaAfi {
|
||||
Ipv4,
|
||||
Ipv6,
|
||||
}
|
||||
|
||||
impl RoaAfi {
|
||||
fn ub(self) -> u16 {
|
||||
match self {
|
||||
RoaAfi::Ipv4 => 32,
|
||||
RoaAfi::Ipv6 => 128,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RoaIpAddressFamily {
|
||||
pub afi: RoaAfi,
|
||||
pub addresses: Vec<RoaIpAddress>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct RoaIpAddress {
|
||||
pub prefix: IpPrefix,
|
||||
pub max_length: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct IpPrefix {
|
||||
pub afi: RoaAfi,
|
||||
/// Prefix length in bits.
|
||||
pub prefix_len: u16,
|
||||
/// Network order address bytes (IPv4 4 bytes / IPv6 16 bytes), with host bits cleared.
|
||||
pub addr: Vec<u8>,
|
||||
}
|
||||
|
||||
impl RoaEContent {
|
||||
/// Decode the DER-encoded RouteOriginAttestation defined in RFC 9582 §4.
|
||||
pub fn decode_der(der: &[u8]) -> Result<Self, RoaDecodeError> {
|
||||
let (rem, obj) = parse_der(der).map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(RoaDecodeError::TrailingBytes(rem.len()));
|
||||
}
|
||||
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if seq.len() != 2 && seq.len() != 3 {
|
||||
return Err(RoaDecodeError::InvalidAttestationSequenceLen(seq.len()));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
let mut version: u32 = 0;
|
||||
if seq.len() == 3 {
|
||||
let v_obj = &seq[0];
|
||||
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
|
||||
return Err(RoaDecodeError::Parse(
|
||||
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
|
||||
));
|
||||
}
|
||||
let inner_der = v_obj
|
||||
.as_slice()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
let (rem, inner) =
|
||||
parse_der(inner_der).map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(RoaDecodeError::Parse(
|
||||
"trailing bytes inside RouteOriginAttestation.version".into(),
|
||||
));
|
||||
}
|
||||
let v = inner
|
||||
.as_u64()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if v != 0 {
|
||||
return Err(RoaDecodeError::InvalidVersion(v));
|
||||
}
|
||||
version = 0;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
let as_id_u64 = seq[idx]
|
||||
.as_u64()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if as_id_u64 > u32::MAX as u64 {
|
||||
return Err(RoaDecodeError::AsIdOutOfRange(as_id_u64));
|
||||
}
|
||||
let as_id = as_id_u64 as u32;
|
||||
idx += 1;
|
||||
|
||||
let ip_addr_blocks = parse_ip_addr_blocks(&seq[idx])?;
|
||||
|
||||
let mut out = Self {
|
||||
version,
|
||||
as_id,
|
||||
ip_addr_blocks,
|
||||
};
|
||||
out.canonicalize();
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn canonicalize(&mut self) {
|
||||
self.ip_addr_blocks.sort_by_key(|f| f.afi);
|
||||
for fam in &mut self.ip_addr_blocks {
|
||||
fam.addresses.sort();
|
||||
fam.addresses.dedup();
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate ROA payload against EE resources extracted by the caller.
|
||||
///
|
||||
/// This implements the ROA/EE semantic checks from RFC 9582 §5 that do not
|
||||
/// require certificate path validation.
|
||||
pub fn validate_against_ee_resources(&self, ee: &EeResources) -> Result<(), RoaValidateError> {
|
||||
if ee.as_resources_present {
|
||||
return Err(RoaValidateError::EeAsResourcesPresent);
|
||||
}
|
||||
if ee.ip_resources_inherit {
|
||||
return Err(RoaValidateError::EeIpResourcesInherit);
|
||||
}
|
||||
|
||||
for fam in &self.ip_addr_blocks {
|
||||
for entry in &fam.addresses {
|
||||
if !ee.ip_resources.contains_prefix(&entry.prefix) {
|
||||
return Err(RoaValidateError::PrefixNotInEeResources {
|
||||
afi: entry.prefix.afi,
|
||||
addr: entry.prefix.addr.clone(),
|
||||
prefix_len: entry.prefix.prefix_len,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ip_addr_blocks(obj: &DerObject<'_>) -> Result<Vec<RoaIpAddressFamily>, RoaDecodeError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if seq.is_empty() || seq.len() > 2 {
|
||||
return Err(RoaDecodeError::InvalidIpAddrBlocksLen(seq.len()));
|
||||
}
|
||||
|
||||
let mut out: Vec<RoaIpAddressFamily> = Vec::new();
|
||||
for fam in seq {
|
||||
let family = parse_ip_address_family(fam)?;
|
||||
if out.iter().any(|f| f.afi == family.afi) {
|
||||
return Err(RoaDecodeError::DuplicateAfi(family.afi));
|
||||
}
|
||||
out.push(family);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_ip_address_family(obj: &DerObject<'_>) -> Result<RoaIpAddressFamily, RoaDecodeError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if seq.len() != 2 {
|
||||
return Err(RoaDecodeError::InvalidIpAddressFamily);
|
||||
}
|
||||
|
||||
let afi = parse_afi(&seq[0])?;
|
||||
let addresses = parse_roa_addresses(afi, &seq[1])?;
|
||||
if addresses.is_empty() {
|
||||
return Err(RoaDecodeError::EmptyAddressList);
|
||||
}
|
||||
|
||||
Ok(RoaIpAddressFamily { afi, addresses })
|
||||
}
|
||||
|
||||
fn parse_afi(obj: &DerObject<'_>) -> Result<RoaAfi, RoaDecodeError> {
|
||||
let bytes = obj
|
||||
.as_slice()
|
||||
.map_err(|_e| RoaDecodeError::InvalidAddressFamily)?;
|
||||
if bytes.len() != 2 {
|
||||
return Err(RoaDecodeError::InvalidAddressFamily);
|
||||
}
|
||||
match bytes {
|
||||
[0x00, 0x01] => Ok(RoaAfi::Ipv4),
|
||||
[0x00, 0x02] => Ok(RoaAfi::Ipv6),
|
||||
_ => Err(RoaDecodeError::UnsupportedAfi(bytes.to_vec())),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_roa_addresses(afi: RoaAfi, obj: &DerObject<'_>) -> Result<Vec<RoaIpAddress>, RoaDecodeError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
let mut out = Vec::with_capacity(seq.len());
|
||||
for entry in seq {
|
||||
out.push(parse_roa_ip_address(afi, entry)?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_roa_ip_address(afi: RoaAfi, obj: &DerObject<'_>) -> Result<RoaIpAddress, RoaDecodeError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
if seq.is_empty() || seq.len() > 2 {
|
||||
return Err(RoaDecodeError::InvalidRoaIpAddress);
|
||||
}
|
||||
|
||||
let prefix = parse_prefix_bits(afi, &seq[0])?;
|
||||
let max_length = match seq.get(1) {
|
||||
None => None,
|
||||
Some(m) => {
|
||||
let v = m
|
||||
.as_u64()
|
||||
.map_err(|e| RoaDecodeError::Parse(e.to_string()))?;
|
||||
let max_len: u16 = v
|
||||
.try_into()
|
||||
.map_err(|_e| RoaDecodeError::InvalidMaxLength {
|
||||
afi,
|
||||
prefix_len: prefix.prefix_len,
|
||||
max_len: u16::MAX,
|
||||
})?;
|
||||
Some(max_len)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(max_len) = max_length {
|
||||
let ub = afi.ub();
|
||||
if max_len > ub || max_len < prefix.prefix_len {
|
||||
return Err(RoaDecodeError::InvalidMaxLength {
|
||||
afi,
|
||||
prefix_len: prefix.prefix_len,
|
||||
max_len,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RoaIpAddress { prefix, max_length })
|
||||
}
|
||||
|
||||
fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaDecodeError> {
|
||||
let (unused_bits, bytes) = match &obj.content {
|
||||
BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()),
|
||||
_ => return Err(RoaDecodeError::InvalidPrefixBitString),
|
||||
};
|
||||
|
||||
if unused_bits > 7 {
|
||||
return Err(RoaDecodeError::InvalidPrefixUnusedBits);
|
||||
}
|
||||
if bytes.is_empty() {
|
||||
if unused_bits != 0 {
|
||||
return Err(RoaDecodeError::InvalidPrefixUnusedBits);
|
||||
}
|
||||
} else if unused_bits != 0 {
|
||||
let mask = (1u8 << unused_bits) - 1;
|
||||
if (bytes[bytes.len() - 1] & mask) != 0 {
|
||||
return Err(RoaDecodeError::InvalidPrefixUnusedBits);
|
||||
}
|
||||
}
|
||||
|
||||
let prefix_len = (bytes.len() * 8)
|
||||
.checked_sub(unused_bits as usize)
|
||||
.ok_or(RoaDecodeError::InvalidPrefixUnusedBits)? as u16;
|
||||
if prefix_len > afi.ub() {
|
||||
return Err(RoaDecodeError::PrefixLenOutOfRange { afi, prefix_len });
|
||||
}
|
||||
|
||||
let addr = canonicalize_prefix_addr(afi, prefix_len, &bytes);
|
||||
Ok(IpPrefix {
|
||||
afi,
|
||||
prefix_len,
|
||||
addr,
|
||||
})
|
||||
}
|
||||
|
||||
fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> Vec<u8> {
|
||||
let full_len = match afi {
|
||||
RoaAfi::Ipv4 => 4,
|
||||
RoaAfi::Ipv6 => 16,
|
||||
};
|
||||
let mut addr = vec![0u8; full_len];
|
||||
let copy_len = bytes.len().min(full_len);
|
||||
addr[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||
|
||||
if prefix_len == 0 {
|
||||
return addr;
|
||||
}
|
||||
|
||||
let last_prefix_bit = (prefix_len - 1) as usize;
|
||||
let last_prefix_byte = last_prefix_bit / 8;
|
||||
let rem = (prefix_len % 8) as u8;
|
||||
if rem != 0 {
|
||||
let mask: u8 = 0xFF << (8 - rem);
|
||||
if last_prefix_byte < addr.len() {
|
||||
addr[last_prefix_byte] &= mask;
|
||||
}
|
||||
}
|
||||
addr
|
||||
}
|
||||
|
||||
fn prefix_covers(resource: &IpPrefix, subject: &IpPrefix) -> bool {
|
||||
if resource.afi != subject.afi {
|
||||
return false;
|
||||
}
|
||||
if resource.prefix_len > subject.prefix_len {
|
||||
return false;
|
||||
}
|
||||
|
||||
let full_len = match resource.afi {
|
||||
RoaAfi::Ipv4 => 4,
|
||||
RoaAfi::Ipv6 => 16,
|
||||
};
|
||||
if resource.addr.len() != full_len || subject.addr.len() != full_len {
|
||||
return false;
|
||||
}
|
||||
|
||||
let n = resource.prefix_len as usize;
|
||||
let whole = n / 8;
|
||||
let rem = (n % 8) as u8;
|
||||
|
||||
if resource.addr[..whole] != subject.addr[..whole] {
|
||||
return false;
|
||||
}
|
||||
if rem == 0 {
|
||||
return true;
|
||||
}
|
||||
let mask = 0xFFu8 << (8 - rem);
|
||||
(resource.addr[whole] & mask) == (subject.addr[whole] & mask)
|
||||
}
|
||||
Reference in New Issue
Block a user