use der_parser::ber::{BerObjectContent, Class}; use der_parser::der::{DerObject, Tag, parse_der}; use der_parser::num_bigint::BigUint; use url::Url; use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag}; use x509_parser::extensions::ParsedExtension; use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version}; use crate::data_model::common::{ Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, asn1_time_to_model, }; use crate::data_model::oid::{ OID_AD_SIGNED_OBJECT, OID_AUTONOMOUS_SYS_IDS, OID_CP_IPADDR_ASNUMBER, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER, }; /// Resource Certificate kind (semantic classification). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ResourceCertKind { Ca, Ee, } /// A parsed RPKI Resource Certificate (RFC 6487) data model. /// /// This module intentionally focuses on the semantics needed by Signed Object validation and /// object-specific EE certificate checks (MFT/ROA/ASPA), as described in /// `rpki/specs/03_resource_certificate_rc.md`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResourceCertificate { pub raw_der: Vec, pub tbs: RpkixTbsCertificate, pub kind: ResourceCertKind, } pub type ResourceCaCertificate = ResourceCertificate; pub type ResourceEeCertificate = ResourceCertificate; #[derive(Clone, Debug, PartialEq, Eq)] pub struct RpkixTbsCertificate { pub version: u32, pub serial_number: BigUint, pub signature_algorithm: String, pub issuer_dn: String, pub subject_dn: String, pub validity_not_before: UtcTime, pub validity_not_after: UtcTime, /// DER encoding of SubjectPublicKeyInfo. pub subject_public_key_info: Vec, pub extensions: RcExtensions, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RcExtensions { pub basic_constraints_ca: bool, pub subject_key_identifier: Option>, pub subject_info_access: Option, pub certificate_policies_oid: Option, pub ip_resources: Option, pub as_resources: Option, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResourceCertificateParsed { pub raw_der: Vec, pub version: X509Version, pub serial_number: BigUint, pub signature_algorithm: AlgorithmIdentifierValue, pub tbs_signature_algorithm: AlgorithmIdentifierValue, pub issuer_dn: String, pub subject_dn: String, pub validity_not_before: Asn1TimeUtc, pub validity_not_after: Asn1TimeUtc, /// DER encoding of SubjectPublicKeyInfo. pub subject_public_key_info: Vec, pub extensions: RcExtensionsParsed, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct AlgorithmIdentifierValue { pub oid: String, pub parameters: Option, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct AlgorithmParametersValue { pub class: Asn1Class, pub tag: Asn1Tag, pub data: Vec, } impl AlgorithmIdentifierValue { pub fn params_absent_or_null(&self) -> bool { match &self.parameters { None => true, Some(p) if p.class == Asn1Class::Universal && p.tag == Asn1Tag::Null => true, Some(_p) => false, } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RcExtensionsParsed { pub basic_constraints_ca: Vec, pub subject_key_identifier: Vec<(Vec, bool)>, pub subject_info_access: Vec<(SubjectInfoAccessParsed, bool)>, pub certificate_policies: Vec<(Vec, bool)>, pub ip_resources: Vec<(IpResourceSet, bool)>, pub as_resources: Vec<(AsResourceSet, bool)>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SubjectInfoAccessParsed { pub access_descriptions: Vec, pub signed_object_uris: Vec, pub signed_object_access_location_not_uri: bool, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum SubjectInfoAccess { Ca(SubjectInfoAccessCa), Ee(SubjectInfoAccessEe), } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SubjectInfoAccessCa { pub access_descriptions: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SubjectInfoAccessEe { pub signed_object_uris: Vec, /// The full list of access descriptions as carried in the SIA extension. pub access_descriptions: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct AccessDescription { pub access_method_oid: String, pub access_location: Url, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Afi { Ipv4, Ipv6, } impl Afi { pub fn ub(self) -> u16 { match self { Afi::Ipv4 => 32, Afi::Ipv6 => 128, } } pub fn octets_len(self) -> usize { match self { Afi::Ipv4 => 4, Afi::Ipv6 => 16, } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct IpResourceSet { pub families: Vec, } impl IpResourceSet { /// Decode the DER bytes carried inside the X.509 `extnValue` OCTET STRING for /// `id-pe-ipAddrBlocks` (RFC 3779 / RFC 6487). pub fn decode_extn_value(extn_value: &[u8]) -> Result { parse_ip_addr_blocks(extn_value).map_err(|_| IpResourceSetDecodeError::InvalidEncoding) } pub fn is_all_inherit(&self) -> bool { self.families .iter() .all(|f| matches!(f.choice, IpAddressChoice::Inherit)) } pub fn has_any_inherit(&self) -> bool { self.families .iter() .any(|f| matches!(f.choice, IpAddressChoice::Inherit)) } pub fn contains_prefix(&self, prefix: &IpPrefix) -> bool { self.families.iter().any(|fam| fam.contains_prefix(prefix)) } } #[derive(Debug, thiserror::Error)] pub enum IpResourceSetDecodeError { #[error("invalid ipAddrBlocks encoding (RFC 3779 §2.2.3; RFC 6487 §4.8.10)")] InvalidEncoding, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct IpAddressFamily { pub afi: Afi, pub choice: IpAddressChoice, } impl IpAddressFamily { pub fn contains_prefix(&self, prefix: &IpPrefix) -> bool { if self.afi != prefix.afi { return false; } match &self.choice { IpAddressChoice::Inherit => true, IpAddressChoice::AddressesOrRanges(items) => items.iter().any(|item| match item { IpAddressOrRange::Prefix(p) => prefix_covers(p, prefix), IpAddressOrRange::Range(r) => range_covers_prefix(self.afi, r, prefix), }), } } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum IpAddressChoice { Inherit, AddressesOrRanges(Vec), } #[derive(Clone, Debug, PartialEq, Eq)] pub enum IpAddressOrRange { Prefix(IpPrefix), Range(IpAddressRange), } #[derive(Clone, Debug, PartialEq, Eq)] pub struct IpAddressRange { pub min: Vec, pub max: Vec, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct IpPrefix { pub afi: Afi, pub prefix_len: u16, pub addr: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct AsResourceSet { pub asnum: Option, pub rdi: Option, } impl AsResourceSet { /// Decode the DER bytes carried inside the X.509 `extnValue` OCTET STRING for /// `id-pe-autonomousSysIds` (RFC 3779 / RFC 6487). pub fn decode_extn_value(extn_value: &[u8]) -> Result { parse_as_identifiers(extn_value).map_err(|_| AsResourceSetDecodeError::InvalidEncoding) } pub fn is_asnum_inherit(&self) -> bool { matches!(self.asnum, Some(AsIdentifierChoice::Inherit)) } pub fn has_any_range(&self) -> bool { self.asnum.as_ref().map(|c| c.has_range()).unwrap_or(false) || self.rdi.as_ref().map(|c| c.has_range()).unwrap_or(false) } pub fn asnum_single_id(&self) -> Option { match self.asnum.as_ref()? { AsIdentifierChoice::Inherit => None, AsIdentifierChoice::AsIdsOrRanges(items) => { if items.len() != 1 { return None; } match &items[0] { AsIdOrRange::Id(v) => Some(*v), AsIdOrRange::Range { .. } => None, } } } } } #[derive(Debug, thiserror::Error)] pub enum AsResourceSetDecodeError { #[error("invalid autonomousSysIds encoding (RFC 3779 §3.2.3; RFC 6487 §4.8.11)")] InvalidEncoding, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum AsIdentifierChoice { Inherit, AsIdsOrRanges(Vec), } impl AsIdentifierChoice { pub fn has_range(&self) -> bool { match self { AsIdentifierChoice::Inherit => false, AsIdentifierChoice::AsIdsOrRanges(items) => { items.iter().any(|i| matches!(i, AsIdOrRange::Range { .. })) } } } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum AsIdOrRange { Id(u32), Range { min: u32, max: u32 }, } #[derive(Debug, thiserror::Error)] pub enum ResourceCertificateParseError { #[error("X.509 parse error: {0} (RFC 5280 §4.1; RFC 6487 §4)")] Parse(String), #[error("trailing bytes after certificate DER: {0} bytes (DER; RFC 5280 §4.1)")] TrailingBytes(usize), #[error("invalid RFC 3779 IP resources extension encoding (RFC 6487 §4.8.10; RFC 3779 §2.2)")] InvalidIpResourcesEncoding, #[error("invalid RFC 3779 AS resources extension encoding (RFC 6487 §4.8.11; RFC 3779 §3.2)")] InvalidAsResourcesEncoding, } #[derive(Debug, thiserror::Error)] pub enum ResourceCertificateProfileError { #[error("{0}")] InvalidTimeEncoding(#[from] InvalidTimeEncodingError), #[error("certificate version must be v3 (RFC 5280 §4.1; RFC 6487 §4)")] InvalidVersion, #[error("signatureAlgorithm does not match tbsCertificate.signature (RFC 5280 §4.1)")] SignatureAlgorithmMismatch, #[error( "unsupported signature algorithm (expected sha256WithRSAEncryption {OID_SHA256_WITH_RSA_ENCRYPTION}) (RFC 7935 §2; RFC 6487 §4)" )] UnsupportedSignatureAlgorithm, #[error("invalid signature algorithm parameters (RFC 5280 §4.1.1.2)")] InvalidSignatureAlgorithmParameters, #[error("duplicate extension: {0} (RFC 5280 §4.2; RFC 6487 §4.8)")] DuplicateExtension(&'static str), #[error("SubjectKeyIdentifier criticality must be non-critical (RFC 6487 §4.8.2)")] SkiCriticality, #[error("SubjectInfoAccess criticality must be non-critical (RFC 6487 §4.8.8)")] SiaCriticality, #[error("certificatePolicies criticality must be critical (RFC 6487 §4.8.9)")] CertificatePoliciesCriticality, #[error( "certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)" )] InvalidCertificatePolicy(String), #[error( "SIA id-ad-signedObject accessLocation must be URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)" )] SignedObjectSiaNotUri, #[error("SIA id-ad-signedObject must include at least one rsync:// URI (RFC 6487 §4.8.8.2)")] SignedObjectSiaNoRsync, #[error("ipAddrBlocks criticality must be critical when present (RFC 6487 §4.8.10)")] IpResourcesCriticality, #[error("autonomousSysIds criticality must be critical when present (RFC 6487 §4.8.11)")] AsResourcesCriticality, } #[derive(Debug, thiserror::Error)] pub enum ResourceCertificateDecodeError { #[error("{0}")] Parse(#[from] ResourceCertificateParseError), #[error("{0}")] Validate(#[from] ResourceCertificateProfileError), } pub type ResourceCertificateError = ResourceCertificateDecodeError; impl ResourceCertificate { /// Parse step of scheme A (`parse → validate → verify`). pub fn parse_der( der: &[u8], ) -> Result { let (rem, cert) = X509Certificate::from_der(der) .map_err(|e| ResourceCertificateParseError::Parse(e.to_string()))?; if !rem.is_empty() { return Err(ResourceCertificateParseError::TrailingBytes(rem.len())); } let validity_not_before = asn1_time_to_model(cert.validity().not_before); let validity_not_after = asn1_time_to_model(cert.validity().not_after); let subject_public_key_info = cert.tbs_certificate.subject_pki.raw.to_vec(); let signature_algorithm = algorithm_identifier_value(&cert.signature_algorithm); let tbs_signature_algorithm = algorithm_identifier_value(&cert.tbs_certificate.signature); let extensions = parse_extensions_parse(cert.extensions())?; Ok(ResourceCertificateParsed { raw_der: der.to_vec(), version: cert.version(), serial_number: cert.tbs_certificate.serial.clone(), signature_algorithm, tbs_signature_algorithm, issuer_dn: cert.issuer().to_string(), subject_dn: cert.subject().to_string(), validity_not_before, validity_not_after, subject_public_key_info, extensions, }) } /// Profile validate step of scheme A (`parse → validate → verify`). /// /// `ResourceCertificate` is already profile-validated when constructed via `decode_der()` / /// `ResourceCertificateParsed::validate_profile()`. pub fn validate_profile(&self) -> Result<(), ResourceCertificateProfileError> { Ok(()) } /// Decode a resource certificate (`parse + validate`). pub fn decode_der(der: &[u8]) -> Result { Ok(Self::parse_der(der)?.validate_profile()?) } /// Backwards-compatible helper (historical name). pub fn from_der(der: &[u8]) -> Result { Self::decode_der(der) } } impl ResourceCertificateParsed { pub fn validate_profile(self) -> Result { let version = match self.version { X509Version::V3 => 2u32, _ => return Err(ResourceCertificateProfileError::InvalidVersion), }; self.validity_not_before .validate_encoding_rfc5280("notBefore")?; self.validity_not_after .validate_encoding_rfc5280("notAfter")?; if self.signature_algorithm != self.tbs_signature_algorithm { return Err(ResourceCertificateProfileError::SignatureAlgorithmMismatch); } if self.signature_algorithm.oid != OID_SHA256_WITH_RSA_ENCRYPTION { return Err(ResourceCertificateProfileError::UnsupportedSignatureAlgorithm); } if !self.signature_algorithm.params_absent_or_null() { return Err(ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters); } let extensions = self.extensions.validate_profile()?; let kind = if extensions.basic_constraints_ca { ResourceCertKind::Ca } else { ResourceCertKind::Ee }; Ok(ResourceCertificate { raw_der: self.raw_der, tbs: RpkixTbsCertificate { version, serial_number: self.serial_number, signature_algorithm: self.signature_algorithm.oid, issuer_dn: self.issuer_dn, subject_dn: self.subject_dn, validity_not_before: self.validity_not_before.utc, validity_not_after: self.validity_not_after.utc, subject_public_key_info: self.subject_public_key_info, extensions, }, kind, }) } } impl RcExtensionsParsed { pub fn validate_profile(self) -> Result { if self.basic_constraints_ca.len() > 1 { return Err(ResourceCertificateProfileError::DuplicateExtension( "basicConstraints", )); } let basic_constraints_ca = self.basic_constraints_ca.first().copied().unwrap_or(false); let subject_key_identifier = match self.subject_key_identifier.as_slice() { [] => None, [(ski, critical)] => { if *critical { return Err(ResourceCertificateProfileError::SkiCriticality); } Some(ski.clone()) } _ => { return Err(ResourceCertificateProfileError::DuplicateExtension( "subjectKeyIdentifier", )); } }; let subject_info_access = match self.subject_info_access.as_slice() { [] => None, [(sia, critical)] => { if *critical { return Err(ResourceCertificateProfileError::SiaCriticality); } if sia.signed_object_access_location_not_uri { return Err(ResourceCertificateProfileError::SignedObjectSiaNotUri); } if !sia.signed_object_uris.is_empty() && !sia.signed_object_uris.iter().any(|u| u.scheme() == "rsync") { return Err(ResourceCertificateProfileError::SignedObjectSiaNoRsync); } if sia.signed_object_uris.is_empty() { Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa { access_descriptions: sia.access_descriptions.clone(), })) } else { Some(SubjectInfoAccess::Ee(SubjectInfoAccessEe { signed_object_uris: sia.signed_object_uris.clone(), access_descriptions: sia.access_descriptions.clone(), })) } } _ => { return Err(ResourceCertificateProfileError::DuplicateExtension( "subjectInfoAccess", )); } }; let certificate_policies_oid = match self.certificate_policies.as_slice() { [] => None, [(oids, critical)] => { if !*critical { return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality); } if oids.len() != 1 { return Err(ResourceCertificateProfileError::InvalidCertificatePolicy( "expected exactly one policy".into(), )); } let policy_oid = oids[0].clone(); if policy_oid != OID_CP_IPADDR_ASNUMBER { return Err(ResourceCertificateProfileError::InvalidCertificatePolicy( policy_oid, )); } Some(OID_CP_IPADDR_ASNUMBER.to_string()) } _ => { return Err(ResourceCertificateProfileError::DuplicateExtension( "certificatePolicies", )); } }; let ip_resources = match self.ip_resources.as_slice() { [] => None, [(ip, critical)] => { if !*critical { return Err(ResourceCertificateProfileError::IpResourcesCriticality); } Some(ip.clone()) } _ => { return Err(ResourceCertificateProfileError::DuplicateExtension( "ipAddrBlocks", )); } }; let as_resources = match self.as_resources.as_slice() { [] => None, [(asn, critical)] => { if !*critical { return Err(ResourceCertificateProfileError::AsResourcesCriticality); } Some(asn.clone()) } _ => { return Err(ResourceCertificateProfileError::DuplicateExtension( "autonomousSysIds", )); } }; Ok(RcExtensions { basic_constraints_ca, subject_key_identifier, subject_info_access, certificate_policies_oid, ip_resources, as_resources, }) } } fn algorithm_identifier_value( ai: &x509_parser::x509::AlgorithmIdentifier<'_>, ) -> AlgorithmIdentifierValue { let parameters = ai.parameters.as_ref().map(|p| AlgorithmParametersValue { class: p.class(), tag: p.tag(), data: p.as_bytes().to_vec(), }); AlgorithmIdentifierValue { oid: ai.algorithm.to_id_string(), parameters, } } fn parse_extensions_parse( exts: &[X509Extension<'_>], ) -> Result { let mut basic_constraints_ca: Vec = Vec::new(); let mut ski: Vec<(Vec, bool)> = Vec::new(); let mut sia: Vec<(SubjectInfoAccessParsed, bool)> = Vec::new(); let mut cert_policies: Vec<(Vec, bool)> = Vec::new(); let mut ip_resources: Vec<(IpResourceSet, bool)> = Vec::new(); let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new(); for ext in exts { let oid = ext.oid.to_id_string(); match oid.as_str() { crate::data_model::oid::OID_BASIC_CONSTRAINTS => { let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else { return Err(ResourceCertificateParseError::Parse( "basicConstraints parse failed".into(), )); }; basic_constraints_ca.push(bc.ca); } OID_SUBJECT_KEY_IDENTIFIER => { let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else { return Err(ResourceCertificateParseError::Parse( "subjectKeyIdentifier parse failed".into(), )); }; ski.push((s.0.to_vec(), ext.critical)); } OID_SUBJECT_INFO_ACCESS => { let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else { return Err(ResourceCertificateParseError::Parse( "subjectInfoAccess parse failed".into(), )); }; sia.push((parse_sia_parse(s.accessdescs.as_slice())?, ext.critical)); } crate::data_model::oid::OID_CERTIFICATE_POLICIES => { let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else { return Err(ResourceCertificateParseError::Parse( "certificatePolicies parse failed".into(), )); }; let oids: Vec = cp.iter().map(|p| p.policy_id.to_id_string()).collect(); cert_policies.push((oids, ext.critical)); } OID_IP_ADDR_BLOCKS => { let parsed = IpResourceSet::decode_extn_value(ext.value) .map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?; ip_resources.push((parsed, ext.critical)); } OID_AUTONOMOUS_SYS_IDS => { let parsed = AsResourceSet::decode_extn_value(ext.value) .map_err(|_e| ResourceCertificateParseError::InvalidAsResourcesEncoding)?; as_resources.push((parsed, ext.critical)); } _ => {} } } Ok(RcExtensionsParsed { basic_constraints_ca, subject_key_identifier: ski, subject_info_access: sia, certificate_policies: cert_policies, ip_resources, as_resources, }) } fn parse_sia_parse( access: &[x509_parser::extensions::AccessDescription<'_>], ) -> Result { let mut all = Vec::with_capacity(access.len()); let mut signed_object_uris: Vec = Vec::new(); let mut signed_object_access_location_not_uri = false; for ad in access { let access_method_oid = ad.access_method.to_id_string(); let uri = match &ad.access_location { x509_parser::extensions::GeneralName::URI(u) => u, _ => { if access_method_oid == OID_AD_SIGNED_OBJECT { signed_object_access_location_not_uri = true; } continue; } }; let url = Url::parse(uri) .map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?; if access_method_oid == OID_AD_SIGNED_OBJECT { signed_object_uris.push(url.clone()); } all.push(AccessDescription { access_method_oid, access_location: url, }); } Ok(SubjectInfoAccessParsed { access_descriptions: all, signed_object_uris, signed_object_access_location_not_uri, }) } fn parse_ip_addr_blocks(ext_value: &[u8]) -> Result { let (rem, obj) = parse_der(ext_value).map_err(|_| ())?; if !rem.is_empty() { return Err(()); } let seq = obj.as_sequence().map_err(|_| ())?; let mut families = Vec::with_capacity(seq.len()); for fam in seq { let fam_seq = fam.as_sequence().map_err(|_| ())?; if fam_seq.len() != 2 { return Err(()); } let af_bytes = fam_seq[0].as_slice().map_err(|_| ())?; if af_bytes.len() != 2 { return Err(()); } let afi = match af_bytes { [0x00, 0x01] => Afi::Ipv4, [0x00, 0x02] => Afi::Ipv6, _ => return Err(()), }; let choice = match &fam_seq[1].content { BerObjectContent::Null => IpAddressChoice::Inherit, BerObjectContent::Sequence(_) => { let items_seq = fam_seq[1].as_sequence().map_err(|_| ())?; let mut items = Vec::with_capacity(items_seq.len()); for item in items_seq { items.push(parse_ip_address_or_range(afi, item)?); } IpAddressChoice::AddressesOrRanges(items) } _ => return Err(()), }; families.push(IpAddressFamily { afi, choice }); } Ok(IpResourceSet { families }) } fn parse_ip_address_or_range(afi: Afi, obj: &DerObject<'_>) -> Result { match &obj.content { BerObjectContent::BitString(_, _) => { Ok(IpAddressOrRange::Prefix(parse_ip_prefix(afi, obj)?)) } BerObjectContent::Sequence(_) => { let seq = obj.as_sequence().map_err(|_| ())?; if seq.len() != 2 { return Err(()); } let min = parse_ip_address_bound(afi, &seq[0], false)?; let max = parse_ip_address_bound(afi, &seq[1], true)?; Ok(IpAddressOrRange::Range(IpAddressRange { min, max })) } _ => Err(()), } } fn parse_ip_prefix(afi: Afi, obj: &DerObject<'_>) -> Result { let (unused_bits, bytes) = match &obj.content { BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()), _ => return Err(()), }; if unused_bits > 7 { return Err(()); } if !bytes.is_empty() && unused_bits != 0 { let mask = (1u8 << unused_bits) - 1; if (bytes[bytes.len() - 1] & mask) != 0 { return Err(()); } } else if bytes.is_empty() && unused_bits != 0 { return Err(()); } let prefix_len = (bytes.len() * 8) .checked_sub(unused_bits as usize) .ok_or(())? as u16; if prefix_len > afi.ub() { return Err(()); } let addr = canonicalize_prefix_addr(afi, prefix_len, &bytes); Ok(IpPrefix { afi, prefix_len, addr, }) } /// Parse an RFC 3779 `IPAddress` BIT STRING into an address-like byte array. /// /// When used as an `IPAddressRange` endpoint, RFC 3779 allows endpoints to be encoded with /// fewer than `ub` bits. In that case, the missing bits are interpreted as 0s for the lower /// bound and 1s for the upper bound. This is essential to correctly interpret ranges that /// are expressed on non-octet boundaries. fn parse_ip_address_bound( afi: Afi, obj: &DerObject<'_>, fill_remaining_ones: bool, ) -> Result, ()> { let (unused_bits, bytes) = match &obj.content { BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()), _ => return Err(()), }; if unused_bits > 7 { return Err(()); } if !bytes.is_empty() && unused_bits != 0 { let mask = (1u8 << unused_bits) - 1; if (bytes[bytes.len() - 1] & mask) != 0 { return Err(()); } } else if bytes.is_empty() && unused_bits != 0 { return Err(()); } let bit_len: u16 = (bytes.len() * 8) .checked_sub(unused_bits as usize) .ok_or(())? .try_into() .map_err(|_| ())?; if bit_len > afi.ub() { return Err(()); } let mut out = vec![0u8; afi.octets_len()]; let copy_len = bytes.len().min(out.len()); out[..copy_len].copy_from_slice(&bytes[..copy_len]); if fill_remaining_ones { if bit_len == 0 { for b in &mut out { *b = 0xFF; } return Ok(out); } let last_bit = (bit_len - 1) as usize; let last_byte = last_bit / 8; let rem = (bit_len % 8) as u8; if rem != 0 && last_byte < out.len() { // Set the (8-rem) trailing bits in the last byte to 1. let mask: u8 = (1u8 << (8 - rem)) - 1; out[last_byte] |= mask; } for b in out.iter_mut().skip(last_byte + 1) { *b = 0xFF; } } Ok(out) } fn parse_as_identifiers(ext_value: &[u8]) -> Result { let (rem, obj) = parse_der(ext_value).map_err(|_| ())?; if !rem.is_empty() { return Err(()); } let seq = obj.as_sequence().map_err(|_| ())?; let mut asnum: Option = None; let mut rdi: Option = None; for item in seq { if item.class() != Class::ContextSpecific { return Err(()); } match item.tag() { Tag(0) => { if asnum.is_some() { return Err(()); } let inner = parse_explicit_inner(item)?; asnum = Some(parse_as_identifier_choice(&inner)?); } Tag(1) => { if rdi.is_some() { return Err(()); } let inner = parse_explicit_inner(item)?; rdi = Some(parse_as_identifier_choice(&inner)?); } _ => return Err(()), } } Ok(AsResourceSet { asnum, rdi }) } fn parse_explicit_inner<'a>(obj: &'a DerObject<'a>) -> Result, ()> { let inner_der = obj.as_slice().map_err(|_| ())?; let (rem, inner) = parse_der(inner_der).map_err(|_| ())?; if !rem.is_empty() { return Err(()); } Ok(inner) } fn parse_as_identifier_choice(obj: &DerObject<'_>) -> Result { match &obj.content { BerObjectContent::Null => Ok(AsIdentifierChoice::Inherit), BerObjectContent::Sequence(_) => { let seq = obj.as_sequence().map_err(|_| ())?; let mut items = Vec::with_capacity(seq.len()); for item in seq { items.push(parse_as_id_or_range(item)?); } Ok(AsIdentifierChoice::AsIdsOrRanges(items)) } _ => Err(()), } } fn parse_as_id_or_range(obj: &DerObject<'_>) -> Result { match &obj.content { BerObjectContent::Integer(_) => { let v = obj.as_u64().map_err(|_| ())?; if v > u32::MAX as u64 { return Err(()); } Ok(AsIdOrRange::Id(v as u32)) } BerObjectContent::Sequence(_) => { let seq = obj.as_sequence().map_err(|_| ())?; if seq.len() != 2 { return Err(()); } let min = seq[0].as_u64().map_err(|_| ())?; let max = seq[1].as_u64().map_err(|_| ())?; if min > u32::MAX as u64 || max > u32::MAX as u64 || min > max { return Err(()); } Ok(AsIdOrRange::Range { min: min as u32, max: max as u32, }) } _ => Err(()), } } fn canonicalize_prefix_addr(afi: Afi, prefix_len: u16, bytes: &[u8]) -> Vec { let full_len = afi.octets_len(); 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 && last_prefix_byte < addr.len() { let mask: u8 = 0xFF << (8 - rem); 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 n = resource.prefix_len as usize; let whole = n / 8; let rem = (n % 8) as u8; if resource.addr.len() != subject.addr.len() { return false; } 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) } fn prefix_range(afi: Afi, p: &IpPrefix) -> (u128, u128) { let mut base_bytes = [0u8; 16]; match afi { Afi::Ipv4 => { base_bytes[12..].copy_from_slice(&p.addr[..4]); } Afi::Ipv6 => { base_bytes.copy_from_slice(&p.addr[..16]); } } let base = u128::from_be_bytes(base_bytes); let host_bits = (afi.ub() - p.prefix_len) as u32; if host_bits == 0 { return (base, base); } let mask = (1u128 << host_bits) - 1; (base, base | mask) } fn range_covers_prefix(afi: Afi, r: &IpAddressRange, p: &IpPrefix) -> bool { let (p_min, p_max) = prefix_range(afi, p); let r_min = bytes_to_u128(afi, &r.min); let r_max = bytes_to_u128(afi, &r.max); r_min <= p_min && p_max <= r_max } fn bytes_to_u128(afi: Afi, bytes: &[u8]) -> u128 { let mut out = [0u8; 16]; match afi { Afi::Ipv4 => { let copy_len = bytes.len().min(4); out[12..12 + copy_len].copy_from_slice(&bytes[..copy_len]); } Afi::Ipv6 => { let copy_len = bytes.len().min(16); out[..copy_len].copy_from_slice(&bytes[..copy_len]); } } u128::from_be_bytes(out) }