20260324_2 增加ccr & router key support
This commit is contained in:
@@ -8,3 +8,5 @@ pub mod roa;
|
||||
pub mod signed_object;
|
||||
pub mod ta;
|
||||
pub mod tal;
|
||||
|
||||
pub mod router_cert;
|
||||
|
||||
@@ -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";
|
||||
|
||||
327
src/data_model/router_cert.rs
Normal file
327
src/data_model/router_cert.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user