use std::sync::Arc;
use structured_asn1::memory::{Memory, MemoryAccount};
use structured_asn1::{Encoding, Limits, Parser, Reader};
use structured_cms::archive::{HashIndex, token_reader};
use structured_cms::timestamp::Token;
use structured_cms::{ErrorCode, SignedData, oid};
const SHA256_OID: &[u8] = &[0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2, 1];
fn memory() -> Memory {
Memory::new(Arc::new(MemoryAccount::new(1_048_576, 1_048_576)))
}
fn tlv(tag: u8, contents: &[u8]) -> Vec<u8> {
let mut encoded = vec![tag];
let length = contents.len();
if length < 0x80 {
encoded.push(u8::try_from(length).unwrap());
} else if length < 0x100 {
encoded.extend([0x81, u8::try_from(length).unwrap()]);
} else {
encoded.push(0x82);
encoded.extend(u16::try_from(length).unwrap().to_be_bytes());
}
encoded.extend_from_slice(contents);
encoded
}
fn attribute(kind: &[u8], values: &[Vec<u8>]) -> Vec<u8> {
tlv(0x30, &[tlv(6, kind), tlv(0x31, &values.concat())].concat())
}
fn index() -> Vec<u8> {
let fields = [
tlv(0x30, &tlv(6, SHA256_OID)),
tlv(0x30, &tlv(4, &[7; 32])),
tlv(0x30, &[]),
tlv(0x30, &[]),
];
tlv(0x30, &fields.concat())
}
fn signed(kind: &[u8], content: Option<&[u8]>, unsigned: &[Vec<u8>]) -> Vec<u8> {
let mut fields = vec![2, 1, 3, 0x80, 1, 7];
fields.extend(tlv(0x30, &tlv(6, oid::SHA256.as_bytes())));
fields.extend(tlv(0x30, &tlv(6, &[0x2b, 0x65, 0x70])));
fields.extend(tlv(4, &[8]));
if !unsigned.is_empty() {
fields.extend(tlv(0xa1, &unsigned.concat()));
}
let mut encapsulated = tlv(6, kind);
if let Some(content) = content {
encapsulated.extend(tlv(0xa0, &tlv(4, content)));
}
let mut inside = vec![2, 1, 3, 0x31, 0];
inside.extend(tlv(0x30, &encapsulated));
inside.extend(tlv(0x31, &tlv(0x30, &fields)));
let envelope = [
tlv(6, oid::SIGNED_DATA.as_bytes()),
tlv(0xa0, &tlv(0x30, &inside)),
]
.concat();
tlv(0x30, &envelope)
}
fn token(unsigned: &[Vec<u8>]) -> Vec<u8> {
signed(oid::TST_INFO.as_bytes(), Some(&[0x30, 0]), unsigned)
}
fn index_of(unsigned: &[Vec<u8>]) -> structured_cms::Result<Vec<u8>> {
let encoded = token(unsigned);
let parser = Parser::ber(memory());
let token = Token::read(&parser, &encoded, 0).unwrap();
let operation = parser.read(&[], 0).unwrap();
HashIndex::of_token(&token, &operation).map(|index| index.as_bytes().to_vec())
}
#[test]
fn the_single_index_value_is_read_as_written() {
let value = index();
let found = index_of(&[attribute(
oid::ATS_HASH_INDEX_V3.as_bytes(),
std::slice::from_ref(&value),
)]);
assert_eq!(found.unwrap(), value);
}
#[test]
fn a_token_without_its_v3_index_is_refused() {
assert_eq!(index_of(&[]).unwrap_err().code(), ErrorCode::MissingField);
let superseded = attribute(&[4, 0, 0x8d, 0x45, 2, 5], &[index()]);
assert_eq!(
index_of(&[superseded]).unwrap_err().code(),
ErrorCode::MissingField
);
}
#[test]
fn a_repeated_index_attribute_or_value_is_refused() {
let one = attribute(oid::ATS_HASH_INDEX_V3.as_bytes(), &[index()]);
assert_eq!(
index_of(&[one.clone(), one]).unwrap_err().code(),
ErrorCode::DuplicateAttribute
);
let two = attribute(oid::ATS_HASH_INDEX_V3.as_bytes(), &[index(), index()]);
assert_eq!(
index_of(&[two]).unwrap_err().code(),
ErrorCode::AttributeCardinality
);
}
#[test]
fn a_foreign_token_is_refused_before_its_attributes_are_searched() {
let encoded = token(&[]);
let parser = Parser::ber(memory());
let token = Token::read(&parser, &encoded, 0).unwrap();
let foreign = Parser::ber(memory());
let other = foreign.read(&[], 0).unwrap();
assert_eq!(
HashIndex::of_token(&token, &other).unwrap_err().code(),
ErrorCode::Pkix(structured_pkix::ErrorCode::ForeignReader)
);
}
fn walk(reader: &mut Reader<'_>) -> bool {
loop {
match reader.next_element() {
Ok(None) => return true,
Ok(Some(element)) if element.tag().is_constructed() => {
let Ok(mut inside) = reader.enter(&element) else {
return false;
};
if !walk(&mut inside) {
return false;
}
}
Ok(Some(_)) => {}
Err(_) => return false,
}
}
}
#[test]
fn the_index_is_read_at_its_original_depth() {
let mut parameters = tlv(5, &[]);
for _ in 0..8 {
parameters = tlv(0x30, ¶meters);
}
let fields = [
tlv(0x30, &[tlv(6, SHA256_OID), parameters].concat()),
tlv(0x30, &[]),
tlv(0x30, &[]),
tlv(0x30, &[]),
];
let value = tlv(0x30, &fields.concat());
let encoded = token(&[attribute(oid::ATS_HASH_INDEX_V3.as_bytes(), &[value])]);
let mut exercised = false;
for max_depth in 1..40 {
let limits = Limits {
max_depth,
..Limits::default()
};
let whole = Parser::new(Encoding::Ber, limits, memory());
let in_place = walk(&mut whole.read(&encoded, 0).unwrap());
let parser = Parser::new(Encoding::Ber, limits, memory());
let Ok(token) = Token::read(&parser, &encoded, 0) else {
continue;
};
let query = parser.read(&[], 0).unwrap();
let read = HashIndex::of_token(&token, &query);
if !in_place {
exercised = true;
assert!(read.is_err(), "depth {max_depth} reset for the index");
}
}
assert!(exercised, "no ceiling admitted the token but not the index");
}
#[test]
fn the_whole_index_attribute_must_be_der() {
let value = index();
let mut values = vec![0x31, 0x81, u8::try_from(value.len()).unwrap()];
values.extend(&value);
let lax = tlv(
0x30,
&[tlv(6, oid::ATS_HASH_INDEX_V3.as_bytes()), values].concat(),
);
assert!(index_of(&[lax]).is_err());
let mut kind = vec![
6,
0x81,
u8::try_from(oid::ATS_HASH_INDEX_V3.as_bytes().len()).unwrap(),
];
kind.extend(oid::ATS_HASH_INDEX_V3.as_bytes());
let lax_kind = tlv(0x30, &[kind, tlv(0x31, &value)].concat());
assert!(index_of(&[lax_kind]).is_err());
}
#[test]
fn the_archive_attribute_yields_its_one_token() {
let inner = token(&[attribute(oid::ATS_HASH_INDEX_V3.as_bytes(), &[index()])]);
for (values, expected) in [
(vec![inner.clone()], None),
(
vec![inner.clone(), inner.clone()],
Some(ErrorCode::AttributeCardinality),
),
(vec![], Some(ErrorCode::AttributeCardinality)),
] {
let unsigned = [attribute(oid::ARCHIVE_TIMESTAMP_V3.as_bytes(), &values)];
let encoded = signed(oid::DATA.as_bytes(), None, &unsigned);
let parser = Parser::ber(memory());
let value = SignedData::read(&parser, &encoded, 0).unwrap();
let query = parser.read(&[], 0).unwrap();
let attribute = &value.signers()[0].unsigned_attributes().unwrap().items()[0];
match (token_reader(attribute, &query), expected) {
(Ok(mut reader), None) => {
let token = Token::read_from(&mut reader).unwrap();
assert!(HashIndex::of_token(&token, &reader).is_ok());
}
(Err(error), Some(code)) => assert_eq!(error.code(), code),
(other, expected) => panic!("{expected:?} expected, got {:?}", other.err()),
}
}
let unsigned = [attribute(oid::SIGNATURE_TIMESTAMP.as_bytes(), &[inner])];
let encoded = signed(oid::DATA.as_bytes(), None, &unsigned);
let parser = Parser::ber(memory());
let value = SignedData::read(&parser, &encoded, 0).unwrap();
let query = parser.read(&[], 0).unwrap();
let attribute = &value.signers()[0].unsigned_attributes().unwrap().items()[0];
assert_eq!(
token_reader(attribute, &query).unwrap_err().code(),
ErrorCode::AttributeSyntax
);
}