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    /// 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: AsnDigest<'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        self.check_requested_input(
82            requested,
83            provider,
84            parents,
85            &mut super::RawInput::new(covered),
86        )
87    }
88
89    /// Compares the parsed request and executes a computed input using the
90    /// SAME resolved token identity. Request identity/byte agreement remains
91    /// independent of the covered-input result; request bytes are not hashed.
92    pub fn check_requested_input<P, I>(
93        &self,
94        requested: &MessageImprint<'a>,
95        provider: &'a P,
96        parents: (&Reader<'a>, &Reader<'a>),
97        covered: &mut I,
98    ) -> Result<(ImprintAgreement, RequestAgreement), RequestedContentError<I::Error, P::Error>>
99    where
100        P: AsnDigest<'a>,
101        I: super::ComputedInput<P, P::Error>,
102    {
103        let (token_parent, requested_parent) = parents;
104        preflight(self, requested, token_parent, requested_parent)
105            .map_err(RequestedContentError::Request)?;
106        let token = match resolve(self, provider, token_parent, ImprintOperand::Token)
107            .map_err(RequestedContentError::Request)?
108        {
109            Resolved::Ready(identity) => identity,
110            Resolved::Unusable(reason) => {
111                return Ok((
112                    reason,
113                    RequestAgreement::Unusable {
114                        operand: ImprintOperand::Token,
115                        reason,
116                    },
117                ));
118            }
119        };
120        let agreement = compare_resolved(
121            self,
122            requested,
123            provider,
124            token_parent,
125            requested_parent,
126            &token,
127        )
128        .map_err(RequestedContentError::Request)?;
129        let content = self
130            .compute_input(provider, token_parent, covered, token)
131            .map_err(RequestedContentError::Content)?;
132        Ok((content, agreement))
133    }
134    /// Compares the entire parsed requested imprint under one selected provider.
135    ///
136    /// RFC3161 ยงยง2.2/2.4.2 requires the requested hash and algorithm. Both operands
137    /// use the same semantic resolution/known-width kernel as content checking.
138    /// Each reader must be from its own operand's session; legitimate different
139    /// sessions are allowed. Comparison uses the token's current execution;
140    /// resolution uses each operand's original local/current aggregate ceilings.
141    /// Core creates no hash state or content pass, fabricates no identifier and
142    /// performs no parameter serialization or replacement-parser decoding. The
143    /// provider reads original parameters under each producing reader's budget.
144    /// Malformed equal-width declarations cannot produce successful equality.
145    /// A joined token location uses its token source map; a requested location
146    /// uses its own original provenance. Nonce, policy, `certReq` and timeliness
147    /// remain separate checks. <https://www.rfc-editor.org/rfc/rfc3161#section-2.2>
148    pub fn matches_request<P: AsnDigest<'a>>(
149        &self,
150        requested: &MessageImprint<'a>,
151        provider: &'a P,
152        token_parent: &Reader<'a>,
153        requested_parent: &Reader<'a>,
154    ) -> Result<RequestAgreement, RequestImprintError<P::Error>> {
155        preflight(self, requested, token_parent, requested_parent)?;
156        let token = match resolve(self, provider, token_parent, ImprintOperand::Token)? {
157            Resolved::Ready(identity) => identity,
158            Resolved::Unusable(reason) => {
159                return Ok(RequestAgreement::Unusable {
160                    operand: ImprintOperand::Token,
161                    reason,
162                });
163            }
164        };
165        compare_resolved(
166            self,
167            requested,
168            provider,
169            token_parent,
170            requested_parent,
171            &token,
172        )
173    }
174}
175
176fn preflight<E>(
177    token: &MessageImprint<'_>,
178    requested: &MessageImprint<'_>,
179    token_parent: &Reader<'_>,
180    requested_parent: &Reader<'_>,
181) -> Result<(), RequestImprintError<E>> {
182    // Both operands are checked before callbacks, including unusable input.
183    for (operand, imprint, parent) in [
184        (ImprintOperand::Token, token, token_parent),
185        (ImprintOperand::Requested, requested, requested_parent),
186    ] {
187        imprint
188            .require_session(parent)
189            .map_err(|source| RequestImprintError::Processing { operand, source })?;
190    }
191    Ok(())
192}
193
194fn compare_resolved<'a, P: AsnDigest<'a>>(
195    token_imprint: &MessageImprint<'a>,
196    requested: &MessageImprint<'a>,
197    provider: &'a P,
198    token_parent: &Reader<'a>,
199    requested_parent: &Reader<'a>,
200    token: &P::Identity,
201) -> Result<RequestAgreement, RequestImprintError<P::Error>> {
202    let asked = match resolve(
203        requested,
204        provider,
205        requested_parent,
206        ImprintOperand::Requested,
207    )? {
208        Resolved::Ready(identity) => identity,
209        Resolved::Unusable(reason) => {
210            return Ok(RequestAgreement::Unusable {
211                operand: ImprintOperand::Requested,
212                reason,
213            });
214        }
215    };
216    let execution = token_parent.execution();
217    if provider
218        .compare(token, &asked, &execution)
219        .map_err(|source| RequestImprintError::Callback {
220            operand: ImprintOperand::Token,
221            span: token_imprint.span(),
222            source,
223        })?
224        != Ordering::Equal
225    {
226        return Ok(RequestAgreement::AlgorithmDiffers);
227    }
228    let matches = token_imprint
229        .hashed()
230        .same_value(
231            requested.hashed(),
232            token_parent,
233            "tstInfo.messageImprint.hashedMessage",
234            token_imprint.hashed().span().start,
235        )
236        .map_err(|source| RequestImprintError::Processing {
237            operand: ImprintOperand::Token,
238            source,
239        })?;
240    Ok(if matches {
241        RequestAgreement::Matches
242    } else {
243        RequestAgreement::HashDiffers
244    })
245}
246
247fn resolve<'a, P: AsnDigest<'a>>(
248    imprint: &MessageImprint<'a>,
249    provider: &'a P,
250    parent: &Reader<'a>,
251    operand: ImprintOperand,
252) -> Result<Resolved<P::Identity>, RequestImprintError<P::Error>> {
253    imprint
254        .resolve(provider, parent)
255        .map_err(|source| RequestImprintError::Processing { operand, source })
256}