Skip to main content

structured_cms/
archive.rs

1//! `CAdES` `archive-time-stamp-v3` and its `ats-hash-index-v3` (ETSI EN 319 122-1
2//! V1.3.1 §5.5).
3//!
4//! An `archive-time-stamp-v3` is an unsigned attribute of a signer whose value
5//! is a time-stamp token. That token's own signer carries one
6//! `ats-hash-index-v3`: the hashes of the root `SignedData` certificates,
7//! revocation values and the signer's unsigned attribute values the
8//! time-stamp protects. The token's message imprint covers the content type,
9//! the content hash, the signer's signed fields and that index.
10//!
11//! [`token_reader`] yields the token of the attribute, [`HashIndex::of_token`]
12//! reads its index, [`check_index`] matches the index entries against the
13//! original elements, and [`imprint_input`] gives the ranges the token's
14//! imprint is checked over. Which archive time-stamps a level requires, and
15//! trust, stay with the caller.
16
17use crate::attribute::Attribute;
18use crate::digest::DigestError;
19use crate::signed::{CertificateChoice, SignedData};
20use crate::signer::{SignerInfo, required};
21use crate::timestamp::Token;
22use crate::{Error, ErrorCode, Result, oid};
23use core::cmp::Ordering;
24use structured_asn1::digest::{Digest, Resolution};
25use structured_asn1::memory::{OwnedVec, SortError, Work};
26use structured_asn1::{Element, Encoding, Reader, Span, Tag, universal};
27use structured_pkix::AlgorithmIdentifier;
28use structured_pkix::algorithm::digest::{self as asn_digest, AsnDigest};
29
30/// One `ATSHashIndexV3` value (EN 319 122-1 §5.5.2), in its original DER.
31#[derive(Debug)]
32pub struct HashIndex<'a> {
33    algorithm: AlgorithmIdentifier<'a>,
34    certificates: OwnedVec<&'a [u8]>,
35    revocations: OwnedVec<&'a [u8]>,
36    attribute_values: OwnedVec<&'a [u8]>,
37    element: Element<'a>,
38}
39
40impl<'a> HashIndex<'a> {
41    /// Reads the single `ats-hash-index-v3` of an archive time-stamp token.
42    ///
43    /// EN 319 122-1 §5.5.3 requires exactly one such attribute in the token's
44    /// unsigned attributes, and §5.5.2 exactly one value, with the whole
45    /// attribute DER encoded.
46    ///
47    /// # Errors
48    ///
49    /// A missing or repeated attribute, a value count other than one, an
50    /// attribute that is not DER, a value that is not an `ATSHashIndexV3`, a
51    /// foreign reader, or a spent work ceiling.
52    pub fn of_token(token: &Token<'a>, parent: &Reader<'a>) -> Result<Self> {
53        const FIELD: &str = "archiveTimeStampV3.atsHashIndexV3";
54        let signer = token.signer();
55        // The token's parser funds the search below, not another session's.
56        signer.require_session(parent, FIELD)?;
57        let Some(unsigned) = signer.unsigned_attributes() else {
58            return Err(Error::schema(
59                ErrorCode::MissingField,
60                FIELD,
61                signer.span().start,
62            ));
63        };
64        let attribute = unsigned
65            .unique_in(oid::ATS_HASH_INDEX_V3, parent)?
66            .ok_or_else(|| {
67                Error::schema(
68                    ErrorCode::MissingField,
69                    FIELD,
70                    unsigned.element().span().start,
71                )
72            })?;
73        attribute.single(FIELD)?;
74        // The value keeps its original nesting depth here, so its contents are
75        // read under the depth ceiling that applies where they are written.
76        let mut reader = attribute
77            .value_reader_in(0, parent, FIELD)?
78            .ok_or_else(|| Error::schema(ErrorCode::MissingField, FIELD, attribute.span().start))?;
79        let value = required(&mut reader, FIELD)?;
80        // §5.5.2 "The ats-hash-index-v3 attribute shall be DER encoded": the
81        // Attribute SEQUENCE, attrType and attrValues SET headers are checked
82        // here without descending into the value; `read` checks the value.
83        let mut strict = parent
84            .reread(attribute.element(), Encoding::Der)
85            .map_err(|error| Error::encoding(error, FIELD))?;
86        let whole = strict
87            .expect(Tag::constructed(universal::SEQUENCE))
88            .map_err(|error| Error::encoding(error, FIELD))?;
89        let mut fields = strict
90            .enter(&whole)
91            .map_err(|error| Error::encoding(error, FIELD))?;
92        fields
93            .expect(Tag::primitive(universal::OID))
94            .map_err(|error| Error::encoding(error, FIELD))?;
95        let set = fields
96            .expect(Tag::constructed(universal::SET))
97            .map_err(|error| Error::encoding(error, FIELD))?;
98        let mut values = fields
99            .enter(&set)
100            .map_err(|error| Error::encoding(error, FIELD))?;
101        required(&mut values, FIELD)?;
102        Self::read(&reader, &value)
103    }
104}
105
106/// A reader over the one time-stamp token of an `archive-time-stamp-v3`
107/// attribute, for [`Token::read_from`].
108///
109/// EN 319 122-1 §5.5.3 gives the attribute exactly one `AttributeValue`.
110///
111/// # Errors
112///
113/// An attribute of another type, a value count other than one, a foreign
114/// reader, or a spent work ceiling.
115pub fn token_reader<'q, 'a: 'q>(
116    attribute: &Attribute<'a>,
117    parent: &Reader<'q>,
118) -> Result<Reader<'q>> {
119    const FIELD: &str = "archiveTimeStampV3";
120    if attribute.kind() != oid::ARCHIVE_TIMESTAMP_V3 {
121        return Err(Error::schema(
122            ErrorCode::AttributeSyntax,
123            FIELD,
124            attribute.span().start,
125        ));
126    }
127    attribute.single(FIELD)?;
128    attribute
129        .value_reader_in(0, parent, FIELD)?
130        .ok_or_else(|| Error::schema(ErrorCode::MissingField, FIELD, attribute.span().start))
131}
132
133impl<'a> HashIndex<'a> {
134    /// Reads one `ATSHashIndexV3` value, rereading it under DER (§5.5.2).
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use std::sync::Arc;
140    /// use structured_asn1::Parser;
141    /// use structured_asn1::memory::{Memory, MemoryAccount};
142    /// use structured_cms::archive::HashIndex;
143    ///
144    /// // SHA-256, one certificate hash, no revocation or attribute hashes.
145    /// let mut index = vec![0x30, 53, 0x30, 11, 6, 9, 0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2, 1];
146    /// index.extend([0x30, 34, 4, 32]);
147    /// index.extend([7; 32]);
148    /// index.extend([0x30, 0, 0x30, 0]);
149    /// let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(4096, 4096))));
150    /// let mut reader = parser.read(&index, 0).unwrap();
151    /// let value = reader.next_element().unwrap().unwrap();
152    /// let read = HashIndex::read(&reader, &value).unwrap();
153    /// assert_eq!(read.certificates(), &[&[7; 32][..]]);
154    /// assert!(read.revocations().is_empty() && read.attribute_values().is_empty());
155    /// assert_eq!(read.as_bytes(), index.as_slice());
156    /// ```
157    ///
158    /// # Errors
159    ///
160    /// A value that is not DER, not the four-field sequence, or whose index
161    /// entries are not OCTET STRINGs; a spent work ceiling.
162    pub fn read(parent: &Reader<'a>, value: &Element<'a>) -> Result<Self> {
163        const FIELD: &str = "atsHashIndexV3";
164        let mut strict = parent
165            .reread(value, Encoding::Der)
166            .map_err(|error| Error::encoding(error, FIELD))?;
167        let element = strict
168            .expect(Tag::constructed(universal::SEQUENCE))
169            .map_err(|error| Error::encoding(error, FIELD))?;
170        let mut inside = strict
171            .enter(&element)
172            .map_err(|error| Error::encoding(error, FIELD))?;
173        // Annex D gives hashIndAlgorithm no DEFAULT: it is always written.
174        let algorithm_element = required(&mut inside, "atsHashIndexV3.hashIndAlgorithm")?;
175        let algorithm = structured_pkix::algorithm::read(
176            &inside,
177            &algorithm_element,
178            "atsHashIndexV3.hashIndAlgorithm",
179        )
180        .map_err(Error::pkix)?;
181        let certificates = hashes(&mut inside, "atsHashIndexV3.certificatesHashIndex")?;
182        let revocations = hashes(&mut inside, "atsHashIndexV3.crlsHashIndex")?;
183        let attribute_values = hashes(&mut inside, "atsHashIndexV3.unsignedAttrValuesHashIndex")?;
184        if !inside.is_empty() {
185            return Err(Error::schema(
186                ErrorCode::TrailingData,
187                FIELD,
188                inside.offset(),
189            ));
190        }
191        Ok(Self {
192            algorithm,
193            certificates,
194            revocations,
195            attribute_values,
196            element,
197        })
198    }
199
200    /// The algorithm every index entry and the message imprint are hashed with.
201    #[must_use]
202    pub fn algorithm(&self) -> &AlgorithmIdentifier<'a> {
203        &self.algorithm
204    }
205    /// `certificatesHashIndex` entries, in their written order.
206    #[must_use]
207    pub fn certificates(&self) -> &[&'a [u8]] {
208        &self.certificates
209    }
210    /// `crlsHashIndex` entries, in their written order.
211    #[must_use]
212    pub fn revocations(&self) -> &[&'a [u8]] {
213        &self.revocations
214    }
215    /// `unsignedAttrValuesHashIndex` entries, in their written order.
216    #[must_use]
217    pub fn attribute_values(&self) -> &[&'a [u8]] {
218        &self.attribute_values
219    }
220    /// The original `ATSHashIndexV3` encoding, the last part of the imprint input.
221    #[must_use]
222    pub fn as_bytes(&self) -> &'a [u8] {
223        self.element.as_bytes()
224    }
225    /// The original range of the index value.
226    #[must_use]
227    pub fn span(&self) -> Span {
228        self.element.span()
229    }
230}
231
232fn hashes<'a>(inside: &mut Reader<'a>, field: &'static str) -> Result<OwnedVec<&'a [u8]>> {
233    let list = required(inside, field)?;
234    if list.tag() != Tag::constructed(universal::SEQUENCE) {
235        return Err(Error::schema(
236            ErrorCode::UnexpectedTag,
237            field,
238            list.span().start,
239        ));
240    }
241    let mut members = inside
242        .enter(&list)
243        .map_err(|error| Error::encoding(error, field))?;
244    let mut entries = inside.execution().vector();
245    while let Some(entry) = members
246        .next_element()
247        .map_err(|error| Error::encoding(error, field))?
248    {
249        if entry.tag() != Tag::primitive(universal::OCTET_STRING) {
250            return Err(Error::schema(
251                ErrorCode::UnexpectedTag,
252                field,
253                entry.span().start,
254            ));
255        }
256        entries
257            .try_push(entry.contents())
258            .map_err(|reason| Error::resource(reason, field, entry.span().start))?;
259    }
260    Ok(entries.into_owned())
261}
262
263/// Which list of the index an entry belongs to.
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub enum IndexList {
266    /// `certificatesHashIndex`.
267    Certificates,
268    /// `crlsHashIndex`.
269    Revocations,
270    /// `unsignedAttrValuesHashIndex`.
271    AttributeValues,
272}
273
274/// An index entry that matches no original element, which makes the whole
275/// `ats-hash-index-v3` invalid (EN 319 122-1 §5.5.2).
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277pub struct Unmatched {
278    /// The list holding the entry.
279    pub list: IndexList,
280    /// The entry's position in that list.
281    pub ordinal: usize,
282}
283
284/// One unsigned attribute value of the archived signer.
285#[derive(Debug, Clone, Copy, PartialEq, Eq)]
286pub struct AttributeValue {
287    /// Position of the attribute in the signer's unsigned attributes.
288    pub attribute: usize,
289    /// Position of the value in that attribute's values.
290    pub value: usize,
291}
292
293/// What an `ats-hash-index-v3` protects in the root `SignedData` and signer.
294///
295/// An element without an entry is not an error: it was added after the
296/// time-stamp, or is not protected by it. Only an entry without an element
297/// invalidates the index (§5.5.2).
298#[derive(Debug)]
299pub struct IndexCoverage {
300    certificates: OwnedVec<bool>,
301    revocations: OwnedVec<bool>,
302    attribute_values: OwnedVec<(AttributeValue, bool)>,
303    unmatched: OwnedVec<Unmatched>,
304}
305
306impl IndexCoverage {
307    /// Every index entry matches an original element.
308    #[must_use]
309    pub fn is_valid(&self) -> bool {
310        self.unmatched.is_empty()
311    }
312    /// Entries that match no element, in list and written order.
313    #[must_use]
314    pub fn unmatched(&self) -> &[Unmatched] {
315        &self.unmatched
316    }
317    /// Whether each certificate choice, in original order, has an entry.
318    #[must_use]
319    pub fn certificates(&self) -> &[bool] {
320        &self.certificates
321    }
322    /// Whether each revocation choice, in original order, has an entry.
323    #[must_use]
324    pub fn revocations(&self) -> &[bool] {
325        &self.revocations
326    }
327    /// Every unsigned attribute value of the signer, in original order, and
328    /// whether it has an entry.
329    #[must_use]
330    pub fn attribute_values(&self) -> &[(AttributeValue, bool)] {
331        &self.attribute_values
332    }
333}
334
335/// Result of matching an index, or the provider's answer that it cannot hash
336/// with the index algorithm.
337pub type CoverageResult<E> = core::result::Result<Resolution<IndexCoverage>, DigestError<E>>;
338
339/// Matches every entry of `index` against the original elements it may name.
340///
341/// Each certificate and revocation choice of the root `message` is hashed over
342/// its whole original encoding, and each unsigned attribute value of `signer`
343/// over its `attrType` encoding followed by the value's encoding (§5.5.2).
344/// Nothing is re-encoded: an entry computed over another encoding of the same
345/// value does not match, and an `OtherCertificateFormat`, which §5.5.2 has its
346/// producer write in DER, is hashed as written. Only the root `SignedData`
347/// fields count; an element carried elsewhere, such as in a revocation-values
348/// attribute, names no entry. The index algorithm is resolved through
349/// `provider`; an algorithm it does not know or that is malformed is answered
350/// as such, before anything is hashed.
351///
352/// # Errors
353///
354/// A zero-sized provider, a message or index from another parser than
355/// `parent`'s, a `signer` that is not one of `message`'s, a spent work ceiling,
356/// a provider failure, or a provider output whose width contradicts its
357/// identity.
358pub fn check_index<'a, P: AsnDigest<'a>>(
359    index: &HashIndex<'_>,
360    message: &SignedData<'_>,
361    signer: &SignerInfo<'_>,
362    provider: &'a P,
363    parent: &Reader<'_>,
364) -> CoverageResult<P::Error> {
365    let execution = parent.execution();
366    let at = index.span().start;
367    let field = "atsHashIndexV3";
368    let refusal = |reason| DigestError::Processing(Error::resource(reason, field, at));
369    admit(index, message, signer, provider, parent).map_err(DigestError::Processing)?;
370    let identity = match asn_digest::resolve(provider, index.algorithm(), &execution)
371        .map_err(|error| DigestError::Processing(Error::pkix(error)))?
372    {
373        Resolution::Supported(identity) => identity,
374        Resolution::Unsupported => return Ok(Resolution::Unsupported),
375        Resolution::Malformed => return Ok(Resolution::Malformed),
376    };
377    let hasher = Hasher {
378        provider,
379        identity: &identity,
380        field,
381        at,
382    };
383
384    let certificate_hashes = hasher.hash_each(
385        message
386            .certificate_choices()
387            .iter()
388            .map(|choice| match choice {
389                CertificateChoice::X509(ordinal) => message.certificates()[*ordinal].as_bytes(),
390                CertificateChoice::Extended(element)
391                | CertificateChoice::AttributeV1(element)
392                | CertificateChoice::AttributeV2(element)
393                | CertificateChoice::Other(element) => element.as_bytes(),
394            }),
395        &execution,
396    )?;
397    let revocation_hashes = hasher.hash_each(
398        message
399            .revocations()
400            .iter()
401            .map(|revocation| revocation.as_bytes()),
402        &execution,
403    )?;
404
405    let attributes = signer
406        .unsigned_attributes()
407        .map_or(&[][..], |set| set.items());
408    let (value_hashes, mut values) = hasher.hash_values(attributes, &execution)?;
409
410    let mut unmatched = execution.vector();
411    let certificates = mark(
412        &hasher,
413        &certificate_hashes,
414        index.certificates(),
415        IndexList::Certificates,
416        &mut unmatched,
417        &execution,
418        refusal,
419    )?;
420    let revocations = mark(
421        &hasher,
422        &revocation_hashes,
423        index.revocations(),
424        IndexList::Revocations,
425        &mut unmatched,
426        &execution,
427        refusal,
428    )?;
429    let covered = mark(
430        &hasher,
431        &value_hashes,
432        index.attribute_values(),
433        IndexList::AttributeValues,
434        &mut unmatched,
435        &execution,
436        refusal,
437    )?;
438    // The final projection is a pass over every value, paid before it runs.
439    execution
440        .charge_work(values.len() as u64)
441        .map_err(refusal)?;
442    for (entry, flag) in values.iter_mut().zip(covered.iter()) {
443        entry.1 = *flag;
444    }
445    Ok(Resolution::Supported(IndexCoverage {
446        certificates,
447        revocations,
448        attribute_values: values.into_owned(),
449        unmatched: unmatched.into_owned(),
450    }))
451}
452
453// Refuses before any work: a zero-sized provider has no instance identity,
454// only the producing parser's ceilings may fund the hashes and comparisons,
455// and the archived signer is one of the root message's own (§5.5.1).
456fn admit<P>(
457    index: &HashIndex<'_>,
458    message: &SignedData<'_>,
459    signer: &SignerInfo<'_>,
460    provider: &P,
461    parent: &Reader<'_>,
462) -> Result<()> {
463    const FIELD: &str = "atsHashIndexV3";
464    let at = index.span().start;
465    crate::provider::require_instance(provider, FIELD, at)?;
466    message.require_session(parent, FIELD)?;
467    message.require_signer(signer, FIELD)?;
468    if !index.algorithm().same_session(parent) {
469        return Err(Error::pkix(structured_pkix::Error::new(
470            structured_pkix::ErrorCode::ForeignReader,
471            FIELD,
472            at,
473        )));
474    }
475    Ok(())
476}
477
478// The value hashes and, in the same order, each value's position and flag.
479type ValueHashes<O, W> = (OwnedVec<O, W>, OwnedVec<(AttributeValue, bool), W>);
480
481struct Hasher<'p, 'a, P: Digest<'a>> {
482    provider: &'a P,
483    identity: &'p P::Identity,
484    field: &'static str,
485    at: u64,
486}
487
488impl<'a, P: Digest<'a>> Hasher<'_, 'a, P> {
489    fn hash<W: Work>(
490        &self,
491        parts: &[&[u8]],
492        execution: &W,
493    ) -> core::result::Result<P::Output, DigestError<P::Error>> {
494        execution.charge_work(1).map_err(|reason| {
495            DigestError::Processing(Error::resource(reason, self.field, self.at))
496        })?;
497        let mut state = match self
498            .provider
499            .start(self.identity, execution)
500            .map_err(DigestError::Callback)?
501        {
502            Resolution::Supported(state) => state,
503            Resolution::Unsupported | Resolution::Malformed => {
504                return Err(DigestError::Unstreamable);
505            }
506        };
507        for part in parts {
508            self.provider
509                .update(&mut state, part, execution)
510                .map_err(DigestError::Callback)?;
511        }
512        let output = self
513            .provider
514            .finish(state, execution)
515            .map_err(DigestError::Callback)?;
516        if let Some(expected) = self.provider.width(self.identity) {
517            let actual = output.as_ref().len();
518            if actual != expected {
519                return Err(DigestError::OutputWidth { expected, actual });
520            }
521        }
522        Ok(output)
523    }
524
525    // One hash per whole encoding, in order, with exact storage up front.
526    fn hash_each<'e, W: Work>(
527        &self,
528        encodings: impl ExactSizeIterator<Item = &'e [u8]>,
529        execution: &W,
530    ) -> core::result::Result<OwnedVec<P::Output, W>, DigestError<P::Error>> {
531        let refusal =
532            |reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
533        let mut outputs = execution.vector();
534        outputs
535            .try_reserve_exact(encodings.len())
536            .map_err(refusal)?;
537        for encoding in encodings {
538            outputs
539                .try_push(self.hash(&[encoding], execution)?)
540                .map_err(refusal)?;
541        }
542        Ok(outputs)
543    }
544
545    // One hash per unsigned attribute value, over its attrType encoding and the
546    // value's encoding (§5.5.2), with the value's position and an unset flag.
547    fn hash_values<W: Work>(
548        &self,
549        attributes: &[Attribute<'_>],
550        execution: &W,
551    ) -> core::result::Result<ValueHashes<P::Output, W>, DigestError<P::Error>> {
552        let refusal =
553            |reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
554        let mut hashes = execution.vector();
555        let mut values = execution.vector();
556        for (attribute_ordinal, attribute) in attributes.iter().enumerate() {
557            // Each attribute inspected is paid, including one with no values.
558            execution.charge_work(1).map_err(refusal)?;
559            for (value_ordinal, value) in attribute.values().iter().enumerate() {
560                hashes.try_reserve(1).map_err(refusal)?;
561                values.try_reserve(1).map_err(refusal)?;
562                let hash = self.hash(&[attribute.kind_encoding(), value.as_bytes()], execution)?;
563                hashes.try_push(hash).map_err(refusal)?;
564                values
565                    .try_push((
566                        AttributeValue {
567                            attribute: attribute_ordinal,
568                            value: value_ordinal,
569                        },
570                        false,
571                    ))
572                    .map_err(refusal)?;
573            }
574        }
575        Ok((hashes, values))
576    }
577
578    fn matches<W: Work>(
579        &self,
580        output: &P::Output,
581        entry: &[u8],
582        execution: &W,
583    ) -> core::result::Result<bool, DigestError<P::Error>> {
584        self.provider
585            .matches(self.identity, output, entry, execution)
586            .map_err(DigestError::Callback)
587    }
588}
589
590// Sorts the element hashes once and finds, by binary search, the one hash each
591// entry could equal: the first not ordered before it, or the last hash when the
592// entry is ordered after all of them. The provider's own comparison then judges
593// that pair, so every entry reaches the provider whenever any hash exists, at
594// one provider comparison and log n byte comparisons. `Digest::matches` answers
595// exact byte equality (its strategy governs how bytes are compared, not which
596// differing bytes are equal), so no other hash can match an entry its nearest
597// one does not, and every hash need not be tried. Each equal-hash group has
598// one consumption cursor at its first sorted member: EN319122-1 §5.5.2
599// requires one index entry PER INSTANCE, not membership in a set. A missing
600// entry leaves one occurrence uncovered; an excess entry is unmatched.
601// https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
602struct Ranked {
603    ordinal: usize,
604    consumed: usize,
605}
606
607fn mark<'a, P: Digest<'a>>(
608    digest: &Hasher<'_, 'a, P>,
609    hashes: &[P::Output],
610    entries: &[&[u8]],
611    list: IndexList,
612    unmatched: &mut OwnedVec<Unmatched, structured_asn1::memory::Execution<'_>>,
613    execution: &impl Work,
614    refusal: impl Fn(structured_asn1::memory::MemoryError) -> DigestError<P::Error> + Copy,
615) -> core::result::Result<OwnedVec<bool>, DigestError<P::Error>> {
616    // The two linear passes below (sort order and coverage flags) are paid up
617    // front; the sort and the lookups pay their comparisons as they go.
618    execution
619        .charge_work(2 * hashes.len() as u64)
620        .map_err(refusal)?;
621    let mut order = execution.vector();
622    order.try_reserve_exact(hashes.len()).map_err(refusal)?;
623    for ordinal in 0..hashes.len() {
624        order
625            .try_push(Ranked {
626                ordinal,
627                consumed: 0,
628            })
629            .map_err(refusal)?;
630    }
631    order
632        .try_sort_unstable_by(|left, right| {
633            let compared = compare(
634                hashes[left.ordinal].as_ref(),
635                hashes[right.ordinal].as_ref(),
636                execution,
637            )?;
638            if compared == Ordering::Equal {
639                execution.charge_work(1)?;
640                // Consume indistinguishable instances in original order;
641                // unstable sort must not pick an arbitrary source occurrence.
642                Ok(left.ordinal.cmp(&right.ordinal))
643            } else {
644                Ok(compared)
645            }
646        })
647        .map_err(|error| match error {
648            SortError::Resource(reason) | SortError::Comparison(reason) => refusal(reason),
649        })?;
650    let mut covered = execution.vector();
651    covered.try_reserve_exact(hashes.len()).map_err(refusal)?;
652    for _ in 0..hashes.len() {
653        covered.try_push(false).map_err(refusal)?;
654    }
655    for (ordinal, entry) in entries.iter().enumerate() {
656        execution.charge_work(1).map_err(refusal)?;
657        let first = lower_bound(&order, hashes, entry, execution).map_err(refusal)?;
658        // No hash at all leaves nothing an entry could name.
659        let nearest = order.get(first).or_else(|| order.last());
660        let matched = match nearest {
661            Some(candidate) => digest.matches(&hashes[candidate.ordinal], entry, execution)?,
662            None => false,
663        };
664        if matched && first < order.len() {
665            debug_assert!(order[first].consumed <= order.len() - first);
666            let position = first + order[first].consumed;
667            if position < order.len()
668                && compare(hashes[order[position].ordinal].as_ref(), entry, execution)
669                    .map_err(refusal)?
670                    == Ordering::Equal
671            {
672                covered[order[position].ordinal] = true;
673                order[first].consumed += 1;
674                continue;
675            }
676        }
677        unmatched.try_reserve(1).map_err(refusal)?;
678        unmatched
679            .try_push(Unmatched { list, ordinal })
680            .map_err(refusal)?;
681    }
682    Ok(covered.into_owned())
683}
684
685fn lower_bound<O: AsRef<[u8]>>(
686    order: &[Ranked],
687    hashes: &[O],
688    entry: &[u8],
689    execution: &impl Work,
690) -> core::result::Result<usize, structured_asn1::memory::MemoryError> {
691    let mut from = 0;
692    let mut to = order.len();
693    while from < to {
694        let middle = from + (to - from) / 2;
695        if compare(hashes[order[middle].ordinal].as_ref(), entry, execution)? == Ordering::Less {
696            from = middle + 1;
697        } else {
698            to = middle;
699        }
700    }
701    Ok(from)
702}
703
704fn compare(
705    left: &[u8],
706    right: &[u8],
707    execution: &impl Work,
708) -> core::result::Result<Ordering, structured_asn1::memory::MemoryError> {
709    execution.charge_work(left.len().min(right.len()) as u64 + 1)?;
710    Ok(left.cmp(right))
711}
712
713/// The message-imprint input of an `archive-time-stamp-v3` (EN 319 122-1
714/// §5.5.3), as original ranges in their required order: the content type
715/// encoding, `content_hash`, the signer's `version` through `signature`
716/// encodings, and the `ATSHashIndexV3` encoding.
717///
718/// `content_hash` is the hash of the signed content, computed as for the
719/// `message-digest` attribute but with the time-stamp's imprint algorithm; for
720/// detached content the caller supplies it. The returned ranges are a
721/// [`crate::stream::Source`] for the token's
722/// [`crate::timestamp::MessageImprint::check_content`].
723///
724/// # Errors
725///
726/// A `signer` that is not one of `message`'s own signers.
727pub fn imprint_input<'s>(
728    message: &SignedData<'s>,
729    signer: &SignerInfo<'s>,
730    index: &HashIndex<'s>,
731    content_hash: &'s [u8],
732) -> Result<[&'s [u8]; 4]> {
733    message.require_signer(signer, "archiveTimeStampV3")?;
734    Ok([
735        message.content().kind_encoding(),
736        content_hash,
737        signer.signed_fields(),
738        index.as_bytes(),
739    ])
740}
741
742/// Whether the index algorithm is the time-stamp's imprint algorithm, as
743/// §5.5.2 requires. Both are compared as resolved standard digests; an
744/// algorithm either side does not name as one is answered as such.
745///
746/// # Errors
747///
748/// A spent work ceiling.
749pub fn same_algorithm<W: Work>(
750    index: &HashIndex<'_>,
751    imprint: &AlgorithmIdentifier<'_>,
752    execution: &W,
753) -> Result<Resolution<bool>> {
754    let left = asn_digest::algorithm(index.algorithm(), execution).map_err(Error::pkix)?;
755    let right = asn_digest::algorithm(imprint, execution).map_err(Error::pkix)?;
756    Ok(match (left, right) {
757        (Resolution::Supported(left), Resolution::Supported(right)) => {
758            Resolution::Supported(left == right)
759        }
760        (Resolution::Malformed, _) | (_, Resolution::Malformed) => Resolution::Malformed,
761        _ => Resolution::Unsupported,
762    })
763}