use async_generic::async_generic;
use c2pa_status_tracker::StatusTracker;
use chrono::{DateTime, Utc};
use ciborium::value::Value;
use coset::{CoseSign1, Label};
use crate::{
cose::{
check_certificate_profile, validate_cose_tst_info, validate_cose_tst_info_async,
CertificateTrustPolicy, CoseError,
},
ocsp::OcspResponse,
};
#[async_generic]
pub fn check_ocsp_status(
sign1: &CoseSign1,
data: &[u8],
fetch_policy: OcspFetchPolicy,
ctp: &CertificateTrustPolicy,
validation_log: &mut impl StatusTracker,
) -> Result<OcspResponse, CoseError> {
match get_ocsp_der(sign1) {
Some(ocsp_response_der) => {
if _sync {
check_stapled_ocsp_response(sign1, &ocsp_response_der, data, ctp, validation_log)
} else {
check_stapled_ocsp_response_async(
sign1,
&ocsp_response_der,
data,
ctp,
validation_log,
)
.await
}
}
None => match fetch_policy {
OcspFetchPolicy::FetchAllowed => {
fetch_and_check_ocsp_response(sign1, data, ctp, validation_log)
}
OcspFetchPolicy::DoNotFetch => Ok(OcspResponse::default()),
},
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OcspFetchPolicy {
FetchAllowed,
DoNotFetch,
}
#[async_generic]
fn check_stapled_ocsp_response(
sign1: &CoseSign1,
ocsp_response_der: &[u8],
data: &[u8],
ctp: &CertificateTrustPolicy,
validation_log: &mut impl StatusTracker,
) -> Result<OcspResponse, CoseError> {
let time_stamp_info = if _sync {
validate_cose_tst_info(sign1, data)
} else {
validate_cose_tst_info_async(sign1, data).await
};
let Ok(tst_info) = &time_stamp_info else {
return Ok(OcspResponse::default());
};
let signing_time: DateTime<Utc> = tst_info.gen_time.clone().into();
let Ok(ocsp_data) =
OcspResponse::from_der_checked(ocsp_response_der, Some(signing_time), validation_log)
else {
return Ok(OcspResponse::default());
};
if ocsp_data.revoked_at.is_none() {
if let Some(ocsp_certs) = &ocsp_data.ocsp_certs {
check_certificate_profile(&ocsp_certs[0], ctp, validation_log, Some(tst_info))?;
}
}
Ok(ocsp_data)
}
fn fetch_and_check_ocsp_response(
sign1: &CoseSign1,
data: &[u8],
ctp: &CertificateTrustPolicy,
validation_log: &mut impl StatusTracker,
) -> Result<OcspResponse, CoseError> {
#[cfg(target_arch = "wasm32")]
{
let _ = (sign1, data, ctp, validation_log);
Ok(OcspResponse::default())
}
#[cfg(not(target_arch = "wasm32"))]
{
use crate::cose::cert_chain_from_sign1;
let certs = cert_chain_from_sign1(sign1)?;
let Some(ocsp_der) = crate::ocsp::fetch_ocsp_response(&certs) else {
return Ok(OcspResponse::default());
};
let ocsp_response_der = ocsp_der;
let signing_time: Option<DateTime<Utc>> = validate_cose_tst_info(sign1, data)
.ok()
.map(|tst_info| tst_info.gen_time.clone().into());
let Ok(ocsp_data) =
OcspResponse::from_der_checked(&ocsp_response_der, signing_time, validation_log)
else {
return Ok(OcspResponse::default());
};
if ocsp_data.revoked_at.is_none() {
if let Some(ocsp_certs) = &ocsp_data.ocsp_certs {
check_certificate_profile(&ocsp_certs[0], ctp, validation_log, None)?;
}
}
Ok(ocsp_data)
}
}
fn get_ocsp_der(sign1: &coset::CoseSign1) -> Option<Vec<u8>> {
let der = sign1
.unprotected
.rest
.iter()
.find_map(|x: &(Label, Value)| {
if x.0 == Label::Text("rVals".to_string()) {
Some(x.1.clone())
} else {
None
}
})?;
let Value::Map(rvals_map) = der else {
return None;
};
rvals_map.iter().find_map(|x: &(Value, Value)| {
if x.0 == Value::Text("ocspVals".to_string()) {
x.1.as_array()
.and_then(|ocsp_rsp_val| ocsp_rsp_val.first())
.and_then(Value::as_bytes)
.cloned()
} else {
None
}
})
}