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