use crate::data_model::oid::{ OID_EC_PUBLIC_KEY, OID_EXTENDED_KEY_USAGE_RAW, OID_KP_BGPSEC_ROUTER, OID_SECP256R1, }; use crate::data_model::rc::{ AsIdOrRange, AsIdentifierChoice, ResourceCertKind, ResourceCertificate, ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError, ResourceCertificateRole, }; use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_predecoded_ee}; use x509_parser::extensions::ParsedExtension; use x509_parser::prelude::{FromDer, X509Certificate}; use x509_parser::public_key::PublicKey; use x509_parser::x509::SubjectPublicKeyInfo; #[derive(Clone, Debug, PartialEq, Eq)] pub struct BgpsecRouterCertificateParsed { pub rc_parsed: ResourceCertificateParsed, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct BgpsecRouterCertificate { pub raw_der: Vec, pub resource_cert: ResourceCertificate, pub subject_key_identifier: Vec, pub spki_der: Vec, pub asns: Vec, } #[derive(Debug, thiserror::Error)] pub enum BgpsecRouterCertificateParseError { #[error("resource certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4; RFC 8209 §3.1)")] ResourceCertificate(#[from] ResourceCertificateParseError), #[error("X.509 parse error: {0} (RFC 5280 §4.1; RFC 8209 §3.1)")] X509(String), #[error("trailing bytes after router certificate DER: {0} bytes (DER; RFC 5280 §4.1)")] TrailingBytes(usize), #[error("router SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7; RFC 8208 §3.1)")] SpkiParse(String), #[error("trailing bytes after router SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 8208 §3.1)")] SpkiTrailingBytes(usize), } #[derive(Debug, thiserror::Error)] pub enum BgpsecRouterCertificateProfileError { #[error("resource certificate profile error: {0} (RFC 6487 §4; RFC 8209 §3.1)")] ResourceCertificate(#[from] ResourceCertificateProfileError), #[error("BGPsec router certificate must be an EE certificate (RFC 8209 §3.1)")] NotEe, #[error( "BGPsec router certificate must contain SubjectKeyIdentifier (RFC 6487 §4.8.2; RFC 8209 §3.3)" )] MissingSki, #[error( "BGPsec router certificate must include ExtendedKeyUsage (RFC 8209 §3.1.3.2; RFC 8209 §3.3)" )] MissingExtendedKeyUsage, #[error( "BGPsec router certificate ExtendedKeyUsage must be non-critical (RFC 6487 §4.8.4; RFC 8209 §3.1.3.2)" )] ExtendedKeyUsageCriticality, #[error( "BGPsec router certificate ExtendedKeyUsage must contain id-kp-bgpsec-router ({OID_KP_BGPSEC_ROUTER}) (RFC 8209 §3.1.3.2; RFC 8209 §3.3)" )] MissingBgpsecRouterEku, #[error( "BGPsec router certificate MUST NOT include Subject Information Access (RFC 8209 §3.1.3.3; RFC 8209 §3.3)" )] SubjectInfoAccessPresent, #[error( "BGPsec router certificate MUST NOT include IP resources extension (RFC 8209 §3.1.3.4; RFC 8209 §3.3)" )] IpResourcesPresent, #[error( "BGPsec router certificate MUST include AS resources extension (RFC 8209 §3.1.3.5; RFC 8209 §3.3)" )] AsResourcesMissing, #[error( "BGPsec router certificate AS resources MUST include one or more ASNs (RFC 8209 §3.1.3.5)" )] AsResourcesAsnumMissing, #[error("BGPsec router certificate AS resources MUST NOT use inherit (RFC 8209 §3.1.3.5)")] AsResourcesInherit, #[error( "BGPsec router certificate AS resources MUST contain explicit ASNs, not ranges (RFC 8209 §3.1.3.5)" )] AsResourcesRangeNotAllowed, #[error( "BGPsec router certificate subjectPublicKeyInfo.algorithm must be id-ecPublicKey ({OID_EC_PUBLIC_KEY}) (RFC 8208 §3.1)" )] SpkiAlgorithmNotEcPublicKey, #[error( "BGPsec router certificate subjectPublicKeyInfo.parameters must be secp256r1 ({OID_SECP256R1}) (RFC 8208 §3.1)" )] SpkiWrongCurve, #[error( "BGPsec router certificate subjectPublicKeyInfo.parameters missing or invalid (RFC 8208 §3.1)" )] SpkiParametersMissingOrInvalid, #[error( "BGPsec router certificate subjectPublicKey MUST be uncompressed P-256 ECPoint (RFC 8208 §3.1)" )] SpkiEcPointNotUncompressedP256, } #[derive(Debug, thiserror::Error)] pub enum BgpsecRouterCertificateDecodeError { #[error("{0}")] Parse(#[from] BgpsecRouterCertificateParseError), #[error("{0}")] Validate(#[from] BgpsecRouterCertificateProfileError), } #[derive(Debug, thiserror::Error)] pub enum BgpsecRouterCertificatePathError { #[error("{0}")] Decode(#[from] BgpsecRouterCertificateDecodeError), #[error("{0}")] CertPath(#[from] CertPathError), } impl BgpsecRouterCertificate { pub fn parse_der( der: &[u8], ) -> Result { let (rem, cert) = X509Certificate::from_der(der) .map_err(|e| BgpsecRouterCertificateParseError::X509(e.to_string()))?; if !rem.is_empty() { return Err(BgpsecRouterCertificateParseError::TrailingBytes(rem.len())); } let (spki_rem, _spki) = SubjectPublicKeyInfo::from_der(cert.tbs_certificate.subject_pki.raw) .map_err(|e| BgpsecRouterCertificateParseError::SpkiParse(e.to_string()))?; if !spki_rem.is_empty() { return Err(BgpsecRouterCertificateParseError::SpkiTrailingBytes( spki_rem.len(), )); } let rc_parsed = ResourceCertificate::parse_der(der)?; Ok(BgpsecRouterCertificateParsed { rc_parsed }) } pub fn validate_profile(&self) -> Result<(), BgpsecRouterCertificateProfileError> { Ok(()) } pub fn decode_der(der: &[u8]) -> Result { Ok(Self::parse_der(der)?.validate_profile()?) } pub fn from_der(der: &[u8]) -> Result { Self::decode_der(der) } pub fn validate_path_with_prevalidated_issuer( der: &[u8], issuer_ca: &ResourceCertificate, issuer_spki: &SubjectPublicKeyInfo<'_>, issuer_crl: &crate::data_model::crl::RpkixCrl, issuer_crl_revoked_serials: &std::collections::HashSet>, issuer_ca_rsync_uri: Option<&str>, issuer_crl_rsync_uri: Option<&str>, validation_time: time::OffsetDateTime, ) -> Result { let cert = Self::decode_der(der)?; validate_ee_cert_path_with_predecoded_ee( &cert.resource_cert, der, issuer_ca, issuer_spki, issuer_crl, issuer_crl_revoked_serials, issuer_ca_rsync_uri, issuer_crl_rsync_uri, validation_time, )?; Ok(cert) } } impl BgpsecRouterCertificateParsed { pub fn validate_profile( self, ) -> Result { let rc = self.rc_parsed.validate_profile()?; rc.validate_rfc6487_profile(ResourceCertificateRole::RouterEe)?; if rc.kind != ResourceCertKind::Ee { return Err(BgpsecRouterCertificateProfileError::NotEe); } let ski = rc .tbs .extensions .subject_key_identifier .clone() .ok_or(BgpsecRouterCertificateProfileError::MissingSki)?; if rc.tbs.extensions.subject_info_access.is_some() { return Err(BgpsecRouterCertificateProfileError::SubjectInfoAccessPresent); } if rc.tbs.extensions.ip_resources.is_some() { return Err(BgpsecRouterCertificateProfileError::IpResourcesPresent); } let as_resources = rc .tbs .extensions .as_resources .as_ref() .ok_or(BgpsecRouterCertificateProfileError::AsResourcesMissing)?; let asns = extract_router_asns(as_resources)?; let (rem, cert) = X509Certificate::from_der(&rc.raw_der).map_err(|e| { BgpsecRouterCertificateProfileError::ResourceCertificate( ResourceCertificateProfileError::InvalidCertificatePolicy(e.to_string()), ) })?; if !rem.is_empty() { return Err(BgpsecRouterCertificateProfileError::ResourceCertificate( ResourceCertificateProfileError::InvalidCertificatePolicy(format!( "trailing bytes after router certificate DER: {}", rem.len() )), )); } validate_router_eku(&cert)?; validate_router_spki(&rc.tbs.subject_public_key_info)?; Ok(BgpsecRouterCertificate { raw_der: rc.raw_der.clone(), resource_cert: rc.clone(), subject_key_identifier: ski, spki_der: rc.tbs.subject_public_key_info.clone(), asns, }) } } fn extract_router_asns( as_resources: &crate::data_model::rc::AsResourceSet, ) -> Result, BgpsecRouterCertificateProfileError> { let asnum = as_resources .asnum .as_ref() .ok_or(BgpsecRouterCertificateProfileError::AsResourcesAsnumMissing)?; if matches!(asnum, AsIdentifierChoice::Inherit) || matches!(as_resources.rdi.as_ref(), Some(AsIdentifierChoice::Inherit)) { return Err(BgpsecRouterCertificateProfileError::AsResourcesInherit); } let AsIdentifierChoice::AsIdsOrRanges(items) = asnum else { return Err(BgpsecRouterCertificateProfileError::AsResourcesInherit); }; if items.is_empty() { return Err(BgpsecRouterCertificateProfileError::AsResourcesAsnumMissing); } let mut asns = Vec::with_capacity(items.len()); for item in items { match item { AsIdOrRange::Id(v) => asns.push(*v), AsIdOrRange::Range { .. } => { return Err(BgpsecRouterCertificateProfileError::AsResourcesRangeNotAllowed); } } } asns.sort_unstable(); asns.dedup(); Ok(asns) } fn validate_router_eku( cert: &X509Certificate<'_>, ) -> Result<(), BgpsecRouterCertificateProfileError> { let mut matches = cert .tbs_certificate .extensions() .iter() .filter(|ext| ext.oid.as_bytes() == OID_EXTENDED_KEY_USAGE_RAW); let Some(ext) = matches.next() else { return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage); }; if matches.next().is_some() { return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage); } if ext.critical { return Err(BgpsecRouterCertificateProfileError::ExtendedKeyUsageCriticality); } let ParsedExtension::ExtendedKeyUsage(eku) = ext.parsed_extension() else { return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage); }; let found = eku .other .iter() .any(|oid| oid.to_id_string() == OID_KP_BGPSEC_ROUTER); if !found { return Err(BgpsecRouterCertificateProfileError::MissingBgpsecRouterEku); } Ok(()) } fn validate_router_spki(spki_der: &[u8]) -> Result<(), BgpsecRouterCertificateProfileError> { let (rem, spki) = SubjectPublicKeyInfo::from_der(spki_der) .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)?; if !rem.is_empty() { return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); } if spki.algorithm.algorithm.to_id_string() != OID_EC_PUBLIC_KEY { return Err(BgpsecRouterCertificateProfileError::SpkiAlgorithmNotEcPublicKey); } let Some(params) = spki.algorithm.parameters.as_ref() else { return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); }; if params.header.tag().0 != 0x06 { return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); } let mut der = Vec::with_capacity(params.data.len() + 2); der.push(0x06); if params.data.len() >= 0x80 { return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); } der.push(params.data.len() as u8); der.extend_from_slice(params.data); let (prem, oid) = der_parser::der::parse_der_oid(&der) .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)?; if !prem.is_empty() { return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); } let curve = oid .as_oid_val() .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)? .to_string(); if curve != OID_SECP256R1 { return Err(BgpsecRouterCertificateProfileError::SpkiWrongCurve); } let parsed = spki .parsed() .map_err(|_| BgpsecRouterCertificateProfileError::SpkiEcPointNotUncompressedP256)?; let PublicKey::EC(ec) = parsed else { return Err(BgpsecRouterCertificateProfileError::SpkiAlgorithmNotEcPublicKey); }; if ec.data().len() != 65 || ec.data().first() != Some(&0x04) { return Err(BgpsecRouterCertificateProfileError::SpkiEcPointNotUncompressedP256); } Ok(()) }