Skip to main content

structured_cms/timestamp/
requested.rs

1//! Full parsed-request imprint comparison with both original source contexts.
2
3use super::imprint_check::{ResolveError, Resolved};
4use super::{ImprintAgreement, MessageImprint};
5use crate::Error;
6use crate::provider::DigestProvider;
7use core::cmp::Ordering;
8use structured_asn1::{Reader, Span};
9
10/// Combined execution preserves request versus covered-content failure classes.
11#[derive(Debug)]
12pub enum RequestedContentError<SE, PE> {
13    /// Original request comparison refusal, with its operand/source role.
14    Request(RequestImprintError<PE>),
15    /// Original covered-source/digest refusal.
16    Content(super::ImprintError<SE, PE>),
17}
18
19/// Identifies which original source supplied an operand or located failure.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ImprintOperand {
22    /// The timestamp token's original carried information source.
23    Token,
24    /// The caller's original parsed request source, possibly another session.
25    Requested,
26}
27
28/// Complete imprint agreement, independently of other request checks.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum RequestAgreement {
31    /// Full selected-provider identities and logical declared digest bytes agree.
32    Matches,
33    /// Selected-provider algorithm/parameter/output identities disagree.
34    AlgorithmDiffers,
35    /// Complete algorithm identity agrees but declared digest bytes disagree.
36    HashDiffers,
37    /// An operand cannot establish a conforming supported imprint declaration.
38    Unusable {
39        /// Original source whose declaration cannot be compared successfully.
40        operand: ImprintOperand,
41        /// Malformed encoding/algorithm/width or unsupported mechanism.
42        reason: ImprintAgreement,
43    },
44}
45
46/// Request comparison failure retains source role and actual source span.
47#[derive(Debug)]
48pub enum RequestImprintError<E> {
49    /// Processing/resource refusal, not a semantic mismatch or request failure.
50    Processing {
51        /// Source to use when interpreting the located diagnostic.
52        operand: ImprintOperand,
53        /// Original common refusal in that source's reader coordinates.
54        source: Error,
55    },
56    /// Original selected-provider cause with honest original-source enclosure.
57    Callback {
58        /// Source to use for known inner reader diagnostics in the opaque cause.
59        operand: ImprintOperand,
60        /// Actual original enclosing imprint; it may use logical coordinates.
61        span: Span,
62        /// Unchanged callback cause, including provider resource refusal.
63        source: E,
64    },
65}
66
67impl<'a> MessageImprint<'a> {
68    /// Compares the full request and hashes covered bytes using one token identity.
69    /// Request bytes themselves are never hashed. Semantic request disagreement
70    /// remains independent of covered-content agreement; an unusable token
71    /// declaration starts no digest state or content pass. No identity Clone,
72    /// second token parameter resolve, state reconstruction or rehash is used.
73    pub fn check_requested_content<P: DigestProvider<'a>, S: crate::stream::Source>(
74        &self,
75        requested: &MessageImprint<'a>,
76        provider: &'a P,
77        parents: (&Reader<'a>, &Reader<'a>),
78        covered: &mut S,
79    ) -> Result<(ImprintAgreement, RequestAgreement), RequestedContentError<S::Error, P::Error>>
80    {
81        let (token_parent, requested_parent) = parents;
82        preflight(self, requested, token_parent, requested_parent)
83            .map_err(RequestedContentError::Request)?;
84        let token = match resolve(self, provider, token_parent, ImprintOperand::Token)
85            .map_err(RequestedContentError::Request)?
86        {
87            Resolved::Ready(identity) => identity,
88            Resolved::Unusable(reason) => {
89                return Ok((
90                    reason,
91                    RequestAgreement::Unusable {
92                        operand: ImprintOperand::Token,
93                        reason,
94                    },
95                ));
96            }
97        };
98        let agreement = compare_resolved(
99            self,
100            requested,
101            provider,
102            token_parent,
103            requested_parent,
104            &token,
105        )
106        .map_err(RequestedContentError::Request)?;
107        let content = self
108            .compute(provider, token_parent, covered, token)
109            .map_err(RequestedContentError::Content)?;
110        Ok((content, agreement))
111    }
112    /// Compares the entire parsed requested imprint under one selected provider.
113    ///
114    /// RFC3161 ยงยง2.2/2.4.2 requires the requested hash and algorithm. Both operands
115    /// use the same semantic resolution/known-width kernel as content checking.
116    /// Each reader must be from its own operand's session; legitimate different
117    /// sessions are allowed. Comparison uses the token's current execution;
118    /// resolution uses each operand's original local/current aggregate ceilings.
119    /// Core creates no hash state or content pass, fabricates no identifier and
120    /// performs no parameter serialization or replacement-parser decoding. The
121    /// provider reads original parameters under each producing reader's budget.
122    /// Malformed equal-width declarations cannot produce successful equality.
123    /// A joined token location uses its token source map; a requested location
124    /// uses its own original provenance. Nonce, policy, `certReq` and timeliness
125    /// remain separate checks. <https://www.rfc-editor.org/rfc/rfc3161#section-2.2>
126    pub fn matches_request<P: DigestProvider<'a>>(
127        &self,
128        requested: &MessageImprint<'a>,
129        provider: &'a P,
130        token_parent: &Reader<'a>,
131        requested_parent: &Reader<'a>,
132    ) -> Result<RequestAgreement, RequestImprintError<P::Error>> {
133        preflight(self, requested, token_parent, requested_parent)?;
134        let token = match resolve(self, provider, token_parent, ImprintOperand::Token)? {
135            Resolved::Ready(identity) => identity,
136            Resolved::Unusable(reason) => {
137                return Ok(RequestAgreement::Unusable {
138                    operand: ImprintOperand::Token,
139                    reason,
140                });
141            }
142        };
143        compare_resolved(
144            self,
145            requested,
146            provider,
147            token_parent,
148            requested_parent,
149            &token,
150        )
151    }
152}
153
154fn preflight<E>(
155    token: &MessageImprint<'_>,
156    requested: &MessageImprint<'_>,
157    token_parent: &Reader<'_>,
158    requested_parent: &Reader<'_>,
159) -> Result<(), RequestImprintError<E>> {
160    // Both operands are checked before callbacks, including unusable input.
161    for (operand, imprint, parent) in [
162        (ImprintOperand::Token, token, token_parent),
163        (ImprintOperand::Requested, requested, requested_parent),
164    ] {
165        imprint
166            .require_session(parent)
167            .map_err(|source| RequestImprintError::Processing { operand, source })?;
168    }
169    Ok(())
170}
171
172fn compare_resolved<'a, P: DigestProvider<'a>>(
173    token_imprint: &MessageImprint<'a>,
174    requested: &MessageImprint<'a>,
175    provider: &'a P,
176    token_parent: &Reader<'a>,
177    requested_parent: &Reader<'a>,
178    token: &P::Identity,
179) -> Result<RequestAgreement, RequestImprintError<P::Error>> {
180    let asked = match resolve(
181        requested,
182        provider,
183        requested_parent,
184        ImprintOperand::Requested,
185    )? {
186        Resolved::Ready(identity) => identity,
187        Resolved::Unusable(reason) => {
188            return Ok(RequestAgreement::Unusable {
189                operand: ImprintOperand::Requested,
190                reason,
191            });
192        }
193    };
194    let execution = token_parent.execution();
195    if provider
196        .compare_digest(token, &asked, &execution)
197        .map_err(|source| RequestImprintError::Callback {
198            operand: ImprintOperand::Token,
199            span: token_imprint.span(),
200            source,
201        })?
202        != Ordering::Equal
203    {
204        return Ok(RequestAgreement::AlgorithmDiffers);
205    }
206    let matches = token_imprint
207        .hashed()
208        .same_value(
209            requested.hashed(),
210            token_parent,
211            "tstInfo.messageImprint.hashedMessage",
212            token_imprint.hashed().span().start,
213        )
214        .map_err(|source| RequestImprintError::Processing {
215            operand: ImprintOperand::Token,
216            source,
217        })?;
218    Ok(if matches {
219        RequestAgreement::Matches
220    } else {
221        RequestAgreement::HashDiffers
222    })
223}
224
225fn resolve<'a, P: DigestProvider<'a>>(
226    imprint: &MessageImprint<'a>,
227    provider: &'a P,
228    parent: &Reader<'a>,
229    operand: ImprintOperand,
230) -> Result<Resolved<P::Identity>, RequestImprintError<P::Error>> {
231    imprint
232        .resolve(provider, parent)
233        .map_err(|error| match error {
234            ResolveError::Processing(source) => RequestImprintError::Processing { operand, source },
235            ResolveError::Callback(source) => RequestImprintError::Callback {
236                operand,
237                span: imprint.span(),
238                source,
239            },
240        })
241}