use std::str::FromStr;
use asn1_rs::FromDer;
use async_generic::async_generic;
use bcder::OctetString;
use c2pa_raw_crypto::validator_for_sig_and_hash_algs;
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::{Sha256, Sha384, Sha512};
use crate::{
crypto::{
asn1::rfc3161::{Accuracy, TstInfo},
cose::{check_end_entity_certificate_profile, CertificateTrustPolicy},
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();
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 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 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;
}
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();
let accuracy_margin = tst_accuracy_seconds(tst.accuracy.as_ref());
if !(signing_time >= not_before.saturating_sub(accuracy_margin)
&& signing_time <= not_after.saturating_add(accuracy_margin))
{
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::for_message_imprint(&mi.hash_algorithm.algorithm) {
Some(d) => d,
None => {
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 = CertificateTrustPolicy::default();
let ordered_cert_ders = order_certificates_leaf_to_root(&cert_ders, cert_pos)?;
adjusted_ctp.clear_ekus();
adjusted_ctp.add_mandatory_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;
}
match ctp.check_certificate_trust(
&ordered_cert_ders[0..],
&ordered_cert_ders[0],
Some(signing_time),
) {
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;
}
Ok((_trust_type, trust_uri)) => {
log_item!(
"",
format!(
"timestamp cert trusted: {}, trust list: {}",
&common_name, &trust_uri
),
"verify_time_stamp"
)
.validation_status(TIMESTAMP_TRUSTED)
.set_trust_list_uri(&trust_uri)
.success(&mut current_validation_log);
validation_log.append(¤t_validation_log);
return Ok(tst);
}
}
} else {
log_item!(
"",
format!("legacy 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) => time.try_into().ok(),
_ => None,
}
}
#[derive(Clone, Copy, Debug)]
enum DigestAlgorithm {
Sha1,
Sha256,
Sha384,
Sha512,
}
impl DigestAlgorithm {
fn digester(self) -> Hasher {
match self {
DigestAlgorithm::Sha1 => Hasher::Sha1(<Sha1 as sha1::Digest>::new()),
DigestAlgorithm::Sha256 => Hasher::Sha256(<Sha256 as sha2::Digest>::new()),
DigestAlgorithm::Sha384 => Hasher::Sha384(<Sha384 as sha2::Digest>::new()),
DigestAlgorithm::Sha512 => Hasher::Sha512(<Sha512 as sha2::Digest>::new()),
}
}
fn for_message_imprint(oid: &bcder::Oid) -> Option<Self> {
Self::try_from(oid)
.ok()
.filter(|d| !matches!(d, DigestAlgorithm::Sha1))
}
}
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 integer_to_i64(int: &bcder::Integer) -> i64 {
let bytes = int.as_slice();
if bytes.is_empty() || bytes.len() > 8 {
return 0;
}
let mut result = 0i64;
for &b in bytes {
result = (result << 8) | (b as i64);
}
result
}
fn tst_accuracy_seconds(accuracy: Option<&Accuracy>) -> i64 {
let Some(acc) = accuracy else {
return 0;
};
let secs = acc.seconds.as_ref().map(integer_to_i64).unwrap_or(0);
let millis = acc.millis.as_ref().map(integer_to_i64).unwrap_or(0);
let micros = acc.micros.as_ref().map(integer_to_i64).unwrap_or(0);
let total_micros = secs
.saturating_mul(1_000_000)
.saturating_add(millis.saturating_mul(1_000))
.saturating_add(micros);
total_micros.saturating_add(999_999) / 1_000_000
}
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(
&c2pa_raw_crypto::Oid::new(sig_alg.as_ref()),
&c2pa_raw_crypto::Oid::new(hash_alg.as_ref()),
) else {
return Err(TimeStampError::UnsupportedAlgorithm);
};
validator
.validate(&sig_val.to_bytes(), tbs, signing_key_der)
.map_err(|_| TimeStampError::InvalidData)
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use std::str::FromStr;
use super::DigestAlgorithm;
fn oid(s: &str) -> bcder::Oid {
bcder::Oid::from_str(s).expect("valid OID")
}
#[test]
fn message_imprint_rejects_sha1_and_unlisted_algorithms() {
for o in [
"1.3.14.3.2.26", "2.16.840.1.101.3.4.2.4", "2.16.840.1.101.3.4.2.8", "1.2.840.113549.2.5", ] {
assert!(
DigestAlgorithm::for_message_imprint(&oid(o)).is_none(),
"{o}"
);
}
}
#[test]
fn message_imprint_accepts_sha2() {
for o in [
"2.16.840.1.101.3.4.2.1",
"2.16.840.1.101.3.4.2.2",
"2.16.840.1.101.3.4.2.3",
] {
assert!(DigestAlgorithm::for_message_imprint(&oid(o)).is_some());
}
}
#[test]
fn cms_digest_still_accepts_sha1() {
assert!(DigestAlgorithm::try_from(&oid("1.3.14.3.2.26")).is_ok());
}
}
#[cfg(all(test, feature = "openssl", not(target_arch = "wasm32")))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
pub(crate) mod message_imprint_regression {
use openssl::{
asn1::Asn1Time,
bn::BigNum,
ec::{EcGroup, EcKey},
hash::{hash, MessageDigest},
nid::Nid,
pkey::PKey,
sign::Signer,
x509::{extension::ExtendedKeyUsage, X509Builder, X509NameBuilder},
};
use super::{generalized_time_to_datetime, verify_time_stamp};
use crate::{
crypto::{cose::CertificateTrustPolicy, time_stamp::TimeStampError},
status_tracker::StatusTracker,
validation_status::{TIMESTAMP_TRUSTED, TIMESTAMP_UNTRUSTED, TIMESTAMP_VALIDATED},
};
const OID_MD5: &[u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05];
pub(crate) const OID_SHA1: &[u8] = &[0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a];
const OID_SHA224: &[u8] = &[
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
];
pub(crate) const OID_SHA256: &[u8] = &[
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
];
const OID_SHA384: &[u8] = &[
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
];
const OID_SHA512: &[u8] = &[
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
];
const OID_SHA3_256: &[u8] = &[
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08,
];
const OID_ECDSA_SHA256: &[u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02];
const OID_SIGNED_DATA: &[u8] = &[
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02,
];
const OID_TST_INFO: &[u8] = &[
0x06, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x04,
];
const OID_POLICY: &[u8] = &[0x06, 0x03, 0x2a, 0x03, 0x04];
const INT_1: &[u8] = &[0x02, 0x01, 0x01];
const DATA: &[u8] = b"claim signature bytes";
fn tlv(tag: u8, body: &[u8]) -> Vec<u8> {
let len = body.len();
let mut out = vec![tag];
if len < 0x80 {
out.push(len as u8);
} else if len <= 0xff {
out.extend([0x81, len as u8]);
} else {
out.extend([0x82, (len >> 8) as u8, len as u8]);
}
out.extend_from_slice(body);
out
}
fn cat(parts: &[&[u8]]) -> Vec<u8> {
parts.concat()
}
pub(crate) fn build_token(
imprint_oid: &[u8],
imprint_md: MessageDigest,
data: &[u8],
) -> Vec<u8> {
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
let key = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
let mut name = X509NameBuilder::new().unwrap();
name.append_entry_by_nid(Nid::COMMONNAME, "Test TSA")
.unwrap();
let name = name.build();
let mut cb = X509Builder::new().unwrap();
cb.set_version(2).unwrap();
cb.set_serial_number(&BigNum::from_u32(1).unwrap().to_asn1_integer().unwrap())
.unwrap();
cb.set_subject_name(&name).unwrap();
cb.set_issuer_name(&name).unwrap();
cb.set_pubkey(&key).unwrap();
cb.set_not_before(&Asn1Time::from_unix(1_500_000_000).unwrap())
.unwrap();
cb.set_not_after(&Asn1Time::from_unix(1_900_000_000).unwrap())
.unwrap();
cb.append_extension(ExtendedKeyUsage::new().time_stamping().build().unwrap())
.unwrap();
cb.sign(&key, MessageDigest::sha256()).unwrap();
let cert = cb.build();
let imprint_hash = hash(imprint_md, data).unwrap();
let message_imprint = tlv(
0x30,
&cat(&[
&tlv(0x30, &cat(&[imprint_oid, &[0x05, 0x00]])),
&tlv(0x04, &imprint_hash),
]),
);
let tst_info = tlv(
0x30,
&cat(&[
INT_1,
OID_POLICY,
&message_imprint,
INT_1,
&tlv(0x18, b"20200101000000Z"),
]),
);
let signature = Signer::new(MessageDigest::sha256(), &key)
.unwrap()
.sign_oneshot_to_vec(&tst_info)
.unwrap();
let issuer_and_serial = tlv(0x30, &cat(&[&cert.issuer_name().to_der().unwrap(), INT_1]));
let signer_info = tlv(
0x30,
&cat(&[
INT_1,
&issuer_and_serial,
&tlv(0x30, OID_SHA256),
&tlv(0x30, OID_ECDSA_SHA256),
&tlv(0x04, &signature),
]),
);
let signed_data = tlv(
0x30,
&cat(&[
&[0x02, 0x01, 0x03],
&tlv(0x31, &tlv(0x30, OID_SHA256)),
&tlv(
0x30,
&cat(&[OID_TST_INFO, &tlv(0xa0, &tlv(0x04, &tst_info))]),
),
&tlv(0xa0, &cert.to_der().unwrap()),
&tlv(0x31, &signer_info),
]),
);
tlv(0x30, &cat(&[OID_SIGNED_DATA, &tlv(0xa0, &signed_data)]))
}
#[test]
fn allowed_message_imprint_algorithms_are_accepted() {
for (name, oid, md) in [
("SHA-256", OID_SHA256, MessageDigest::sha256()),
("SHA-384", OID_SHA384, MessageDigest::sha384()),
("SHA-512", OID_SHA512, MessageDigest::sha512()),
] {
let token = build_token(oid, md, DATA);
let mut log = StatusTracker::default();
let result = verify_time_stamp(
&token,
DATA,
&CertificateTrustPolicy::default(),
&mut log,
false,
);
assert!(result.is_ok(), "{name}: {result:?}");
let tst = result.unwrap();
assert_eq!(
generalized_time_to_datetime(tst.gen_time.clone()).timestamp(),
1_577_836_800,
"{name}"
);
assert!(log.has_status(TIMESTAMP_VALIDATED), "{name}");
assert!(log.has_status(TIMESTAMP_TRUSTED), "{name}");
}
}
#[test]
fn disallowed_message_imprint_algorithms_are_untrusted_and_ignored() {
for (name, oid, md) in [
("SHA-1", OID_SHA1, MessageDigest::sha1()),
("SHA-224", OID_SHA224, MessageDigest::sha224()),
("SHA3-256", OID_SHA3_256, MessageDigest::sha3_256()),
("MD5", OID_MD5, MessageDigest::md5()),
] {
let token = build_token(oid, md, DATA);
let mut log = StatusTracker::default();
let result = verify_time_stamp(
&token,
DATA,
&CertificateTrustPolicy::default(),
&mut log,
false,
);
assert!(
matches!(result, Err(TimeStampError::UnsupportedAlgorithm)),
"{name}: {result:?}"
);
assert!(log.has_status(TIMESTAMP_UNTRUSTED), "{name}");
assert!(!log.has_status(TIMESTAMP_VALIDATED), "{name}");
assert!(!log.has_status(TIMESTAMP_TRUSTED), "{name}");
}
}
#[test]
fn mismatched_imprint_is_rejected() {
let token = build_token(OID_SHA256, MessageDigest::sha256(), DATA);
let mut log = StatusTracker::default();
let result = verify_time_stamp(
&token,
b"different data",
&CertificateTrustPolicy::default(),
&mut log,
false,
);
assert!(matches!(result, Err(TimeStampError::InvalidData)));
assert!(!log.has_status(TIMESTAMP_TRUSTED));
}
}