use std::str::FromStr;
use asn1_rs::FromDer;
use async_generic::async_generic;
use bcder::OctetString;
use chrono::{offset::LocalResult, DateTime, TimeZone, Utc};
use der::asn1::ObjectIdentifier;
use rasn::{prelude::*, types};
use rasn_cms::{CertificateChoices, SignerIdentifier};
use sha1::Sha1;
use sha2::{Digest as _, Sha256, Sha384, Sha512};
use crate::{
crypto::{
asn1::rfc3161::TstInfo,
cose::{check_end_entity_certificate_profile, 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,
status_tracker::StatusTracker,
validation_status::{
TIMESTAMP_MALFORMED, TIMESTAMP_MISMATCH, TIMESTAMP_OUTSIDE_VALIDITY, TIMESTAMP_TRUSTED,
TIMESTAMP_UNTRUSTED, TIMESTAMP_VALIDATED,
},
};
const TIMESTAMP_OID_STR: &str = "1.3.6.1.5.5.7.3.8";
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,
verify_trust: bool,
) -> Result<TstInfo, TimeStampError> {
let Ok(Some(sd)) = signed_data_from_time_stamp_response(ts) else {
log_item!("", "could 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!("", "could 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!("", "could 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();
let dt: chrono::DateTime<chrono::Utc> = gt.into();
tst.gen_time = dt.into();
};
}
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 signed 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;
}
if verify_trust {
let mut adjusted_ctp = ctp.clone();
let ordered_cert_ders = order_certificates_leaf_to_root(&cert_ders, cert_pos)?;
adjusted_ctp.clear_ekus();
adjusted_ctp.add_valid_ekus(TIMESTAMP_OID_STR.as_bytes()); if check_end_entity_certificate_profile(
&ordered_cert_ders[0],
&adjusted_ctp,
&mut current_validation_log,
Some(&tst),
)
.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;
}
if adjusted_ctp
.check_certificate_trust(
&ordered_cert_ders[0..],
&ordered_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)
}
pub fn tsa_signer_cert_der_from_token(ts: &[u8]) -> Result<Option<Vec<u8>>, TimeStampError> {
let Some(sd) = signed_data_from_time_stamp_response(ts)? else {
return Ok(None);
};
let Some(certs) = &sd.certificates else {
return Ok(None);
};
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_vec.len() {
return Err(TimeStampError::DecodeError(
"time stamp certificate could not be processed".to_string(),
));
}
let Some(signer_info) = sd.signer_infos.to_vec().into_iter().next() else {
return Ok(None);
};
let Some(cert_pos) = 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
}
}
}
}) else {
return Ok(None);
};
Ok(Some(cert_ders[cert_pos].clone()))
}
fn generalized_time_to_datetime<T: Into<DateTime<Utc>>>(gt: T) -> DateTime<Utc> {
gt.into()
}
fn timestamp_to_generalized_time(dt: i64) -> Option<crate::crypto::asn1::GeneralizedTime> {
match Utc.timestamp_opt(dt, 0) {
LocalResult::Single(time) => Some(time.into()),
_ => None,
}
}
#[derive(Clone, Copy, Debug)]
enum DigestAlgorithm {
Sha1,
Sha256,
Sha384,
Sha512,
}
impl DigestAlgorithm {
fn digester(self) -> Hasher {
match self {
DigestAlgorithm::Sha1 => Hasher::Sha1(Sha1::new()),
DigestAlgorithm::Sha256 => Hasher::Sha256(Sha256::new()),
DigestAlgorithm::Sha384 => Hasher::Sha384(Sha384::new()),
DigestAlgorithm::Sha512 => Hasher::Sha512(Sha512::new()),
}
}
}
impl TryFrom<&bcder::Oid> for DigestAlgorithm {
type Error = ();
fn try_from(oid: &bcder::Oid) -> Result<Self, Self::Error> {
let oid_str = oid.to_string();
let const_oid = ObjectIdentifier::new(&oid_str).map_err(|_| ())?;
const SHA1_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.14.3.2.26");
const SHA256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
const SHA384_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2");
const SHA512_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3");
if const_oid == SHA1_OID {
Ok(DigestAlgorithm::Sha1)
} else if const_oid == SHA256_OID {
Ok(DigestAlgorithm::Sha256)
} else if const_oid == SHA384_OID {
Ok(DigestAlgorithm::Sha384)
} else if const_oid == SHA512_OID {
Ok(DigestAlgorithm::Sha512)
} else {
Err(())
}
}
}
enum Hasher {
Sha1(Sha1),
Sha256(Sha256),
Sha384(Sha384),
Sha512(Sha512),
}
impl Hasher {
fn update(&mut self, data: &[u8]) {
match self {
Hasher::Sha1(h) => {
use sha1::Digest;
h.update(data);
}
Hasher::Sha256(h) => {
use sha2::Digest;
h.update(data);
}
Hasher::Sha384(h) => {
use sha2::Digest;
h.update(data);
}
Hasher::Sha512(h) => {
use sha2::Digest;
h.update(data);
}
}
}
fn finish(self) -> HasherOutput {
match self {
Hasher::Sha1(h) => {
use sha1::Digest;
HasherOutput(h.finalize().to_vec())
}
Hasher::Sha256(h) => {
use sha2::Digest;
HasherOutput(h.finalize().to_vec())
}
Hasher::Sha384(h) => {
use sha2::Digest;
HasherOutput(h.finalize().to_vec())
}
Hasher::Sha512(h) => {
use sha2::Digest;
HasherOutput(h.finalize().to_vec())
}
}
}
}
struct HasherOutput(Vec<u8>);
impl AsRef<[u8]> for HasherOutput {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
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 order_certificates_leaf_to_root(
cert_ders: &[Vec<u8>],
leaf_cert_pos: usize,
) -> Result<Vec<Vec<u8>>, TimeStampError> {
if leaf_cert_pos >= cert_ders.len() {
return Err(TimeStampError::DecodeError(
"invalid leaf certificate position".to_string(),
));
}
let parsed_certs: Result<Vec<_>, _> = cert_ders
.iter()
.map(|cert_der| x509_parser::certificate::X509Certificate::from_der(cert_der))
.collect();
let parsed_certs = match parsed_certs {
Ok(certs) => certs,
Err(_) => {
return Err(TimeStampError::DecodeError(
"failed to parse certificates".to_string(),
));
}
};
let mut ordered_certs = Vec::new();
let mut used_indices = std::collections::HashSet::new();
ordered_certs.push(cert_ders[leaf_cert_pos].clone());
used_indices.insert(leaf_cert_pos);
let mut current_cert_index = leaf_cert_pos;
for _ in 0..cert_ders.len() {
let current_cert = &parsed_certs[current_cert_index].1;
let mut found_next = false;
for (i, (_, next_cert)) in parsed_certs.iter().enumerate() {
if used_indices.contains(&i) {
continue;
}
if current_cert.issuer() == next_cert.subject() {
ordered_certs.push(cert_ders[i].clone());
used_indices.insert(i);
current_cert_index = i;
found_next = true;
break;
}
}
if !found_next {
break;
}
}
Ok(ordered_certs)
}
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)
}
}