Skip to main content

structured_cms/
verify.rs

1//! Selected-signer arithmetic and content facts from one complete payload pass.
2
3use crate::binding::{self, BindError, DigestBinding};
4use crate::digest::{DigestBatch, DigestError};
5use crate::payload::{PayloadBatch, PayloadError, RawRequest};
6use crate::prepare::{ResolvedBinding, ResolvedSigner};
7use crate::provider::{SignatureAnswer, SignatureMode, SignatureProvider};
8use crate::signer::SignerInfo;
9use crate::stream::{self, RangesError, Sink, Source, StreamError};
10use crate::{Error, ErrorCode};
11use structured_asn1::memory::{OwnedVec, Work};
12use structured_pkix::path::WorkingKey;
13
14mod document;
15pub use document::{ContentMode, DocumentBatch, DocumentChecks};
16
17/// Failure to execute, distinct from per-signer arithmetic/content answers.
18#[derive(Debug)]
19pub enum CheckError<E> {
20    /// Common processing admission failed at its original source location.
21    Processing(Error),
22    /// Original selected-provider failure, including its resource refusal.
23    Callback(E),
24    /// Completed provider bytes contradict their full resolved fixed width.
25    OutputWidth {
26        /// Width promised by the resolved provider identity.
27        expected: usize,
28        /// Actual completed output width.
29        actual: usize,
30    },
31    /// The provider resolved the digest but cannot stream it.
32    Unstreamable,
33}
34
35/// Original payload acquisition failure remains distinct from execution failure.
36pub type PassError<SE, PE> = StreamError<SE, CheckError<PE>>;
37
38/// Content agreement is separate from signature arithmetic and certificate trust.
39#[derive(Debug)]
40pub enum ContentAgreement {
41    /// Original logical payload was the signature input, without signedAttrs.
42    Direct,
43    /// Payload digest agrees with the original message-digest declaration.
44    Matches,
45    /// Correctly sized payload digest differs; no attrs signature was attempted.
46    Differs,
47    /// Declared width cannot match the actual completed output.
48    Malformed(Error),
49}
50
51/// Facts for one original signer, without SID, path or application acceptance.
52pub struct Checked<'a> {
53    signer: &'a SignerInfo<'a>,
54    key: WorkingKey<'a>,
55    content: ContentAgreement,
56    signature: Option<SignatureAnswer>,
57}
58
59impl<'a> Checked<'a> {
60    /// Moves complete content/arithmetic facts out of a scoped signer check.
61    /// This avoids cloning a malformed-content diagnostic when an enclosing
62    /// format retains the facts beyond its temporary parsed signer model.
63    #[must_use]
64    pub fn into_parts(
65        self,
66    ) -> (
67        &'a SignerInfo<'a>,
68        WorkingKey<'a>,
69        ContentAgreement,
70        Option<SignatureAnswer>,
71    ) {
72        (self.signer, self.key, self.content, self.signature)
73    }
74    /// Original signer and retained conformance faults; nothing is repaired.
75    #[must_use]
76    pub fn signer(&self) -> &'a SignerInfo<'a> {
77        self.signer
78    }
79    /// The exact effective key supplied to this signature challenge, including
80    /// inherited parameters. This descriptor is not a certificate trust verdict.
81    #[must_use]
82    pub fn key(&self) -> WorkingKey<'a> {
83        self.key
84    }
85    /// Actual content binding, independently of signature arithmetic.
86    #[must_use]
87    pub fn content(&self) -> &ContentAgreement {
88        &self.content
89    }
90    /// None when an attribute imprint differs or is malformed.
91    /// A Valid answer never asserts conformance, signer identity or trust.
92    #[must_use]
93    pub fn signature(&self) -> Option<SignatureAnswer> {
94        self.signature
95    }
96}
97
98enum Challenge<'a, P: SignatureProvider<'a>> {
99    RawPayload(usize),
100    RawAttributes {
101        digest: usize,
102        state: P::RawState,
103    },
104    Prehashed {
105        digest: usize,
106        identity: P::SignatureIdentity,
107    },
108}
109
110struct Pending<'a, P: SignatureProvider<'a>> {
111    signer: &'a SignerInfo<'a>,
112    key: WorkingKey<'a>,
113    binding: ResolvedBinding<'a>,
114    challenge: Challenge<'a, P>,
115}
116
117/// Resolved requests bound to one exact provider instance and original ranges.
118///
119/// Call `prepare::resolve` for every selected signer before constructing this
120/// batch. Unsupported/malformed preparation answers remain independent facts
121/// in the caller; only Ready requests can enter this driver. Selection, SID
122/// binding, ESS/path restrictions and multi-signer policy remain separate.
123pub struct Batch<'a, P: SignatureProvider<'a>> {
124    provider: &'a P,
125    payload: PayloadBatch<'a, P>,
126    pending: OwnedVec<Pending<'a, P>>,
127    field: &'static str,
128    at: u64,
129}
130
131impl<'a, P: SignatureProvider<'a>> Batch<'a, P> {
132    /// Admits metadata and verifies provider identity before initializing states.
133    ///
134    /// Raw direct signatures need no unused digest state, but their declared
135    /// digestAlgorithm was still resolved by `prepare::resolve` before this call.
136    /// Raw states receive original signatures/effective keys before payload I/O;
137    /// signedAttrs states never receive payload bytes in place of attrs bytes.
138    pub fn prepare<W: Work>(
139        provider: &'a P,
140        requests: OwnedVec<ResolvedSigner<'a, P>>,
141        execution: &W,
142        field: &'static str,
143        at: u64,
144    ) -> Result<Self, CheckError<P::Error>> {
145        let refusal = |reason| CheckError::Processing(Error::resource(reason, field, at));
146        let mut digest_count = 0;
147        let mut raw_count = 0;
148        for request in &requests {
149            execution.charge_work(1).map_err(refusal)?;
150            if !core::ptr::eq(provider, request.provider()) {
151                return Err(CheckError::Processing(Error::schema(
152                    ErrorCode::ProviderMismatch,
153                    field,
154                    request.signer().span().start,
155                )));
156            }
157            if matches!(request.signature(), SignatureMode::Raw(_))
158                && matches!(request.binding(), ResolvedBinding::Direct)
159            {
160                raw_count += 1;
161            } else {
162                digest_count += 1;
163            }
164        }
165        let mut pending = execution.vector();
166        let mut identities = execution.vector();
167        let mut raw = execution.vector();
168        pending.try_reserve_exact(requests.len()).map_err(refusal)?;
169        identities
170            .try_reserve_exact(digest_count)
171            .map_err(refusal)?;
172        raw.try_reserve_exact(raw_count).map_err(refusal)?;
173        execution
174            .charge_work(core::mem::size_of_val(&*requests) as u64)
175            .map_err(refusal)?;
176        for request in requests {
177            execution.charge_work(1).map_err(refusal)?;
178            let parts = request.into_parts();
179            let challenge = match parts.signature {
180                SignatureMode::Raw(identity)
181                    if matches!(parts.binding, ResolvedBinding::Direct) =>
182                {
183                    let index = raw.len();
184                    raw.try_push(RawRequest {
185                        identity,
186                        key: parts.key,
187                        signature: parts.signer.signature(),
188                    })
189                    .map_err(refusal)?;
190                    Challenge::RawPayload(index)
191                }
192                SignatureMode::Raw(identity) => {
193                    let digest = identities.len();
194                    identities.try_push(parts.digest).map_err(refusal)?;
195                    let state = provider
196                        .start_raw(&identity, parts.key, parts.signer.signature(), execution)
197                        .map_err(CheckError::Callback)?;
198                    Challenge::RawAttributes { digest, state }
199                }
200                SignatureMode::Prehashed(identity) => {
201                    let digest = identities.len();
202                    identities.try_push(parts.digest).map_err(refusal)?;
203                    Challenge::Prehashed { digest, identity }
204                }
205            };
206            pending
207                .try_push(Pending {
208                    signer: parts.signer,
209                    key: parts.key,
210                    binding: parts.binding,
211                    challenge,
212                })
213                .map_err(refusal)?;
214        }
215        let digests = DigestBatch::prepare(provider, identities.into_owned(), execution, field, at)
216            .map_err(digest_error)?;
217        let payload =
218            PayloadBatch::prepare(digests, raw.into_owned(), execution).map_err(payload_error)?;
219        Ok(Self {
220            provider,
221            payload,
222            pending: pending.into_owned(),
223            field,
224            at,
225        })
226    }
227
228    /// Executes one payload pass and returns facts only after complete successful EOF.
229    ///
230    /// Original retagged signedAttrs have their own signature input. Their
231    /// message-digest is compared against the payload output first (RFC5652
232    /// §§5.4–5.6), then raw attrs or its separately finalized typed prehash is
233    /// checked. No identity/state Clone, payload replay or re-encoding occurs.
234    /// <https://www.rfc-editor.org/rfc/rfc5652#section-5.4>
235    pub fn run<S: Source, W: Work>(
236        self,
237        source: &mut S,
238        execution: &W,
239    ) -> Result<OwnedVec<Checked<'a>>, PassError<S::Error, P::Error>> {
240        let refusal =
241            |reason| StreamError::Processing(Error::resource(reason, self.field, self.at));
242        let mut results = execution.vector();
243        results
244            .try_reserve_exact(self.pending.len())
245            .map_err(refusal)?;
246        // With no selected Ready challenges there is no reason to acquire bytes.
247        if self.pending.is_empty() {
248            return Ok(results.into_owned());
249        }
250        let completed = self
251            .payload
252            .run(source, execution)
253            .map_err(|error| match error {
254                StreamError::Source(error) => StreamError::Source(error),
255                StreamError::Processing(error) => StreamError::Processing(error),
256                StreamError::Callback(error) => StreamError::Callback(payload_error(error)),
257            })?;
258        finish(
259            self.provider,
260            self.pending,
261            &completed,
262            results,
263            execution,
264            self.field,
265            self.at,
266        )
267    }
268
269    /// Executes the same selected-signer checks over an asynchronous source.
270    /// No-ready batches acquire nothing; signedAttrs still use their original
271    /// independently hashed signature input after payload agreement.
272    pub async fn run_async<S: stream::AsyncSource, W: Work>(
273        self,
274        source: &mut S,
275        execution: &W,
276    ) -> Result<OwnedVec<Checked<'a>>, PassError<S::Error, P::Error>> {
277        let refusal = |cause| StreamError::Processing(Error::resource(cause, self.field, self.at));
278        let mut results = execution.vector();
279        results
280            .try_reserve_exact(self.pending.len())
281            .map_err(refusal)?;
282        if self.pending.is_empty() {
283            return Ok(results.into_owned());
284        }
285        let completed =
286            self.payload
287                .run_async(source, execution)
288                .await
289                .map_err(|error| match error {
290                    StreamError::Source(error) => StreamError::Source(error),
291                    StreamError::Processing(error) => StreamError::Processing(error),
292                    StreamError::Callback(error) => StreamError::Callback(payload_error(error)),
293                })?;
294        finish(
295            self.provider,
296            self.pending,
297            &completed,
298            results,
299            execution,
300            self.field,
301            self.at,
302        )
303    }
304}
305
306fn finish<'a, P: SignatureProvider<'a>, SE, W: Work>(
307    provider: &'a P,
308    pending: OwnedVec<Pending<'a, P>>,
309    completed: &crate::payload::Completed<'a, P>,
310    mut results: OwnedVec<Checked<'a>, W>,
311    execution: &W,
312    field: &'static str,
313    at: u64,
314) -> Result<OwnedVec<Checked<'a>>, PassError<SE, P::Error>> {
315    let refusal = |cause| StreamError::Processing(Error::resource(cause, field, at));
316    execution
317        .charge_work(core::mem::size_of_val(&*pending) as u64)
318        .map_err(refusal)?;
319    for request in pending {
320        execution.charge_work(1).map_err(refusal)?;
321        let (content, signature) = check(
322            provider,
323            request.signer,
324            request.key,
325            &request.binding,
326            request.challenge,
327            completed,
328            execution,
329        )
330        .map_err(StreamError::Callback)?;
331        results
332            .try_push(Checked {
333                signer: request.signer,
334                key: request.key,
335                content,
336                signature,
337            })
338            .map_err(refusal)?;
339    }
340    Ok(results.into_owned())
341}
342
343fn check<'a, P: SignatureProvider<'a>, W: Work>(
344    provider: &'a P,
345    signer: &'a SignerInfo<'a>,
346    key: WorkingKey<'a>,
347    binding: &ResolvedBinding<'a>,
348    challenge: Challenge<'a, P>,
349    completed: &crate::payload::Completed<'a, P>,
350    execution: &W,
351) -> Result<(ContentAgreement, Option<SignatureAnswer>), CheckError<P::Error>> {
352    let digest = match &challenge {
353        Challenge::RawPayload(index) => {
354            debug_assert!(*index < completed.raw().len());
355            return Ok((ContentAgreement::Direct, Some(completed.raw()[*index])));
356        }
357        Challenge::RawAttributes { digest, .. } | Challenge::Prehashed { digest, .. } => *digest,
358    };
359    let payload = completed
360        .digests()
361        .for_request(digest, execution)
362        .map_err(digest_error)?;
363    let payload = payload.expect("prepared request ordinal has a completed output");
364    if let ResolvedBinding::Attributes {
365        digest: expected,
366        digest_span,
367    } = binding
368    {
369        match binding::matches_digest(
370            provider,
371            payload.finished(),
372            expected,
373            execution,
374            "signerInfo.signedAttrs.messageDigest",
375            *digest_span,
376        )
377        .map_err(bind_error)?
378        {
379            DigestBinding::Differs => return Ok((ContentAgreement::Differs, None)),
380            DigestBinding::Malformed(error) => {
381                return Ok((ContentAgreement::Malformed(error), None));
382            }
383            DigestBinding::Matches => {}
384        }
385        let attributes = signer
386            .signed_attributes()
387            .expect("prepared attrs binding retained its original attrs");
388        let mut ranges = attributes
389            .signed_bytes()
390            .expect("prepared signed attrs have SET signature ranges");
391        let signature = match challenge {
392            Challenge::RawAttributes { mut state, .. } => {
393                stream::run(
394                    &mut ranges,
395                    &mut RawSink {
396                        provider,
397                        state: &mut state,
398                    },
399                    execution,
400                    "signerInfo.signedAttrs",
401                    attributes.element().span().start,
402                )
403                .map_err(|error| range_error(error, attributes.element().span().start))?;
404                provider
405                    .finish_raw(state, execution)
406                    .map_err(CheckError::Callback)?
407            }
408            Challenge::Prehashed { identity, .. } => {
409                let related = completed
410                    .digests()
411                    .digest_related(
412                        digest,
413                        &mut ranges,
414                        execution,
415                        "signerInfo.signedAttrs",
416                        attributes.element().span().start,
417                    )
418                    .map_err(|error| related_error(error, attributes.element().span().start))?
419                    .expect("prepared request ordinal retains its identity");
420                provider
421                    .verify_prehashed(
422                        &identity,
423                        key,
424                        signer.signature(),
425                        related.prehash(),
426                        execution,
427                    )
428                    .map_err(CheckError::Callback)?
429            }
430            Challenge::RawPayload(_) => {
431                unreachable!("raw payload challenges returned before attrs processing")
432            }
433        };
434        return Ok((ContentAgreement::Matches, Some(signature)));
435    }
436    let Challenge::Prehashed { identity, .. } = challenge else {
437        unreachable!("only prehashed direct input reaches the digest branch");
438    };
439    let signature = provider
440        .verify_prehashed(
441            &identity,
442            key,
443            signer.signature(),
444            payload.prehash(),
445            execution,
446        )
447        .map_err(CheckError::Callback)?;
448    Ok((ContentAgreement::Direct, Some(signature)))
449}
450
451struct RawSink<'s, 'a, P: SignatureProvider<'a>> {
452    provider: &'a P,
453    state: &'s mut P::RawState,
454}
455impl<'a, P: SignatureProvider<'a>> Sink for RawSink<'_, 'a, P> {
456    type Error = P::Error;
457    fn write<W: Work>(&mut self, bytes: &[u8], execution: &W) -> Result<(), Self::Error> {
458        self.provider.update_raw(self.state, bytes, execution)
459    }
460}
461
462fn digest_error<E>(error: DigestError<E>) -> CheckError<E> {
463    match error {
464        DigestError::Processing(error) => CheckError::Processing(error),
465        DigestError::Callback(error) => CheckError::Callback(error),
466        DigestError::OutputWidth { expected, actual } => {
467            CheckError::OutputWidth { expected, actual }
468        }
469        DigestError::Unstreamable => CheckError::Unstreamable,
470    }
471}
472fn payload_error<E>(error: PayloadError<E>) -> CheckError<E> {
473    match error {
474        PayloadError::Processing(error) => CheckError::Processing(error),
475        PayloadError::Callback(error) => CheckError::Callback(error),
476        PayloadError::Digest(error) => digest_error(error),
477    }
478}
479fn bind_error<E>(error: BindError<E>) -> CheckError<E> {
480    match error {
481        BindError::Processing(error) => CheckError::Processing(error),
482        BindError::Callback(error) => CheckError::Callback(error),
483        BindError::OutputWidth { expected, actual } => CheckError::OutputWidth { expected, actual },
484    }
485}
486fn range_error<E>(error: StreamError<RangesError, E>, at: u64) -> CheckError<E> {
487    match error {
488        StreamError::Source(RangesError::Resource(reason)) => {
489            CheckError::Processing(Error::resource(reason, "signerInfo.signedAttrs", at))
490        }
491        StreamError::Source(RangesError::Receiver(_)) => {
492            unreachable!("stream guard preserves the original sink refusal")
493        }
494        StreamError::Processing(error) => CheckError::Processing(error),
495        StreamError::Callback(error) => CheckError::Callback(error),
496    }
497}
498fn related_error<E>(error: StreamError<RangesError, DigestError<E>>, at: u64) -> CheckError<E> {
499    match error {
500        StreamError::Callback(error) => digest_error(error),
501        error => range_error(error, at).map_callback(),
502    }
503}
504
505impl<E> CheckError<DigestError<E>> {
506    fn map_callback(self) -> CheckError<E> {
507        match self {
508            Self::Processing(error) => CheckError::Processing(error),
509            Self::Callback(error) => digest_error(error),
510            Self::OutputWidth { expected, actual } => CheckError::OutputWidth { expected, actual },
511            Self::Unstreamable => CheckError::Unstreamable,
512        }
513    }
514}