add role-aware resource certificate profile

This commit is contained in:
2026-07-11 21:40:52 +08:00
parent 8e37ef48cb
commit 6de0bced1c
12 changed files with 329 additions and 39 deletions

View File

@@ -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,
},

View File

@@ -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,
},

View File

@@ -2314,8 +2314,18 @@ 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) => {
issuer_ca_decode_error = None;
Some(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!(