串行验证通过

This commit is contained in:
2026-02-09 19:35:54 +08:00
parent 7be865d7f1
commit afc31c02ab
66 changed files with 12049 additions and 4 deletions

View File

@@ -10,8 +10,10 @@ use crate::data_model::common::{
Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, asn1_time_to_model,
};
use crate::data_model::oid::{
OID_AD_SIGNED_OBJECT, OID_AUTONOMOUS_SYS_IDS, OID_CP_IPADDR_ASNUMBER, OID_IP_ADDR_BLOCKS,
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER,
OID_AD_CA_ISSUERS, OID_AD_SIGNED_OBJECT, OID_AUTHORITY_INFO_ACCESS,
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTONOMOUS_SYS_IDS, OID_CRL_DISTRIBUTION_POINTS,
OID_CP_IPADDR_ASNUMBER, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION,
OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER,
};
/// Resource Certificate kind (semantic classification).
@@ -54,6 +56,12 @@ pub struct RpkixTbsCertificate {
pub struct RcExtensions {
pub basic_constraints_ca: bool,
pub subject_key_identifier: Option<Vec<u8>>,
/// Authority Key Identifier (AKI) keyIdentifier value.
pub authority_key_identifier: Option<Vec<u8>>,
/// CRL Distribution Points URIs (fullName).
pub crl_distribution_points_uris: Option<Vec<Url>>,
/// Authority Information Access (AIA) caIssuers URIs.
pub ca_issuers_uris: Option<Vec<Url>>,
pub subject_info_access: Option<SubjectInfoAccess>,
pub certificate_policies_oid: Option<String>,
@@ -104,12 +112,44 @@ impl AlgorithmIdentifierValue {
pub struct RcExtensionsParsed {
pub basic_constraints_ca: Vec<bool>,
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 ip_resources: Vec<(IpResourceSet, bool)>,
pub as_resources: Vec<(AsResourceSet, bool)>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthorityKeyIdentifierParsed {
pub key_identifier: Option<Vec<u8>>,
pub has_authority_cert_issuer: bool,
pub has_authority_cert_serial: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthorityInfoAccessParsed {
pub ca_issuers_uris: Vec<Url>,
pub ca_issuers_access_location_not_uri: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CrlDistributionPointsParsed {
pub distribution_points: Vec<CrlDistributionPointParsed>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CrlDistributionPointParsed {
pub distribution_point_present: bool,
pub reasons_present: bool,
pub crl_issuer_present: bool,
pub name_relative_to_crl_issuer_present: bool,
pub full_name_uris: Vec<Url>,
pub full_name_not_uri: bool,
pub full_name_present: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectInfoAccessParsed {
pub access_descriptions: Vec<AccessDescription>,
@@ -375,6 +415,103 @@ pub enum ResourceCertificateProfileError {
#[error("autonomousSysIds criticality must be critical when present (RFC 6487 §4.8.11)")]
AsResourcesCriticality,
#[error(
"authorityKeyIdentifier must be present in non-self-signed certificates (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
)]
AkiMissing,
#[error(
"authorityKeyIdentifier criticality must be non-critical (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
)]
AkiCriticality,
#[error(
"authorityKeyIdentifier authorityCertIssuer MUST NOT be present (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
)]
AkiAuthorityCertIssuerPresent,
#[error(
"authorityKeyIdentifier authorityCertSerialNumber MUST NOT be present (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
)]
AkiAuthorityCertSerialPresent,
#[error(
"self-signed certificate authorityKeyIdentifier must equal subjectKeyIdentifier when present (RFC 6487 §4.8.3)"
)]
AkiSelfSignedNotEqualSki,
#[error(
"CRLDistributionPoints must be present in non-self-signed certificates (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)"
)]
CrlDistributionPointsMissing,
#[error(
"CRLDistributionPoints criticality must be non-critical (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)"
)]
CrlDistributionPointsCriticality,
#[error(
"CRLDistributionPoints MUST be omitted in self-signed certificates (RFC 6487 §4.8.6)"
)]
CrlDistributionPointsSelfSignedMustOmit,
#[error(
"CRLDistributionPoints must contain exactly one DistributionPoint (RFC 6487 §4.8.6)"
)]
CrlDistributionPointsNotSingle,
#[error(
"CRLDistributionPoints distributionPoint field MUST be present (RFC 6487 §4.8.6)"
)]
CrlDistributionPointsNoDistributionPoint,
#[error("CRLDistributionPoints reasons field MUST be omitted (RFC 6487 §4.8.6)")]
CrlDistributionPointsHasReasons,
#[error("CRLDistributionPoints cRLIssuer field MUST be omitted (RFC 6487 §4.8.6)")]
CrlDistributionPointsHasCrlIssuer,
#[error(
"CRLDistributionPoints distributionPoint MUST contain fullName and MUST NOT contain nameRelativeToCRLIssuer (RFC 6487 §4.8.6)"
)]
CrlDistributionPointsInvalidName,
#[error(
"CRLDistributionPoints fullName must contain only URI GeneralNames (RFC 6487 §4.8.6; RFC 5280 §4.2.1.6)"
)]
CrlDistributionPointsFullNameNotUri,
#[error("CRLDistributionPoints must include at least one rsync:// URI (RFC 6487 §4.8.6)")]
CrlDistributionPointsNoRsync,
#[error(
"authorityInfoAccess must be present in non-self-signed certificates (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)"
)]
AuthorityInfoAccessMissing,
#[error(
"authorityInfoAccess criticality must be non-critical (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)"
)]
AuthorityInfoAccessCriticality,
#[error(
"authorityInfoAccess MUST be omitted in self-signed certificates (RFC 6487 §4.8.7)"
)]
AuthorityInfoAccessSelfSignedMustOmit,
#[error(
"authorityInfoAccess id-ad-caIssuers accessLocation must be URI (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)"
)]
AuthorityInfoAccessCaIssuersNotUri,
#[error(
"authorityInfoAccess must include at least one id-ad-caIssuers URI (RFC 6487 §4.8.7)"
)]
AuthorityInfoAccessMissingCaIssuers,
#[error("authorityInfoAccess must include at least one rsync:// URI (RFC 6487 §4.8.7)")]
AuthorityInfoAccessNoRsync,
}
#[derive(Debug, thiserror::Error)]
@@ -464,7 +601,8 @@ impl ResourceCertificateParsed {
return Err(ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters);
}
let extensions = self.extensions.validate_profile()?;
let is_self_signed = self.issuer_dn == self.subject_dn;
let extensions = self.extensions.validate_profile(is_self_signed)?;
let kind = if extensions.basic_constraints_ca {
ResourceCertKind::Ca
} else {
@@ -490,7 +628,10 @@ impl ResourceCertificateParsed {
}
impl RcExtensionsParsed {
pub fn validate_profile(self) -> Result<RcExtensions, ResourceCertificateProfileError> {
pub fn validate_profile(
self,
is_self_signed: bool,
) -> Result<RcExtensions, ResourceCertificateProfileError> {
if self.basic_constraints_ca.len() > 1 {
return Err(ResourceCertificateProfileError::DuplicateExtension(
"basicConstraints",
@@ -513,6 +654,123 @@ impl RcExtensionsParsed {
}
};
let authority_key_identifier = match self.authority_key_identifier.as_slice() {
[] => {
if is_self_signed {
None
} else {
return Err(ResourceCertificateProfileError::AkiMissing);
}
}
[(aki, critical)] => {
if *critical {
return Err(ResourceCertificateProfileError::AkiCriticality);
}
if aki.has_authority_cert_issuer {
return Err(ResourceCertificateProfileError::AkiAuthorityCertIssuerPresent);
}
if aki.has_authority_cert_serial {
return Err(ResourceCertificateProfileError::AkiAuthorityCertSerialPresent);
}
let keyid = aki.key_identifier.clone();
if is_self_signed {
if let (Some(keyid), Some(ski)) = (keyid.as_ref(), subject_key_identifier.as_ref())
{
if keyid != ski {
return Err(ResourceCertificateProfileError::AkiSelfSignedNotEqualSki);
}
}
} else if keyid.is_none() {
return Err(ResourceCertificateProfileError::AkiMissing);
}
keyid
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
"authorityKeyIdentifier",
));
}
};
let crl_distribution_points_uris = match self.crl_distribution_points.as_slice() {
[] => {
if is_self_signed {
None
} else {
return Err(ResourceCertificateProfileError::CrlDistributionPointsMissing);
}
}
[(crldp, critical)] => {
if *critical {
return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
}
if is_self_signed {
return Err(ResourceCertificateProfileError::CrlDistributionPointsSelfSignedMustOmit);
}
if crldp.distribution_points.len() != 1 {
return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle);
}
let dp = &crldp.distribution_points[0];
if dp.reasons_present {
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasReasons);
}
if dp.crl_issuer_present {
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasCrlIssuer);
}
if !dp.distribution_point_present {
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoDistributionPoint);
}
if dp.name_relative_to_crl_issuer_present || !dp.full_name_present {
return Err(ResourceCertificateProfileError::CrlDistributionPointsInvalidName);
}
if dp.full_name_not_uri {
return Err(ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri);
}
if !dp.full_name_uris.iter().any(|u| u.scheme() == "rsync") {
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync);
}
Some(dp.full_name_uris.clone())
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
"cRLDistributionPoints",
));
}
};
let ca_issuers_uris = match self.authority_info_access.as_slice() {
[] => {
if is_self_signed {
None
} else {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissing);
}
}
[(aia, critical)] => {
if *critical {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
}
if is_self_signed {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessSelfSignedMustOmit);
}
if aia.ca_issuers_access_location_not_uri {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCaIssuersNotUri);
}
if aia.ca_issuers_uris.is_empty() {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers);
}
if !aia.ca_issuers_uris.iter().any(|u| u.scheme() == "rsync") {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync);
}
Some(aia.ca_issuers_uris.clone())
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
"authorityInfoAccess",
));
}
};
let subject_info_access = match self.subject_info_access.as_slice() {
[] => None,
[(sia, critical)] => {
@@ -604,6 +862,9 @@ impl RcExtensionsParsed {
Ok(RcExtensions {
basic_constraints_ca,
subject_key_identifier,
authority_key_identifier,
crl_distribution_points_uris,
ca_issuers_uris,
subject_info_access,
certificate_policies_oid,
ip_resources,
@@ -631,6 +892,9 @@ fn parse_extensions_parse(
) -> Result<RcExtensionsParsed, ResourceCertificateParseError> {
let mut basic_constraints_ca: Vec<bool> = 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();
@@ -656,6 +920,37 @@ fn parse_extensions_parse(
};
ski.push((s.0.to_vec(), ext.critical));
}
OID_AUTHORITY_KEY_IDENTIFIER => {
let ParsedExtension::AuthorityKeyIdentifier(a) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"authorityKeyIdentifier parse failed".into(),
));
};
aki.push((
AuthorityKeyIdentifierParsed {
key_identifier: a.key_identifier.as_ref().map(|k| k.0.to_vec()),
has_authority_cert_issuer: a.authority_cert_issuer.is_some(),
has_authority_cert_serial: a.authority_cert_serial.is_some(),
},
ext.critical,
));
}
OID_CRL_DISTRIBUTION_POINTS => {
let ParsedExtension::CRLDistributionPoints(p) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"cRLDistributionPoints parse failed".into(),
));
};
crldp.push((parse_crldp_parse(p)?, ext.critical));
}
OID_AUTHORITY_INFO_ACCESS => {
let ParsedExtension::AuthorityInfoAccess(p) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"authorityInfoAccess parse failed".into(),
));
};
aia.push((parse_aia_parse(p.accessdescs.as_slice())?, ext.critical));
}
OID_SUBJECT_INFO_ACCESS => {
let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
@@ -690,6 +985,9 @@ fn parse_extensions_parse(
Ok(RcExtensionsParsed {
basic_constraints_ca,
subject_key_identifier: ski,
authority_key_identifier: aki,
crl_distribution_points: crldp,
authority_info_access: aia,
subject_info_access: sia,
certificate_policies: cert_policies,
ip_resources,
@@ -697,6 +995,88 @@ fn parse_extensions_parse(
})
}
fn parse_aia_parse(
access: &[x509_parser::extensions::AccessDescription<'_>],
) -> Result<AuthorityInfoAccessParsed, ResourceCertificateParseError> {
let mut ca_issuers_uris: Vec<Url> = Vec::new();
let mut ca_issuers_access_location_not_uri = false;
for ad in access {
let access_method_oid = ad.access_method.to_id_string();
if access_method_oid != OID_AD_CA_ISSUERS {
continue;
}
let uri = match &ad.access_location {
x509_parser::extensions::GeneralName::URI(u) => u,
_ => {
ca_issuers_access_location_not_uri = true;
continue;
}
};
let url = Url::parse(uri)
.map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?;
ca_issuers_uris.push(url);
}
Ok(AuthorityInfoAccessParsed {
ca_issuers_uris,
ca_issuers_access_location_not_uri,
})
}
fn parse_crldp_parse(
crldp: &x509_parser::extensions::CRLDistributionPoints<'_>,
) -> Result<CrlDistributionPointsParsed, ResourceCertificateParseError> {
let mut out: Vec<CrlDistributionPointParsed> = Vec::new();
for p in crldp.iter() {
let mut full_name_uris: Vec<Url> = Vec::new();
let mut full_name_not_uri = false;
let mut full_name_present = false;
let mut name_relative_to_crl_issuer_present = false;
let mut distribution_point_present = false;
if let Some(dp) = &p.distribution_point {
distribution_point_present = true;
match dp {
x509_parser::extensions::DistributionPointName::FullName(names) => {
full_name_present = true;
for n in names {
match n {
x509_parser::extensions::GeneralName::URI(u) => {
let url = Url::parse(u).map_err(|_| {
ResourceCertificateParseError::Parse(format!(
"invalid URI: {u}"
))
})?;
full_name_uris.push(url);
}
_ => {
full_name_not_uri = true;
}
}
}
}
x509_parser::extensions::DistributionPointName::NameRelativeToCRLIssuer(_) => {
name_relative_to_crl_issuer_present = true;
}
}
}
out.push(CrlDistributionPointParsed {
distribution_point_present,
reasons_present: p.reasons.is_some(),
crl_issuer_present: p.crl_issuer.is_some(),
name_relative_to_crl_issuer_present,
full_name_uris,
full_name_not_uri,
full_name_present,
});
}
Ok(CrlDistributionPointsParsed {
distribution_points: out,
})
}
fn parse_sia_parse(
access: &[x509_parser::extensions::AccessDescription<'_>],
) -> Result<SubjectInfoAccessParsed, ResourceCertificateParseError> {