20260711 完善资源证书基础profile校验
This commit is contained in:
commit
1ce897dbff
@ -29,6 +29,7 @@ pub const OID_AUTHORITY_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.1";
|
||||
pub const OID_AUTHORITY_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.1);
|
||||
pub const OID_CERTIFICATE_POLICIES: &str = "2.5.29.32";
|
||||
pub const OID_CERTIFICATE_POLICIES_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.32);
|
||||
pub const OID_QT_CPS: &str = "1.3.6.1.5.5.7.2.1";
|
||||
|
||||
pub const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
|
||||
pub const OID_AUTHORITY_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.35);
|
||||
|
||||
@ -13,11 +13,14 @@ use crate::data_model::common::{
|
||||
use crate::data_model::oid::{
|
||||
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
||||
OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT,
|
||||
OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS_RAW, OID_AUTHORITY_KEY_IDENTIFIER_RAW,
|
||||
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS_RAW, OID_CERTIFICATE_POLICIES_RAW,
|
||||
OID_CP_IPADDR_ASNUMBER, OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS_RAW,
|
||||
OID_IP_ADDR_BLOCKS_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW,
|
||||
OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER_RAW,
|
||||
OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS, OID_AUTHORITY_INFO_ACCESS_RAW,
|
||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_AUTONOMOUS_SYS_IDS,
|
||||
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS, OID_BASIC_CONSTRAINTS_RAW,
|
||||
OID_CERTIFICATE_POLICIES, OID_CERTIFICATE_POLICIES_RAW, OID_CP_IPADDR_ASNUMBER,
|
||||
OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS, OID_CRL_DISTRIBUTION_POINTS_RAW,
|
||||
OID_EXTENDED_KEY_USAGE, OID_IP_ADDR_BLOCKS, OID_IP_ADDR_BLOCKS_RAW, OID_KEY_USAGE, OID_QT_CPS,
|
||||
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SUBJECT_INFO_ACCESS,
|
||||
OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER, OID_SUBJECT_KEY_IDENTIFIER_RAW,
|
||||
};
|
||||
|
||||
/// Resource Certificate kind (semantic classification).
|
||||
@ -27,6 +30,14 @@ pub enum ResourceCertKind {
|
||||
Ee,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ResourceCertificateRole {
|
||||
TrustAnchor,
|
||||
Ca,
|
||||
SignedObjectEe,
|
||||
RouterEe,
|
||||
}
|
||||
|
||||
/// A parsed RPKI Resource Certificate (RFC 6487) data model.
|
||||
///
|
||||
/// This module intentionally focuses on the semantics needed by Signed Object validation and
|
||||
@ -59,6 +70,7 @@ pub struct RpkixTbsCertificate {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RcExtensions {
|
||||
pub basic_constraints_ca: bool,
|
||||
pub basic_constraints: Option<BasicConstraintsProfile>,
|
||||
pub subject_key_identifier: Option<Vec<u8>>,
|
||||
/// Authority Key Identifier (AKI) keyIdentifier value.
|
||||
pub authority_key_identifier: Option<Vec<u8>>,
|
||||
@ -68,11 +80,26 @@ pub struct RcExtensions {
|
||||
pub ca_issuers_uris: Option<Vec<String>>,
|
||||
pub subject_info_access: Option<SubjectInfoAccess>,
|
||||
pub certificate_policies_oid: Option<String>,
|
||||
pub certificate_policies: Option<CertificatePoliciesProfile>,
|
||||
pub extension_oids: Vec<String>,
|
||||
|
||||
pub ip_resources: Option<IpResourceSet>,
|
||||
pub as_resources: Option<AsResourceSet>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BasicConstraintsProfile {
|
||||
pub ca: bool,
|
||||
pub critical: bool,
|
||||
pub path_len_constraint: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CertificatePoliciesProfile {
|
||||
pub policy_oid: String,
|
||||
pub qualifier_oids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ResourceCertificateParsed {
|
||||
pub raw_der: Vec<u8>,
|
||||
@ -114,13 +141,14 @@ impl AlgorithmIdentifierValue {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RcExtensionsParsed {
|
||||
pub basic_constraints_ca: Vec<bool>,
|
||||
pub basic_constraints: Vec<BasicConstraintsProfile>,
|
||||
pub subject_key_identifier: Vec<(Vec<u8>, bool)>,
|
||||
pub authority_key_identifier: Vec<(AuthorityKeyIdentifierParsed, bool)>,
|
||||
pub crl_distribution_points: Vec<(CrlDistributionPointsParsed, bool)>,
|
||||
pub authority_info_access: Vec<(AuthorityInfoAccessParsed, bool)>,
|
||||
pub subject_info_access: Vec<(SubjectInfoAccessParsed, bool)>,
|
||||
pub certificate_policies: Vec<(Vec<String>, bool)>,
|
||||
pub certificate_policies: Vec<(Vec<CertificatePoliciesProfile>, bool)>,
|
||||
pub extension_oids: Vec<String>,
|
||||
pub ip_resources: Vec<(IpResourceSet, bool)>,
|
||||
pub as_resources: Vec<(AsResourceSet, bool)>,
|
||||
}
|
||||
@ -406,11 +434,45 @@ pub enum ResourceCertificateProfileError {
|
||||
#[error("certificatePolicies criticality must be critical (RFC 6487 §4.8.9)")]
|
||||
CertificatePoliciesCriticality,
|
||||
|
||||
#[error("certificatePolicies must be present (RFC 6487 §4.8.9)")]
|
||||
CertificatePoliciesMissing,
|
||||
|
||||
#[error(
|
||||
"certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)"
|
||||
)]
|
||||
InvalidCertificatePolicy(String),
|
||||
|
||||
#[error("certificatePolicies may contain at most one CPS qualifier (RFC 6487 §4.8.9)")]
|
||||
CertificatePoliciesTooManyQualifiers,
|
||||
|
||||
#[error(
|
||||
"certificatePolicies qualifier must be id-qt-cps ({OID_QT_CPS}), got {0} (RFC 6487 §4.8.9)"
|
||||
)]
|
||||
CertificatePoliciesInvalidQualifier(String),
|
||||
|
||||
#[error("basicConstraints must be present in CA certificates (RFC 6487 §4.8.1)")]
|
||||
BasicConstraintsMissing,
|
||||
|
||||
#[error("basicConstraints criticality must be critical in CA certificates (RFC 6487 §4.8.1)")]
|
||||
BasicConstraintsCriticality,
|
||||
|
||||
#[error("basicConstraints cA must be TRUE in CA certificates (RFC 6487 §4.8.1)")]
|
||||
BasicConstraintsCaFalse,
|
||||
|
||||
#[error(
|
||||
"basicConstraints pathLenConstraint must be absent in CA certificates (RFC 6487 §4.8.1)"
|
||||
)]
|
||||
BasicConstraintsPathLenPresent,
|
||||
|
||||
#[error("basicConstraints must be absent in EE certificates (RFC 6487 §4.8.1)")]
|
||||
BasicConstraintsEeMustOmit,
|
||||
|
||||
#[error("extension {oid} is not permitted for {role} resource certificates (RFC 6487 §4.8)")]
|
||||
DisallowedExtension { role: &'static str, oid: String },
|
||||
|
||||
#[error("autonomousSysIds RDI field must be absent (RFC 6487 §4.8.11; RFC 3779 §3.2.3)")]
|
||||
AsResourcesRdiPresent,
|
||||
|
||||
#[error(
|
||||
"SIA id-ad-signedObject accessLocation must be URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)"
|
||||
)]
|
||||
@ -567,6 +629,87 @@ impl ResourceCertificate {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_rfc6487_profile(
|
||||
&self,
|
||||
role: ResourceCertificateRole,
|
||||
) -> Result<(), ResourceCertificateProfileError> {
|
||||
let role_name = match role {
|
||||
ResourceCertificateRole::TrustAnchor => "trust anchor CA",
|
||||
ResourceCertificateRole::Ca => "CA",
|
||||
ResourceCertificateRole::SignedObjectEe => "signed-object EE",
|
||||
ResourceCertificateRole::RouterEe => "router EE",
|
||||
};
|
||||
let ca_role = matches!(
|
||||
role,
|
||||
ResourceCertificateRole::TrustAnchor | ResourceCertificateRole::Ca
|
||||
);
|
||||
|
||||
if ca_role {
|
||||
let constraints = self
|
||||
.tbs
|
||||
.extensions
|
||||
.basic_constraints
|
||||
.as_ref()
|
||||
.ok_or(ResourceCertificateProfileError::BasicConstraintsMissing)?;
|
||||
if !constraints.critical {
|
||||
return Err(ResourceCertificateProfileError::BasicConstraintsCriticality);
|
||||
}
|
||||
if !constraints.ca {
|
||||
return Err(ResourceCertificateProfileError::BasicConstraintsCaFalse);
|
||||
}
|
||||
if constraints.path_len_constraint.is_some() {
|
||||
return Err(ResourceCertificateProfileError::BasicConstraintsPathLenPresent);
|
||||
}
|
||||
} else if self.tbs.extensions.basic_constraints.is_some() {
|
||||
return Err(ResourceCertificateProfileError::BasicConstraintsEeMustOmit);
|
||||
}
|
||||
|
||||
for oid in &self.tbs.extensions.extension_oids {
|
||||
if !is_permitted_extension(oid, role) {
|
||||
return Err(ResourceCertificateProfileError::DisallowedExtension {
|
||||
role: role_name,
|
||||
oid: oid.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let policies = self
|
||||
.tbs
|
||||
.extensions
|
||||
.certificate_policies
|
||||
.as_ref()
|
||||
.ok_or(ResourceCertificateProfileError::CertificatePoliciesMissing)?;
|
||||
if policies.policy_oid != OID_CP_IPADDR_ASNUMBER {
|
||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||
policies.policy_oid.clone(),
|
||||
));
|
||||
}
|
||||
if policies.qualifier_oids.len() > 1 {
|
||||
return Err(ResourceCertificateProfileError::CertificatePoliciesTooManyQualifiers);
|
||||
}
|
||||
if let Some(qualifier_oid) = policies.qualifier_oids.first() {
|
||||
if qualifier_oid != OID_QT_CPS {
|
||||
return Err(
|
||||
ResourceCertificateProfileError::CertificatePoliciesInvalidQualifier(
|
||||
qualifier_oid.clone(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self
|
||||
.tbs
|
||||
.extensions
|
||||
.as_resources
|
||||
.as_ref()
|
||||
.is_some_and(|resources| resources.rdi.is_some())
|
||||
{
|
||||
return Err(ResourceCertificateProfileError::AsResourcesRdiPresent);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> {
|
||||
validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
|
||||
validate_strict_rpki_name(&self.tbs.subject_name, "subject")?;
|
||||
@ -590,6 +733,22 @@ impl ResourceCertificate {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_permitted_extension(oid: &str, role: ResourceCertificateRole) -> bool {
|
||||
matches!(
|
||||
oid,
|
||||
OID_BASIC_CONSTRAINTS
|
||||
| OID_KEY_USAGE
|
||||
| OID_SUBJECT_KEY_IDENTIFIER
|
||||
| OID_AUTHORITY_KEY_IDENTIFIER
|
||||
| OID_CRL_DISTRIBUTION_POINTS
|
||||
| OID_AUTHORITY_INFO_ACCESS
|
||||
| OID_SUBJECT_INFO_ACCESS
|
||||
| OID_CERTIFICATE_POLICIES
|
||||
| OID_IP_ADDR_BLOCKS
|
||||
| OID_AUTONOMOUS_SYS_IDS
|
||||
) || (role == ResourceCertificateRole::RouterEe && oid == OID_EXTENDED_KEY_USAGE)
|
||||
}
|
||||
|
||||
fn validate_strict_rpki_name(
|
||||
name: &X509NameDer,
|
||||
role: &'static str,
|
||||
@ -744,13 +903,24 @@ mod strict_name_tests {
|
||||
#[test]
|
||||
fn profile_rejects_rfc8360_v2_policy_oid() {
|
||||
let extensions = RcExtensionsParsed {
|
||||
basic_constraints_ca: vec![true],
|
||||
basic_constraints: vec![BasicConstraintsProfile {
|
||||
ca: true,
|
||||
critical: true,
|
||||
path_len_constraint: None,
|
||||
}],
|
||||
subject_key_identifier: Vec::new(),
|
||||
authority_key_identifier: Vec::new(),
|
||||
crl_distribution_points: Vec::new(),
|
||||
authority_info_access: Vec::new(),
|
||||
subject_info_access: Vec::new(),
|
||||
certificate_policies: vec![(vec!["1.3.6.1.5.5.7.14.3".to_string()], true)],
|
||||
certificate_policies: vec![(
|
||||
vec![CertificatePoliciesProfile {
|
||||
policy_oid: "1.3.6.1.5.5.7.14.3".to_string(),
|
||||
qualifier_oids: Vec::new(),
|
||||
}],
|
||||
true,
|
||||
)],
|
||||
extension_oids: Vec::new(),
|
||||
ip_resources: Vec::new(),
|
||||
as_resources: Vec::new(),
|
||||
};
|
||||
@ -821,23 +991,25 @@ impl RcExtensionsParsed {
|
||||
// NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning,
|
||||
// especially for large resource sets and URI lists.
|
||||
let RcExtensionsParsed {
|
||||
basic_constraints_ca,
|
||||
basic_constraints,
|
||||
subject_key_identifier,
|
||||
authority_key_identifier,
|
||||
crl_distribution_points,
|
||||
authority_info_access,
|
||||
subject_info_access,
|
||||
certificate_policies,
|
||||
extension_oids,
|
||||
ip_resources,
|
||||
as_resources,
|
||||
} = self;
|
||||
|
||||
if basic_constraints_ca.len() > 1 {
|
||||
if basic_constraints.len() > 1 {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
"basicConstraints",
|
||||
));
|
||||
}
|
||||
let basic_constraints_ca = basic_constraints_ca.first().copied().unwrap_or(false);
|
||||
let basic_constraints = basic_constraints.into_iter().next();
|
||||
let basic_constraints_ca = basic_constraints.as_ref().is_some_and(|bc| bc.ca);
|
||||
|
||||
let subject_key_identifier = match subject_key_identifier.len() {
|
||||
0 => None,
|
||||
@ -1032,25 +1204,25 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let certificate_policies_oid = match certificate_policies.len() {
|
||||
let certificate_policies = match certificate_policies.len() {
|
||||
0 => None,
|
||||
1 => {
|
||||
let (oids, critical) = certificate_policies.into_iter().next().expect("len==1");
|
||||
let (policies, critical) = certificate_policies.into_iter().next().expect("len==1");
|
||||
if !critical {
|
||||
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
||||
}
|
||||
if oids.len() != 1 {
|
||||
if policies.len() != 1 {
|
||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||
"expected exactly one policy".into(),
|
||||
));
|
||||
}
|
||||
let policy_oid = oids.into_iter().next().expect("len==1");
|
||||
if policy_oid != OID_CP_IPADDR_ASNUMBER {
|
||||
let policy = policies.into_iter().next().expect("len==1");
|
||||
if policy.policy_oid != OID_CP_IPADDR_ASNUMBER {
|
||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||
policy_oid,
|
||||
policy.policy_oid,
|
||||
));
|
||||
}
|
||||
Some(OID_CP_IPADDR_ASNUMBER.to_string())
|
||||
Some(policy)
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
@ -1093,12 +1265,17 @@ impl RcExtensionsParsed {
|
||||
|
||||
Ok(RcExtensions {
|
||||
basic_constraints_ca,
|
||||
basic_constraints,
|
||||
subject_key_identifier,
|
||||
authority_key_identifier,
|
||||
crl_distribution_points_uris,
|
||||
ca_issuers_uris,
|
||||
subject_info_access,
|
||||
certificate_policies_oid,
|
||||
certificate_policies_oid: certificate_policies
|
||||
.as_ref()
|
||||
.map(|_| OID_CP_IPADDR_ASNUMBER.to_string()),
|
||||
certificate_policies,
|
||||
extension_oids,
|
||||
ip_resources,
|
||||
as_resources,
|
||||
})
|
||||
@ -1127,26 +1304,32 @@ fn algorithm_identifier_value(
|
||||
fn parse_extensions_parse(
|
||||
exts: &[X509Extension<'_>],
|
||||
) -> Result<RcExtensionsParsed, ResourceCertificateParseError> {
|
||||
let mut basic_constraints_ca: Vec<bool> = Vec::new();
|
||||
let mut basic_constraints: Vec<BasicConstraintsProfile> = Vec::new();
|
||||
let mut ski: Vec<(Vec<u8>, bool)> = Vec::new();
|
||||
let mut aki: Vec<(AuthorityKeyIdentifierParsed, bool)> = Vec::new();
|
||||
let mut crldp: Vec<(CrlDistributionPointsParsed, bool)> = Vec::new();
|
||||
let mut aia: Vec<(AuthorityInfoAccessParsed, bool)> = Vec::new();
|
||||
let mut sia: Vec<(SubjectInfoAccessParsed, bool)> = Vec::new();
|
||||
let mut cert_policies: Vec<(Vec<String>, bool)> = Vec::new();
|
||||
let mut cert_policies: Vec<(Vec<CertificatePoliciesProfile>, bool)> = Vec::new();
|
||||
let mut extension_oids: Vec<String> = Vec::with_capacity(exts.len());
|
||||
|
||||
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.as_bytes();
|
||||
extension_oids.push(ext.oid.to_id_string());
|
||||
if oid == OID_BASIC_CONSTRAINTS_RAW {
|
||||
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"basicConstraints parse failed".into(),
|
||||
));
|
||||
};
|
||||
basic_constraints_ca.push(bc.ca);
|
||||
basic_constraints.push(BasicConstraintsProfile {
|
||||
ca: bc.ca,
|
||||
critical: ext.critical,
|
||||
path_len_constraint: bc.path_len_constraint,
|
||||
});
|
||||
} else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
|
||||
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
@ -1195,16 +1378,30 @@ fn parse_extensions_parse(
|
||||
"certificatePolicies parse failed".into(),
|
||||
));
|
||||
};
|
||||
let mut oids: Vec<String> = Vec::with_capacity(cp.len());
|
||||
let mut policies: Vec<CertificatePoliciesProfile> = Vec::with_capacity(cp.len());
|
||||
for p in cp.iter() {
|
||||
let b = p.policy_id.as_bytes();
|
||||
if b == OID_CP_IPADDR_ASNUMBER_RAW {
|
||||
oids.push(OID_CP_IPADDR_ASNUMBER.to_string());
|
||||
let policy_oid = if b == OID_CP_IPADDR_ASNUMBER_RAW {
|
||||
OID_CP_IPADDR_ASNUMBER.to_string()
|
||||
} else {
|
||||
oids.push(p.policy_id.to_id_string());
|
||||
p.policy_id.to_id_string()
|
||||
};
|
||||
let qualifier_oids = p
|
||||
.policy_qualifiers
|
||||
.as_ref()
|
||||
.map(|qualifiers| {
|
||||
qualifiers
|
||||
.iter()
|
||||
.map(|qualifier| qualifier.policy_qualifier_id.to_id_string())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
policies.push(CertificatePoliciesProfile {
|
||||
policy_oid,
|
||||
qualifier_oids,
|
||||
});
|
||||
}
|
||||
}
|
||||
cert_policies.push((oids, ext.critical));
|
||||
cert_policies.push((policies, ext.critical));
|
||||
} else if oid == OID_IP_ADDR_BLOCKS_RAW {
|
||||
let parsed = IpResourceSet::decode_extn_value(ext.value)
|
||||
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
||||
@ -1217,13 +1414,14 @@ fn parse_extensions_parse(
|
||||
}
|
||||
|
||||
Ok(RcExtensionsParsed {
|
||||
basic_constraints_ca,
|
||||
basic_constraints,
|
||||
subject_key_identifier: ski,
|
||||
authority_key_identifier: aki,
|
||||
crl_distribution_points: crldp,
|
||||
authority_info_access: aia,
|
||||
subject_info_access: sia,
|
||||
certificate_policies: cert_policies,
|
||||
extension_oids,
|
||||
ip_resources,
|
||||
as_resources,
|
||||
})
|
||||
|
||||
@ -4,6 +4,7 @@ use crate::data_model::oid::{
|
||||
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;
|
||||
@ -202,6 +203,7 @@ impl BgpsecRouterCertificateParsed {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use crate::data_model::oid::{
|
||||
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA,
|
||||
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
||||
};
|
||||
use crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess};
|
||||
use crate::data_model::rc::{ResourceCertificate, ResourceCertificateRole, SubjectInfoAccess};
|
||||
use asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag};
|
||||
use ring::digest;
|
||||
use x509_parser::extensions::ParsedExtension;
|
||||
@ -822,6 +822,8 @@ fn validate_ee_certificate(
|
||||
};
|
||||
}
|
||||
};
|
||||
rc.validate_rfc6487_profile(ResourceCertificateRole::SignedObjectEe)
|
||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
||||
if strict_name {
|
||||
rc.validate_strict_name_profile()
|
||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
||||
|
||||
@ -5,6 +5,7 @@ use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
||||
use crate::data_model::rc::{
|
||||
AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate,
|
||||
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
||||
ResourceCertificateRole,
|
||||
};
|
||||
use crate::data_model::tal::Tal;
|
||||
|
||||
@ -218,6 +219,7 @@ impl TaCertificateParsed {
|
||||
if rc_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(TaCertificateProfileError::NotCa);
|
||||
}
|
||||
rc_ca.validate_rfc6487_profile(ResourceCertificateRole::TrustAnchor)?;
|
||||
|
||||
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
|
||||
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
|
||||
|
||||
@ -3,7 +3,8 @@ use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
||||
use crate::data_model::oid::OID_KEY_USAGE_RAW;
|
||||
use crate::data_model::rc::{
|
||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
||||
ResourceCertificate, ResourceCertificateDecodeError,
|
||||
ResourceCertificate, ResourceCertificateDecodeError, ResourceCertificateProfileError,
|
||||
ResourceCertificateRole,
|
||||
};
|
||||
use crate::policy::ResourceValidationMode;
|
||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
||||
@ -108,6 +109,12 @@ pub enum CaPathError {
|
||||
#[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
|
||||
IssuerDecode(ResourceCertificateDecodeError),
|
||||
|
||||
#[error("child CA certificate profile validation failed: {0} (RFC 6487 §4.8)")]
|
||||
ChildProfile(ResourceCertificateProfileError),
|
||||
|
||||
#[error("issuer CA certificate profile validation failed: {0} (RFC 6487 §4.8)")]
|
||||
IssuerProfile(ResourceCertificateProfileError),
|
||||
|
||||
#[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")]
|
||||
CrlDecode(#[from] CrlDecodeError),
|
||||
|
||||
@ -244,12 +251,18 @@ pub fn validate_subordinate_ca_cert_with_resource_validation_mode(
|
||||
if child_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(CaPathError::ChildNotCa);
|
||||
}
|
||||
child_ca
|
||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
||||
.map_err(CaPathError::ChildProfile)?;
|
||||
|
||||
let issuer_ca =
|
||||
ResourceCertificate::decode_der(issuer_ca_der).map_err(CaPathError::IssuerDecode)?;
|
||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(CaPathError::IssuerNotCa);
|
||||
}
|
||||
issuer_ca
|
||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
||||
.map_err(CaPathError::IssuerProfile)?;
|
||||
let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?;
|
||||
|
||||
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||
@ -379,6 +392,12 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(CaPathError::IssuerNotCa);
|
||||
}
|
||||
child_ca
|
||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
||||
.map_err(CaPathError::ChildProfile)?;
|
||||
issuer_ca
|
||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
||||
.map_err(CaPathError::IssuerProfile)?;
|
||||
|
||||
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||
return Err(CaPathError::IssuerSubjectMismatch {
|
||||
@ -1375,12 +1394,14 @@ fn bytes_leq(a: &[u8], b: &[u8]) -> bool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::data_model::common::X509NameDer;
|
||||
use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
||||
use crate::data_model::rc::{
|
||||
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
||||
IpAddressOrRange, IpPrefix, IpResourceSet,
|
||||
};
|
||||
use crate::data_model::rc::{
|
||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||
BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
|
||||
ResourceCertificate, RpkixTbsCertificate,
|
||||
};
|
||||
use der_parser::num_bigint::BigUint;
|
||||
use std::process::Command;
|
||||
@ -1410,12 +1431,27 @@ mod tests {
|
||||
subject_public_key_info: Vec::new(),
|
||||
extensions: RcExtensions {
|
||||
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
||||
basic_constraints: (kind == ResourceCertKind::Ca).then_some(
|
||||
BasicConstraintsProfile {
|
||||
ca: true,
|
||||
critical: true,
|
||||
path_len_constraint: None,
|
||||
},
|
||||
),
|
||||
subject_key_identifier: ski,
|
||||
authority_key_identifier: aki,
|
||||
crl_distribution_points_uris: crldp,
|
||||
ca_issuers_uris: aia,
|
||||
subject_info_access: None,
|
||||
certificate_policies_oid: None,
|
||||
certificate_policies_oid: (kind == ResourceCertKind::Ca)
|
||||
.then_some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
||||
certificate_policies: (kind == ResourceCertKind::Ca).then_some(
|
||||
CertificatePoliciesProfile {
|
||||
policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(),
|
||||
qualifier_oids: Vec::new(),
|
||||
},
|
||||
),
|
||||
extension_oids: Vec::new(),
|
||||
ip_resources: None,
|
||||
as_resources: None,
|
||||
},
|
||||
|
||||
@ -2,6 +2,7 @@ use crate::data_model::common::BigUnsigned;
|
||||
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
||||
use crate::data_model::rc::{
|
||||
ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError,
|
||||
ResourceCertificateProfileError, ResourceCertificateRole,
|
||||
};
|
||||
use crate::data_model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate};
|
||||
use ring::signature;
|
||||
@ -34,6 +35,9 @@ pub enum CertPathError {
|
||||
)]
|
||||
IssuerNotCa,
|
||||
|
||||
#[error("issuer CA certificate profile validation failed: {0} (RFC 6487 §4.8)")]
|
||||
IssuerProfile(ResourceCertificateProfileError),
|
||||
|
||||
#[error("EE certificate must be an EE resource certificate (RFC 6487 §4)")]
|
||||
EeNotEe,
|
||||
|
||||
@ -128,6 +132,9 @@ pub fn validate_ee_cert_path(
|
||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(CertPathError::IssuerNotCa);
|
||||
}
|
||||
issuer_ca
|
||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
||||
.map_err(CertPathError::IssuerProfile)?;
|
||||
let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?;
|
||||
|
||||
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||
@ -311,6 +318,9 @@ fn validate_ee_cert_path_bindings_and_status(
|
||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(CertPathError::IssuerNotCa);
|
||||
}
|
||||
issuer_ca
|
||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
||||
.map_err(CertPathError::IssuerProfile)?;
|
||||
|
||||
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||
return Err(CertPathError::IssuerSubjectMismatch {
|
||||
@ -515,8 +525,10 @@ fn is_serial_revoked_by_crl(ee: &ResourceCertificate, crl: &RpkixCrl) -> bool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::data_model::common::X509NameDer;
|
||||
use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
||||
use crate::data_model::rc::{
|
||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||
BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
|
||||
ResourceCertificate, RpkixTbsCertificate,
|
||||
};
|
||||
use der_parser::num_bigint::BigUint;
|
||||
|
||||
@ -545,12 +557,27 @@ mod tests {
|
||||
subject_public_key_info: Vec::new(),
|
||||
extensions: RcExtensions {
|
||||
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
||||
basic_constraints: (kind == ResourceCertKind::Ca).then_some(
|
||||
BasicConstraintsProfile {
|
||||
ca: true,
|
||||
critical: true,
|
||||
path_len_constraint: None,
|
||||
},
|
||||
),
|
||||
subject_key_identifier: ski,
|
||||
authority_key_identifier: aki,
|
||||
crl_distribution_points_uris: crldp,
|
||||
ca_issuers_uris: aia,
|
||||
subject_info_access: None,
|
||||
certificate_policies_oid: None,
|
||||
certificate_policies_oid: (kind == ResourceCertKind::Ca)
|
||||
.then_some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
||||
certificate_policies: (kind == ResourceCertKind::Ca).then_some(
|
||||
CertificatePoliciesProfile {
|
||||
policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(),
|
||||
qualifier_oids: Vec::new(),
|
||||
},
|
||||
),
|
||||
extension_oids: Vec::new(),
|
||||
ip_resources: None,
|
||||
as_resources: None,
|
||||
},
|
||||
|
||||
@ -2316,9 +2316,19 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
|
||||
let issuer_ca_decode_error: Option<String>;
|
||||
let issuer_ca = match crate::data_model::rc::ResourceCertificate::decode_der(issuer_ca_der) {
|
||||
Ok(v) => {
|
||||
match v.validate_rfc6487_profile(crate::data_model::rc::ResourceCertificateRole::Ca) {
|
||||
Ok(()) => {
|
||||
issuer_ca_decode_error = None;
|
||||
Some(v)
|
||||
}
|
||||
Err(e) => {
|
||||
issuer_ca_decode_error = Some(format!(
|
||||
"issuer CA profile validation failed: {e} (RFC 6487 §4.8)"
|
||||
));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
issuer_ca_decode_error = Some(format!(
|
||||
"issuer CA decode failed: {e} (RFC 5280 §4.1; RFC 6487 §4)"
|
||||
|
||||
@ -383,7 +383,7 @@ fn metrics_for(obj_type: ObjType, der: &[u8]) -> Result<Metrics, String> {
|
||||
ObjType::Cer => {
|
||||
let parsed = ResourceCertificate::parse_der(der).map_err(|e| e.to_string())?;
|
||||
let spki_len = parsed.subject_public_key_info.len() as u32;
|
||||
let ext_count = (parsed.extensions.basic_constraints_ca.len()
|
||||
let ext_count = (parsed.extensions.basic_constraints.len()
|
||||
+ parsed.extensions.subject_key_identifier.len()
|
||||
+ parsed.extensions.authority_key_identifier.len()
|
||||
+ parsed.extensions.crl_distribution_points.len()
|
||||
|
||||
@ -25,6 +25,7 @@ fn dummy_ee(
|
||||
subject_public_key_info: vec![],
|
||||
extensions: RcExtensions {
|
||||
basic_constraints_ca: false,
|
||||
basic_constraints: None,
|
||||
subject_key_identifier: Some(vec![0x01]),
|
||||
authority_key_identifier: None,
|
||||
crl_distribution_points_uris: None,
|
||||
@ -35,6 +36,8 @@ fn dummy_ee(
|
||||
},
|
||||
)),
|
||||
certificate_policies_oid: None,
|
||||
certificate_policies: None,
|
||||
extension_oids: Vec::new(),
|
||||
ip_resources,
|
||||
as_resources,
|
||||
},
|
||||
|
||||
@ -28,6 +28,7 @@ fn dummy_ee(
|
||||
subject_public_key_info: vec![],
|
||||
extensions: RcExtensions {
|
||||
basic_constraints_ca: false,
|
||||
basic_constraints: None,
|
||||
subject_key_identifier: Some(vec![0x01]),
|
||||
authority_key_identifier: None,
|
||||
crl_distribution_points_uris: None,
|
||||
@ -38,6 +39,8 @@ fn dummy_ee(
|
||||
},
|
||||
)),
|
||||
certificate_policies_oid: None,
|
||||
certificate_policies: None,
|
||||
extension_oids: Vec::new(),
|
||||
ip_resources,
|
||||
as_resources,
|
||||
},
|
||||
|
||||
@ -68,12 +68,15 @@ fn ta_certificate_rejects_non_self_signed_ca() {
|
||||
fn ta_constraints_require_policies_and_ski() {
|
||||
let rc = dummy_rc_ca(RcExtensions {
|
||||
basic_constraints_ca: true,
|
||||
basic_constraints: None,
|
||||
subject_key_identifier: None,
|
||||
authority_key_identifier: None,
|
||||
crl_distribution_points_uris: None,
|
||||
ca_issuers_uris: None,
|
||||
subject_info_access: None,
|
||||
certificate_policies_oid: None,
|
||||
certificate_policies: None,
|
||||
extension_oids: Vec::new(),
|
||||
ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
|
||||
as_resources: None,
|
||||
});
|
||||
@ -97,12 +100,15 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
|
||||
// Missing both IP and AS resources.
|
||||
let rc = dummy_rc_ca(RcExtensions {
|
||||
basic_constraints_ca: true,
|
||||
basic_constraints: None,
|
||||
subject_key_identifier: Some(vec![1]),
|
||||
authority_key_identifier: None,
|
||||
crl_distribution_points_uris: None,
|
||||
ca_issuers_uris: None,
|
||||
subject_info_access: None,
|
||||
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
||||
certificate_policies: None,
|
||||
extension_oids: Vec::new(),
|
||||
ip_resources: None,
|
||||
as_resources: None,
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user