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