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        execution
259            .charge_work(core::mem::size_of_val(&*self.pending) as u64)
260            .map_err(refusal)?;
261        for request in self.pending {
262            execution.charge_work(1).map_err(refusal)?;
263            let (content, signature) = check(
264                self.provider,
265                request.signer,
266                request.key,
267                &request.binding,
268                request.challenge,
269                &completed,
270                execution,
271            )
272            .map_err(StreamError::Callback)?;
273            results
274                .try_push(Checked {
275                    signer: request.signer,
276                    key: request.key,
277                    content,
278                    signature,
279                })
280                .map_err(refusal)?;
281        }
282        Ok(results.into_owned())
283    }
284}
285
286fn check<'a, P: SignatureProvider<'a>, W: Work>(
287    provider: &'a P,
288    signer: &'a SignerInfo<'a>,
289    key: WorkingKey<'a>,
290    binding: &ResolvedBinding<'a>,
291    challenge: Challenge<'a, P>,
292    completed: &crate::payload::Completed<'a, P>,
293    execution: &W,
294) -> Result<(ContentAgreement, Option<SignatureAnswer>), CheckError<P::Error>> {
295    let digest = match &challenge {
296        Challenge::RawPayload(index) => {
297            debug_assert!(*index < completed.raw().len());
298            return Ok((ContentAgreement::Direct, Some(completed.raw()[*index])));
299        }
300        Challenge::RawAttributes { digest, .. } | Challenge::Prehashed { digest, .. } => *digest,
301    };
302    let payload = completed
303        .digests()
304        .for_request(digest, execution)
305        .map_err(digest_error)?;
306    let payload = payload.expect("prepared request ordinal has a completed output");
307    if let ResolvedBinding::Attributes {
308        digest: expected,
309        digest_span,
310    } = binding
311    {
312        match binding::matches_digest(
313            provider,
314            payload.finished(),
315            expected,
316            execution,
317            "signerInfo.signedAttrs.messageDigest",
318            *digest_span,
319        )
320        .map_err(bind_error)?
321        {
322            DigestBinding::Differs => return Ok((ContentAgreement::Differs, None)),
323            DigestBinding::Malformed(error) => {
324                return Ok((ContentAgreement::Malformed(error), None));
325            }
326            DigestBinding::Matches => {}
327        }
328        let attributes = signer
329            .signed_attributes()
330            .expect("prepared attrs binding retained its original attrs");
331        let mut ranges = attributes
332            .signed_bytes()
333            .expect("prepared signed attrs have SET signature ranges");
334        let signature = match challenge {
335            Challenge::RawAttributes { mut state, .. } => {
336                stream::run(
337                    &mut ranges,
338                    &mut RawSink {
339                        provider,
340                        state: &mut state,
341                    },
342                    execution,
343                    "signerInfo.signedAttrs",
344                    attributes.element().span().start,
345                )
346                .map_err(|error| range_error(error, attributes.element().span().start))?;
347                provider
348                    .finish_raw(state, execution)
349                    .map_err(CheckError::Callback)?
350            }
351            Challenge::Prehashed { identity, .. } => {
352                let related = completed
353                    .digests()
354                    .digest_related(
355                        digest,
356                        &mut ranges,
357                        execution,
358                        "signerInfo.signedAttrs",
359                        attributes.element().span().start,
360                    )
361                    .map_err(|error| related_error(error, attributes.element().span().start))?
362                    .expect("prepared request ordinal retains its identity");
363                provider
364                    .verify_prehashed(
365                        &identity,
366                        key,
367                        signer.signature(),
368                        related.prehash(),
369                        execution,
370                    )
371                    .map_err(CheckError::Callback)?
372            }
373            Challenge::RawPayload(_) => {
374                unreachable!("raw payload challenges returned before attrs processing")
375            }
376        };
377        return Ok((ContentAgreement::Matches, Some(signature)));
378    }
379    let Challenge::Prehashed { identity, .. } = challenge else {
380        unreachable!("only prehashed direct input reaches the digest branch");
381    };
382    let signature = provider
383        .verify_prehashed(
384            &identity,
385            key,
386            signer.signature(),
387            payload.prehash(),
388            execution,
389        )
390        .map_err(CheckError::Callback)?;
391    Ok((ContentAgreement::Direct, Some(signature)))
392}
393
394struct RawSink<'s, 'a, P: SignatureProvider<'a>> {
395    provider: &'a P,
396    state: &'s mut P::RawState,
397}
398impl<'a, P: SignatureProvider<'a>> Sink for RawSink<'_, 'a, P> {
399    type Error = P::Error;
400    fn write<W: Work>(&mut self, bytes: &[u8], execution: &W) -> Result<(), Self::Error> {
401        self.provider.update_raw(self.state, bytes, execution)
402    }
403}
404
405fn digest_error<E>(error: DigestError<E>) -> CheckError<E> {
406    match error {
407        DigestError::Processing(error) => CheckError::Processing(error),
408        DigestError::Callback(error) => CheckError::Callback(error),
409        DigestError::OutputWidth { expected, actual } => {
410            CheckError::OutputWidth { expected, actual }
411        }
412        DigestError::Unstreamable => CheckError::Unstreamable,
413    }
414}
415fn payload_error<E>(error: PayloadError<E>) -> CheckError<E> {
416    match error {
417        PayloadError::Processing(error) => CheckError::Processing(error),
418        PayloadError::Callback(error) => CheckError::Callback(error),
419        PayloadError::Digest(error) => digest_error(error),
420    }
421}
422fn bind_error<E>(error: BindError<E>) -> CheckError<E> {
423    match error {
424        BindError::Processing(error) => CheckError::Processing(error),
425        BindError::Callback(error) => CheckError::Callback(error),
426        BindError::OutputWidth { expected, actual } => CheckError::OutputWidth { expected, actual },
427    }
428}
429fn range_error<E>(error: StreamError<RangesError, E>, at: u64) -> CheckError<E> {
430    match error {
431        StreamError::Source(RangesError::Resource(reason)) => {
432            CheckError::Processing(Error::resource(reason, "signerInfo.signedAttrs", at))
433        }
434        StreamError::Source(RangesError::Receiver(_)) => {
435            unreachable!("stream guard preserves the original sink refusal")
436        }
437        StreamError::Processing(error) => CheckError::Processing(error),
438        StreamError::Callback(error) => CheckError::Callback(error),
439    }
440}
441fn related_error<E>(error: StreamError<RangesError, DigestError<E>>, at: u64) -> CheckError<E> {
442    match error {
443        StreamError::Callback(error) => digest_error(error),
444        error => range_error(error, at).map_callback(),
445    }
446}
447
448impl<E> CheckError<DigestError<E>> {
449    fn map_callback(self) -> CheckError<E> {
450        match self {
451            Self::Processing(error) => CheckError::Processing(error),
452            Self::Callback(error) => digest_error(error),
453            Self::OutputWidth { expected, actual } => CheckError::OutputWidth { expected, actual },
454            Self::Unstreamable => CheckError::Unstreamable,
455        }
456    }
457}