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::{DigestError, PassError};
6use crate::provider::DigestAlgorithm;
7use crate::stream::Source;
8use structured_asn1::Reader;
9use structured_asn1::digest::Resolution;
10use structured_pkix::algorithm::digest::AsnDigest;
11
12/// Imprint agreement alone does not establish token signature or authority trust.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ImprintAgreement {
15    /// The complete caller-supplied content agrees with the declared digest.
16    Matches,
17    /// A complete digest of that content disagrees with the declaration.
18    Differs,
19    /// Inspection retained a malformed imprint encoding or schema fault.
20    MalformedImprint,
21    /// The written algorithm violates the selected algorithm's parameter syntax.
22    MalformedAlgorithm,
23    /// A resolved fixed-output algorithm cannot produce the written hash width.
24    MalformedHash,
25    /// The selected provider cannot compute the explicit written algorithm.
26    Unsupported,
27}
28
29/// Failed execution stays distinct from semantic mismatch and unsupported input.
30#[derive(Debug)]
31pub enum ImprintError<SE, PE> {
32    /// Original common processing/resource failure.
33    Processing(Error),
34    /// Digest admission/finalization or provider output-contract failure.
35    Digest(DigestError<PE>),
36    /// Final output comparison or provider output-contract failure.
37    Comparison(crate::binding::BindError<PE>),
38    /// A source/common/provider failure prevents a complete content pass.
39    Pass(PassError<SE, PE>),
40    /// A prepared request was not present after successful common finalization.
41    LostRequest,
42    /// A recipe substituted another operation's state, output or receipt.
43    ForeignInput,
44    /// A recipe abandoned a state or returned refusal without a retained cause.
45    /// This is a caller/computation failure, never malformed document syntax.
46    IncompleteInput,
47}
48
49pub(super) enum Resolved<I> {
50    Ready(I),
51    Unusable(ImprintAgreement),
52}
53
54impl<'a> MessageImprint<'a> {
55    /// The same schema/provider/width preflight and closed final comparison
56    /// over an input that may await bounded original-range acquisition.
57    pub async fn check_input_async<P, I>(
58        &self,
59        provider: &'a P,
60        parent: &Reader<'a>,
61        input: &mut I,
62    ) -> Result<ImprintAgreement, ImprintError<I::Error, P::Error>>
63    where
64        P: AsnDigest<'a>,
65        I: super::AsyncComputedInput<P, P::Error>,
66    {
67        match self
68            .resolve(provider, parent)
69            .map_err(ImprintError::Processing)?
70        {
71            Resolved::Ready(identity) => {
72                super::computed::compute_async(self, provider, parent, input, identity).await
73            }
74            Resolved::Unusable(answer) => Ok(answer),
75        }
76    }
77    /// Computes the declared imprint over exactly one complete source pass.
78    ///
79    /// RFC3161 ยง2.4.1 identifies the hash by its complete written algorithm, not
80    /// by output length. Resolution, malformed syntax and known impossible width
81    /// precede state creation and source I/O. Finalized output is matched under
82    /// the same selected provider, without replay, re-resolution or rehashing.
83    /// Source/provider/resource failures cannot become `Differs`. Success is an
84    /// imprint fact only; signature, ESS, path, request and time acceptance remain
85    /// separate checks. <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.1>
86    pub fn check_content<P: AsnDigest<'a>, S: Source>(
87        &self,
88        provider: &'a P,
89        parent: &Reader<'a>,
90        source: &mut S,
91    ) -> Result<ImprintAgreement, ImprintError<S::Error, P::Error>> {
92        self.check_input(provider, parent, &mut super::RawInput::new(source))
93    }
94
95    /// Checks a complete computed input under the original written algorithm.
96    /// Alternative encodings or reduced hash trees reuse the same resolution,
97    /// width, current-work and closed final comparison as ordinary raw input.
98    /// No digest bytes/OID pair, externally writable boolean or unfinished
99    /// state can produce a successful comparison receipt.
100    pub fn check_input<P, I>(
101        &self,
102        provider: &'a P,
103        parent: &Reader<'a>,
104        input: &mut I,
105    ) -> Result<ImprintAgreement, ImprintError<I::Error, P::Error>>
106    where
107        P: AsnDigest<'a>,
108        I: super::ComputedInput<P, P::Error>,
109    {
110        let resolved = self
111            .resolve(provider, parent)
112            .map_err(ImprintError::Processing)?;
113        match resolved {
114            Resolved::Ready(identity) => self.compute_input(provider, parent, input, identity),
115            Resolved::Unusable(answer) => Ok(answer),
116        }
117    }
118
119    pub(super) fn require_session(&self, parent: &Reader<'_>) -> crate::Result<()> {
120        if !core::ptr::eq(self.hashed().parser(), parent.parser()) {
121            return Err(Error::pkix(structured_pkix::Error::new(
122                structured_pkix::ErrorCode::ForeignReader,
123                self.fields().enclosing,
124                self.span().start,
125            )));
126        }
127        Ok(())
128    }
129
130    pub(super) fn resolve<P: AsnDigest<'a>>(
131        &self,
132        provider: &'a P,
133        parent: &Reader<'a>,
134    ) -> crate::Result<Resolved<P::Identity>> {
135        crate::provider::require_instance(provider, self.fields().enclosing, self.span().start)?;
136        self.require_session(parent)?;
137        if !self.faults().is_empty() {
138            return Ok(Resolved::Unusable(ImprintAgreement::MalformedImprint));
139        }
140        let HashAlgorithmReading::Identifier(identifier) = self.algorithm() else {
141            return Ok(Resolved::Unusable(ImprintAgreement::MalformedImprint));
142        };
143        let identity = match crate::provider::resolve_digest(
144            provider,
145            DigestAlgorithm::Written(*identifier),
146            parent,
147        )? {
148            Resolution::Supported(identity) => identity,
149            Resolution::Malformed => {
150                return Ok(Resolved::Unusable(ImprintAgreement::MalformedAlgorithm));
151            }
152            Resolution::Unsupported => {
153                return Ok(Resolved::Unusable(ImprintAgreement::Unsupported));
154            }
155        };
156        if let Some(width) = provider.width(&identity)
157            && self.hashed().len() != width
158        {
159            return Ok(Resolved::Unusable(ImprintAgreement::MalformedHash));
160        }
161        Ok(Resolved::Ready(identity))
162    }
163
164    pub(super) fn compute_input<P, I>(
165        &self,
166        provider: &'a P,
167        parent: &Reader<'a>,
168        input: &mut I,
169        identity: P::Identity,
170    ) -> Result<ImprintAgreement, ImprintError<I::Error, P::Error>>
171    where
172        P: AsnDigest<'a>,
173        I: super::ComputedInput<P, P::Error>,
174    {
175        super::computed::compute(self, provider, parent, input, identity)
176    }
177}