use async_generic::async_generic;
use chrono::{DateTime, Utc};
use coset::{cbor::value::Value, CoseSign1, Label};
use crate::{
context::Context,
crypto::{
asn1::rfc3161::TstInfo,
cose::{
cert_chain_from_sign1, check_end_entity_certificate_profile, validate_cose_tst_info,
validate_cose_tst_info_async, CertificateTrustError, CertificateTrustPolicy, CoseError,
},
ocsp::OcspResponse,
},
log_item,
settings::Settings,
status_tracker::StatusTracker,
validation_status::{
self, SIGNING_CREDENTIAL_NOT_REVOKED, SIGNING_CREDENTIAL_OCSP_INACCESSIBLE,
SIGNING_CREDENTIAL_REVOKED,
},
};
const OCSP_OID_STR: &str = "1.3.6.1.5.5.7.3.9";
#[async_generic(async_signature(
sign1: &CoseSign1,
data: &[u8],
fetch_policy: OcspFetchPolicy,
ctp: &CertificateTrustPolicy,
ocsp_responses: Option<&Vec<Vec<u8>>>,
tst_info: Option<&TstInfo>,
validation_log: &mut StatusTracker,
context: &Context,
))]
#[allow(clippy::too_many_arguments)]
pub fn check_ocsp_status(
sign1: &CoseSign1,
data: &[u8],
fetch_policy: OcspFetchPolicy,
ctp: &CertificateTrustPolicy,
ocsp_responses: Option<&Vec<Vec<u8>>>,
tst_info: Option<&TstInfo>,
validation_log: &mut StatusTracker,
context: &Context,
) -> Result<OcspResponse, CoseError> {
if context
.settings()
.builder
.certificate_status_should_override
.unwrap_or(false)
{
if let Some(ocsp_response_ders) = ocsp_responses {
if !ocsp_response_ders.is_empty() {
return if _sync {
process_ocsp_responses(
sign1,
data,
ctp,
ocsp_response_ders,
tst_info,
validation_log,
context.settings(),
)
} else {
process_ocsp_responses_async(
sign1,
data,
ctp,
ocsp_response_ders,
tst_info,
validation_log,
context.settings(),
)
.await
};
}
}
}
match get_ocsp_der(sign1) {
Some(ocsp_response_der) => {
let mut ocsp_log = StatusTracker::default();
let result = if _sync {
check_stapled_ocsp_response(
sign1,
&ocsp_response_der,
data,
ctp,
tst_info,
&mut ocsp_log,
context.settings(),
)
} else {
check_stapled_ocsp_response_async(
sign1,
&ocsp_response_der,
data,
ctp,
tst_info,
&mut ocsp_log,
context.settings(),
)
.await
};
if let Ok(ocsp_response) = result {
if ocsp_log.has_status(validation_status::SIGNING_CREDENTIAL_REVOKED) {
log_item!(
"",
format!(
"signing cert revoked: {}",
ocsp_response.certificate_serial_num
),
"check_ocsp_status"
)
.validation_status(SIGNING_CREDENTIAL_REVOKED)
.informational(validation_log);
return Err(CoseError::CertificateTrustError(
CertificateTrustError::CertificateNotTrusted,
));
}
if ocsp_log.has_status(validation_status::SIGNING_CREDENTIAL_NOT_REVOKED) {
log_item!(
"",
format!(
"signing cert not revoked: {}",
ocsp_response.certificate_serial_num
),
"check_ocsp_status"
)
.validation_status(SIGNING_CREDENTIAL_NOT_REVOKED)
.informational(validation_log);
return Ok(ocsp_response);
}
}
Ok(OcspResponse::default())
}
None => match fetch_policy {
OcspFetchPolicy::FetchAllowed => {
if _sync {
fetch_and_check_ocsp_response(
sign1,
data,
ctp,
tst_info,
validation_log,
context,
)
} else {
fetch_and_check_ocsp_response_async(
sign1,
data,
ctp,
tst_info,
validation_log,
context,
)
.await
}
}
OcspFetchPolicy::DoNotFetch => {
if let Some(ocsp_response_ders) = ocsp_responses {
if !ocsp_response_ders.is_empty() {
if _sync {
process_ocsp_responses(
sign1,
data,
ctp,
ocsp_response_ders,
tst_info,
validation_log,
context.settings(),
)
} else {
process_ocsp_responses_async(
sign1,
data,
ctp,
ocsp_response_ders,
tst_info,
validation_log,
context.settings(),
)
.await
}
} else {
Ok(OcspResponse::default())
}
} else {
Ok(OcspResponse::default())
}
}
},
}
}
#[async_generic]
fn process_ocsp_responses(
sign1: &CoseSign1,
data: &[u8],
ctp: &CertificateTrustPolicy,
ocsp_response_ders: &[Vec<u8>],
tst_info: Option<&TstInfo>,
validation_log: &mut StatusTracker,
settings: &Settings,
) -> Result<OcspResponse, CoseError> {
for ocsp_response_der in ocsp_response_ders {
let mut current_validation_log = StatusTracker::default();
if let Ok(ocsp_response) = if _sync {
check_stapled_ocsp_response(
sign1,
ocsp_response_der,
data,
ctp,
tst_info,
&mut current_validation_log,
settings,
)
} else {
check_stapled_ocsp_response_async(
sign1,
ocsp_response_der,
data,
ctp,
tst_info,
&mut current_validation_log,
settings,
)
.await
} {
if current_validation_log.has_status(validation_status::SIGNING_CREDENTIAL_REVOKED) {
log_item!(
"",
format!(
"signing cert revoked: {}",
ocsp_response.certificate_serial_num
),
"check_ocsp_status"
)
.validation_status(SIGNING_CREDENTIAL_REVOKED)
.informational(validation_log);
return Err(CoseError::CertificateTrustError(
CertificateTrustError::CertificateNotTrusted,
));
}
if current_validation_log.has_status(validation_status::SIGNING_CREDENTIAL_NOT_REVOKED)
{
log_item!(
"",
format!(
"signing cert not revoked: {}",
ocsp_response.certificate_serial_num
),
"check_ocsp_status"
)
.validation_status(SIGNING_CREDENTIAL_NOT_REVOKED)
.informational(validation_log);
return Ok(ocsp_response);
}
}
}
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,
settings: &Settings,
) -> 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,
settings.verify.verify_timestamp_trust,
)
} else {
validate_cose_tst_info_async(
sign1,
data,
ctp,
&mut local_log_sync,
settings.verify.verify_timestamp_trust,
)
.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 mut current_validation_log = StatusTracker::default();
let Ok(ocsp_data) = OcspResponse::from_der_checked(
ocsp_response_der,
signing_time,
&mut current_validation_log,
) else {
return Ok(OcspResponse::default());
};
if let Some(ocsp_certs) = &ocsp_data.ocsp_certs {
let mut new_ctp = ctp.clone();
new_ctp.clear_ekus();
new_ctp.add_valid_ekus(OCSP_OID_STR.as_bytes()); if check_end_entity_certificate_profile(
&ocsp_certs[0],
&new_ctp,
validation_log,
tst_info.as_ref(),
)
.is_err()
{
return Ok(OcspResponse::default());
}
if new_ctp
.check_certificate_trust(
ocsp_certs,
&ocsp_certs[0],
signing_time.map(|t| t.timestamp()),
)
.is_err()
{
return Ok(OcspResponse::default());
}
} else {
return Ok(OcspResponse::default());
}
validation_log.append(¤t_validation_log);
Ok(ocsp_data)
}
#[async_generic(async_signature(
sign1: &CoseSign1,
data: &[u8],
ctp: &CertificateTrustPolicy,
tst_info: Option<&TstInfo>,
validation_log: &mut StatusTracker,
context: &crate::context::Context,
))]
pub(crate) fn fetch_and_check_ocsp_response(
sign1: &CoseSign1,
data: &[u8],
ctp: &CertificateTrustPolicy,
tst_info: Option<&TstInfo>,
validation_log: &mut StatusTracker,
context: &crate::context::Context,
) -> Result<OcspResponse, CoseError> {
let certs = cert_chain_from_sign1(sign1)?;
let ocsp_der = if _sync {
crate::crypto::ocsp::fetch_ocsp_response(&certs, context)
} else {
crate::crypto::ocsp::fetch_ocsp_response_async(&certs, context).await
};
let Some(ocsp_response_der) = ocsp_der else {
log_item!(
"",
"signing cert not fetched".to_string(),
"fetch_and_check_ocsp_response"
)
.validation_status(SIGNING_CREDENTIAL_OCSP_INACCESSIBLE)
.informational(validation_log);
return Ok(OcspResponse::default());
};
let signing_time: Option<DateTime<Utc>> = match tst_info {
Some(tst_info) => Some(tst_info.gen_time.clone().into()),
None => validate_cose_tst_info(
sign1,
data,
ctp,
validation_log,
context.settings().verify.verify_timestamp_trust,
)
.ok()
.map(|tst_info| tst_info.gen_time.clone().into()),
};
let ocsp_data =
match OcspResponse::from_der_checked(&ocsp_response_der, signing_time, validation_log) {
Ok(data) => data,
Err(_) => return Ok(OcspResponse::default()),
};
if let Some(ocsp_certs) = &ocsp_data.ocsp_certs {
let mut new_ctp = ctp.clone();
new_ctp.clear_ekus();
new_ctp.add_valid_ekus(OCSP_OID_STR.as_bytes());
if check_end_entity_certificate_profile(&ocsp_certs[0], &new_ctp, validation_log, None)
.is_err()
{
return Ok(OcspResponse::default());
}
} else {
return Ok(OcspResponse::default());
}
Ok(ocsp_data)
}
pub 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
}
})
}