Skip to main content

structured_cms/timestamp/
imprint_check.rs

1//! Complete one-pass imprint agreement under the explicitly selected provider.
2
3use super::{HashAlgorithmReading, MessageImprint};
4use crate::Error;
5use crate::digest::{DigestBatch, DigestError, PassError};
6use crate::provider::DigestAlgorithm;
7use crate::stream::Source;
8use structured_asn1::Reader;
9use structured_asn1::digest::Resolution;
10use structured_asn1::memory::Work;
11use structured_pkix::algorithm::digest::AsnDigest;
12
13/// Imprint agreement alone does not establish token signature or authority trust.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ImprintAgreement {
16    /// The complete caller-supplied content agrees with the declared digest.
17    Matches,
18    /// A complete digest of that content disagrees with the declaration.
19    Differs,
20    /// Inspection retained a malformed imprint encoding or schema fault.
21    MalformedImprint,
22    /// The written algorithm violates the selected algorithm's parameter syntax.
23    MalformedAlgorithm,
24    /// A resolved fixed-output algorithm cannot produce the written hash width.
25    MalformedHash,
26    /// The selected provider cannot compute the explicit written algorithm.
27    Unsupported,
28}
29
30/// Failed execution stays distinct from semantic mismatch and unsupported input.
31#[derive(Debug)]
32pub enum ImprintError<SE, PE> {
33    /// Original common processing/resource failure.
34    Processing(Error),
35    /// Digest admission/finalization or provider output-contract failure.
36    Digest(DigestError<PE>),
37    /// Final output comparison or provider output-contract failure.
38    Comparison(crate::binding::BindError<PE>),
39    /// A source/common/provider failure prevents a complete content pass.
40    Pass(PassError<SE, PE>),
41    /// A prepared request was not present after successful common finalization.
42    LostRequest,
43}
44
45pub(super) enum Resolved<I> {
46    Ready(I),
47    Unusable(ImprintAgreement),
48}
49
50impl<'a> MessageImprint<'a> {
51    /// Computes the declared imprint over exactly one complete source pass.
52    ///
53    /// RFC3161 ยง2.4.1 identifies the hash by its complete written algorithm, not
54    /// by output length. Resolution, malformed syntax and known impossible width
55    /// precede state creation and source I/O. Finalized output is matched under
56    /// the same selected provider, without replay, re-resolution or rehashing.
57    /// Source/provider/resource failures cannot become `Differs`. Success is an
58    /// imprint fact only; signature, ESS, path, request and time acceptance remain
59    /// separate checks. <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.1>
60    pub fn check_content<P: AsnDigest<'a>, S: Source>(
61        &self,
62        provider: &'a P,
63        parent: &Reader<'a>,
64        source: &mut S,
65    ) -> Result<ImprintAgreement, ImprintError<S::Error, P::Error>> {
66        let resolved = self
67            .resolve(provider, parent)
68            .map_err(ImprintError::Processing)?;
69        match resolved {
70            Resolved::Ready(identity) => self.compute(provider, parent, source, identity),
71            Resolved::Unusable(answer) => Ok(answer),
72        }
73    }
74
75    pub(super) fn require_session(&self, parent: &Reader<'_>) -> crate::Result<()> {
76        if !core::ptr::eq(self.hashed().parser(), parent.parser()) {
77            return Err(Error::pkix(structured_pkix::Error::new(
78                structured_pkix::ErrorCode::ForeignReader,
79                self.fields().enclosing,
80                self.span().start,
81            )));
82        }
83        Ok(())
84    }
85
86    pub(super) fn resolve<P: AsnDigest<'a>>(
87        &self,
88        provider: &'a P,
89        parent: &Reader<'a>,
90    ) -> crate::Result<Resolved<P::Identity>> {
91        crate::provider::require_instance(provider, self.fields().enclosing, self.span().start)?;
92        self.require_session(parent)?;
93        if !self.faults().is_empty() {
94            return Ok(Resolved::Unusable(ImprintAgreement::MalformedImprint));
95        }
96        let HashAlgorithmReading::Identifier(identifier) = self.algorithm() else {
97            return Ok(Resolved::Unusable(ImprintAgreement::MalformedImprint));
98        };
99        let identity = match crate::provider::resolve_digest(
100            provider,
101            DigestAlgorithm::Written(*identifier),
102            parent,
103        )? {
104            Resolution::Supported(identity) => identity,
105            Resolution::Malformed => {
106                return Ok(Resolved::Unusable(ImprintAgreement::MalformedAlgorithm));
107            }
108            Resolution::Unsupported => {
109                return Ok(Resolved::Unusable(ImprintAgreement::Unsupported));
110            }
111        };
112        if let Some(width) = provider.width(&identity)
113            && self.hashed().len() != width
114        {
115            return Ok(Resolved::Unusable(ImprintAgreement::MalformedHash));
116        }
117        Ok(Resolved::Ready(identity))
118    }
119
120    pub(super) fn compute<P: AsnDigest<'a>, S: Source>(
121        &self,
122        provider: &'a P,
123        parent: &Reader<'a>,
124        source: &mut S,
125        identity: P::Identity,
126    ) -> Result<ImprintAgreement, ImprintError<S::Error, P::Error>> {
127        let field = self.fields().hashed;
128        let at = self.hashed().span().start;
129        let joined;
130        // Admit any expected-hash materialization before state or source effects.
131        let expected = if let Some(bytes) = self.hashed().as_one() {
132            bytes
133        } else {
134            joined = self
135                .hashed()
136                .copy_in(parent, field, at)
137                .map_err(ImprintError::Processing)?;
138            &*joined
139        };
140        let execution = parent.execution();
141        let mut identities = execution.vector();
142        identities
143            .try_push(identity)
144            .map_err(|reason| ImprintError::Processing(Error::resource(reason, field, at)))?;
145        let batch = DigestBatch::prepare(provider, identities.into_owned(), &execution, field, at)
146            .map_err(ImprintError::Digest)?;
147        let memo = batch.run(source, &execution).map_err(ImprintError::Pass)?;
148        let output = memo
149            .for_request(0, &execution)
150            .map_err(ImprintError::Digest)?
151            .ok_or(ImprintError::LostRequest)?;
152        let matches = crate::binding::matches_digest(
153            provider,
154            output.finished(),
155            expected,
156            &execution,
157            field,
158            self.hashed().span(),
159        )
160        .map_err(ImprintError::Comparison)?;
161        Ok(match matches {
162            crate::binding::DigestBinding::Matches => ImprintAgreement::Matches,
163            crate::binding::DigestBinding::Differs => ImprintAgreement::Differs,
164            crate::binding::DigestBinding::Malformed(_) => ImprintAgreement::MalformedHash,
165        })
166    }
167}