use crate::error::{Error, Result};
use crate::signatures::der_util::{der_oid, der_sequence, der_set};
const OID_SIGNATURE_TIME_STAMP: &[u8] = &[
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x10, 0x02, 0x0E,
];
fn der_single_element_len(der: &[u8]) -> Result<usize> {
let invalid =
|| Error::InvalidPdf("signature-time-stamp: malformed DER token header".to_string());
if der.len() < 2 {
return Err(invalid());
}
let l0 = der[1] as usize;
if l0 < 0x80 {
return 2usize.checked_add(l0).ok_or_else(invalid);
}
if l0 == 0x80 {
return Err(invalid());
}
let n = l0 & 0x7f;
if n == 0x7f || n > core::mem::size_of::<usize>() || der.len() < 2 + n {
return Err(invalid());
}
let mut len: usize = 0;
for &b in &der[2..2 + n] {
len = len.checked_shl(8).ok_or_else(invalid)? | b as usize;
}
2usize
.checked_add(n)
.and_then(|h| h.checked_add(len))
.ok_or_else(invalid)
}
pub fn build_signature_timestamp_attr(token_der: &[u8]) -> Result<Vec<u8>> {
if token_der.first() != Some(&0x30) {
return Err(Error::InvalidPdf(
"signature-time-stamp: token is not a DER SEQUENCE".to_string(),
));
}
if der_single_element_len(token_der)? != token_der.len() {
return Err(Error::InvalidPdf(
"signature-time-stamp: token is not a single self-delimiting \
DER element (truncated or has trailing bytes)"
.to_string(),
));
}
let mut attr = Vec::with_capacity(16 + token_der.len());
attr.extend_from_slice(&der_oid(OID_SIGNATURE_TIME_STAMP));
attr.extend_from_slice(&der_set(token_der));
Ok(der_sequence(&attr))
}
#[cfg(test)]
mod tests {
use super::*;
use cms::cert::x509::attr::Attribute;
use der::oid::ObjectIdentifier;
use der::Decode;
use der::Encode;
const ID_AA_SIG_TS: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.16.2.14");
fn fake_token() -> Vec<u8> {
vec![0x30, 0x07, 0x02, 0x01, 0x01, 0x04, 0x02, b't', b's']
}
#[test]
fn attribute_round_trips_with_correct_oid() {
let token = fake_token();
let attr_der = build_signature_timestamp_attr(&token).unwrap();
let parsed = Attribute::from_der(&attr_der).expect("valid DER Attribute");
assert_eq!(parsed.oid, ID_AA_SIG_TS);
assert_eq!(parsed.values.len(), 1, "one TimeStampToken value");
assert_eq!(parsed.to_der().unwrap(), attr_der);
}
#[test]
fn token_bytes_are_embedded_verbatim() {
let token = fake_token();
let attr = build_signature_timestamp_attr(&token).unwrap();
assert!(
attr.windows(token.len()).any(|w| w == token.as_slice()),
"the token DER must be embedded unchanged in the attribute"
);
}
#[test]
fn rejects_non_sequence_token_fail_closed() {
assert!(build_signature_timestamp_attr(&[0x02, 0x01, 0x00]).is_err());
assert!(build_signature_timestamp_attr(&[0x04, 0x01, 0xAA]).is_err());
assert!(build_signature_timestamp_attr(&[]).is_err());
}
#[test]
fn rejects_non_self_delimiting_token_fail_closed() {
let mut trailing = fake_token();
trailing.extend_from_slice(&[0xDE, 0xAD]);
assert!(
build_signature_timestamp_attr(&trailing).is_err(),
"token with trailing bytes must be rejected"
);
assert!(
build_signature_timestamp_attr(&[0x30, 0x07, 0x02, 0x01, 0x01]).is_err(),
"truncated token must be rejected"
);
assert!(
build_signature_timestamp_attr(&[0x30, 0x80, 0x00, 0x00]).is_err(),
"indefinite-length encoding must be rejected"
);
assert!(build_signature_timestamp_attr(&fake_token()).is_ok());
}
}