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::Resolved;
4use super::{ImprintAgreement, MessageImprint};
5use crate::Error;
6use core::cmp::Ordering;
7use structured_asn1::{Reader, Span};
8use structured_pkix::algorithm::digest::AsnDigest;
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    /// Asynchronous covered input, retaining both original request/token
69    /// contexts and using one resolved token identity for the complete pass.
70    pub async fn check_requested_input_async<P, I>(
71        &self,
72        requested: &MessageImprint<'a>,
73        provider: &'a P,
74        parents: (&Reader<'a>, &Reader<'a>),
75        covered: &mut I,
76    ) -> Result<(ImprintAgreement, RequestAgreement), RequestedContentError<I::Error, P::Error>>
77    where
78        P: AsnDigest<'a>,
79        I: super::AsyncComputedInput<P, 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 = super::computed::compute_async(self, provider, token_parent, covered, token)
108            .await
109            .map_err(RequestedContentError::Content)?;
110        Ok((content, agreement))
111    }
112    /// Compares the full request and hashes covered bytes using one token identity.
113    /// Request bytes themselves are never hashed. Semantic request disagreement
114    /// remains independent of covered-content agreement; an unusable token
115    /// declaration starts no digest state or content pass. No identity Clone,
116    /// second token parameter resolve, state reconstruction or rehash is used.
117    pub fn check_requested_content<P: AsnDigest<'a>, S: crate::stream::Source>(
118        &self,
119        requested: &MessageImprint<'a>,
120        provider: &'a P,
121        parents: (&Reader<'a>, &Reader<'a>),
122        covered: &mut S,
123    ) -> Result<(ImprintAgreement, RequestAgreement), RequestedContentError<S::Error, P::Error>>
124    {
125        self.check_requested_input(
126            requested,
127            provider,
128            parents,
129            &mut super::RawInput::new(covered),
130        )
131    }
132
133    /// Compares the parsed request and executes a computed input using the
134    /// SAME resolved token identity. Request identity/byte agreement remains
135    /// independent of the covered-input result; request bytes are not hashed.
136    pub fn check_requested_input<P, I>(
137        &self,
138        requested: &MessageImprint<'a>,
139        provider: &'a P,
140        parents: (&Reader<'a>, &Reader<'a>),
141        covered: &mut I,
142    ) -> Result<(ImprintAgreement, RequestAgreement), RequestedContentError<I::Error, P::Error>>
143    where
144        P: AsnDigest<'a>,
145        I: super::ComputedInput<P, P::Error>,
146    {
147        let (token_parent, requested_parent) = parents;
148        preflight(self, requested, token_parent, requested_parent)
149            .map_err(RequestedContentError::Request)?;
150        let token = match resolve(self, provider, token_parent, ImprintOperand::Token)
151            .map_err(RequestedContentError::Request)?
152        {
153            Resolved::Ready(identity) => identity,
154            Resolved::Unusable(reason) => {
155                return Ok((
156                    reason,
157                    RequestAgreement::Unusable {
158                        operand: ImprintOperand::Token,
159                        reason,
160                    },
161                ));
162            }
163        };
164        let agreement = compare_resolved(
165            self,
166            requested,
167            provider,
168            token_parent,
169            requested_parent,
170            &token,
171        )
172        .map_err(RequestedContentError::Request)?;
173        let content = self
174            .compute_input(provider, token_parent, covered, token)
175            .map_err(RequestedContentError::Content)?;
176        Ok((content, agreement))
177    }
178    /// Compares the entire parsed requested imprint under one selected provider.
179    ///
180    /// RFC3161 ยงยง2.2/2.4.2 requires the requested hash and algorithm. Both operands
181    /// use the same semantic resolution/known-width kernel as content checking.
182    /// Each reader must be from its own operand's session; legitimate different
183    /// sessions are allowed. Comparison uses the token's current execution;
184    /// resolution uses each operand's original local/current aggregate ceilings.
185    /// Core creates no hash state or content pass, fabricates no identifier and
186    /// performs no parameter serialization or replacement-parser decoding. The
187    /// provider reads original parameters under each producing reader's budget.
188    /// Malformed equal-width declarations cannot produce successful equality.
189    /// A joined token location uses its token source map; a requested location
190    /// uses its own original provenance. Nonce, policy, `certReq` and timeliness
191    /// remain separate checks. <https://www.rfc-editor.org/rfc/rfc3161#section-2.2>
192    pub fn matches_request<P: AsnDigest<'a>>(
193        &self,
194        requested: &MessageImprint<'a>,
195        provider: &'a P,
196        token_parent: &Reader<'a>,
197        requested_parent: &Reader<'a>,
198    ) -> Result<RequestAgreement, RequestImprintError<P::Error>> {
199        preflight(self, requested, token_parent, requested_parent)?;
200        let token = match resolve(self, provider, token_parent, ImprintOperand::Token)? {
201            Resolved::Ready(identity) => identity,
202            Resolved::Unusable(reason) => {
203                return Ok(RequestAgreement::Unusable {
204                    operand: ImprintOperand::Token,
205                    reason,
206                });
207            }
208        };
209        compare_resolved(
210            self,
211            requested,
212            provider,
213            token_parent,
214            requested_parent,
215            &token,
216        )
217    }
218}
219
220fn preflight<E>(
221    token: &MessageImprint<'_>,
222    requested: &MessageImprint<'_>,
223    token_parent: &Reader<'_>,
224    requested_parent: &Reader<'_>,
225) -> Result<(), RequestImprintError<E>> {
226    // Both operands are checked before callbacks, including unusable input.
227    for (operand, imprint, parent) in [
228        (ImprintOperand::Token, token, token_parent),
229        (ImprintOperand::Requested, requested, requested_parent),
230    ] {
231        imprint
232            .require_session(parent)
233            .map_err(|source| RequestImprintError::Processing { operand, source })?;
234    }
235    Ok(())
236}
237
238fn compare_resolved<'a, P: AsnDigest<'a>>(
239    token_imprint: &MessageImprint<'a>,
240    requested: &MessageImprint<'a>,
241    provider: &'a P,
242    token_parent: &Reader<'a>,
243    requested_parent: &Reader<'a>,
244    token: &P::Identity,
245) -> Result<RequestAgreement, RequestImprintError<P::Error>> {
246    let asked = match resolve(
247        requested,
248        provider,
249        requested_parent,
250        ImprintOperand::Requested,
251    )? {
252        Resolved::Ready(identity) => identity,
253        Resolved::Unusable(reason) => {
254            return Ok(RequestAgreement::Unusable {
255                operand: ImprintOperand::Requested,
256                reason,
257            });
258        }
259    };
260    let execution = token_parent.execution();
261    if provider
262        .compare(token, &asked, &execution)
263        .map_err(|source| RequestImprintError::Callback {
264            operand: ImprintOperand::Token,
265            span: token_imprint.span(),
266            source,
267        })?
268        != Ordering::Equal
269    {
270        return Ok(RequestAgreement::AlgorithmDiffers);
271    }
272    let matches = token_imprint
273        .hashed()
274        .same_value(
275            requested.hashed(),
276            token_parent,
277            "tstInfo.messageImprint.hashedMessage",
278            token_imprint.hashed().span().start,
279        )
280        .map_err(|source| RequestImprintError::Processing {
281            operand: ImprintOperand::Token,
282            source,
283        })?;
284    Ok(if matches {
285        RequestAgreement::Matches
286    } else {
287        RequestAgreement::HashDiffers
288    })
289}
290
291fn resolve<'a, P: AsnDigest<'a>>(
292    imprint: &MessageImprint<'a>,
293    provider: &'a P,
294    parent: &Reader<'a>,
295    operand: ImprintOperand,
296) -> Result<Resolved<P::Identity>, RequestImprintError<P::Error>> {
297    imprint
298        .resolve(provider, parent)
299        .map_err(|source| RequestImprintError::Processing { operand, source })
300}