串行验证通过
This commit is contained in:
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user