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