use std::str::FromStr;
use asn1_rs::FromDer;
use async_generic::async_generic;
use bcder::{decode::SliceSource, OctetString};
use chrono::{offset::LocalResult, DateTime, TimeZone, Utc};
use rasn::{prelude::*, types};
use rasn_cms::{CertificateChoices, SignerIdentifier};
use x509_certificate::{
asn1time::{GeneralizedTime, GeneralizedTimeAllowedTimezone},
DigestAlgorithm,
};
use crate::{
crypto::{
asn1::rfc3161::TstInfo,
cose::CertificateTrustPolicy,
raw_signature::validator_for_sig_and_hash_algs,
time_stamp::{
response::{signed_data_from_time_stamp_response, tst_info_from_signed_data},
TimeStampError,
},
},
log_item,
settings::get_settings_value,
status_tracker::StatusTracker,
validation_status::{
TIMESTAMP_MALFORMED, TIMESTAMP_MISMATCH, TIMESTAMP_OUTSIDE_VALIDITY, TIMESTAMP_TRUSTED,
TIMESTAMP_UNTRUSTED, TIMESTAMP_VALIDATED,
},
};
fn signed_attributes_digested_content(
signer_info: &rasn_cms::SignerInfo,
) -> Result<Option<Vec<u8>>, rasn::error::EncodeError> {
if let Some(signed_attributes) = &signer_info.signed_attrs {
match rasn::der::encode(signed_attributes) {
Ok(encoded) => Ok(Some(encoded)),
Err(e) => Err(e),
}
} else {
Ok(None)
}
}
#[async_generic]
pub fn verify_time_stamp(
ts: &[u8],
data: &[u8],
ctp: &CertificateTrustPolicy,
validation_log: &mut StatusTracker,
) -> Result<TstInfo, TimeStampError> {
let Ok(Some(sd)) = signed_data_from_time_stamp_response(ts) else {
log_item!("", "count not parse timestamp data", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(validation_log);
return Err(TimeStampError::DecodeError(
"unable to find signed data".to_string(),
));
};
let Some(certs) = &sd.certificates else {
log_item!("", "count not parse timestamp data", "verify_time_stamp")
.validation_status(TIMESTAMP_UNTRUSTED)
.informational(validation_log);
return Err(TimeStampError::DecodeError(
"time stamp contains no certificates".to_string(),
));
};
let certs_vec = certs.to_vec();
let cert_ders: Vec<Vec<u8>> = certs_vec
.iter()
.filter_map(|cc| {
if let CertificateChoices::Certificate(c) = cc {
rasn::der::encode(c).ok()
} else {
None
}
})
.collect();
if cert_ders.len() != certs.len() {
log_item!("", "count not parse timestamp data", "verify_time_stamp")
.validation_status(TIMESTAMP_UNTRUSTED)
.informational(validation_log);
return Err(TimeStampError::DecodeError(
"time stamp certificate could not be processed".to_string(),
));
}
let mut last_err = TimeStampError::InvalidData;
let mut current_validation_log = StatusTracker::default();
for signer_info in sd.signer_infos.to_vec().iter() {
current_validation_log = StatusTracker::default();
let cert_pos = match certs_vec.iter().position(|cc| {
let c = match cc {
CertificateChoices::Certificate(c) => c,
_ => return false,
};
match &signer_info.sid {
SignerIdentifier::IssuerAndSerialNumber(sn) => {
sn.issuer == c.tbs_certificate.issuer
&& sn.serial_number == c.tbs_certificate.serial_number
}
SignerIdentifier::SubjectKeyIdentifier(ski) => {
if let Some(extensions) = &c.tbs_certificate.extensions {
extensions.iter().any(|e| {
if e.extn_id == Oid::JOINT_ISO_ITU_T_DS_CERTIFICATE_EXTENSION_SUBJECT_KEY_IDENTIFIER {
return *ski == e.extn_value;
}
false
})
} else {
false
}
}
}
}) {
Some(c) => c,
None => continue,
};
let CertificateChoices::Certificate(cert) = certs_vec[cert_pos] else {
continue;
};
let mut common_name = String::new();
if let Ok((_, new_c)) =
x509_parser::certificate::X509Certificate::from_der(&cert_ders[cert_pos])
{
for rdn in new_c.subject().iter_common_name() {
if let Ok(cn) = rdn.as_str() {
common_name.push_str(cn);
}
}
}
let Ok(Some(mut tst)) = tst_info_from_signed_data(&sd) else {
log_item!("", "timestamp response had no TstInfo", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::InvalidData;
continue;
};
let mi = &tst.message_imprint;
let mut signing_time = generalized_time_to_datetime(tst.gen_time.clone()).timestamp();
if let Some(attributes) = &signer_info.signed_attrs {
if let Some(Some(attrib_signing_time)) = attributes
.to_vec()
.iter()
.find(|attr| attr.r#type == Oid::ISO_MEMBER_BODY_US_RSADSI_PKCS9_SIGNING_TIME)
.map(|attr| {
if attr.values.len() != 1 {
return None;
}
attr.values
.to_vec()
.first()
.and_then(|v| rasn::der::decode::<rasn_pkix::Time>(v.as_bytes()).ok())
})
{
let signed_signing_time = match attrib_signing_time {
rasn_pkix::Time::Utc(date_time) => date_time.timestamp(),
rasn_pkix::Time::General(date_time) => {
generalized_time_to_datetime(date_time).timestamp()
}
};
if let Some(gt) = timestamp_to_generalized_time(signed_signing_time) {
signing_time = generalized_time_to_datetime(gt.clone()).timestamp();
tst.gen_time = gt;
};
}
match attributes
.to_vec()
.iter()
.find(|attr| attr.r#type == Oid::ISO_MEMBER_BODY_US_RSADSI_PKCS9_MESSAGE_DIGEST)
{
Some(message_digest) => {
if message_digest.values.len() != 1 {
log_item!(
"",
"timestamp response contained multiple message digests",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError(format!(
"message digest attribute has {n} values, should have one",
n = message_digest.values.len()
));
continue;
}
let signed_message_digest = match message_digest.values.to_vec().first() {
Some(a) => match rasn::der::decode::<types::OctetString>(a.as_bytes()) {
Ok(os) => os.to_vec(),
Err(_) => {
log_item!(
"",
"timestamp could not decode signed message data",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError(
"unable to decode igned message data".to_string(),
);
continue;
}
},
None => {
log_item!("", "timestamp bad message digest", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError(
"unable to decode message digest".to_string(),
);
continue;
}
};
let Ok(di_oid) =
bcder::Oid::from_str(&signer_info.digest_algorithm.algorithm.to_string())
else {
log_item!(
"",
"timestamp bad message digest algorithm",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err =
TimeStampError::DecodeError("unsupported digest algorithm".to_string());
continue;
};
let digest_algorithm = match DigestAlgorithm::try_from(&di_oid) {
Ok(d) => d,
Err(_) => {
log_item!(
"",
"timestamp bad message digest algorithm",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError(
"unsupported digest algorithm".to_string(),
);
continue;
}
};
let mut h = digest_algorithm.digester();
if let Some(content) = &sd.encap_content_info.content {
h.update(content);
}
let digest = h.finish();
if signed_message_digest != digest.as_ref() {
log_item!("", "timestamp bad message digest", "verify_time_stamp")
.validation_status(TIMESTAMP_MISMATCH)
.informational(&mut current_validation_log);
last_err = TimeStampError::InvalidData;
continue;
}
}
None => {
log_item!("", "timestamp no message digest", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError("no message imprint".to_string());
continue;
}
}
}
let tbs = match signed_attributes_digested_content(signer_info) {
Ok(sdc) => match sdc {
Some(tbs) => tbs,
None => match &sd.encap_content_info.content {
Some(d) => d.to_vec(),
None => {
log_item!("", "timestamp no message digest", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError(
"time stamp does not contain digested content".to_string(),
);
continue;
}
},
},
Err(_) => {
log_item!(
"",
"timestamp signer attributes malformed",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err =
TimeStampError::DecodeError("timestamp signer info malformed".to_string());
continue;
}
};
let Ok(hash_alg) =
bcder::Oid::from_str(&signer_info.digest_algorithm.algorithm.to_string())
else {
log_item!("", "timestamp bad hash alg", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError("timestamp bad tbs certificate".to_string());
continue;
};
let sig_val =
bcder::OctetString::new(bytes::Bytes::copy_from_slice(&signer_info.signature));
let signing_key_der_results =
rasn::der::encode(&cert.tbs_certificate.subject_public_key_info);
let Ok(signing_key_der) = signing_key_der_results else {
log_item!("", "timestamp bad signing key", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError("timestamp bad tbs certificate".to_string());
continue;
};
let Ok(sig_alg) = bcder::Oid::from_str(
&cert
.tbs_certificate
.subject_public_key_info
.algorithm
.algorithm
.to_string(),
) else {
log_item!("", "timestamp bad tbs certificate alg", "verify_time_stamp")
.validation_status(TIMESTAMP_MALFORMED)
.informational(&mut current_validation_log);
last_err = TimeStampError::DecodeError("timestamp bad tbs certificate".to_string());
continue;
};
if _sync {
if validate_timestamp_sig(&sig_alg, &hash_alg, &sig_val, &tbs, &signing_key_der)
.is_err()
{
log_item!(
"",
"timestamp signed data did not match signature",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_UNTRUSTED)
.informational(&mut current_validation_log);
last_err = TimeStampError::Untrusted;
continue;
}
} else {
#[cfg(not(target_arch = "wasm32"))]
if validate_timestamp_sig(&sig_alg, &hash_alg, &sig_val, &tbs, &signing_key_der)
.is_err()
{
log_item!(
"",
"timestamp signed data did not match signature",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_UNTRUSTED)
.informational(&mut current_validation_log);
last_err = TimeStampError::Untrusted;
continue;
}
#[cfg(target_arch = "wasm32")]
if validate_timestamp_sig_async(&sig_alg, &hash_alg, &sig_val, &tbs, &signing_key_der)
.await
.is_err()
{
log_item!(
"",
"timestamp signed data did not match signature",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_UNTRUSTED)
.informational(&mut current_validation_log);
last_err = TimeStampError::Untrusted;
continue;
}
}
let not_before = time_to_datetime(cert.tbs_certificate.validity.not_before).timestamp();
let not_after = time_to_datetime(cert.tbs_certificate.validity.not_after).timestamp();
if !(signing_time >= not_before && signing_time <= not_after) {
log_item!(
"",
"timestamp signer outside of certificate validity",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_OUTSIDE_VALIDITY)
.informational(&mut current_validation_log);
last_err = TimeStampError::ExpiredCertificate;
continue;
}
let digest_algorithm = match DigestAlgorithm::try_from(&mi.hash_algorithm.algorithm) {
Ok(d) => d,
Err(_) => {
log_item!(
"",
"timestamp unknown message digest algorithm",
"verify_time_stamp"
)
.validation_status(TIMESTAMP_UNTRUSTED)
.informational(&mut current_validation_log);
last_err = TimeStampError::UnsupportedAlgorithm;
continue;
}
};
let mut h = digest_algorithm.digester();
h.update(data);
let digest = h.finish();
if digest.as_ref() == mi.hashed_message.to_bytes() {
log_item!(
"",
format!("timestamp message digest matched: {}", &common_name),
"verify_time_stamp"
)
.validation_status(TIMESTAMP_VALIDATED)
.success(&mut current_validation_log);
} else {
log_item!(
"",
format!("timestamp message digest did not match: {}", &common_name),
"verify_time_stamp"
)
.validation_status(TIMESTAMP_MISMATCH)
.informational(&mut current_validation_log);
last_err = TimeStampError::InvalidData;
continue;
}
let verify_trust = get_settings_value("verify.verify_timestamp_trust").unwrap_or(true);
if verify_trust
&& ctp
.check_certificate_trust(&cert_ders[0..], &cert_ders[0], Some(signing_time))
.is_err()
{
log_item!(
"",
format!("timestamp cert untrusted: {}", &common_name),
"verify_time_stamp"
)
.validation_status(TIMESTAMP_UNTRUSTED)
.informational(&mut current_validation_log);
last_err = TimeStampError::Untrusted;
continue;
}
log_item!(
"",
format!("timestamp cert trusted: {}", &common_name),
"verify_time_stamp"
)
.validation_status(TIMESTAMP_TRUSTED)
.success(&mut current_validation_log);
validation_log.append(¤t_validation_log);
return Ok(tst);
}
validation_log.append(¤t_validation_log);
Err(last_err)
}
fn generalized_time_to_datetime<T: Into<DateTime<Utc>>>(gt: T) -> DateTime<Utc> {
gt.into()
}
fn timestamp_to_generalized_time(dt: i64) -> Option<GeneralizedTime> {
match Utc.timestamp_opt(dt, 0) {
LocalResult::Single(time) => {
let formatted_time = time.format("%Y%m%d%H%M%SZ").to_string();
GeneralizedTime::parse(
SliceSource::new(formatted_time.as_bytes()),
false,
GeneralizedTimeAllowedTimezone::Z,
)
.ok()
}
_ => None,
}
}
fn time_to_datetime(t: rasn_pkix::Time) -> DateTime<Utc> {
match t {
rasn_pkix::Time::Utc(u) => u,
rasn_pkix::Time::General(gt) => generalized_time_to_datetime(gt),
}
}
fn validate_timestamp_sig(
sig_alg: &bcder::Oid,
hash_alg: &bcder::Oid,
sig_val: &OctetString,
tbs: &[u8],
signing_key_der: &[u8],
) -> Result<(), TimeStampError> {
let Some(validator) = validator_for_sig_and_hash_algs(sig_alg, hash_alg) else {
return Err(TimeStampError::UnsupportedAlgorithm);
};
validator
.validate(&sig_val.to_bytes(), tbs, signing_key_der)
.map_err(|_| TimeStampError::InvalidData)
}
#[cfg(target_arch = "wasm32")]
async fn validate_timestamp_sig_async(
sig_alg: &bcder::Oid,
hash_alg: &bcder::Oid,
sig_val: &OctetString,
tbs: &[u8],
signing_key_der: &[u8],
) -> Result<(), TimeStampError> {
if let Some(validator) =
crate::crypto::raw_signature::async_validator_for_sig_and_hash_algs(sig_alg, hash_alg)
{
validator
.validate_async(&sig_val.to_bytes(), tbs, signing_key_der)
.await
.map_err(|_| TimeStampError::InvalidData)
} else if let Some(validator) =
crate::crypto::raw_signature::validator_for_sig_and_hash_algs(sig_alg, hash_alg)
{
validator
.validate(&sig_val.to_bytes(), tbs, signing_key_der)
.map_err(|_| TimeStampError::InvalidData)
} else {
Err(TimeStampError::UnsupportedAlgorithm)
}
}