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_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: &str = "2.5.29.32";
|
||||||
pub const OID_CERTIFICATE_POLICIES_RAW: &[u8] = &asn1_rs::oid!(raw 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: &str = "2.5.29.35";
|
||||||
pub const OID_AUTHORITY_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 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::{
|
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_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_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_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS, OID_AUTHORITY_INFO_ACCESS_RAW,
|
||||||
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS_RAW, OID_CERTIFICATE_POLICIES_RAW,
|
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_AUTONOMOUS_SYS_IDS,
|
||||||
OID_CP_IPADDR_ASNUMBER, OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS_RAW,
|
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS, OID_BASIC_CONSTRAINTS_RAW,
|
||||||
OID_IP_ADDR_BLOCKS_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW,
|
OID_CERTIFICATE_POLICIES, OID_CERTIFICATE_POLICIES_RAW, OID_CP_IPADDR_ASNUMBER,
|
||||||
OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER_RAW,
|
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).
|
/// Resource Certificate kind (semantic classification).
|
||||||
@ -27,6 +30,14 @@ pub enum ResourceCertKind {
|
|||||||
Ee,
|
Ee,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ResourceCertificateRole {
|
||||||
|
TrustAnchor,
|
||||||
|
Ca,
|
||||||
|
SignedObjectEe,
|
||||||
|
RouterEe,
|
||||||
|
}
|
||||||
|
|
||||||
/// A parsed RPKI Resource Certificate (RFC 6487) data model.
|
/// A parsed RPKI Resource Certificate (RFC 6487) data model.
|
||||||
///
|
///
|
||||||
/// This module intentionally focuses on the semantics needed by Signed Object validation and
|
/// 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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RcExtensions {
|
pub struct RcExtensions {
|
||||||
pub basic_constraints_ca: bool,
|
pub basic_constraints_ca: bool,
|
||||||
|
pub basic_constraints: Option<BasicConstraintsProfile>,
|
||||||
pub subject_key_identifier: Option<Vec<u8>>,
|
pub subject_key_identifier: Option<Vec<u8>>,
|
||||||
/// Authority Key Identifier (AKI) keyIdentifier value.
|
/// Authority Key Identifier (AKI) keyIdentifier value.
|
||||||
pub authority_key_identifier: Option<Vec<u8>>,
|
pub authority_key_identifier: Option<Vec<u8>>,
|
||||||
@ -68,11 +80,26 @@ pub struct RcExtensions {
|
|||||||
pub ca_issuers_uris: Option<Vec<String>>,
|
pub ca_issuers_uris: Option<Vec<String>>,
|
||||||
pub subject_info_access: Option<SubjectInfoAccess>,
|
pub subject_info_access: Option<SubjectInfoAccess>,
|
||||||
pub certificate_policies_oid: Option<String>,
|
pub certificate_policies_oid: Option<String>,
|
||||||
|
pub certificate_policies: Option<CertificatePoliciesProfile>,
|
||||||
|
pub extension_oids: Vec<String>,
|
||||||
|
|
||||||
pub ip_resources: Option<IpResourceSet>,
|
pub ip_resources: Option<IpResourceSet>,
|
||||||
pub as_resources: Option<AsResourceSet>,
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ResourceCertificateParsed {
|
pub struct ResourceCertificateParsed {
|
||||||
pub raw_der: Vec<u8>,
|
pub raw_der: Vec<u8>,
|
||||||
@ -114,13 +141,14 @@ impl AlgorithmIdentifierValue {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RcExtensionsParsed {
|
pub struct RcExtensionsParsed {
|
||||||
pub basic_constraints_ca: Vec<bool>,
|
pub basic_constraints: Vec<BasicConstraintsProfile>,
|
||||||
pub subject_key_identifier: Vec<(Vec<u8>, bool)>,
|
pub subject_key_identifier: Vec<(Vec<u8>, bool)>,
|
||||||
pub authority_key_identifier: Vec<(AuthorityKeyIdentifierParsed, bool)>,
|
pub authority_key_identifier: Vec<(AuthorityKeyIdentifierParsed, bool)>,
|
||||||
pub crl_distribution_points: Vec<(CrlDistributionPointsParsed, bool)>,
|
pub crl_distribution_points: Vec<(CrlDistributionPointsParsed, bool)>,
|
||||||
pub authority_info_access: Vec<(AuthorityInfoAccessParsed, bool)>,
|
pub authority_info_access: Vec<(AuthorityInfoAccessParsed, bool)>,
|
||||||
pub subject_info_access: Vec<(SubjectInfoAccessParsed, 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 ip_resources: Vec<(IpResourceSet, bool)>,
|
||||||
pub as_resources: Vec<(AsResourceSet, 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)")]
|
#[error("certificatePolicies criticality must be critical (RFC 6487 §4.8.9)")]
|
||||||
CertificatePoliciesCriticality,
|
CertificatePoliciesCriticality,
|
||||||
|
|
||||||
|
#[error("certificatePolicies must be present (RFC 6487 §4.8.9)")]
|
||||||
|
CertificatePoliciesMissing,
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)"
|
"certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)"
|
||||||
)]
|
)]
|
||||||
InvalidCertificatePolicy(String),
|
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(
|
#[error(
|
||||||
"SIA id-ad-signedObject accessLocation must be URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)"
|
"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(())
|
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> {
|
pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> {
|
||||||
validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
|
validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
|
||||||
validate_strict_rpki_name(&self.tbs.subject_name, "subject")?;
|
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(
|
fn validate_strict_rpki_name(
|
||||||
name: &X509NameDer,
|
name: &X509NameDer,
|
||||||
role: &'static str,
|
role: &'static str,
|
||||||
@ -744,13 +903,24 @@ mod strict_name_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn profile_rejects_rfc8360_v2_policy_oid() {
|
fn profile_rejects_rfc8360_v2_policy_oid() {
|
||||||
let extensions = RcExtensionsParsed {
|
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(),
|
subject_key_identifier: Vec::new(),
|
||||||
authority_key_identifier: Vec::new(),
|
authority_key_identifier: Vec::new(),
|
||||||
crl_distribution_points: Vec::new(),
|
crl_distribution_points: Vec::new(),
|
||||||
authority_info_access: Vec::new(),
|
authority_info_access: Vec::new(),
|
||||||
subject_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(),
|
ip_resources: Vec::new(),
|
||||||
as_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,
|
// NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning,
|
||||||
// especially for large resource sets and URI lists.
|
// especially for large resource sets and URI lists.
|
||||||
let RcExtensionsParsed {
|
let RcExtensionsParsed {
|
||||||
basic_constraints_ca,
|
basic_constraints,
|
||||||
subject_key_identifier,
|
subject_key_identifier,
|
||||||
authority_key_identifier,
|
authority_key_identifier,
|
||||||
crl_distribution_points,
|
crl_distribution_points,
|
||||||
authority_info_access,
|
authority_info_access,
|
||||||
subject_info_access,
|
subject_info_access,
|
||||||
certificate_policies,
|
certificate_policies,
|
||||||
|
extension_oids,
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if basic_constraints_ca.len() > 1 {
|
if basic_constraints.len() > 1 {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
"basicConstraints",
|
"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() {
|
let subject_key_identifier = match subject_key_identifier.len() {
|
||||||
0 => None,
|
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,
|
0 => None,
|
||||||
1 => {
|
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 {
|
if !critical {
|
||||||
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
||||||
}
|
}
|
||||||
if oids.len() != 1 {
|
if policies.len() != 1 {
|
||||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||||
"expected exactly one policy".into(),
|
"expected exactly one policy".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let policy_oid = oids.into_iter().next().expect("len==1");
|
let policy = policies.into_iter().next().expect("len==1");
|
||||||
if policy_oid != OID_CP_IPADDR_ASNUMBER {
|
if policy.policy_oid != OID_CP_IPADDR_ASNUMBER {
|
||||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||||
policy_oid,
|
policy.policy_oid,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Some(OID_CP_IPADDR_ASNUMBER.to_string())
|
Some(policy)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
@ -1093,12 +1265,17 @@ impl RcExtensionsParsed {
|
|||||||
|
|
||||||
Ok(RcExtensions {
|
Ok(RcExtensions {
|
||||||
basic_constraints_ca,
|
basic_constraints_ca,
|
||||||
|
basic_constraints,
|
||||||
subject_key_identifier,
|
subject_key_identifier,
|
||||||
authority_key_identifier,
|
authority_key_identifier,
|
||||||
crl_distribution_points_uris,
|
crl_distribution_points_uris,
|
||||||
ca_issuers_uris,
|
ca_issuers_uris,
|
||||||
subject_info_access,
|
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,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
})
|
})
|
||||||
@ -1127,26 +1304,32 @@ fn algorithm_identifier_value(
|
|||||||
fn parse_extensions_parse(
|
fn parse_extensions_parse(
|
||||||
exts: &[X509Extension<'_>],
|
exts: &[X509Extension<'_>],
|
||||||
) -> Result<RcExtensionsParsed, ResourceCertificateParseError> {
|
) -> 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 ski: Vec<(Vec<u8>, bool)> = Vec::new();
|
||||||
let mut aki: Vec<(AuthorityKeyIdentifierParsed, bool)> = Vec::new();
|
let mut aki: Vec<(AuthorityKeyIdentifierParsed, bool)> = Vec::new();
|
||||||
let mut crldp: Vec<(CrlDistributionPointsParsed, bool)> = Vec::new();
|
let mut crldp: Vec<(CrlDistributionPointsParsed, bool)> = Vec::new();
|
||||||
let mut aia: Vec<(AuthorityInfoAccessParsed, bool)> = Vec::new();
|
let mut aia: Vec<(AuthorityInfoAccessParsed, bool)> = Vec::new();
|
||||||
let mut sia: Vec<(SubjectInfoAccessParsed, 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 ip_resources: Vec<(IpResourceSet, bool)> = Vec::new();
|
||||||
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
|
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
|
||||||
|
|
||||||
for ext in exts {
|
for ext in exts {
|
||||||
let oid = ext.oid.as_bytes();
|
let oid = ext.oid.as_bytes();
|
||||||
|
extension_oids.push(ext.oid.to_id_string());
|
||||||
if oid == OID_BASIC_CONSTRAINTS_RAW {
|
if oid == OID_BASIC_CONSTRAINTS_RAW {
|
||||||
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"basicConstraints parse failed".into(),
|
"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 {
|
} else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
|
||||||
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
@ -1195,16 +1378,30 @@ fn parse_extensions_parse(
|
|||||||
"certificatePolicies parse failed".into(),
|
"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() {
|
for p in cp.iter() {
|
||||||
let b = p.policy_id.as_bytes();
|
let b = p.policy_id.as_bytes();
|
||||||
if b == OID_CP_IPADDR_ASNUMBER_RAW {
|
let policy_oid = if b == OID_CP_IPADDR_ASNUMBER_RAW {
|
||||||
oids.push(OID_CP_IPADDR_ASNUMBER.to_string());
|
OID_CP_IPADDR_ASNUMBER.to_string()
|
||||||
} else {
|
} 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((policies, ext.critical));
|
||||||
cert_policies.push((oids, ext.critical));
|
|
||||||
} else if oid == OID_IP_ADDR_BLOCKS_RAW {
|
} else if oid == OID_IP_ADDR_BLOCKS_RAW {
|
||||||
let parsed = IpResourceSet::decode_extn_value(ext.value)
|
let parsed = IpResourceSet::decode_extn_value(ext.value)
|
||||||
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
||||||
@ -1217,13 +1414,14 @@ fn parse_extensions_parse(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(RcExtensionsParsed {
|
Ok(RcExtensionsParsed {
|
||||||
basic_constraints_ca,
|
basic_constraints,
|
||||||
subject_key_identifier: ski,
|
subject_key_identifier: ski,
|
||||||
authority_key_identifier: aki,
|
authority_key_identifier: aki,
|
||||||
crl_distribution_points: crldp,
|
crl_distribution_points: crldp,
|
||||||
authority_info_access: aia,
|
authority_info_access: aia,
|
||||||
subject_info_access: sia,
|
subject_info_access: sia,
|
||||||
certificate_policies: cert_policies,
|
certificate_policies: cert_policies,
|
||||||
|
extension_oids,
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use crate::data_model::oid::{
|
|||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
AsIdOrRange, AsIdentifierChoice, ResourceCertKind, ResourceCertificate,
|
AsIdOrRange, AsIdentifierChoice, ResourceCertKind, ResourceCertificate,
|
||||||
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
||||||
|
ResourceCertificateRole,
|
||||||
};
|
};
|
||||||
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_predecoded_ee};
|
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_predecoded_ee};
|
||||||
use x509_parser::extensions::ParsedExtension;
|
use x509_parser::extensions::ParsedExtension;
|
||||||
@ -202,6 +203,7 @@ impl BgpsecRouterCertificateParsed {
|
|||||||
self,
|
self,
|
||||||
) -> Result<BgpsecRouterCertificate, BgpsecRouterCertificateProfileError> {
|
) -> Result<BgpsecRouterCertificate, BgpsecRouterCertificateProfileError> {
|
||||||
let rc = self.rc_parsed.validate_profile()?;
|
let rc = self.rc_parsed.validate_profile()?;
|
||||||
|
rc.validate_rfc6487_profile(ResourceCertificateRole::RouterEe)?;
|
||||||
if rc.kind != ResourceCertKind::Ee {
|
if rc.kind != ResourceCertKind::Ee {
|
||||||
return Err(BgpsecRouterCertificateProfileError::NotEe);
|
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_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA,
|
||||||
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
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 asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag};
|
||||||
use ring::digest;
|
use ring::digest;
|
||||||
use x509_parser::extensions::ParsedExtension;
|
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 {
|
if strict_name {
|
||||||
rc.validate_strict_name_profile()
|
rc.validate_strict_name_profile()
|
||||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
.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::{
|
use crate::data_model::rc::{
|
||||||
AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate,
|
AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate,
|
||||||
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
||||||
|
ResourceCertificateRole,
|
||||||
};
|
};
|
||||||
use crate::data_model::tal::Tal;
|
use crate::data_model::tal::Tal;
|
||||||
|
|
||||||
@ -218,6 +219,7 @@ impl TaCertificateParsed {
|
|||||||
if rc_ca.kind != ResourceCertKind::Ca {
|
if rc_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(TaCertificateProfileError::NotCa);
|
return Err(TaCertificateProfileError::NotCa);
|
||||||
}
|
}
|
||||||
|
rc_ca.validate_rfc6487_profile(ResourceCertificateRole::TrustAnchor)?;
|
||||||
|
|
||||||
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
|
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
|
||||||
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
|
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::oid::OID_KEY_USAGE_RAW;
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
||||||
ResourceCertificate, ResourceCertificateDecodeError,
|
ResourceCertificate, ResourceCertificateDecodeError, ResourceCertificateProfileError,
|
||||||
|
ResourceCertificateRole,
|
||||||
};
|
};
|
||||||
use crate::policy::ResourceValidationMode;
|
use crate::policy::ResourceValidationMode;
|
||||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
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)")]
|
#[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
|
||||||
IssuerDecode(ResourceCertificateDecodeError),
|
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)")]
|
#[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")]
|
||||||
CrlDecode(#[from] CrlDecodeError),
|
CrlDecode(#[from] CrlDecodeError),
|
||||||
|
|
||||||
@ -244,12 +251,18 @@ pub fn validate_subordinate_ca_cert_with_resource_validation_mode(
|
|||||||
if child_ca.kind != ResourceCertKind::Ca {
|
if child_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CaPathError::ChildNotCa);
|
return Err(CaPathError::ChildNotCa);
|
||||||
}
|
}
|
||||||
|
child_ca
|
||||||
|
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
||||||
|
.map_err(CaPathError::ChildProfile)?;
|
||||||
|
|
||||||
let issuer_ca =
|
let issuer_ca =
|
||||||
ResourceCertificate::decode_der(issuer_ca_der).map_err(CaPathError::IssuerDecode)?;
|
ResourceCertificate::decode_der(issuer_ca_der).map_err(CaPathError::IssuerDecode)?;
|
||||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CaPathError::IssuerNotCa);
|
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)?;
|
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) {
|
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 {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CaPathError::IssuerNotCa);
|
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) {
|
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||||
return Err(CaPathError::IssuerSubjectMismatch {
|
return Err(CaPathError::IssuerSubjectMismatch {
|
||||||
@ -1375,12 +1394,14 @@ fn bytes_leq(a: &[u8], b: &[u8]) -> bool {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::data_model::common::X509NameDer;
|
use crate::data_model::common::X509NameDer;
|
||||||
|
use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
||||||
IpAddressOrRange, IpPrefix, IpResourceSet,
|
IpAddressOrRange, IpPrefix, IpResourceSet,
|
||||||
};
|
};
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
|
||||||
|
ResourceCertificate, RpkixTbsCertificate,
|
||||||
};
|
};
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@ -1410,12 +1431,27 @@ mod tests {
|
|||||||
subject_public_key_info: Vec::new(),
|
subject_public_key_info: Vec::new(),
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
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,
|
subject_key_identifier: ski,
|
||||||
authority_key_identifier: aki,
|
authority_key_identifier: aki,
|
||||||
crl_distribution_points_uris: crldp,
|
crl_distribution_points_uris: crldp,
|
||||||
ca_issuers_uris: aia,
|
ca_issuers_uris: aia,
|
||||||
subject_info_access: None,
|
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,
|
ip_resources: None,
|
||||||
as_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::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError,
|
ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError,
|
||||||
|
ResourceCertificateProfileError, ResourceCertificateRole,
|
||||||
};
|
};
|
||||||
use crate::data_model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate};
|
use crate::data_model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate};
|
||||||
use ring::signature;
|
use ring::signature;
|
||||||
@ -34,6 +35,9 @@ pub enum CertPathError {
|
|||||||
)]
|
)]
|
||||||
IssuerNotCa,
|
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)")]
|
#[error("EE certificate must be an EE resource certificate (RFC 6487 §4)")]
|
||||||
EeNotEe,
|
EeNotEe,
|
||||||
|
|
||||||
@ -128,6 +132,9 @@ pub fn validate_ee_cert_path(
|
|||||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CertPathError::IssuerNotCa);
|
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)?;
|
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) {
|
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 {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CertPathError::IssuerNotCa);
|
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) {
|
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||||
return Err(CertPathError::IssuerSubjectMismatch {
|
return Err(CertPathError::IssuerSubjectMismatch {
|
||||||
@ -515,8 +525,10 @@ fn is_serial_revoked_by_crl(ee: &ResourceCertificate, crl: &RpkixCrl) -> bool {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::data_model::common::X509NameDer;
|
use crate::data_model::common::X509NameDer;
|
||||||
|
use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
|
||||||
|
ResourceCertificate, RpkixTbsCertificate,
|
||||||
};
|
};
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
|
|
||||||
@ -545,12 +557,27 @@ mod tests {
|
|||||||
subject_public_key_info: Vec::new(),
|
subject_public_key_info: Vec::new(),
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
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,
|
subject_key_identifier: ski,
|
||||||
authority_key_identifier: aki,
|
authority_key_identifier: aki,
|
||||||
crl_distribution_points_uris: crldp,
|
crl_distribution_points_uris: crldp,
|
||||||
ca_issuers_uris: aia,
|
ca_issuers_uris: aia,
|
||||||
subject_info_access: None,
|
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,
|
ip_resources: None,
|
||||||
as_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_decode_error: Option<String>;
|
||||||
let issuer_ca = match crate::data_model::rc::ResourceCertificate::decode_der(issuer_ca_der) {
|
let issuer_ca = match crate::data_model::rc::ResourceCertificate::decode_der(issuer_ca_der) {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
|
match v.validate_rfc6487_profile(crate::data_model::rc::ResourceCertificateRole::Ca) {
|
||||||
|
Ok(()) => {
|
||||||
issuer_ca_decode_error = None;
|
issuer_ca_decode_error = None;
|
||||||
Some(v)
|
Some(v)
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
issuer_ca_decode_error = Some(format!(
|
||||||
|
"issuer CA profile validation failed: {e} (RFC 6487 §4.8)"
|
||||||
|
));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
issuer_ca_decode_error = Some(format!(
|
issuer_ca_decode_error = Some(format!(
|
||||||
"issuer CA decode failed: {e} (RFC 5280 §4.1; RFC 6487 §4)"
|
"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 => {
|
ObjType::Cer => {
|
||||||
let parsed = ResourceCertificate::parse_der(der).map_err(|e| e.to_string())?;
|
let parsed = ResourceCertificate::parse_der(der).map_err(|e| e.to_string())?;
|
||||||
let spki_len = parsed.subject_public_key_info.len() as u32;
|
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.subject_key_identifier.len()
|
||||||
+ parsed.extensions.authority_key_identifier.len()
|
+ parsed.extensions.authority_key_identifier.len()
|
||||||
+ parsed.extensions.crl_distribution_points.len()
|
+ parsed.extensions.crl_distribution_points.len()
|
||||||
|
|||||||
@ -25,6 +25,7 @@ fn dummy_ee(
|
|||||||
subject_public_key_info: vec![],
|
subject_public_key_info: vec![],
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: false,
|
basic_constraints_ca: false,
|
||||||
|
basic_constraints: None,
|
||||||
subject_key_identifier: Some(vec![0x01]),
|
subject_key_identifier: Some(vec![0x01]),
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
@ -35,6 +36,8 @@ fn dummy_ee(
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
certificate_policies_oid: None,
|
certificate_policies_oid: None,
|
||||||
|
certificate_policies: None,
|
||||||
|
extension_oids: Vec::new(),
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,6 +28,7 @@ fn dummy_ee(
|
|||||||
subject_public_key_info: vec![],
|
subject_public_key_info: vec![],
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: false,
|
basic_constraints_ca: false,
|
||||||
|
basic_constraints: None,
|
||||||
subject_key_identifier: Some(vec![0x01]),
|
subject_key_identifier: Some(vec![0x01]),
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
@ -38,6 +39,8 @@ fn dummy_ee(
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
certificate_policies_oid: None,
|
certificate_policies_oid: None,
|
||||||
|
certificate_policies: None,
|
||||||
|
extension_oids: Vec::new(),
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -68,12 +68,15 @@ fn ta_certificate_rejects_non_self_signed_ca() {
|
|||||||
fn ta_constraints_require_policies_and_ski() {
|
fn ta_constraints_require_policies_and_ski() {
|
||||||
let rc = dummy_rc_ca(RcExtensions {
|
let rc = dummy_rc_ca(RcExtensions {
|
||||||
basic_constraints_ca: true,
|
basic_constraints_ca: true,
|
||||||
|
basic_constraints: None,
|
||||||
subject_key_identifier: None,
|
subject_key_identifier: None,
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
ca_issuers_uris: None,
|
ca_issuers_uris: None,
|
||||||
subject_info_access: None,
|
subject_info_access: None,
|
||||||
certificate_policies_oid: None,
|
certificate_policies_oid: None,
|
||||||
|
certificate_policies: None,
|
||||||
|
extension_oids: Vec::new(),
|
||||||
ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
|
ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
|
||||||
as_resources: None,
|
as_resources: None,
|
||||||
});
|
});
|
||||||
@ -97,12 +100,15 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
|
|||||||
// Missing both IP and AS resources.
|
// Missing both IP and AS resources.
|
||||||
let rc = dummy_rc_ca(RcExtensions {
|
let rc = dummy_rc_ca(RcExtensions {
|
||||||
basic_constraints_ca: true,
|
basic_constraints_ca: true,
|
||||||
|
basic_constraints: None,
|
||||||
subject_key_identifier: Some(vec![1]),
|
subject_key_identifier: Some(vec![1]),
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
ca_issuers_uris: None,
|
ca_issuers_uris: None,
|
||||||
subject_info_access: None,
|
subject_info_access: None,
|
||||||
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
||||||
|
certificate_policies: None,
|
||||||
|
extension_oids: Vec::new(),
|
||||||
ip_resources: None,
|
ip_resources: None,
|
||||||
as_resources: None,
|
as_resources: None,
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user