20260324_2 增加ccr & router key support

This commit is contained in:
2026-03-26 11:52:06 +08:00
parent d6d44669b4
commit fe8b89d829
33 changed files with 5337 additions and 27 deletions

View File

@@ -8,3 +8,5 @@ pub mod roa;
pub mod signed_object;
pub mod ta;
pub mod tal;
pub mod router_cert;

View File

@@ -68,3 +68,10 @@ pub const OID_AUTONOMOUS_SYS_IDS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1
// RPKI CP (RFC 6484 / RFC 6487)
pub const OID_CP_IPADDR_ASNUMBER: &str = "1.3.6.1.5.5.7.14.2";
pub const OID_CP_IPADDR_ASNUMBER_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.14.2);
pub const OID_CT_RPKI_CCR: &str = "1.2.840.113549.1.9.16.1.54";
pub const OID_CT_RPKI_CCR_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.54);
pub const OID_KP_BGPSEC_ROUTER: &str = "1.3.6.1.5.5.7.3.30";
pub const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
pub const OID_SECP256R1: &str = "1.2.840.10045.3.1.7";

View File

@@ -0,0 +1,327 @@
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,
};
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<u8>,
pub resource_cert: ResourceCertificate,
pub subject_key_identifier: Vec<u8>,
pub spki_der: Vec<u8>,
pub asns: Vec<u32>,
}
#[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<BgpsecRouterCertificateParsed, BgpsecRouterCertificateParseError> {
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<Self, BgpsecRouterCertificateDecodeError> {
Ok(Self::parse_der(der)?.validate_profile()?)
}
pub fn from_der(der: &[u8]) -> Result<Self, BgpsecRouterCertificateDecodeError> {
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<Vec<u8>>,
issuer_ca_rsync_uri: Option<&str>,
issuer_crl_rsync_uri: Option<&str>,
validation_time: time::OffsetDateTime,
) -> Result<Self, BgpsecRouterCertificatePathError> {
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<BgpsecRouterCertificate, BgpsecRouterCertificateProfileError> {
let rc = self.rc_parsed.validate_profile()?;
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<Vec<u32>, 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(())
}