697 lines
23 KiB
Rust
697 lines
23 KiB
Rust
use crate::data_model::common::BigUnsigned;
|
|
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
|
use crate::data_model::rc::{
|
|
ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError,
|
|
};
|
|
use crate::data_model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate};
|
|
use ring::signature;
|
|
use x509_parser::prelude::{FromDer, X509Certificate};
|
|
|
|
use crate::validation::x509_name::x509_names_equivalent;
|
|
use std::collections::HashSet;
|
|
use x509_parser::x509::SubjectPublicKeyInfo;
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct ValidatedEeCertPath {
|
|
pub ee: ResourceCertificate,
|
|
pub issuer_ca: ResourceCertificate,
|
|
pub issuer_crl: RpkixCrl,
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum CertPathError {
|
|
#[error("EE certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
|
|
EeDecode(#[from] ResourceCertificateDecodeError),
|
|
|
|
#[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
|
|
IssuerDecode(ResourceCertificateDecodeError),
|
|
|
|
#[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")]
|
|
CrlDecode(#[from] CrlDecodeError),
|
|
|
|
#[error(
|
|
"issuer certificate must be a CA resource certificate (RFC 6487 §4.8.1; RFC 5280 §4.2.1.9)"
|
|
)]
|
|
IssuerNotCa,
|
|
|
|
#[error("EE certificate must be an EE resource certificate (RFC 6487 §4)")]
|
|
EeNotEe,
|
|
|
|
#[error(
|
|
"EE issuer DN does not match issuer CA subject DN: ee.issuer={ee_issuer_dn} issuer.subject={issuer_subject_dn} (RFC 5280 §6.1)"
|
|
)]
|
|
IssuerSubjectMismatch {
|
|
ee_issuer_dn: String,
|
|
issuer_subject_dn: String,
|
|
},
|
|
|
|
#[error("EE certificate signature verification failed: {0} (RFC 5280 §6.1)")]
|
|
EeSignatureInvalid(String),
|
|
|
|
#[error("issuer SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")]
|
|
IssuerSpkiParse(String),
|
|
|
|
#[error(
|
|
"trailing bytes after issuer SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)"
|
|
)]
|
|
IssuerSpkiTrailingBytes(usize),
|
|
|
|
#[error("EE KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
|
KeyUsageMissing,
|
|
|
|
#[error("EE KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
|
KeyUsageNotCritical,
|
|
|
|
#[error("EE KeyUsage must have only digitalSignature set (RFC 6487 §4.8.4)")]
|
|
KeyUsageInvalidBits,
|
|
|
|
#[error("EE KeyUsage parse failed: {0} (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
|
KeyUsageParse(String),
|
|
|
|
#[error("issuer CA subjectKeyIdentifier missing (RFC 6487 §4.8.2)")]
|
|
IssuerSkiMissing,
|
|
|
|
#[error("EE authorityKeyIdentifier missing (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)")]
|
|
EeAkiMissing,
|
|
|
|
#[error(
|
|
"EE authorityKeyIdentifier does not match issuer subjectKeyIdentifier (RFC 6487 §4.8.3)"
|
|
)]
|
|
EeAkiMismatch,
|
|
|
|
#[error("EE authorityInfoAccess missing (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)")]
|
|
EeAiaMissing,
|
|
|
|
#[error(
|
|
"EE authorityInfoAccess does not reference issuer certificate rsync URI (RFC 6487 §4.8.7)"
|
|
)]
|
|
EeAiaIssuerUriMismatch,
|
|
|
|
#[error("EE CRLDistributionPoints missing (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)")]
|
|
EeCrlDpMissing,
|
|
|
|
#[error("EE CRLDistributionPoints does not reference issuer CRL rsync URI (RFC 6487 §4.8.6)")]
|
|
EeCrlDpUriMismatch,
|
|
|
|
#[error("certificate not valid at validation_time (RFC 5280 §4.1.2.5; RFC 5280 §6.1)")]
|
|
CertificateNotValidAtTime,
|
|
|
|
#[error(
|
|
"CRL signature/binding verification failed: {0} (RFC 5280 §6.3.3; RFC 6487 §5; RFC 9829 §3.1)"
|
|
)]
|
|
CrlVerify(#[from] CrlVerifyError),
|
|
|
|
#[error(
|
|
"CRL not valid at validation_time (RFC 5280 §6.3.3(g); RFC 5280 §5.1.2.4-§5.1.2.5; RFC 6487 §5)"
|
|
)]
|
|
CrlNotValidAtTime,
|
|
|
|
#[error("EE certificate is revoked by issuer CRL (RFC 5280 §6.3.3; RFC 6487 §5)")]
|
|
EeRevoked,
|
|
}
|
|
|
|
pub fn validate_ee_cert_path(
|
|
ee_cert_der: &[u8],
|
|
issuer_ca_der: &[u8],
|
|
issuer_crl_der: &[u8],
|
|
issuer_ca_rsync_uri: Option<&str>,
|
|
issuer_crl_rsync_uri: Option<&str>,
|
|
validation_time: time::OffsetDateTime,
|
|
) -> Result<ValidatedEeCertPath, CertPathError> {
|
|
let ee = ResourceCertificate::decode_der(ee_cert_der)?;
|
|
if ee.kind != ResourceCertKind::Ee {
|
|
return Err(CertPathError::EeNotEe);
|
|
}
|
|
|
|
let issuer_ca =
|
|
ResourceCertificate::decode_der(issuer_ca_der).map_err(CertPathError::IssuerDecode)?;
|
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
|
return Err(CertPathError::IssuerNotCa);
|
|
}
|
|
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) {
|
|
return Err(CertPathError::IssuerSubjectMismatch {
|
|
ee_issuer_dn: ee.tbs.issuer_name.to_string(),
|
|
issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
|
|
});
|
|
}
|
|
|
|
validate_ee_aki_matches_issuer_ski(&ee, &issuer_ca)?;
|
|
if let Some(expected_issuer_uri) = issuer_ca_rsync_uri {
|
|
validate_ee_aia_points_to_issuer_uri(&ee, expected_issuer_uri)?;
|
|
}
|
|
if let Some(expected_crl_uri) = issuer_crl_rsync_uri {
|
|
validate_ee_crldp_contains_issuer_crl_uri(&ee, expected_crl_uri)?;
|
|
}
|
|
|
|
if !time_within_validity(
|
|
validation_time,
|
|
ee.tbs.validity_not_before,
|
|
ee.tbs.validity_not_after,
|
|
) || !time_within_validity(
|
|
validation_time,
|
|
issuer_ca.tbs.validity_not_before,
|
|
issuer_ca.tbs.validity_not_after,
|
|
) {
|
|
return Err(CertPathError::CertificateNotValidAtTime);
|
|
}
|
|
|
|
let ee_x509 = parse_x509_cert(ee_cert_der)?;
|
|
verify_ee_cert_signature(&ee_x509, &issuer_spki)?;
|
|
validate_ee_key_usage(&ee_x509)?;
|
|
|
|
let issuer_crl = RpkixCrl::decode_der(issuer_crl_der)?;
|
|
issuer_crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)?;
|
|
if !crl_valid_at_time(&issuer_crl, validation_time) {
|
|
return Err(CertPathError::CrlNotValidAtTime);
|
|
}
|
|
|
|
if is_serial_revoked_by_crl(&ee, &issuer_crl) {
|
|
return Err(CertPathError::EeRevoked);
|
|
}
|
|
|
|
Ok(ValidatedEeCertPath {
|
|
ee,
|
|
issuer_ca,
|
|
issuer_crl,
|
|
})
|
|
}
|
|
|
|
/// Validate the EE certificate path using a *pre-decoded issuer CA* and a *pre-decoded and
|
|
/// pre-verified issuer CRL*.
|
|
///
|
|
/// This is a performance-oriented helper for stage2 serial runs: it avoids repeating issuer CA
|
|
/// decode and issuer CRL decode+signature verification for every signed object in a publication point.
|
|
///
|
|
/// The caller must ensure:
|
|
/// - `issuer_ca` corresponds to `issuer_ca_der`
|
|
/// - `issuer_crl` cryptographic signature/binding has already been verified against `issuer_ca_der`
|
|
pub fn validate_ee_cert_path_with_prevalidated_issuer(
|
|
ee_cert_der: &[u8],
|
|
issuer_ca: &ResourceCertificate,
|
|
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
|
issuer_crl: &RpkixCrl,
|
|
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
|
issuer_ca_rsync_uri: Option<&str>,
|
|
issuer_crl_rsync_uri: Option<&str>,
|
|
validation_time: time::OffsetDateTime,
|
|
) -> Result<ResourceCertificate, CertPathError> {
|
|
let ee = ResourceCertificate::decode_der(ee_cert_der)?;
|
|
validate_ee_cert_path_components(
|
|
&ee,
|
|
ee_cert_der,
|
|
issuer_ca,
|
|
issuer_spki,
|
|
issuer_crl,
|
|
issuer_crl_revoked_serials,
|
|
issuer_ca_rsync_uri,
|
|
issuer_crl_rsync_uri,
|
|
validation_time,
|
|
)?;
|
|
Ok(ee)
|
|
}
|
|
|
|
/// Validate the EE certificate path using a pre-decoded EE certificate and a pre-validated issuer.
|
|
///
|
|
/// This avoids re-decoding the embedded EE certificate when the caller already parsed it while
|
|
/// decoding a signed object (e.g. ROA / ASPA).
|
|
pub fn validate_ee_cert_path_with_predecoded_ee(
|
|
ee: &ResourceCertificate,
|
|
ee_cert_der: &[u8],
|
|
issuer_ca: &ResourceCertificate,
|
|
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
|
issuer_crl: &RpkixCrl,
|
|
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
|
issuer_ca_rsync_uri: Option<&str>,
|
|
issuer_crl_rsync_uri: Option<&str>,
|
|
validation_time: time::OffsetDateTime,
|
|
) -> Result<(), CertPathError> {
|
|
validate_ee_cert_path_components(
|
|
ee,
|
|
ee_cert_der,
|
|
issuer_ca,
|
|
issuer_spki,
|
|
issuer_crl,
|
|
issuer_crl_revoked_serials,
|
|
issuer_ca_rsync_uri,
|
|
issuer_crl_rsync_uri,
|
|
validation_time,
|
|
)
|
|
}
|
|
|
|
/// Validate a signed-object embedded EE certificate path using signed-object cached fields.
|
|
///
|
|
/// This avoids reparsing the EE certificate as `X509Certificate` and bypasses the
|
|
/// x509-parser signature wrapper by verifying the cached TBS/signature bytes directly.
|
|
pub fn validate_signed_object_ee_cert_path_fast(
|
|
ee: &ResourceEeCertificate,
|
|
issuer_ca: &ResourceCertificate,
|
|
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
|
issuer_crl: &RpkixCrl,
|
|
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
|
issuer_ca_rsync_uri: Option<&str>,
|
|
issuer_crl_rsync_uri: Option<&str>,
|
|
validation_time: time::OffsetDateTime,
|
|
) -> Result<(), CertPathError> {
|
|
validate_ee_cert_path_bindings_and_status(
|
|
&ee.resource_cert,
|
|
issuer_ca,
|
|
issuer_crl,
|
|
issuer_crl_revoked_serials,
|
|
issuer_ca_rsync_uri,
|
|
issuer_crl_rsync_uri,
|
|
validation_time,
|
|
)?;
|
|
validate_ee_key_usage_summary(&ee.key_usage_summary)?;
|
|
verify_ee_cert_signature_fast(&ee.tbs_certificate_der, &ee.signature_bytes, issuer_spki)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_ee_cert_path_components(
|
|
ee: &ResourceCertificate,
|
|
ee_cert_der: &[u8],
|
|
issuer_ca: &ResourceCertificate,
|
|
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
|
issuer_crl: &RpkixCrl,
|
|
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
|
issuer_ca_rsync_uri: Option<&str>,
|
|
issuer_crl_rsync_uri: Option<&str>,
|
|
validation_time: time::OffsetDateTime,
|
|
) -> Result<(), CertPathError> {
|
|
validate_ee_cert_path_bindings_and_status(
|
|
ee,
|
|
issuer_ca,
|
|
issuer_crl,
|
|
issuer_crl_revoked_serials,
|
|
issuer_ca_rsync_uri,
|
|
issuer_crl_rsync_uri,
|
|
validation_time,
|
|
)?;
|
|
|
|
let ee_x509 = parse_x509_cert(ee_cert_der)?;
|
|
verify_ee_cert_signature(&ee_x509, issuer_spki)?;
|
|
validate_ee_key_usage(&ee_x509)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_ee_cert_path_bindings_and_status(
|
|
ee: &ResourceCertificate,
|
|
issuer_ca: &ResourceCertificate,
|
|
issuer_crl: &RpkixCrl,
|
|
issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
|
|
issuer_ca_rsync_uri: Option<&str>,
|
|
issuer_crl_rsync_uri: Option<&str>,
|
|
validation_time: time::OffsetDateTime,
|
|
) -> Result<(), CertPathError> {
|
|
if ee.kind != ResourceCertKind::Ee {
|
|
return Err(CertPathError::EeNotEe);
|
|
}
|
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
|
return Err(CertPathError::IssuerNotCa);
|
|
}
|
|
|
|
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
|
return Err(CertPathError::IssuerSubjectMismatch {
|
|
ee_issuer_dn: ee.tbs.issuer_name.to_string(),
|
|
issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
|
|
});
|
|
}
|
|
|
|
validate_ee_aki_matches_issuer_ski(ee, issuer_ca)?;
|
|
if let Some(expected_issuer_uri) = issuer_ca_rsync_uri {
|
|
validate_ee_aia_points_to_issuer_uri(ee, expected_issuer_uri)?;
|
|
}
|
|
if let Some(expected_crl_uri) = issuer_crl_rsync_uri {
|
|
validate_ee_crldp_contains_issuer_crl_uri(ee, expected_crl_uri)?;
|
|
}
|
|
|
|
if !time_within_validity(
|
|
validation_time,
|
|
ee.tbs.validity_not_before,
|
|
ee.tbs.validity_not_after,
|
|
) || !time_within_validity(
|
|
validation_time,
|
|
issuer_ca.tbs.validity_not_before,
|
|
issuer_ca.tbs.validity_not_after,
|
|
) {
|
|
return Err(CertPathError::CertificateNotValidAtTime);
|
|
}
|
|
|
|
if !crl_valid_at_time(issuer_crl, validation_time) {
|
|
return Err(CertPathError::CrlNotValidAtTime);
|
|
}
|
|
|
|
let serial = BigUnsigned::from_biguint(&ee.tbs.serial_number);
|
|
if issuer_crl_revoked_serials.contains(&serial.bytes_be) {
|
|
return Err(CertPathError::EeRevoked);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_subject_pki_from_der(der: &[u8]) -> Result<SubjectPublicKeyInfo<'_>, CertPathError> {
|
|
let (rem, spki) = SubjectPublicKeyInfo::from_der(der)
|
|
.map_err(|e| CertPathError::IssuerSpkiParse(e.to_string()))?;
|
|
if !rem.is_empty() {
|
|
return Err(CertPathError::IssuerSpkiTrailingBytes(rem.len()));
|
|
}
|
|
Ok(spki)
|
|
}
|
|
|
|
fn parse_x509_cert(der: &[u8]) -> Result<X509Certificate<'_>, CertPathError> {
|
|
let (rem, cert) = X509Certificate::from_der(der)
|
|
.map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))?;
|
|
if !rem.is_empty() {
|
|
return Err(CertPathError::EeSignatureInvalid(
|
|
"trailing bytes after EE certificate".to_string(),
|
|
));
|
|
}
|
|
Ok(cert)
|
|
}
|
|
|
|
fn verify_ee_cert_signature(
|
|
ee: &X509Certificate<'_>,
|
|
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
|
) -> Result<(), CertPathError> {
|
|
ee.verify_signature(Some(issuer_spki))
|
|
.map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))
|
|
}
|
|
|
|
fn verify_ee_cert_signature_fast(
|
|
ee_tbs_der: &[u8],
|
|
ee_signature_bytes: &[u8],
|
|
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
|
) -> Result<(), CertPathError> {
|
|
let key = signature::UnparsedPublicKey::new(
|
|
&signature::RSA_PKCS1_2048_8192_SHA256,
|
|
&issuer_spki.subject_public_key.data,
|
|
);
|
|
key.verify(ee_tbs_der, ee_signature_bytes)
|
|
.map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))
|
|
}
|
|
|
|
fn validate_ee_aki_matches_issuer_ski(
|
|
ee: &ResourceCertificate,
|
|
issuer_ca: &ResourceCertificate,
|
|
) -> Result<(), CertPathError> {
|
|
let Some(issuer_ski) = issuer_ca.tbs.extensions.subject_key_identifier.as_deref() else {
|
|
return Err(CertPathError::IssuerSkiMissing);
|
|
};
|
|
let Some(ee_aki) = ee.tbs.extensions.authority_key_identifier.as_deref() else {
|
|
return Err(CertPathError::EeAkiMissing);
|
|
};
|
|
if ee_aki != issuer_ski {
|
|
return Err(CertPathError::EeAkiMismatch);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_ee_aia_points_to_issuer_uri(
|
|
ee: &ResourceCertificate,
|
|
issuer_ca_rsync_uri: &str,
|
|
) -> Result<(), CertPathError> {
|
|
let Some(uris) = ee.tbs.extensions.ca_issuers_uris.as_ref() else {
|
|
return Err(CertPathError::EeAiaMissing);
|
|
};
|
|
if uris.iter().any(|u| u.as_str() == issuer_ca_rsync_uri) {
|
|
return Ok(());
|
|
}
|
|
if uris.iter().any(|u| u.starts_with("rsync://")) {
|
|
return Ok(());
|
|
}
|
|
Err(CertPathError::EeAiaIssuerUriMismatch)
|
|
}
|
|
|
|
fn validate_ee_crldp_contains_issuer_crl_uri(
|
|
ee: &ResourceCertificate,
|
|
issuer_crl_rsync_uri: &str,
|
|
) -> Result<(), CertPathError> {
|
|
let Some(uris) = ee.tbs.extensions.crl_distribution_points_uris.as_ref() else {
|
|
return Err(CertPathError::EeCrlDpMissing);
|
|
};
|
|
if !uris.iter().any(|u| u.as_str() == issuer_crl_rsync_uri) {
|
|
return Err(CertPathError::EeCrlDpUriMismatch);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_ee_key_usage(cert: &X509Certificate<'_>) -> Result<(), CertPathError> {
|
|
let mut ku_critical: Option<bool> = None;
|
|
for ext in cert.extensions() {
|
|
if ext.oid.as_bytes() == crate::data_model::oid::OID_KEY_USAGE_RAW {
|
|
ku_critical = Some(ext.critical);
|
|
break;
|
|
}
|
|
}
|
|
|
|
let Some(critical) = ku_critical else {
|
|
return Err(CertPathError::KeyUsageMissing);
|
|
};
|
|
if !critical {
|
|
return Err(CertPathError::KeyUsageNotCritical);
|
|
}
|
|
|
|
let Some(ku) = cert
|
|
.key_usage()
|
|
.map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))?
|
|
else {
|
|
return Err(CertPathError::KeyUsageMissing);
|
|
};
|
|
|
|
let v = &ku.value;
|
|
let ok = v.digital_signature()
|
|
&& !v.key_cert_sign()
|
|
&& !v.crl_sign()
|
|
&& !v.non_repudiation()
|
|
&& !v.key_encipherment()
|
|
&& !v.data_encipherment()
|
|
&& !v.key_agreement()
|
|
&& !v.encipher_only()
|
|
&& !v.decipher_only();
|
|
if !ok {
|
|
return Err(CertPathError::KeyUsageInvalidBits);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_ee_key_usage_summary(summary: &EeKeyUsageSummary) -> Result<(), CertPathError> {
|
|
match summary {
|
|
EeKeyUsageSummary::DigitalSignatureOnly => Ok(()),
|
|
EeKeyUsageSummary::Missing => Err(CertPathError::KeyUsageMissing),
|
|
EeKeyUsageSummary::NotCritical => Err(CertPathError::KeyUsageNotCritical),
|
|
EeKeyUsageSummary::InvalidBits => Err(CertPathError::KeyUsageInvalidBits),
|
|
EeKeyUsageSummary::ParseError(err) => Err(CertPathError::KeyUsageParse(err.clone())),
|
|
}
|
|
}
|
|
|
|
fn time_within_validity(
|
|
t: time::OffsetDateTime,
|
|
not_before: time::OffsetDateTime,
|
|
not_after: time::OffsetDateTime,
|
|
) -> bool {
|
|
let t = t.to_offset(time::UtcOffset::UTC);
|
|
let not_before = not_before.to_offset(time::UtcOffset::UTC);
|
|
let not_after = not_after.to_offset(time::UtcOffset::UTC);
|
|
t >= not_before && t <= not_after
|
|
}
|
|
|
|
fn crl_valid_at_time(crl: &RpkixCrl, t: time::OffsetDateTime) -> bool {
|
|
let t = t.to_offset(time::UtcOffset::UTC);
|
|
let this_update = crl.this_update.utc.to_offset(time::UtcOffset::UTC);
|
|
let next_update = crl.next_update.utc.to_offset(time::UtcOffset::UTC);
|
|
t >= this_update && t < next_update
|
|
}
|
|
|
|
fn is_serial_revoked_by_crl(ee: &ResourceCertificate, crl: &RpkixCrl) -> bool {
|
|
let serial = BigUnsigned::from_biguint(&ee.tbs.serial_number);
|
|
crl.revoked_certs
|
|
.iter()
|
|
.any(|rc| rc.serial_number == serial)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::data_model::common::X509NameDer;
|
|
use crate::data_model::rc::{
|
|
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
|
};
|
|
use der_parser::num_bigint::BigUint;
|
|
|
|
fn dummy_cert(
|
|
kind: ResourceCertKind,
|
|
subject_dn: &str,
|
|
issuer_dn: &str,
|
|
ski: Option<Vec<u8>>,
|
|
aki: Option<Vec<u8>>,
|
|
aia: Option<Vec<&str>>,
|
|
crldp: Option<Vec<&str>>,
|
|
) -> ResourceCertificate {
|
|
let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
|
let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
|
ResourceCertificate {
|
|
raw_der: Vec::new(),
|
|
kind,
|
|
tbs: RpkixTbsCertificate {
|
|
version: 2,
|
|
serial_number: BigUint::from(1u8),
|
|
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
|
issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
|
|
subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
|
|
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
|
|
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
|
|
subject_public_key_info: Vec::new(),
|
|
extensions: RcExtensions {
|
|
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
|
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,
|
|
ip_resources: None,
|
|
as_resources: None,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ee_aki_and_aia_and_crldp_binding_checks_report_errors() {
|
|
let issuer = dummy_cert(
|
|
ResourceCertKind::Ca,
|
|
"CN=issuer",
|
|
"CN=issuer",
|
|
Some(vec![1]),
|
|
None,
|
|
None,
|
|
None,
|
|
);
|
|
|
|
let ee = dummy_cert(
|
|
ResourceCertKind::Ee,
|
|
"CN=ee",
|
|
"CN=issuer",
|
|
Some(vec![2]),
|
|
Some(vec![9]),
|
|
Some(vec!["rsync://example.test/issuer.cer"]),
|
|
Some(vec!["rsync://example.test/issuer.crl"]),
|
|
);
|
|
let err = validate_ee_aki_matches_issuer_ski(&ee, &issuer).unwrap_err();
|
|
assert!(matches!(err, CertPathError::EeAkiMismatch), "{err}");
|
|
|
|
let issuer_missing_ski = dummy_cert(
|
|
ResourceCertKind::Ca,
|
|
"CN=issuer",
|
|
"CN=issuer",
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
);
|
|
let err = validate_ee_aki_matches_issuer_ski(&ee, &issuer_missing_ski).unwrap_err();
|
|
assert!(matches!(err, CertPathError::IssuerSkiMissing), "{err}");
|
|
|
|
let ee_missing_aki = dummy_cert(
|
|
ResourceCertKind::Ee,
|
|
"CN=ee",
|
|
"CN=issuer",
|
|
Some(vec![2]),
|
|
None,
|
|
Some(vec!["rsync://example.test/issuer.cer"]),
|
|
Some(vec!["rsync://example.test/issuer.crl"]),
|
|
);
|
|
let err = validate_ee_aki_matches_issuer_ski(&ee_missing_aki, &issuer).unwrap_err();
|
|
assert!(matches!(err, CertPathError::EeAkiMissing), "{err}");
|
|
|
|
let ee_missing_aia = dummy_cert(
|
|
ResourceCertKind::Ee,
|
|
"CN=ee",
|
|
"CN=issuer",
|
|
Some(vec![2]),
|
|
Some(vec![1]),
|
|
None,
|
|
Some(vec!["rsync://example.test/issuer.crl"]),
|
|
);
|
|
let err = validate_ee_aia_points_to_issuer_uri(
|
|
&ee_missing_aia,
|
|
"rsync://example.test/issuer.cer",
|
|
)
|
|
.unwrap_err();
|
|
assert!(matches!(err, CertPathError::EeAiaMissing), "{err}");
|
|
|
|
let ee_wrong_aia = dummy_cert(
|
|
ResourceCertKind::Ee,
|
|
"CN=ee",
|
|
"CN=issuer",
|
|
Some(vec![2]),
|
|
Some(vec![1]),
|
|
Some(vec!["rsync://example.test/other.cer"]),
|
|
Some(vec!["rsync://example.test/issuer.crl"]),
|
|
);
|
|
validate_ee_aia_points_to_issuer_uri(&ee_wrong_aia, "rsync://example.test/issuer.cer")
|
|
.expect("non-matching rsync AIA is currently accepted");
|
|
|
|
let ee_missing_crldp = dummy_cert(
|
|
ResourceCertKind::Ee,
|
|
"CN=ee",
|
|
"CN=issuer",
|
|
Some(vec![2]),
|
|
Some(vec![1]),
|
|
Some(vec!["rsync://example.test/issuer.cer"]),
|
|
None,
|
|
);
|
|
let err = validate_ee_crldp_contains_issuer_crl_uri(
|
|
&ee_missing_crldp,
|
|
"rsync://example.test/issuer.crl",
|
|
)
|
|
.unwrap_err();
|
|
assert!(matches!(err, CertPathError::EeCrlDpMissing), "{err}");
|
|
|
|
let ee_wrong_crldp = dummy_cert(
|
|
ResourceCertKind::Ee,
|
|
"CN=ee",
|
|
"CN=issuer",
|
|
Some(vec![2]),
|
|
Some(vec![1]),
|
|
Some(vec!["rsync://example.test/issuer.cer"]),
|
|
Some(vec!["rsync://example.test/other.crl"]),
|
|
);
|
|
let err = validate_ee_crldp_contains_issuer_crl_uri(
|
|
&ee_wrong_crldp,
|
|
"rsync://example.test/issuer.crl",
|
|
)
|
|
.unwrap_err();
|
|
assert!(matches!(err, CertPathError::EeCrlDpUriMismatch), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn ee_binding_checks_accept_when_matching() {
|
|
let issuer = dummy_cert(
|
|
ResourceCertKind::Ca,
|
|
"CN=issuer",
|
|
"CN=issuer",
|
|
Some(vec![1]),
|
|
None,
|
|
None,
|
|
None,
|
|
);
|
|
let ee = dummy_cert(
|
|
ResourceCertKind::Ee,
|
|
"CN=ee",
|
|
"CN=issuer",
|
|
Some(vec![2]),
|
|
Some(vec![1]),
|
|
Some(vec!["rsync://example.test/issuer.cer"]),
|
|
Some(vec!["rsync://example.test/issuer.crl"]),
|
|
);
|
|
validate_ee_aki_matches_issuer_ski(&ee, &issuer).expect("aki ok");
|
|
validate_ee_aia_points_to_issuer_uri(&ee, "rsync://example.test/issuer.cer")
|
|
.expect("aia ok");
|
|
validate_ee_crldp_contains_issuer_crl_uri(&ee, "rsync://example.test/issuer.crl")
|
|
.expect("crldp ok");
|
|
}
|
|
}
|