use crate::attribute::Attribute;
use crate::digest::DigestError;
use crate::signed::{CertificateChoice, SignedData};
use crate::signer::{SignerInfo, required};
use crate::timestamp::Token;
use crate::{Error, ErrorCode, Result, oid};
use core::cmp::Ordering;
use structured_asn1::digest::{Digest, Resolution};
use structured_asn1::memory::{OwnedVec, SortError, Work};
use structured_asn1::{Element, Encoding, Reader, Span, Tag, universal};
use structured_pkix::AlgorithmIdentifier;
use structured_pkix::algorithm::digest::{self as asn_digest, AsnDigest};
#[derive(Debug)]
pub struct HashIndex<'a> {
algorithm: AlgorithmIdentifier<'a>,
certificates: OwnedVec<&'a [u8]>,
revocations: OwnedVec<&'a [u8]>,
attribute_values: OwnedVec<&'a [u8]>,
element: Element<'a>,
}
impl<'a> HashIndex<'a> {
pub fn of_token(token: &Token<'a>, parent: &Reader<'a>) -> Result<Self> {
const FIELD: &str = "archiveTimeStampV3.atsHashIndexV3";
let signer = token.signer();
signer.require_session(parent, FIELD)?;
let Some(unsigned) = signer.unsigned_attributes() else {
return Err(Error::schema(
ErrorCode::MissingField,
FIELD,
signer.span().start,
));
};
let attribute = unsigned
.unique_in(oid::ATS_HASH_INDEX_V3, parent)?
.ok_or_else(|| {
Error::schema(
ErrorCode::MissingField,
FIELD,
unsigned.element().span().start,
)
})?;
attribute.single(FIELD)?;
let mut reader = attribute
.value_reader_in(0, parent, FIELD)?
.ok_or_else(|| Error::schema(ErrorCode::MissingField, FIELD, attribute.span().start))?;
let value = required(&mut reader, FIELD)?;
let mut strict = parent
.reread(attribute.element(), Encoding::Der)
.map_err(|error| Error::encoding(error, FIELD))?;
let whole = strict
.expect(Tag::constructed(universal::SEQUENCE))
.map_err(|error| Error::encoding(error, FIELD))?;
let mut fields = strict
.enter(&whole)
.map_err(|error| Error::encoding(error, FIELD))?;
fields
.expect(Tag::primitive(universal::OID))
.map_err(|error| Error::encoding(error, FIELD))?;
let set = fields
.expect(Tag::constructed(universal::SET))
.map_err(|error| Error::encoding(error, FIELD))?;
let mut values = fields
.enter(&set)
.map_err(|error| Error::encoding(error, FIELD))?;
required(&mut values, FIELD)?;
Self::read(&reader, &value)
}
}
pub fn token_reader<'q, 'a: 'q>(
attribute: &Attribute<'a>,
parent: &Reader<'q>,
) -> Result<Reader<'q>> {
const FIELD: &str = "archiveTimeStampV3";
if attribute.kind() != oid::ARCHIVE_TIMESTAMP_V3 {
return Err(Error::schema(
ErrorCode::AttributeSyntax,
FIELD,
attribute.span().start,
));
}
attribute.single(FIELD)?;
attribute
.value_reader_in(0, parent, FIELD)?
.ok_or_else(|| Error::schema(ErrorCode::MissingField, FIELD, attribute.span().start))
}
impl<'a> HashIndex<'a> {
pub fn read(parent: &Reader<'a>, value: &Element<'a>) -> Result<Self> {
const FIELD: &str = "atsHashIndexV3";
let mut strict = parent
.reread(value, Encoding::Der)
.map_err(|error| Error::encoding(error, FIELD))?;
let element = strict
.expect(Tag::constructed(universal::SEQUENCE))
.map_err(|error| Error::encoding(error, FIELD))?;
let mut inside = strict
.enter(&element)
.map_err(|error| Error::encoding(error, FIELD))?;
let algorithm_element = required(&mut inside, "atsHashIndexV3.hashIndAlgorithm")?;
let algorithm = structured_pkix::algorithm::read(
&inside,
&algorithm_element,
"atsHashIndexV3.hashIndAlgorithm",
)
.map_err(Error::pkix)?;
let certificates = hashes(&mut inside, "atsHashIndexV3.certificatesHashIndex")?;
let revocations = hashes(&mut inside, "atsHashIndexV3.crlsHashIndex")?;
let attribute_values = hashes(&mut inside, "atsHashIndexV3.unsignedAttrValuesHashIndex")?;
if !inside.is_empty() {
return Err(Error::schema(
ErrorCode::TrailingData,
FIELD,
inside.offset(),
));
}
Ok(Self {
algorithm,
certificates,
revocations,
attribute_values,
element,
})
}
#[must_use]
pub fn algorithm(&self) -> &AlgorithmIdentifier<'a> {
&self.algorithm
}
#[must_use]
pub fn certificates(&self) -> &[&'a [u8]] {
&self.certificates
}
#[must_use]
pub fn revocations(&self) -> &[&'a [u8]] {
&self.revocations
}
#[must_use]
pub fn attribute_values(&self) -> &[&'a [u8]] {
&self.attribute_values
}
#[must_use]
pub fn as_bytes(&self) -> &'a [u8] {
self.element.as_bytes()
}
#[must_use]
pub fn span(&self) -> Span {
self.element.span()
}
}
fn hashes<'a>(inside: &mut Reader<'a>, field: &'static str) -> Result<OwnedVec<&'a [u8]>> {
let list = required(inside, field)?;
if list.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(Error::schema(
ErrorCode::UnexpectedTag,
field,
list.span().start,
));
}
let mut members = inside
.enter(&list)
.map_err(|error| Error::encoding(error, field))?;
let mut entries = inside.execution().vector();
while let Some(entry) = members
.next_element()
.map_err(|error| Error::encoding(error, field))?
{
if entry.tag() != Tag::primitive(universal::OCTET_STRING) {
return Err(Error::schema(
ErrorCode::UnexpectedTag,
field,
entry.span().start,
));
}
entries
.try_push(entry.contents())
.map_err(|reason| Error::resource(reason, field, entry.span().start))?;
}
Ok(entries.into_owned())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexList {
Certificates,
Revocations,
AttributeValues,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unmatched {
pub list: IndexList,
pub ordinal: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AttributeValue {
pub attribute: usize,
pub value: usize,
}
#[derive(Debug)]
pub struct IndexCoverage {
certificates: OwnedVec<bool>,
revocations: OwnedVec<bool>,
attribute_values: OwnedVec<(AttributeValue, bool)>,
unmatched: OwnedVec<Unmatched>,
}
impl IndexCoverage {
#[must_use]
pub fn is_valid(&self) -> bool {
self.unmatched.is_empty()
}
#[must_use]
pub fn unmatched(&self) -> &[Unmatched] {
&self.unmatched
}
#[must_use]
pub fn certificates(&self) -> &[bool] {
&self.certificates
}
#[must_use]
pub fn revocations(&self) -> &[bool] {
&self.revocations
}
#[must_use]
pub fn attribute_values(&self) -> &[(AttributeValue, bool)] {
&self.attribute_values
}
}
pub type CoverageResult<E> = core::result::Result<Resolution<IndexCoverage>, DigestError<E>>;
pub fn check_index<'a, P: AsnDigest<'a>>(
index: &HashIndex<'_>,
message: &SignedData<'_>,
signer: &SignerInfo<'_>,
provider: &'a P,
parent: &Reader<'_>,
) -> CoverageResult<P::Error> {
let execution = parent.execution();
let at = index.span().start;
let field = "atsHashIndexV3";
let refusal = |reason| DigestError::Processing(Error::resource(reason, field, at));
admit(index, message, signer, provider, parent).map_err(DigestError::Processing)?;
let identity = match asn_digest::resolve(provider, index.algorithm(), &execution)
.map_err(|error| DigestError::Processing(Error::pkix(error)))?
{
Resolution::Supported(identity) => identity,
Resolution::Unsupported => return Ok(Resolution::Unsupported),
Resolution::Malformed => return Ok(Resolution::Malformed),
};
let hasher = Hasher {
provider,
identity: &identity,
field,
at,
};
let certificate_hashes = hasher.hash_each(
message
.certificate_choices()
.iter()
.map(|choice| match choice {
CertificateChoice::X509(ordinal) => message.certificates()[*ordinal].as_bytes(),
CertificateChoice::Extended(element)
| CertificateChoice::AttributeV1(element)
| CertificateChoice::AttributeV2(element)
| CertificateChoice::Other(element) => element.as_bytes(),
}),
&execution,
)?;
let revocation_hashes = hasher.hash_each(
message
.revocations()
.iter()
.map(|revocation| revocation.as_bytes()),
&execution,
)?;
let attributes = signer
.unsigned_attributes()
.map_or(&[][..], |set| set.items());
let (value_hashes, mut values) = hasher.hash_values(attributes, &execution)?;
let mut unmatched = execution.vector();
let certificates = mark(
&hasher,
&certificate_hashes,
index.certificates(),
IndexList::Certificates,
&mut unmatched,
&execution,
refusal,
)?;
let revocations = mark(
&hasher,
&revocation_hashes,
index.revocations(),
IndexList::Revocations,
&mut unmatched,
&execution,
refusal,
)?;
let covered = mark(
&hasher,
&value_hashes,
index.attribute_values(),
IndexList::AttributeValues,
&mut unmatched,
&execution,
refusal,
)?;
execution
.charge_work(values.len() as u64)
.map_err(refusal)?;
for (entry, flag) in values.iter_mut().zip(covered.iter()) {
entry.1 = *flag;
}
Ok(Resolution::Supported(IndexCoverage {
certificates,
revocations,
attribute_values: values.into_owned(),
unmatched: unmatched.into_owned(),
}))
}
fn admit<P>(
index: &HashIndex<'_>,
message: &SignedData<'_>,
signer: &SignerInfo<'_>,
provider: &P,
parent: &Reader<'_>,
) -> Result<()> {
const FIELD: &str = "atsHashIndexV3";
let at = index.span().start;
crate::provider::require_instance(provider, FIELD, at)?;
message.require_session(parent, FIELD)?;
message.require_signer(signer, FIELD)?;
if !index.algorithm().same_session(parent) {
return Err(Error::pkix(structured_pkix::Error::new(
structured_pkix::ErrorCode::ForeignReader,
FIELD,
at,
)));
}
Ok(())
}
type ValueHashes<O, W> = (OwnedVec<O, W>, OwnedVec<(AttributeValue, bool), W>);
struct Hasher<'p, 'a, P: Digest<'a>> {
provider: &'a P,
identity: &'p P::Identity,
field: &'static str,
at: u64,
}
impl<'a, P: Digest<'a>> Hasher<'_, 'a, P> {
fn hash<W: Work>(
&self,
parts: &[&[u8]],
execution: &W,
) -> core::result::Result<P::Output, DigestError<P::Error>> {
execution.charge_work(1).map_err(|reason| {
DigestError::Processing(Error::resource(reason, self.field, self.at))
})?;
let mut state = match self
.provider
.start(self.identity, execution)
.map_err(DigestError::Callback)?
{
Resolution::Supported(state) => state,
Resolution::Unsupported | Resolution::Malformed => {
return Err(DigestError::Unstreamable);
}
};
for part in parts {
self.provider
.update(&mut state, part, execution)
.map_err(DigestError::Callback)?;
}
let output = self
.provider
.finish(state, execution)
.map_err(DigestError::Callback)?;
if let Some(expected) = self.provider.width(self.identity) {
let actual = output.as_ref().len();
if actual != expected {
return Err(DigestError::OutputWidth { expected, actual });
}
}
Ok(output)
}
fn hash_each<'e, W: Work>(
&self,
encodings: impl ExactSizeIterator<Item = &'e [u8]>,
execution: &W,
) -> core::result::Result<OwnedVec<P::Output, W>, DigestError<P::Error>> {
let refusal =
|reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
let mut outputs = execution.vector();
outputs
.try_reserve_exact(encodings.len())
.map_err(refusal)?;
for encoding in encodings {
outputs
.try_push(self.hash(&[encoding], execution)?)
.map_err(refusal)?;
}
Ok(outputs)
}
fn hash_values<W: Work>(
&self,
attributes: &[Attribute<'_>],
execution: &W,
) -> core::result::Result<ValueHashes<P::Output, W>, DigestError<P::Error>> {
let refusal =
|reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
let mut hashes = execution.vector();
let mut values = execution.vector();
for (attribute_ordinal, attribute) in attributes.iter().enumerate() {
execution.charge_work(1).map_err(refusal)?;
for (value_ordinal, value) in attribute.values().iter().enumerate() {
hashes.try_reserve(1).map_err(refusal)?;
values.try_reserve(1).map_err(refusal)?;
let hash = self.hash(&[attribute.kind_encoding(), value.as_bytes()], execution)?;
hashes.try_push(hash).map_err(refusal)?;
values
.try_push((
AttributeValue {
attribute: attribute_ordinal,
value: value_ordinal,
},
false,
))
.map_err(refusal)?;
}
}
Ok((hashes, values))
}
fn matches<W: Work>(
&self,
output: &P::Output,
entry: &[u8],
execution: &W,
) -> core::result::Result<bool, DigestError<P::Error>> {
self.provider
.matches(self.identity, output, entry, execution)
.map_err(DigestError::Callback)
}
}
struct Ranked {
ordinal: usize,
consumed: usize,
}
fn mark<'a, P: Digest<'a>>(
digest: &Hasher<'_, 'a, P>,
hashes: &[P::Output],
entries: &[&[u8]],
list: IndexList,
unmatched: &mut OwnedVec<Unmatched, structured_asn1::memory::Execution<'_>>,
execution: &impl Work,
refusal: impl Fn(structured_asn1::memory::MemoryError) -> DigestError<P::Error> + Copy,
) -> core::result::Result<OwnedVec<bool>, DigestError<P::Error>> {
execution
.charge_work(2 * hashes.len() as u64)
.map_err(refusal)?;
let mut order = execution.vector();
order.try_reserve_exact(hashes.len()).map_err(refusal)?;
for ordinal in 0..hashes.len() {
order
.try_push(Ranked {
ordinal,
consumed: 0,
})
.map_err(refusal)?;
}
order
.try_sort_unstable_by(|left, right| {
let compared = compare(
hashes[left.ordinal].as_ref(),
hashes[right.ordinal].as_ref(),
execution,
)?;
if compared == Ordering::Equal {
execution.charge_work(1)?;
Ok(left.ordinal.cmp(&right.ordinal))
} else {
Ok(compared)
}
})
.map_err(|error| match error {
SortError::Resource(reason) | SortError::Comparison(reason) => refusal(reason),
})?;
let mut covered = execution.vector();
covered.try_reserve_exact(hashes.len()).map_err(refusal)?;
for _ in 0..hashes.len() {
covered.try_push(false).map_err(refusal)?;
}
for (ordinal, entry) in entries.iter().enumerate() {
execution.charge_work(1).map_err(refusal)?;
let first = lower_bound(&order, hashes, entry, execution).map_err(refusal)?;
let nearest = order.get(first).or_else(|| order.last());
let matched = match nearest {
Some(candidate) => digest.matches(&hashes[candidate.ordinal], entry, execution)?,
None => false,
};
if matched && first < order.len() {
debug_assert!(order[first].consumed <= order.len() - first);
let position = first + order[first].consumed;
if position < order.len()
&& compare(hashes[order[position].ordinal].as_ref(), entry, execution)
.map_err(refusal)?
== Ordering::Equal
{
covered[order[position].ordinal] = true;
order[first].consumed += 1;
continue;
}
}
unmatched.try_reserve(1).map_err(refusal)?;
unmatched
.try_push(Unmatched { list, ordinal })
.map_err(refusal)?;
}
Ok(covered.into_owned())
}
fn lower_bound<O: AsRef<[u8]>>(
order: &[Ranked],
hashes: &[O],
entry: &[u8],
execution: &impl Work,
) -> core::result::Result<usize, structured_asn1::memory::MemoryError> {
let mut from = 0;
let mut to = order.len();
while from < to {
let middle = from + (to - from) / 2;
if compare(hashes[order[middle].ordinal].as_ref(), entry, execution)? == Ordering::Less {
from = middle + 1;
} else {
to = middle;
}
}
Ok(from)
}
fn compare(
left: &[u8],
right: &[u8],
execution: &impl Work,
) -> core::result::Result<Ordering, structured_asn1::memory::MemoryError> {
execution.charge_work(left.len().min(right.len()) as u64 + 1)?;
Ok(left.cmp(right))
}
pub fn imprint_input<'s>(
message: &SignedData<'s>,
signer: &SignerInfo<'s>,
index: &HashIndex<'s>,
content_hash: &'s [u8],
) -> Result<[&'s [u8]; 4]> {
message.require_signer(signer, "archiveTimeStampV3")?;
Ok([
message.content().kind_encoding(),
content_hash,
signer.signed_fields(),
index.as_bytes(),
])
}
pub fn same_algorithm<W: Work>(
index: &HashIndex<'_>,
imprint: &AlgorithmIdentifier<'_>,
execution: &W,
) -> Result<Resolution<bool>> {
let left = asn_digest::algorithm(index.algorithm(), execution).map_err(Error::pkix)?;
let right = asn_digest::algorithm(imprint, execution).map_err(Error::pkix)?;
Ok(match (left, right) {
(Resolution::Supported(left), Resolution::Supported(right)) => {
Resolution::Supported(left == right)
}
(Resolution::Malformed, _) | (_, Resolution::Malformed) => Resolution::Malformed,
_ => Resolution::Unsupported,
})
}