use async_generic::async_generic;
#[cfg(not(target_arch = "wasm32"))]
use chrono::{DateTime, Utc};
use ciborium::value::Value;
use coset::{CoseSign1, Label};
use crate::{
crypto::{
asn1::rfc3161::TstInfo,
cose::{
check_end_entity_certificate_profile, validate_cose_tst_info,
validate_cose_tst_info_async, CertificateTrustPolicy, CoseError,
},
ocsp::OcspResponse,
},
status_tracker::StatusTracker,
};
#[async_generic]
pub fn check_ocsp_status(
sign1: &CoseSign1,
data: &[u8],
fetch_policy: OcspFetchPolicy,
ctp: &CertificateTrustPolicy,
tst_info: Option<&TstInfo>,
validation_log: &mut 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,
tst_info,
validation_log,
)
} else {
check_stapled_ocsp_response_async(
sign1,
&ocsp_response_der,
data,
ctp,
tst_info,
validation_log,
)
.await
}
}
None => match fetch_policy {
OcspFetchPolicy::FetchAllowed => {
fetch_and_check_ocsp_response(sign1, data, ctp, tst_info, 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,
tst_info: Option<&TstInfo>,
validation_log: &mut StatusTracker,
) -> Result<OcspResponse, CoseError> {
let mut local_log_sync = StatusTracker::default();
let time_stamp_info = match tst_info {
Some(tst_info) => Ok(tst_info.clone()),
None => {
if _sync {
validate_cose_tst_info(sign1, data, ctp, &mut local_log_sync)
} else {
validate_cose_tst_info_async(sign1, data, ctp, &mut local_log_sync).await
}
}
};
let (tst_info, signing_time) = match time_stamp_info {
Ok(tstinfo) => {
let signing_time = tstinfo.gen_time.clone().into();
(Some(tstinfo), Some(signing_time))
}
Err(_) => (None, None),
};
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_end_entity_certificate_profile(
&ocsp_certs[0],
ctp,
validation_log,
tst_info.as_ref(),
)?;
}
}
Ok(ocsp_data)
}
fn fetch_and_check_ocsp_response(
sign1: &CoseSign1,
data: &[u8],
ctp: &CertificateTrustPolicy,
_tst_info: Option<&TstInfo>,
validation_log: &mut StatusTracker,
) -> Result<OcspResponse, CoseError> {
#[cfg(target_arch = "wasm32")]
{
let _ = (sign1, data, ctp, validation_log);
Ok(OcspResponse::default())
}
#[cfg(not(target_arch = "wasm32"))]
{
use crate::crypto::cose::cert_chain_from_sign1;
let certs = cert_chain_from_sign1(sign1)?;
let Some(ocsp_der) = crate::crypto::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, ctp, validation_log)
.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_end_entity_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
}
})
}