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::{DigestAlgorithm, SignatureMode, SignatureProvider, resolve_digest};
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 resolve_digest(provider, DigestAlgorithm::Written(identifier), parent)
218        .map_err(PrepareError::Processing)?
219    {
220        Resolution::Supported(digest) => digest,
221        Resolution::Unsupported => return Ok(Preparation::Unsupported(Part::Digest)),
222        Resolution::Malformed => {
223            return Ok(Preparation::Malformed(Error::schema(
224                ErrorCode::AlgorithmSyntax,
225                "signerInfo.digestAlgorithm",
226                identifier.offset(),
227            )));
228        }
229    };
230    if let Binding::Attributes {
231        digest: expected,
232        digest_span,
233        ..
234    } = &binding
235        && let Some(width) = provider.width(&digest)
236        && expected.len() != width
237    {
238        return Ok(Preparation::Malformed(Error::schema(
239            ErrorCode::DigestWidth,
240            "signerInfo.signedAttrs.messageDigest",
241            digest_span.start,
242        )));
243    }
244    let identifier = identifiers.1;
245    let signature = match provider
246        .resolve_signature(identifier, &digest, key, identifier.encoding(), parent)
247        .map_err(|source| PrepareError::Callback {
248            part: Part::Signature,
249            source,
250        })? {
251        Resolution::Supported(signature) => signature,
252        Resolution::Unsupported => return Ok(Preparation::Unsupported(Part::Signature)),
253        Resolution::Malformed => {
254            return Ok(Preparation::Malformed(Error::schema(
255                ErrorCode::AlgorithmSyntax,
256                "signerInfo.signatureAlgorithm",
257                identifier.offset(),
258            )));
259        }
260    };
261    if let Binding::Attributes {
262        protection: Some(protection),
263        ..
264    } = &binding
265    {
266        match protected(provider, *protection, &digest, &signature, key, parent)? {
267            ProtectionStatus::Matching => {}
268            ProtectionStatus::Unsupported(part) => return Ok(Preparation::Unsupported(part)),
269            ProtectionStatus::Malformed(error) => return Ok(Preparation::Malformed(error)),
270        }
271    }
272    let binding = match binding {
273        Binding::Direct => ResolvedBinding::Direct,
274        Binding::Attributes {
275            digest,
276            digest_span,
277            ..
278        } => ResolvedBinding::Attributes {
279            digest,
280            digest_span,
281        },
282    };
283    Ok(Preparation::Ready(ResolvedSigner {
284        provider,
285        signer,
286        key,
287        binding,
288        digest,
289        signature,
290    }))
291}
292
293enum ProtectionStatus {
294    Matching,
295    Unsupported(Part),
296    Malformed(Error),
297}
298
299fn algorithms<'a>(
300    signer: &SignerInfo<'a>,
301) -> Result<
302    (
303        structured_pkix::AlgorithmIdentifier<'a>,
304        structured_pkix::AlgorithmIdentifier<'a>,
305    ),
306    Error,
307> {
308    let digest = signer
309        .digest_algorithm()
310        .require_identifier()
311        .map_err(Error::pkix)?;
312    let signature = signer
313        .signature_algorithm()
314        .require_identifier()
315        .map_err(Error::pkix)?;
316    Ok((digest, signature))
317}
318
319fn protected<'a, P: SignatureProvider<'a>>(
320    provider: &'a P,
321    protection: Protection<'a>,
322    digest: &P::Identity,
323    signature: &SignatureMode<P::SignatureIdentity>,
324    key: WorkingKey<'a>,
325    parent: &Reader<'a>,
326) -> Result<ProtectionStatus, PrepareError<P::Error>> {
327    const FIELD: &str = "signerInfo.signedAttrs.cmsAlgorithmProtection";
328    let identifier = protection.digest();
329    let protected_digest =
330        match resolve_digest(provider, DigestAlgorithm::Written(identifier), parent)
331            .map_err(PrepareError::Processing)?
332        {
333            Resolution::Supported(digest) => digest,
334            Resolution::Unsupported => {
335                return Ok(ProtectionStatus::Unsupported(Part::ProtectedDigest));
336            }
337            Resolution::Malformed => {
338                return Ok(ProtectionStatus::Malformed(Error::schema(
339                    ErrorCode::AlgorithmSyntax,
340                    FIELD,
341                    identifier.offset(),
342                )));
343            }
344        };
345    if provider
346        .compare(digest, &protected_digest, &parent.execution())
347        .map_err(|source| PrepareError::Callback {
348            part: Part::DigestComparison,
349            source,
350        })?
351        != Ordering::Equal
352    {
353        return Ok(ProtectionStatus::Malformed(Error::schema(
354            ErrorCode::AlgorithmProtection,
355            FIELD,
356            identifier.offset(),
357        )));
358    }
359    let identifier = protection.signature();
360    let protected_signature = match provider
361        .resolve_signature(
362            identifier,
363            &protected_digest,
364            key,
365            identifier.encoding(),
366            parent,
367        )
368        .map_err(|source| PrepareError::Callback {
369            part: Part::ProtectedSignature,
370            source,
371        })? {
372        Resolution::Supported(signature) => signature,
373        Resolution::Unsupported => {
374            return Ok(ProtectionStatus::Unsupported(Part::ProtectedSignature));
375        }
376        Resolution::Malformed => {
377            return Ok(ProtectionStatus::Malformed(Error::schema(
378                ErrorCode::AlgorithmSyntax,
379                FIELD,
380                identifier.offset(),
381            )));
382        }
383    };
384    parent.execution().charge_work(1).map_err(|reason| {
385        PrepareError::Processing(Error::resource(reason, FIELD, identifier.offset()))
386    })?;
387    let same = match (signature, &protected_signature) {
388        (SignatureMode::Raw(left), SignatureMode::Raw(right))
389        | (SignatureMode::Prehashed(left), SignatureMode::Prehashed(right)) => {
390            provider
391                .compare_signature(left, right, &parent.execution())
392                .map_err(|source| PrepareError::Callback {
393                    part: Part::SignatureComparison,
394                    source,
395                })?
396                == Ordering::Equal
397        }
398        _ => false,
399    };
400    if !same {
401        return Ok(ProtectionStatus::Malformed(Error::schema(
402            ErrorCode::AlgorithmProtection,
403            FIELD,
404            identifier.offset(),
405        )));
406    }
407    Ok(ProtectionStatus::Matching)
408}