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