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    /// Computes the declared imprint over exactly one complete source pass.
56    ///
57    /// RFC3161 ยง2.4.1 identifies the hash by its complete written algorithm, not
58    /// by output length. Resolution, malformed syntax and known impossible width
59    /// precede state creation and source I/O. Finalized output is matched under
60    /// the same selected provider, without replay, re-resolution or rehashing.
61    /// Source/provider/resource failures cannot become `Differs`. Success is an
62    /// imprint fact only; signature, ESS, path, request and time acceptance remain
63    /// separate checks. <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.1>
64    pub fn check_content<P: AsnDigest<'a>, S: Source>(
65        &self,
66        provider: &'a P,
67        parent: &Reader<'a>,
68        source: &mut S,
69    ) -> Result<ImprintAgreement, ImprintError<S::Error, P::Error>> {
70        self.check_input(provider, parent, &mut super::RawInput::new(source))
71    }
72
73    /// Checks a complete computed input under the original written algorithm.
74    /// Alternative encodings or reduced hash trees reuse the same resolution,
75    /// width, current-work and closed final comparison as ordinary raw input.
76    /// No digest bytes/OID pair, externally writable boolean or unfinished
77    /// state can produce a successful comparison receipt.
78    pub fn check_input<P, I>(
79        &self,
80        provider: &'a P,
81        parent: &Reader<'a>,
82        input: &mut I,
83    ) -> Result<ImprintAgreement, ImprintError<I::Error, P::Error>>
84    where
85        P: AsnDigest<'a>,
86        I: super::ComputedInput<P, P::Error>,
87    {
88        let resolved = self
89            .resolve(provider, parent)
90            .map_err(ImprintError::Processing)?;
91        match resolved {
92            Resolved::Ready(identity) => self.compute_input(provider, parent, input, identity),
93            Resolved::Unusable(answer) => Ok(answer),
94        }
95    }
96
97    pub(super) fn require_session(&self, parent: &Reader<'_>) -> crate::Result<()> {
98        if !core::ptr::eq(self.hashed().parser(), parent.parser()) {
99            return Err(Error::pkix(structured_pkix::Error::new(
100                structured_pkix::ErrorCode::ForeignReader,
101                self.fields().enclosing,
102                self.span().start,
103            )));
104        }
105        Ok(())
106    }
107
108    pub(super) fn resolve<P: AsnDigest<'a>>(
109        &self,
110        provider: &'a P,
111        parent: &Reader<'a>,
112    ) -> crate::Result<Resolved<P::Identity>> {
113        crate::provider::require_instance(provider, self.fields().enclosing, self.span().start)?;
114        self.require_session(parent)?;
115        if !self.faults().is_empty() {
116            return Ok(Resolved::Unusable(ImprintAgreement::MalformedImprint));
117        }
118        let HashAlgorithmReading::Identifier(identifier) = self.algorithm() else {
119            return Ok(Resolved::Unusable(ImprintAgreement::MalformedImprint));
120        };
121        let identity = match crate::provider::resolve_digest(
122            provider,
123            DigestAlgorithm::Written(*identifier),
124            parent,
125        )? {
126            Resolution::Supported(identity) => identity,
127            Resolution::Malformed => {
128                return Ok(Resolved::Unusable(ImprintAgreement::MalformedAlgorithm));
129            }
130            Resolution::Unsupported => {
131                return Ok(Resolved::Unusable(ImprintAgreement::Unsupported));
132            }
133        };
134        if let Some(width) = provider.width(&identity)
135            && self.hashed().len() != width
136        {
137            return Ok(Resolved::Unusable(ImprintAgreement::MalformedHash));
138        }
139        Ok(Resolved::Ready(identity))
140    }
141
142    pub(super) fn compute_input<P, I>(
143        &self,
144        provider: &'a P,
145        parent: &Reader<'a>,
146        input: &mut I,
147        identity: P::Identity,
148    ) -> Result<ImprintAgreement, ImprintError<I::Error, P::Error>>
149    where
150        P: AsnDigest<'a>,
151        I: super::ComputedInput<P, P::Error>,
152    {
153        super::computed::compute(self, provider, parent, input, identity)
154    }
155}