use asn1_rs::{Any, Class, FromDer, Header, Tag};
use c2pa_raw_crypto::{
oids::*, validator_for_sig_and_hash_algs, RawSignatureValidationError, SigningAlg,
};
use web_time::{SystemTime, UNIX_EPOCH};
use x509_parser::{
certificate::{TbsCertificate, X509Certificate},
x509::AlgorithmIdentifier,
};
use crate::crypto::cose::{CertificateTrustError, CertificateTrustPolicy, TrustAnchorType};
fn raw_crypto_oid_to_bcder_oid(oid: &c2pa_raw_crypto::Oid) -> bcder::Oid {
bcder::Oid(bytes::Bytes::copy_from_slice(oid.as_bytes()))
}
pub(crate) fn check_certificate_trust(
ctp: &CertificateTrustPolicy,
anchor_type: Option<TrustAnchorType>,
chain_der: &[Vec<u8>],
cert_der: &[u8],
signing_time_epoch: Option<i64>,
) -> Result<(TrustAnchorType, String), CertificateTrustError> {
if ctp.anchor_sets_of_type(anchor_type).count() == 0 {
return Err(CertificateTrustError::CertificateNotTrusted);
}
let Ok((_rem, cert)) = X509Certificate::from_der(cert_der) else {
return Err(CertificateTrustError::InvalidCertificate);
};
let Ok(Some(eku)) = cert.extended_key_usage() else {
return Err(CertificateTrustError::InvalidEku);
};
let Some(_approved_oid) = ctp.has_allowed_eku(eku.value) else {
return Err(CertificateTrustError::InvalidEku);
};
let full_chain = if !chain_der.is_empty() && cert_der == chain_der[0] {
chain_der.to_vec()
} else {
let mut full_chain: Vec<Vec<u8>> = Vec::new();
full_chain.push(cert_der.to_vec());
let mut in_chain = chain_der.to_vec();
full_chain.append(&mut in_chain);
full_chain
};
check_chain_order_and_ca_authorization(&full_chain)?;
let check_time = match signing_time_epoch {
Some(t) => t,
None => SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| CertificateTrustError::InternalError("system time invalid".to_string()))?
.as_secs() as i64,
};
let check_time = x509_parser::time::ASN1Time::from_timestamp(check_time)
.map_err(|_| CertificateTrustError::CertificateNotTrusted)?;
let mut ca_outside_validity = false;
for (depth, cert) in full_chain.iter().enumerate() {
let (_, chain_cert) = X509Certificate::from_der(cert)
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?;
if !chain_cert.validity().is_valid_at(check_time) {
if depth == 0 {
return Err(CertificateTrustError::CertificateNotTrusted);
}
ca_outside_validity = true;
}
}
for anchor_set in ctp.anchor_sets_of_type(anchor_type) {
let anchors: Vec<(X509Certificate, &Vec<u8>)> = anchor_set
.trust_anchor_ders
.iter()
.filter_map(|anchor_der| {
let (_, cert) = X509Certificate::from_der(anchor_der)
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)
.ok()?;
Some((cert, anchor_der))
})
.collect();
for cert in full_chain.iter().rev() {
let (_, chain_cert) = X509Certificate::from_der(cert)
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?;
for (anchor_cert, anchor_der) in &anchors {
if chain_cert.issuer() == anchor_cert.subject() {
let data = chain_cert.tbs_certificate.as_ref();
let sig = chain_cert.signature_value.as_ref();
let sig_alg = cert_signing_alg(&chain_cert);
let result = verify_data(anchor_der, sig_alg, sig, data);
match result {
Ok(b) => {
if b {
if ca_outside_validity {
return Err(
CertificateTrustError::CaCertificateOutsideValidity,
);
}
return Ok((
anchor_set.trust_anchor_type,
anchor_set.trust_anchor_uri.clone(),
));
}
}
Err(_) => continue,
}
}
}
}
}
Err(CertificateTrustError::CertificateNotTrusted)
}
fn check_chain_order_and_ca_authorization(certs: &[Vec<u8>]) -> Result<(), CertificateTrustError> {
let chain_length = certs.len();
if chain_length < 2 {
return Ok(());
}
for i in 1..chain_length {
let (_, current_cert) = X509Certificate::from_der(&certs[i - 1])
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?;
let issuer_der = &certs[i];
let (_, issuer_cert) = X509Certificate::from_der(issuer_der)
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?;
let data = current_cert.tbs_certificate.as_ref();
let sig = current_cert.signature_value.as_ref();
let sig_alg = cert_signing_alg(¤t_cert);
if !verify_data(issuer_der, sig_alg, sig, data)? {
return Err(CertificateTrustError::CertificateNotTrusted);
}
let certs_below = (i - 1) as u32;
check_ca_authorization(&issuer_cert.tbs_certificate, certs_below)?;
}
Ok(())
}
fn check_ca_authorization(
issuer_tbscert: &TbsCertificate,
certs_below: u32,
) -> Result<(), CertificateTrustError> {
let basic_constraints = issuer_tbscert
.basic_constraints()
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?
.ok_or(CertificateTrustError::CertificateNotTrusted)?;
if !basic_constraints.value.ca {
return Err(CertificateTrustError::CertificateNotTrusted);
}
if let Some(path_len) = basic_constraints.value.path_len_constraint {
if certs_below > path_len {
return Err(CertificateTrustError::CertificateNotTrusted);
}
}
if let Some(key_usage) = issuer_tbscert
.key_usage()
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?
{
if !key_usage.value.key_cert_sign() {
return Err(CertificateTrustError::CertificateNotTrusted);
}
}
Ok(())
}
fn signing_alg_to_sig_and_hash_oid(alg: &str) -> Option<(bcder::Oid, bcder::Oid)> {
let pair = |sig: &c2pa_raw_crypto::Oid, hash: &c2pa_raw_crypto::Oid| {
Some((
raw_crypto_oid_to_bcder_oid(sig),
raw_crypto_oid_to_bcder_oid(hash),
))
};
match alg {
"rsa256" => pair(&RSA_OID, &SHA256_OID),
"rsa384" => pair(&RSA_OID, &SHA384_OID),
"rsa512" => pair(&RSA_OID, &SHA512_OID),
"es256" => pair(&EC_PUBLICKEY_OID, &SHA256_OID),
"es384" => pair(&EC_PUBLICKEY_OID, &SHA384_OID),
"es512" => pair(&EC_PUBLICKEY_OID, &SHA512_OID),
"ps256" => pair(&RSA_PSS_OID, &SHA256_OID),
"ps384" => pair(&RSA_PSS_OID, &SHA384_OID),
"ps512" => pair(&RSA_PSS_OID, &SHA512_OID),
"ed25519" => pair(&ED25519_OID, &SHA512_OID),
_ => None,
}
}
fn cert_signing_alg(cert: &X509Certificate) -> Option<String> {
let cert_alg = cert.signature_algorithm.algorithm.as_bytes();
if cert_alg == SHA256_WITH_RSAENCRYPTION_OID.as_bytes() {
Some("rsa256".to_string())
} else if cert_alg == SHA384_WITH_RSAENCRYPTION_OID.as_bytes() {
Some("rsa384".to_string())
} else if cert_alg == SHA512_WITH_RSAENCRYPTION_OID.as_bytes() {
Some("rsa512".to_string())
} else if cert_alg == ECDSA_WITH_SHA256_OID.as_bytes() {
Some(SigningAlg::Es256.to_string())
} else if cert_alg == ECDSA_WITH_SHA384_OID.as_bytes() {
Some(SigningAlg::Es384.to_string())
} else if cert_alg == ECDSA_WITH_SHA512_OID.as_bytes() {
Some(SigningAlg::Es512.to_string())
} else if cert_alg == RSA_PSS_OID.as_bytes() {
signing_alg_from_rsapss_alg(&cert.signature_algorithm)
} else if cert_alg == ED25519_OID.as_bytes() {
Some(SigningAlg::Ed25519.to_string())
} else {
None
}
}
fn signing_alg_from_rsapss_alg(alg: &AlgorithmIdentifier) -> Option<String> {
let Some(parameters) = &alg.parameters else {
return None;
};
let Ok(seq) = parameters.as_sequence() else {
return None;
};
let Ok((_i, (ha_alg, mgf_ai))) = seq.parse(|i| {
let (i, h) = <Header as asn1_rs::FromDer>::from_der(i)?;
if h.class() != Class::ContextSpecific || h.tag() != Tag(0) {
return Err(nom::Err::Error(asn1_rs::Error::BerValueError));
}
let (i, ha_alg) = AlgorithmIdentifier::from_der(i)
.map_err(|_| nom::Err::Error(asn1_rs::Error::BerValueError))?;
let (i, h) = <Header as asn1_rs::FromDer>::from_der(i)?;
if h.class() != Class::ContextSpecific || h.tag() != Tag(1) {
return Err(nom::Err::Error(asn1_rs::Error::BerValueError));
}
let (i, mgf_ai) = AlgorithmIdentifier::from_der(i)
.map_err(|_| nom::Err::Error(asn1_rs::Error::BerValueError))?;
Ok((i, (ha_alg, mgf_ai)))
}) else {
return None;
};
let mgf_ai_parameters = mgf_ai.parameters?;
let Ok(mgf_ai_parameters) = mgf_ai_parameters.as_sequence() else {
return None;
};
let Ok((_i, mgf_ai_params_algorithm)) =
<Any as asn1_rs::FromDer>::from_der(&mgf_ai_parameters.content)
else {
return None;
};
let Ok(mgf_ai_params_algorithm) = mgf_ai_params_algorithm.as_oid() else {
return None;
};
if ha_alg.algorithm.to_id_string() != mgf_ai_params_algorithm.to_id_string() {
return None;
}
let ha_alg_bytes = ha_alg.algorithm.as_bytes();
if ha_alg_bytes == SHA256_OID.as_bytes() {
Some("ps256".to_string())
} else if ha_alg_bytes == SHA384_OID.as_bytes() {
Some("ps384".to_string())
} else if ha_alg_bytes == SHA512_OID.as_bytes() {
Some("ps512".to_string())
} else {
None
}
}
fn verify_data(
cert_der: &[u8],
sig_alg: Option<String>,
sig: &[u8],
data: &[u8],
) -> Result<bool, CertificateTrustError> {
let (_, cert) = X509Certificate::from_der(cert_der)
.map_err(|_e| CertificateTrustError::InvalidCertificate)?;
let certificate_public_key = cert.public_key();
let Some(cert_alg_string) = sig_alg else {
return Err(CertificateTrustError::InvalidCertificate);
};
let (sig_alg, hash_alg) = signing_alg_to_sig_and_hash_oid(&cert_alg_string)
.ok_or(CertificateTrustError::InvalidCertificate)?;
let result = if let Some(validator) = validator_for_sig_and_hash_algs(
&c2pa_raw_crypto::Oid::new(sig_alg.as_ref()),
&c2pa_raw_crypto::Oid::new(hash_alg.as_ref()),
) {
validator.validate(sig, data, certificate_public_key.raw.as_ref())
} else {
return Err(CertificateTrustError::InvalidCertificate);
};
match result {
Ok(()) => Ok(true),
Err(RawSignatureValidationError::SignatureMismatch) => Ok(false),
Err(_err) => Err(CertificateTrustError::InvalidCertificate),
}
}