panda-rpki-oss/src/validation/ca_path/certificate_checks.rs
yuyr 591bed90a8
Some checks failed
ci / rust (push) Has been cancelled
ci / docker (push) Has been cancelled
ci / audit (push) Has been cancelled
Harden first-release validation and dependency hygiene
2026-09-10 09:53:45 +08:00

138 lines
4.3 KiB
Rust

// Certificate profile, signature, CRL, and validity checks.
fn parse_subject_pki_from_der(der: &[u8]) -> Result<SubjectPublicKeyInfo<'_>, CaPathError> {
let (rem, spki) = SubjectPublicKeyInfo::from_der(der)
.map_err(|e| CaPathError::IssuerSpkiParse(e.to_string()))?;
if !rem.is_empty() {
return Err(CaPathError::IssuerSpkiTrailingBytes(rem.len()));
}
Ok(spki)
}
fn parse_x509_cert(der: &[u8]) -> Result<X509Certificate<'_>, CaPathError> {
let (rem, cert) = X509Certificate::from_der(der)
.map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))?;
if !rem.is_empty() {
return Err(CaPathError::ChildSignatureInvalid(
"trailing bytes after child certificate".to_string(),
));
}
Ok(cert)
}
fn verify_child_signature(
child: &X509Certificate<'_>,
issuer_spki: &SubjectPublicKeyInfo<'_>,
) -> Result<(), CaPathError> {
child
.verify_signature(Some(issuer_spki))
.map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))
}
fn validate_child_aki_matches_issuer_ski(
child: &ResourceCertificate,
issuer: &ResourceCertificate,
) -> Result<(), CaPathError> {
let Some(issuer_ski) = issuer.tbs.extensions.subject_key_identifier.as_deref() else {
return Err(CaPathError::IssuerSkiMissing);
};
let Some(child_aki) = child.tbs.extensions.authority_key_identifier.as_deref() else {
return Err(CaPathError::ChildAkiMissing);
};
if child_aki != issuer_ski {
return Err(CaPathError::ChildAkiMismatch);
}
Ok(())
}
fn validate_child_aia_points_to_issuer_uri(
child: &ResourceCertificate,
issuer_ca_rsync_uri: &str,
) -> Result<(), CaPathError> {
let Some(uris) = child.tbs.extensions.ca_issuers_uris.as_ref() else {
return Err(CaPathError::ChildAiaMissing);
};
if !uris.iter().any(|u| u.as_str() == issuer_ca_rsync_uri) {
return Err(CaPathError::ChildAiaIssuerUriMismatch);
}
Ok(())
}
fn validate_child_crldp_contains_issuer_crl_uri(
child: &ResourceCertificate,
issuer_crl_rsync_uri: &str,
) -> Result<(), CaPathError> {
let Some(uris) = child.tbs.extensions.crl_distribution_points_uris.as_ref() else {
return Err(CaPathError::ChildCrlDpMissing);
};
if !uris.iter().any(|u| u.as_str() == issuer_crl_rsync_uri) {
return Err(CaPathError::ChildCrlDpUriMismatch);
}
Ok(())
}
fn validate_child_ca_key_usage(cert: &X509Certificate<'_>) -> Result<(), CaPathError> {
let mut ku_critical: Option<bool> = None;
for ext in cert.extensions() {
if ext.oid.as_bytes() == OID_KEY_USAGE_RAW {
ku_critical = Some(ext.critical);
break;
}
}
let Some(critical) = ku_critical else {
return Err(CaPathError::KeyUsageMissing);
};
if !critical {
return Err(CaPathError::KeyUsageNotCritical);
}
let Some(ku) = cert
.key_usage()
.map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))?
else {
return Err(CaPathError::KeyUsageMissing);
};
let v = &ku.value;
let ok = v.key_cert_sign()
&& v.crl_sign()
&& !v.digital_signature()
&& !v.non_repudiation()
&& !v.key_encipherment()
&& !v.data_encipherment()
&& !v.key_agreement()
&& !v.encipher_only()
&& !v.decipher_only();
if !ok {
return Err(CaPathError::KeyUsageInvalidBits);
}
Ok(())
}
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(cert: &ResourceCertificate, crl: &RpkixCrl) -> bool {
let serial = BigUnsigned::from_biguint(&cert.tbs.serial_number);
crl.revoked_certs
.iter()
.any(|rc| rc.serial_number == serial)
}