2026-02-06 15:30:26 +08:00

1056 lines
34 KiB
Rust

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<u8>,
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<u8>,
pub extensions: RcExtensions,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RcExtensions {
pub basic_constraints_ca: bool,
pub subject_key_identifier: Option<Vec<u8>>,
pub subject_info_access: Option<SubjectInfoAccess>,
pub certificate_policies_oid: Option<String>,
pub ip_resources: Option<IpResourceSet>,
pub as_resources: Option<AsResourceSet>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResourceCertificateParsed {
pub raw_der: Vec<u8>,
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<u8>,
pub extensions: RcExtensionsParsed,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgorithmIdentifierValue {
pub oid: String,
pub parameters: Option<AlgorithmParametersValue>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgorithmParametersValue {
pub class: Asn1Class,
pub tag: Asn1Tag,
pub data: Vec<u8>,
}
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<bool>,
pub subject_key_identifier: Vec<(Vec<u8>, bool)>,
pub subject_info_access: Vec<(SubjectInfoAccessParsed, bool)>,
pub certificate_policies: Vec<(Vec<String>, 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<AccessDescription>,
pub signed_object_uris: Vec<Url>,
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<AccessDescription>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectInfoAccessEe {
pub signed_object_uris: Vec<Url>,
/// The full list of access descriptions as carried in the SIA extension.
pub access_descriptions: Vec<AccessDescription>,
}
#[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<IpAddressFamily>,
}
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<Self, IpResourceSetDecodeError> {
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<IpAddressOrRange>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IpAddressOrRange {
Prefix(IpPrefix),
Range(IpAddressRange),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IpAddressRange {
pub min: Vec<u8>,
pub max: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct IpPrefix {
pub afi: Afi,
pub prefix_len: u16,
pub addr: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AsResourceSet {
pub asnum: Option<AsIdentifierChoice>,
pub rdi: Option<AsIdentifierChoice>,
}
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<Self, AsResourceSetDecodeError> {
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<u32> {
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<AsIdOrRange>),
}
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<ResourceCertificateParsed, ResourceCertificateParseError> {
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<Self, ResourceCertificateDecodeError> {
Ok(Self::parse_der(der)?.validate_profile()?)
}
/// Backwards-compatible helper (historical name).
pub fn from_der(der: &[u8]) -> Result<Self, ResourceCertificateError> {
Self::decode_der(der)
}
}
impl ResourceCertificateParsed {
pub fn validate_profile(self) -> Result<ResourceCertificate, ResourceCertificateProfileError> {
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<RcExtensions, ResourceCertificateProfileError> {
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<RcExtensionsParsed, ResourceCertificateParseError> {
let mut basic_constraints_ca: Vec<bool> = Vec::new();
let mut ski: Vec<(Vec<u8>, bool)> = Vec::new();
let mut sia: Vec<(SubjectInfoAccessParsed, bool)> = Vec::new();
let mut cert_policies: Vec<(Vec<String>, 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<String> = 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<SubjectInfoAccessParsed, ResourceCertificateParseError> {
let mut all = Vec::with_capacity(access.len());
let mut signed_object_uris: Vec<Url> = 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<IpResourceSet, ()> {
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<IpAddressOrRange, ()> {
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<IpPrefix, ()> {
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<Vec<u8>, ()> {
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<AsResourceSet, ()> {
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<AsIdentifierChoice> = None;
let mut rdi: Option<AsIdentifierChoice> = 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<DerObject<'a>, ()> {
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<AsIdentifierChoice, ()> {
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<AsIdOrRange, ()> {
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<u8> {
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)
}