Skip to main content

structured_cms/
provider.rs

1//! Selected-provider identities and distinct raw versus finalized-prehash inputs.
2//!
3//! Digest execution is the shared [`structured_asn1::digest::Digest`]
4//! contract; ASN.1 digest identifiers resolve through
5//! [`structured_pkix::algorithm::digest`].
6
7use crate::Octets;
8use core::cmp::Ordering;
9use structured_asn1::digest::Resolution;
10use structured_asn1::memory::Work;
11use structured_asn1::{Encoding, Reader, Span};
12use structured_pkix::AlgorithmIdentifier;
13use structured_pkix::algorithm::digest::{self as asn_digest, Algorithm, AsnDigest};
14use structured_pkix::path::WorkingKey;
15mod session;
16pub use session::ProviderSession;
17
18pub(crate) fn require_instance<P>(_: &P, field: &'static str, at: u64) -> crate::Result<()> {
19    // Distinct ZST objects may have the same address. Their adapters must use
20    // the explicit nonzero ProviderSession owner, not an invented object ID.
21    // This constant condition is removed for ordinary monomorphized adapters.
22    if core::mem::size_of::<P>() == 0 {
23        return Err(crate::Error::schema(
24            crate::ErrorCode::ProviderMismatch,
25            field,
26            at,
27        ));
28    }
29    Ok(())
30}
31
32/// Digest algorithms supplied by ESS schema without an encoded identifier.
33/// These select identity, not a product policy permission or an engine fallback.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ImpliedDigest {
36    /// `ESSCertID` Hash is SHA-1 (RFC2634 §5.4.1).
37    EssSha1,
38    /// Omitted `ESSCertIDv2` hashAlgorithm is SHA-256 with absent parameters
39    /// (RFC5035 §4, corrected certHash description in verified erratum2364).
40    EssV2Sha256,
41}
42
43/// Original written algorithm or a normative schema-supplied identity.
44///
45/// Implied inputs have no encoded `AlgorithmIdentifier` and no parameter TLV.
46/// A written unsupported/malformed identifier must never become an implied
47/// default. The enclosing field/span locate the implied identity with real
48/// document provenance, without synthesizing bytes or a new Parser.
49#[derive(Debug, Clone, Copy)]
50pub enum DigestAlgorithm<'a> {
51    /// The complete original identifier, encoding and parameter provenance.
52    Written(AlgorithmIdentifier<'a>),
53    /// A schema default/mandatory identity without an original identifier TLV.
54    Implied {
55        /// Identity selected by the ESS schema, with no implicit NULL fallback.
56        algorithm: ImpliedDigest,
57        /// Original enclosing ESS field.
58        field: &'static str,
59        /// Original enclosing ESS identifier span.
60        span: Span,
61    },
62}
63
64impl ImpliedDigest {
65    /// The algorithm the ESS schema selects.
66    #[must_use]
67    pub const fn algorithm(self) -> Algorithm {
68        match self {
69            Self::EssSha1 => Algorithm::Sha1,
70            Self::EssV2Sha256 => Algorithm::Sha256,
71        }
72    }
73}
74
75/// Resolves a written identifier through the shared ASN.1 resolver, under the
76/// encoding of the document it came from, or an implied ESS algorithm
77/// directly, then asks the provider for its identity.
78pub(crate) fn resolve_digest<'a, P: AsnDigest<'a>>(
79    provider: &P,
80    algorithm: DigestAlgorithm<'_>,
81    parent: &Reader<'_>,
82) -> crate::Result<Resolution<P::Identity>> {
83    match algorithm {
84        DigestAlgorithm::Written(identifier) => {
85            asn_digest::resolve(provider, &identifier, &parent.execution())
86                .map_err(crate::Error::pkix)
87        }
88        DigestAlgorithm::Implied { algorithm, .. } => {
89            Ok(asn_digest::supported(provider, algorithm.algorithm()))
90        }
91    }
92}
93
94/// A cryptographic signature answer after complete input processing.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum SignatureAnswer {
97    /// Signature arithmetic accepted the exact selected input and key.
98    Valid,
99    /// Signature arithmetic rejected that input/key, independently of resources.
100    Invalid,
101}
102
103/// Fully resolved signature input contract; raw never silently becomes prehash.
104#[derive(Debug)]
105pub enum SignatureMode<T> {
106    /// Original logical message octets, as required by `PureEdDSA` (RFC8419 §3).
107    Raw(T),
108    /// One finalized digest with a typed, fully resolved identity.
109    Prehashed(T),
110}
111
112/// Refusal while resolving a signature's effective digest context.
113#[derive(Debug)]
114pub enum SignatureDigestError<E> {
115    /// Original ASN.1 algorithm/source resource or encoding failure.
116    Encoding(structured_pkix::Error),
117    /// Original provider failure, including key-context resource refusal.
118    Callback(E),
119}
120
121impl<E: core::fmt::Display> core::fmt::Display for SignatureDigestError<E> {
122    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123        match self {
124            Self::Encoding(error) => error.fmt(f),
125            Self::Callback(error) => error.fmt(f),
126        }
127    }
128}
129
130impl<E: core::error::Error + 'static> core::error::Error for SignatureDigestError<E> {
131    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
132        Some(match self {
133            Self::Encoding(error) => error,
134            Self::Callback(error) => error,
135        })
136    }
137}
138
139/// A finalized output and its complete algorithm identity, without a cloneable state.
140#[derive(Debug)]
141pub struct Finalized<'p, P, I, O> {
142    provider: &'p P,
143    identity: I,
144    output: O,
145}
146
147/// A finalized prehash with independent short identity/output borrows.
148///
149/// Only common processing constructs this view from a completed state bound
150/// to the same resolved identity/provider. Byte width alone never establishes
151/// that provenance. There is no public arbitrary digest-plus-OID constructor.
152#[derive(Debug)]
153pub struct Prehash<'i, 'o, P, I> {
154    provider: &'i P,
155    identity: &'i I,
156    bytes: &'o [u8],
157}
158
159/// A finished digest: the provider's own output and identity, and the
160/// instance that produced them. Only common processing constructs it, from a
161/// state it completed; there is no constructor from arbitrary bytes.
162#[derive(Debug)]
163pub struct Finished<'v, P, I, O> {
164    provider: &'v P,
165    identity: &'v I,
166    output: &'v O,
167}
168
169// Only shared references: copying needs no bound on the referenced types.
170impl<P, I, O> Clone for Finished<'_, P, I, O> {
171    fn clone(&self) -> Self {
172        *self
173    }
174}
175impl<P, I, O> Copy for Finished<'_, P, I, O> {}
176
177impl<'v, P, I, O: AsRef<[u8]>> Finished<'v, P, I, O> {
178    /// The actual instance which completed this output. Borrowing it avoids
179    /// accepting a second selected provider or guessing provider provenance.
180    #[must_use]
181    pub fn provider(&self) -> &'v P {
182        self.provider
183    }
184    pub(crate) fn new(provider: &'v P, identity: &'v I, output: &'v O) -> Self {
185        Self {
186            provider,
187            identity,
188            output,
189        }
190    }
191    /// Whether the supplied instance actually completed this output.
192    #[must_use]
193    pub fn from_provider(&self, provider: &P) -> bool {
194        core::ptr::eq(self.provider, provider)
195    }
196    /// Full selected-provider algorithm identity.
197    #[must_use]
198    pub fn identity(&self) -> &'v I {
199        self.identity
200    }
201    /// The provider's own finished output.
202    #[must_use]
203    pub fn output(&self) -> &'v O {
204        self.output
205    }
206}
207
208impl<'i, 'o, P, I> Prehash<'i, 'o, P, I> {
209    pub(crate) fn new(provider: &'i P, identity: &'i I, bytes: &'o [u8]) -> Self {
210        Self {
211            provider,
212            identity,
213            bytes,
214        }
215    }
216    /// Whether the supplied instance actually completed this finalized input.
217    #[must_use]
218    pub fn from_provider(&self, provider: &P) -> bool {
219        core::ptr::eq(self.provider, provider)
220    }
221    /// Full selected-provider algorithm identity, including parameters/defaults.
222    #[must_use]
223    pub fn identity(&self) -> &'i I {
224        self.identity
225    }
226
227    /// Original finalized output bytes; accessing them performs no new hashing.
228    #[must_use]
229    pub fn as_bytes(&self) -> &'o [u8] {
230        self.bytes
231    }
232}
233
234impl<'p, P, I, O: AsRef<[u8]>> Finalized<'p, P, I, O> {
235    /// Resolved identity, including parameters and output-length semantics.
236    #[must_use]
237    pub fn identity(&self) -> &I {
238        &self.identity
239    }
240    /// Borrowed final output; providers must implement this as constant-time access.
241    #[must_use]
242    pub fn as_bytes(&self) -> &[u8] {
243        self.output.as_ref()
244    }
245
246    /// Borrows a typed prehash without cloning identity, state or output.
247    #[must_use]
248    pub fn prehash(&self) -> Prehash<'_, '_, P, I> {
249        Prehash::new(self.provider, &self.identity, self.output.as_ref())
250    }
251
252    /// Borrows the provider's own output for its comparison, with provenance.
253    #[must_use]
254    pub fn finished(&self) -> Finished<'_, P, I, O> {
255        Finished {
256            provider: self.provider,
257            identity: &self.identity,
258            output: &self.output,
259        }
260    }
261
262    pub(crate) fn new(provider: &'p P, identity: I, output: O) -> Self {
263        Self {
264            provider,
265            identity,
266            output,
267        }
268    }
269}
270
271/// Signature operations that stay bound to the same selected provider and key.
272///
273/// Digest execution and identities come from the provider's
274/// [`structured_asn1::digest::Digest`] and [`AsnDigest`] implementations; CMS
275/// payload message-digest, ESS certificate hashes and TSP imprints all run
276/// through them with their distinct original covered inputs.
277///
278/// The effective key retains its original SPKI and inherited parameters.
279/// Resolution validates key/algorithm compatibility before raw initialization
280/// or payload I/O. A contiguous-only backend may explicitly buffer with full
281/// admission in its adapter, or report unsupported; it cannot downgrade raw.
282///
283/// Common processing borrows a stable nonzero-sized selection owner. Ordinary
284/// adapters containing engine/context references already satisfy this. Wrap a
285/// zero-sized stateless adapter in [`ProviderSession`] before using common
286/// processing; otherwise it returns a processing `ProviderMismatch` before
287/// dispatch. Distinct ZST addresses cannot supply instance provenance.
288pub trait SignatureProvider<'a>: AsnDigest<'a> + Sized {
289    /// Fully resolved signature identity, including algorithm-specific defaults.
290    type SignatureIdentity;
291    /// Raw verification state, initialized with original signature/key first.
292    type RawState;
293
294    /// Resolves the original digest against this signature's effective key.
295    ///
296    /// Key-dependent hashes, such as GOST with the signing key's actual DKE,
297    /// must return that complete immutable identity before digest execution.
298    /// Both outer and protected declarations use the same hook and key. Different
299    /// identities may share this provider and one content fan-out pass.
300    /// The default preserves key-independent ASN digest resolution; it does
301    /// not select any key-dependent parameter or substitute an unknown DKE.
302    fn resolve_signature_digest(
303        &'a self,
304        identifier: AlgorithmIdentifier<'a>,
305        _key: WorkingKey<'a>,
306        parent: &Reader<'a>,
307    ) -> core::result::Result<Resolution<Self::Identity>, SignatureDigestError<Self::Error>> {
308        asn_digest::resolve(self, &identifier, &parent.execution())
309            .map_err(SignatureDigestError::Encoding)
310    }
311
312    /// Chooses raw versus prehashed only after complete algorithm/key resolution.
313    fn resolve_signature(
314        &'a self,
315        identifier: AlgorithmIdentifier<'a>,
316        digest: &Self::Identity,
317        key: WorkingKey<'a>,
318        encoding: Encoding,
319        parent: &Reader<'a>,
320    ) -> core::result::Result<Resolution<SignatureMode<Self::SignatureIdentity>>, Self::Error>;
321    /// Semantic signature/default comparison for protected versus outer identifiers.
322    fn compare_signature<W: Work>(
323        &self,
324        left: &Self::SignatureIdentity,
325        right: &Self::SignatureIdentity,
326        execution: &W,
327    ) -> core::result::Result<Ordering, Self::Error>;
328    /// Starts the raw challenge with the original signature and effective key.
329    fn start_raw<W: Work>(
330        &'a self,
331        identity: &Self::SignatureIdentity,
332        key: WorkingKey<'a>,
333        signature: &'a Octets<'a>,
334        execution: &W,
335    ) -> core::result::Result<Self::RawState, Self::Error>;
336    /// Admits and processes original raw message chunks, never digest bytes.
337    fn update_raw<W: Work>(
338        &self,
339        state: &mut Self::RawState,
340        bytes: &[u8],
341        execution: &W,
342    ) -> core::result::Result<(), Self::Error>;
343    /// Completes a raw signature only after successful complete source EOF.
344    fn finish_raw<W: Work>(
345        &self,
346        state: Self::RawState,
347        execution: &W,
348    ) -> core::result::Result<SignatureAnswer, Self::Error>;
349    /// Verifies one borrowed finalized digest with its full identity; no rehash.
350    fn verify_prehashed<W: Work>(
351        &'a self,
352        identity: &Self::SignatureIdentity,
353        key: WorkingKey<'a>,
354        signature: &Octets<'a>,
355        digest: Prehash<'_, '_, Self, Self::Identity>,
356        execution: &W,
357    ) -> core::result::Result<SignatureAnswer, Self::Error>;
358}