364 lines
13 KiB
Rust
364 lines
13 KiB
Rust
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<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()?;
|
|
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<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(())
|
|
}
|