use asn1_rs::nom::AsBytes;
use async_generic::async_generic;
use bcder::decode::Constructed;
use coset::{
cbor::value::Value, sig_structure_data, HeaderBuilder, Label, ProtectedHeader, SignatureContext,
};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use crate::{
crypto::{
asn1::rfc3161::{TimeStampResp, TstInfo},
cose::{CertificateTrustPolicy, CoseError, TimeStampStorage},
raw_signature::{AsyncRawSigner, RawSigner},
time_stamp::{
verify_time_stamp, verify_time_stamp_async, ContentInfo, TimeStampError,
TimeStampResponse,
},
},
log_item,
status_tracker::StatusTracker,
validation_status, Result,
};
pub(crate) fn get_cose_tst_info(sign1: &coset::CoseSign1) -> Option<(&Value, TimeStampStorage)> {
sign1
.unprotected
.rest
.iter()
.find_map(|x: &(Label, Value)| {
if x.0 == Label::Text("sigTst2".to_string()) {
Some((&x.1, TimeStampStorage::V2_sigTst2_CTT))
} else if x.0 == Label::Text("sigTst".to_string()) {
Some((&x.1, TimeStampStorage::V1_sigTst))
} else {
None
}
})
}
pub(crate) fn timestamp_token_bytes_from_sign1(sign1: &coset::CoseSign1) -> Option<Vec<u8>> {
let (sigtst, _tss) = get_cose_tst_info(sign1)?;
let mut time_cbor: Vec<u8> = vec![];
coset::cbor::into_writer(sigtst, &mut time_cbor).ok()?;
let tst_container: TstContainer = coset::cbor::from_reader(time_cbor.as_slice()).ok()?;
let token = tst_container.tst_tokens.first()?;
Some(token.val.clone())
}
#[async_generic]
pub(crate) fn validate_cose_tst_info(
sign1: &coset::CoseSign1,
data: &[u8],
ctp: &CertificateTrustPolicy,
validation_log: &mut StatusTracker,
verify_trust: bool,
) -> Result<TstInfo, CoseError> {
let Some((sigtst, tss)) = get_cose_tst_info(sign1) else {
return Err(CoseError::NoTimeStampToken);
};
let mut maybe_sig_data: Vec<u8> = vec![];
let tbs = match tss {
TimeStampStorage::V1_sigTst => data,
TimeStampStorage::V2_sigTst2_CTT => {
let sig_data = ByteBuf::from(sign1.signature.clone());
coset::cbor::into_writer(&sig_data, &mut maybe_sig_data)
.map_err(|e| CoseError::CborParsingError(e.to_string()))?;
maybe_sig_data.as_slice()
}
};
let mut time_cbor: Vec<u8> = vec![];
coset::cbor::into_writer(sigtst, &mut time_cbor)
.map_err(|e| CoseError::InternalError(e.to_string()))?;
let tst_infos = if _sync {
parse_and_validate_sigtst(
&time_cbor,
tbs,
&sign1.protected,
ctp,
validation_log,
verify_trust,
)?
} else {
parse_and_validate_sigtst_async(
&time_cbor,
tbs,
&sign1.protected,
ctp,
validation_log,
verify_trust,
)
.await?
};
let Some(tst_info) = tst_infos.into_iter().next() else {
return Err(CoseError::NoTimeStampToken);
};
Ok(tst_info)
}
#[async_generic]
pub(crate) fn parse_and_validate_sigtst(
sigtst_cbor: &[u8],
data: &[u8],
p_header: &ProtectedHeader,
ctp: &CertificateTrustPolicy,
validation_log: &mut StatusTracker,
verify_trust: bool,
) -> Result<Vec<TstInfo>, CoseError> {
let tst_container: TstContainer = coset::cbor::from_reader(sigtst_cbor)
.map_err(|err| CoseError::CborParsingError(err.to_string()))?;
let mut tstinfos: Vec<TstInfo> = vec![];
if tst_container.tst_tokens.len() > 1 {
log_item!(
"",
"only a single timestamp response is allowed in a manifest",
"parse_and_validate_sigtst"
)
.validation_status(validation_status::TIMESTAMP_MALFORMED)
.informational(validation_log);
return Err(CoseError::NoTimeStampToken);
}
for token in &tst_container.tst_tokens {
let tbs = cose_countersign_data(data, p_header);
let tst_info_res = if _sync {
verify_time_stamp(&token.val, &tbs, ctp, validation_log, verify_trust)
} else {
verify_time_stamp_async(&token.val, &tbs, ctp, validation_log, verify_trust).await
};
if let Ok(tst_info) = tst_info_res {
tstinfos.push(tst_info);
}
}
if tstinfos.is_empty() {
Err(CoseError::NoTimeStampToken)
} else {
Ok(tstinfos)
}
}
pub fn cose_countersign_data(data: &[u8], p_header: &ProtectedHeader) -> Vec<u8> {
let aad: Vec<u8> = vec![];
sig_structure_data(
SignatureContext::CounterSignature,
p_header.clone(),
None,
&aad,
data,
)
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub(crate) struct TstToken {
#[allow(missing_docs)]
#[serde(with = "serde_bytes")]
pub val: Vec<u8>,
}
#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
struct TstContainer {
#[serde(rename = "tstTokens")]
tst_tokens: Vec<TstToken>,
}
impl TstContainer {
pub(crate) fn add_token(&mut self, token: TstToken) {
self.tst_tokens.push(token);
}
}
#[async_generic(
async_signature(
ts_provider: &dyn AsyncRawSigner,
data: &[u8],
p_header: &ProtectedHeader,
mut header_builder: HeaderBuilder,
tss: TimeStampStorage,
))]
pub(crate) fn add_sigtst_header(
ts_provider: &dyn RawSigner,
data: &[u8],
p_header: &ProtectedHeader,
mut header_builder: HeaderBuilder,
tss: TimeStampStorage,
) -> Result<HeaderBuilder, CoseError> {
let sd = cose_countersign_data(data, p_header);
let maybe_cts = if _sync {
ts_provider.send_time_stamp_request(&sd)
} else {
ts_provider.send_time_stamp_request(&sd).await
};
if let Some(cts) = maybe_cts {
let mut cts = cts?;
if tss == TimeStampStorage::V2_sigTst2_CTT {
cts = timestamptoken_from_timestamprsp(&cts).map_err(|err| {
TimeStampError::DecodeError(format!(
"unable to parse time stamp token from timestamp response: {err:?}"
))
})?;
}
let cts = make_cose_timestamp(&cts);
let mut sigtst_vec: Vec<u8> = vec![];
coset::cbor::into_writer(&cts, &mut sigtst_vec)
.map_err(|e| CoseError::CborGenerationError(e.to_string()))?;
let sigtst_cbor: Value = coset::cbor::from_reader(sigtst_vec.as_slice())
.map_err(|e| CoseError::CborGenerationError(e.to_string()))?;
match tss {
TimeStampStorage::V1_sigTst => {
header_builder = header_builder.text_value("sigTst".to_string(), sigtst_cbor);
}
TimeStampStorage::V2_sigTst2_CTT => {
header_builder = header_builder.text_value("sigTst2".to_string(), sigtst_cbor);
}
}
}
Ok(header_builder)
}
fn make_cose_timestamp(ts_data: &[u8]) -> TstContainer {
let token = TstToken {
val: ts_data.to_vec(),
};
let mut container = TstContainer::default();
container.add_token(token);
container
}
pub fn timestamptoken_from_timestamprsp(ts: &[u8]) -> Result<Vec<u8>> {
let ts_resp = TimeStampResponse(
Constructed::decode(ts, bcder::Mode::Der, TimeStampResp::take_from).map_err(|err| {
CoseError::InternalError(format!("invalid timestamp response: {err:?}"))
})?,
);
let tst = ts_resp
.0
.time_stamp_token
.ok_or_else(|| CoseError::InternalError("invalid timestamp token".to_string()))?;
let a = tst
.content_type
.iter()
.map(|v| {
v.to_u32()
.ok_or(CoseError::InternalError("invalid component".to_string()))
})
.collect::<Result<Vec<u32>, CoseError>>()?;
let ci = ContentInfo {
content_type: rasn::types::ObjectIdentifier::new(a).ok_or(CoseError::InternalError(
"invalid object identifier for timestamp response".to_string(),
))?,
content: rasn::types::Any::new(tst.content.as_bytes().to_vec()),
};
Ok(rasn::der::encode(&ci).map_err(|err| {
CoseError::InternalError(format!("failed to encode timestamp token: {err:?}"))
})?)
}