Skip to main content

structured_cms/
prepare.rs

1//! Schema and selected-provider preflight before state initialization or I/O.
2
3use crate::binding::{self, Binding, Protection, Scope};
4use crate::provider::{SignatureDigestError, SignatureMode, SignatureProvider};
5use crate::signer::SignerInfo;
6use crate::{Error, ErrorCode};
7use core::cmp::Ordering;
8use structured_asn1::Reader;
9use structured_asn1::digest::Resolution;
10use structured_asn1::memory::Work;
11use structured_pkix::path::WorkingKey;
12
13/// Which selected-provider operation could not answer the challenge.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Part {
16    /// Outer digest algorithm.
17    Digest,
18    /// Outer signature algorithm and effective key compatibility.
19    Signature,
20    /// Protected digest algorithm.
21    ProtectedDigest,
22    /// Protected signature algorithm.
23    ProtectedSignature,
24    /// Full resolved digest identity comparison.
25    DigestComparison,
26    /// Full resolved signature identity comparison.
27    SignatureComparison,
28}
29
30/// Processing refusal, independently of unsupported or malformed mechanisms.
31#[derive(Debug)]
32pub enum PrepareError<E> {
33    /// Shared processing/resource failure with original source location.
34    Processing(Error),
35    /// Original selected-provider failure, including its resource refusal.
36    Callback {
37        /// Callback that failed.
38        part: Part,
39        /// Original error; never mapped to mismatch or unsupported.
40        source: E,
41    },
42}
43
44/// Preflight facts, without a signature or trust verdict.
45pub enum Preparation<'a, P: SignatureProvider<'a>> {
46    /// The complete schema and selected algorithm input contract are resolved.
47    Ready(ResolvedSigner<'a, P>),
48    /// The selected provider cannot process this explicit mechanism.
49    Unsupported(Part),
50    /// The schema/algorithm binding is malformed, independently of resources.
51    Malformed(Error),
52}
53
54/// Resolved content binding, after any protected algorithms have been checked.
55/// The temporary protection identifiers are not copied into every ready plan.
56#[derive(Debug)]
57pub enum ResolvedBinding<'a> {
58    /// Original logical content is the signature input.
59    Direct,
60    /// Original payload digest declaration; signature input is signedAttrs.
61    Attributes {
62        /// Expected payload digest, borrowed from the original DER value.
63        digest: &'a [u8],
64        /// That value's original source span.
65        digest_span: structured_asn1::Span,
66    },
67}
68
69/// Movable resolved identities bound to original signer/key/provider inputs.
70pub struct ResolvedSigner<'a, P: SignatureProvider<'a>> {
71    provider: &'a P,
72    signer: &'a SignerInfo<'a>,
73    key: WorkingKey<'a>,
74    binding: ResolvedBinding<'a>,
75    digest: P::Identity,
76    signature: SignatureMode<P::SignatureIdentity>,
77}
78
79impl<'a, P: SignatureProvider<'a>> ResolvedSigner<'a, P> {
80    pub(crate) fn into_parts(self) -> ResolvedParts<'a, P> {
81        ResolvedParts {
82            signer: self.signer,
83            key: self.key,
84            binding: self.binding,
85            digest: self.digest,
86            signature: self.signature,
87        }
88    }
89    /// Exact selected provider instance that resolved this challenge.
90    #[must_use]
91    pub fn provider(&self) -> &'a P {
92        self.provider
93    }
94    /// Original signer, including any independently retained conformance faults.
95    #[must_use]
96    pub fn signer(&self) -> &'a SignerInfo<'a> {
97        self.signer
98    }
99    /// Original effective key descriptor; no new standing key is synthesized.
100    #[must_use]
101    pub fn key(&self) -> WorkingKey<'a> {
102        self.key
103    }
104    /// Original direct/attribute content binding.
105    #[must_use]
106    pub fn binding(&self) -> &ResolvedBinding<'a> {
107        &self.binding
108    }
109    /// Full resolved digest identity.
110    #[must_use]
111    pub fn digest(&self) -> &P::Identity {
112        &self.digest
113    }
114    /// Distinct raw/prehashed signature input contract.
115    #[must_use]
116    pub fn signature(&self) -> &SignatureMode<P::SignatureIdentity> {
117        &self.signature
118    }
119
120    /// Compares prepared signer values under this selected provider's full
121    /// algorithm/default semantics, without new resolution, state or hashing.
122    ///
123    /// Both plans must belong to the same provider instance. Non-algorithm
124    /// document fields use the same original-range kernel as the narrow
125    /// encoded comparison, especially exact signedAttrs octets. This is a
126    /// duplicate-document identity query, not equality of caller trust/key
127    /// selections and not signature verification or a conformance verdict.
128    pub fn same_signer_value(
129        &self,
130        other: &Self,
131        parent: &Reader<'_>,
132    ) -> Result<bool, PrepareError<P::Error>> {
133        if !core::ptr::eq(self.provider, other.provider) {
134            return Err(PrepareError::Processing(Error::schema(
135                ErrorCode::ProviderMismatch,
136                "signedData.signerInfos",
137                other.signer.span().start,
138            )));
139        }
140        if !self
141            .signer
142            .same_fields(other.signer, parent)
143            .map_err(PrepareError::Processing)?
144        {
145            return Ok(false);
146        }
147        let execution = parent.execution();
148        let digest = self
149            .provider
150            .compare(&self.digest, &other.digest, &execution)
151            .map_err(|source| PrepareError::Callback {
152                part: Part::DigestComparison,
153                source,
154            })?;
155        if digest != Ordering::Equal {
156            return Ok(false);
157        }
158        let ((SignatureMode::Raw(left), SignatureMode::Raw(right))
159        | (SignatureMode::Prehashed(left), SignatureMode::Prehashed(right))) =
160            (&self.signature, &other.signature)
161        else {
162            return Ok(false);
163        };
164        self.provider
165            .compare_signature(left, right, &execution)
166            .map(|order| order == Ordering::Equal)
167            .map_err(|source| PrepareError::Callback {
168                part: Part::SignatureComparison,
169                source,
170            })
171    }
172}
173
174pub(crate) struct ResolvedParts<'a, P: SignatureProvider<'a>> {
175    pub signer: &'a SignerInfo<'a>,
176    pub key: WorkingKey<'a>,
177    pub binding: ResolvedBinding<'a>,
178    pub digest: P::Identity,
179    pub signature: SignatureMode<P::SignatureIdentity>,
180}
181
182/// Resolves a selected signer only after its complete binding syntax is checked.
183///
184/// This does not select a certificate, assert SID binding/trust, or discard
185/// retained conformance faults. The caller supplies an effective key selected
186/// through those independent facts. Unknown/invalid algorithms and provider
187/// failures remain distinct. Impossible fixed message-digest widths refuse
188/// before signature initialization or payload acquisition. RFC6211 ยง3 protected
189/// algorithms are resolved and compared through the same selected provider,
190/// including defaults; generic `AlgorithmIdentifier::matches` is not used.
191/// <https://www.rfc-editor.org/rfc/rfc6211#section-3>
192pub fn resolve<'a, P: SignatureProvider<'a>>(
193    provider: &'a P,
194    signer: &'a SignerInfo<'a>,
195    key: WorkingKey<'a>,
196    parent: &Reader<'a>,
197    scope: Scope<'a>,
198) -> Result<Preparation<'a, P>, PrepareError<P::Error>> {
199    crate::provider::require_instance(provider, "signerInfo", signer.span().start)
200        .map_err(PrepareError::Processing)?;
201    signer
202        .require_session(parent, "signerInfo")
203        .map_err(PrepareError::Processing)?;
204    // Both inspection gates precede binding work and all provider callbacks.
205    // A valid digest cannot authorize observable resolution when the later
206    // signature declaration already contains a retained syntax fault.
207    let identifiers = match algorithms(signer) {
208        Ok(identifiers) => identifiers,
209        Err(error) => return Ok(Preparation::Malformed(error)),
210    };
211    let binding = match binding::inspect(signer, parent, scope) {
212        Ok(binding) => binding,
213        Err(error) if error.is_terminal() => return Err(PrepareError::Processing(error)),
214        Err(error) => return Ok(Preparation::Malformed(error)),
215    };
216    let identifier = identifiers.0;
217    let digest = match provider
218        .resolve_signature_digest(identifier, key, parent)
219        .map_err(|error| digest_error(error, Part::Digest))?
220    {
221        Resolution::Supported(digest) => digest,
222        Resolution::Unsupported => return Ok(Preparation::Unsupported(Part::Digest)),
223        Resolution::Malformed => {
224            return Ok(Preparation::Malformed(Error::schema(
225                ErrorCode::AlgorithmSyntax,
226                "signerInfo.digestAlgorithm",
227                identifier.offset(),
228            )));
229        }
230    };
231    if let Binding::Attributes {
232        digest: expected,
233        digest_span,
234        ..
235    } = &binding
236        && let Some(width) = provider.width(&digest)
237        && expected.len() != width
238    {
239        return Ok(Preparation::Malformed(Error::schema(
240            ErrorCode::DigestWidth,
241            "signerInfo.signedAttrs.messageDigest",
242            digest_span.start,
243        )));
244    }
245    let identifier = identifiers.1;
246    let signature = match provider
247        .resolve_signature(identifier, &digest, key, identifier.encoding(), parent)
248        .map_err(|source| PrepareError::Callback {
249            part: Part::Signature,
250            source,
251        })? {
252        Resolution::Supported(signature) => signature,
253        Resolution::Unsupported => return Ok(Preparation::Unsupported(Part::Signature)),
254        Resolution::Malformed => {
255            return Ok(Preparation::Malformed(Error::schema(
256                ErrorCode::AlgorithmSyntax,
257                "signerInfo.signatureAlgorithm",
258                identifier.offset(),
259            )));
260        }
261    };
262    if let Binding::Attributes {
263        protection: Some(protection),
264        ..
265    } = &binding
266    {
267        match protected(provider, *protection, &digest, &signature, key, parent)? {
268            ProtectionStatus::Matching => {}
269            ProtectionStatus::Unsupported(part) => return Ok(Preparation::Unsupported(part)),
270            ProtectionStatus::Malformed(error) => return Ok(Preparation::Malformed(error)),
271        }
272    }
273    let binding = match binding {
274        Binding::Direct => ResolvedBinding::Direct,
275        Binding::Attributes {
276            digest,
277            digest_span,
278            ..
279        } => ResolvedBinding::Attributes {
280            digest,
281            digest_span,
282        },
283    };
284    Ok(Preparation::Ready(ResolvedSigner {
285        provider,
286        signer,
287        key,
288        binding,
289        digest,
290        signature,
291    }))
292}
293
294enum ProtectionStatus {
295    Matching,
296    Unsupported(Part),
297    Malformed(Error),
298}
299
300fn algorithms<'a>(
301    signer: &SignerInfo<'a>,
302) -> Result<
303    (
304        structured_pkix::AlgorithmIdentifier<'a>,
305        structured_pkix::AlgorithmIdentifier<'a>,
306    ),
307    Error,
308> {
309    let digest = signer
310        .digest_algorithm()
311        .require_identifier()
312        .map_err(Error::pkix)?;
313    let signature = signer
314        .signature_algorithm()
315        .require_identifier()
316        .map_err(Error::pkix)?;
317    Ok((digest, signature))
318}
319
320fn protected<'a, P: SignatureProvider<'a>>(
321    provider: &'a P,
322    protection: Protection<'a>,
323    digest: &P::Identity,
324    signature: &SignatureMode<P::SignatureIdentity>,
325    key: WorkingKey<'a>,
326    parent: &Reader<'a>,
327) -> Result<ProtectionStatus, PrepareError<P::Error>> {
328    const FIELD: &str = "signerInfo.signedAttrs.cmsAlgorithmProtection";
329    let identifier = protection.digest();
330    let protected_digest = match provider
331        .resolve_signature_digest(identifier, key, parent)
332        .map_err(|error| digest_error(error, Part::ProtectedDigest))?
333    {
334        Resolution::Supported(digest) => digest,
335        Resolution::Unsupported => {
336            return Ok(ProtectionStatus::Unsupported(Part::ProtectedDigest));
337        }
338        Resolution::Malformed => {
339            return Ok(ProtectionStatus::Malformed(Error::schema(
340                ErrorCode::AlgorithmSyntax,
341                FIELD,
342                identifier.offset(),
343            )));
344        }
345    };
346    if provider
347        .compare(digest, &protected_digest, &parent.execution())
348        .map_err(|source| PrepareError::Callback {
349            part: Part::DigestComparison,
350            source,
351        })?
352        != Ordering::Equal
353    {
354        return Ok(ProtectionStatus::Malformed(Error::schema(
355            ErrorCode::AlgorithmProtection,
356            FIELD,
357            identifier.offset(),
358        )));
359    }
360    let identifier = protection.signature();
361    let protected_signature = match provider
362        .resolve_signature(
363            identifier,
364            &protected_digest,
365            key,
366            identifier.encoding(),
367            parent,
368        )
369        .map_err(|source| PrepareError::Callback {
370            part: Part::ProtectedSignature,
371            source,
372        })? {
373        Resolution::Supported(signature) => signature,
374        Resolution::Unsupported => {
375            return Ok(ProtectionStatus::Unsupported(Part::ProtectedSignature));
376        }
377        Resolution::Malformed => {
378            return Ok(ProtectionStatus::Malformed(Error::schema(
379                ErrorCode::AlgorithmSyntax,
380                FIELD,
381                identifier.offset(),
382            )));
383        }
384    };
385    parent.execution().charge_work(1).map_err(|reason| {
386        PrepareError::Processing(Error::resource(reason, FIELD, identifier.offset()))
387    })?;
388    let same = match (signature, &protected_signature) {
389        (SignatureMode::Raw(left), SignatureMode::Raw(right))
390        | (SignatureMode::Prehashed(left), SignatureMode::Prehashed(right)) => {
391            provider
392                .compare_signature(left, right, &parent.execution())
393                .map_err(|source| PrepareError::Callback {
394                    part: Part::SignatureComparison,
395                    source,
396                })?
397                == Ordering::Equal
398        }
399        _ => false,
400    };
401    if !same {
402        return Ok(ProtectionStatus::Malformed(Error::schema(
403            ErrorCode::AlgorithmProtection,
404            FIELD,
405            identifier.offset(),
406        )));
407    }
408    Ok(ProtectionStatus::Matching)
409}
410
411fn digest_error<E>(error: SignatureDigestError<E>, part: Part) -> PrepareError<E> {
412    match error {
413        SignatureDigestError::Encoding(error) => PrepareError::Processing(Error::pkix(error)),
414        SignatureDigestError::Callback(source) => PrepareError::Callback { part, source },
415    }
416}