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