use chrono::{DateTime, Utc};
use ed25519_dalek::{Signature, VerifyingKey};
use x509_parser::oid_registry::OID_SIG_ED25519;
use x509_parser::prelude::{FromDer, X509Certificate};
use crate::error::{TrustError, TrustResult};
pub fn verify_certificate_chain(cert_der: &[u8], issuer_public_key: &[u8; 32]) -> TrustResult<()> {
verify_certificate_chain_at(cert_der, issuer_public_key, Utc::now())
}
pub fn verify_certificate_chain_at(
cert_der: &[u8],
issuer_public_key: &[u8; 32],
at: DateTime<Utc>,
) -> TrustResult<()> {
let (_, certificate) =
X509Certificate::from_der(cert_der).map_err(|e| TrustError::CertificateParsing {
reason: format!("failed to parse certificate DER: {e:?}"),
})?;
if certificate.subject().as_raw() == certificate.issuer().as_raw() {
return Err(TrustError::CertificateVerification {
reason: "self-signed certificate rejected (issuer equals subject)".to_string(),
});
}
if certificate.signature_algorithm.algorithm != OID_SIG_ED25519 {
return Err(TrustError::CertificateVerification {
reason: "certificate signature algorithm is not Ed25519".to_string(),
});
}
let tbs = certificate.tbs_certificate.as_ref();
let signature_bytes: &[u8] = &certificate.signature_value.data;
let signature_array: &[u8; 64] =
signature_bytes
.try_into()
.map_err(|_err| TrustError::CertificateVerification {
reason: format!(
"certificate signature must be 64 bytes for Ed25519, got {}",
signature_bytes.len()
),
})?;
let signature = Signature::from_bytes(signature_array);
let verifying_key = VerifyingKey::from_bytes(issuer_public_key).map_err(|_err| {
TrustError::CertificateVerification {
reason: "issuer public key is not a valid Ed25519 point".to_string(),
}
})?;
verifying_key
.verify_strict(tbs, &signature)
.map_err(|_err| TrustError::CertificateVerification {
reason: "certificate signature did not verify against the issuer public key"
.to_string(),
})?;
check_validity_window(&certificate, at)
}
fn check_validity_window(certificate: &X509Certificate<'_>, at: DateTime<Utc>) -> TrustResult<()> {
let validity = certificate.validity();
let not_before = datetime_from_asn1_timestamp(validity.not_before.timestamp(), "notBefore")?;
let not_after = datetime_from_asn1_timestamp(validity.not_after.timestamp(), "notAfter")?;
if at < not_before {
return Err(TrustError::CertificateVerification {
reason: format!(
"certificate not yet valid: notBefore is {not_before}, checked at {at}"
),
});
}
if at > not_after {
return Err(TrustError::CertificateVerification {
reason: format!("certificate expired: notAfter was {not_after}, checked at {at}"),
});
}
Ok(())
}
fn datetime_from_asn1_timestamp(timestamp: i64, field: &str) -> TrustResult<DateTime<Utc>> {
DateTime::<Utc>::from_timestamp(timestamp, 0).ok_or_else(|| TrustError::CertificateParsing {
reason: format!(
"certificate {field} timestamp {timestamp} is outside the representable date range"
),
})
}