Skip to main content

xml_sec/xmldsig/verify/session/
key_receipt.rs

1//! Unforgeable, callback-scoped successful-key association.
2
3use super::*;
4pub use crate::provider::KeyAssociation;
5
6/// Receipt of the original selected provider's successful leaf decision.
7/// Private fields prevent construction from certificate metadata or fingerprints.
8/// The public material and provider identity cannot outlive this callback.
9pub struct SelectedKeyReceipt<'a> {
10    seal: &'a [u8; 1],
11    signature: NodeIdentity,
12    provider: &'a dyn crate::provider::CryptoProvider,
13    public: Option<&'a [u8]>,
14    binding: Option<&'a crate::provider::ProviderBinding>,
15}
16
17impl SelectedKeyReceipt<'_> {
18    /// Original Signature and document generation, not a detached certificate ID.
19    pub fn signature(&self) -> NodeIdentity {
20        self.signature
21    }
22
23    /// Exact selected engine name; this is not a trust or FIPS approval claim.
24    pub fn provider_name(&self) -> &'static str {
25        self.provider.name()
26    }
27
28    /// Whether the provider supplied public material for this successful key.
29    /// False does not mean that an opaque/hardware-backed key failed verification.
30    pub fn has_public_evidence(&self) -> bool {
31        self.public.is_some()
32    }
33}
34
35impl<W: Work> VerifiedSession<'_, '_, '_, W> {
36    /// Associate an original authenticated Signature's successful key using only
37    /// the supplied current query execution. Selection, receipt gates, provider
38    /// comparison and callback admission all charge `work`, never the completed
39    /// verification's producer-local execution. Retained storage keeps its
40    /// original account; no Reference catalogue, resolver or verifier is run.
41    ///
42    /// The original document/generation, policy, provider and terminal capture
43    /// cause remain binding. `Same` is key association, not certificate trust.
44    pub fn with_key_association_in<Q: Work, R, E>(
45        &self,
46        signature: NodeIdentity,
47        expected: structured_pkix::path::WorkingKey<'_>,
48        work: &Q,
49        visitor: impl FnOnce(KeyAssociation) -> Result<R, E>,
50    ) -> Result<R, CatalogueError<E>> {
51        let index = self
52            .authenticated_signature_in(signature, work)
53            .map_err(CatalogueError::Processing)?;
54        flatten_catalogue(self.with_key_receipt_at_in(index, work, |receipt| {
55            let outcome = self
56                .associate_receipt_in(index, &receipt, expected, work)
57                .map_err(CatalogueError::Processing)?;
58            visitor(outcome).map_err(CatalogueError::Visitor)
59        }))
60    }
61
62    fn with_key_receipt_at_in<Q: Work, R, E>(
63        &self,
64        index: usize,
65        work: &Q,
66        visitor: impl for<'a> FnOnce(SelectedKeyReceipt<'a>) -> Result<R, E>,
67    ) -> Result<R, CatalogueError<E>> {
68        self.signature_method_in(index, work)
69            .map_err(CatalogueError::Processing)?;
70        work.charge_work(1)
71            .map_err(|error| CatalogueError::Processing(error.into()))?;
72        let capture = &self.operations[index].capture;
73        let key = capture.key.borrow();
74        visitor(SelectedKeyReceipt {
75            seal: &capture.seal,
76            signature: self.evidence.signatures()[index].identity,
77            provider: self.context.provider,
78            public: key.public.as_deref(),
79            binding: key.binding.as_ref(),
80        })
81        .map_err(CatalogueError::Visitor)
82    }
83
84    fn associate_receipt_in<Q: Work>(
85        &self,
86        index: usize,
87        receipt: &SelectedKeyReceipt<'_>,
88        expected: structured_pkix::path::WorkingKey<'_>,
89        work: &Q,
90    ) -> Result<KeyAssociation, DsigError> {
91        work.charge_work(1)?;
92        let capture = &self.operations[index].capture;
93        if !std::ptr::eq(receipt.seal, &capture.seal)
94            || receipt.signature != self.evidence.signatures()[index].identity
95            || !std::ptr::eq(receipt.provider, self.context.provider)
96        {
97            return Err(DsigError::InvalidRequest {
98                reason: "key receipt belongs to another selected operation",
99            });
100        }
101        let domain_matches = match (receipt.binding, receipt.provider.binding()) {
102            (None, None) => true,
103            (Some(key), Some(provider)) => key.matches(provider),
104            _ => false,
105        };
106        if receipt.public.is_some() && !domain_matches {
107            return Err(crate::provider::ProviderError::External(
108                crate::provider::ExternalProviderError::Binding,
109            )
110            .into());
111        }
112        let outcome = match receipt.public {
113            Some(public) => {
114                receipt
115                    .provider
116                    .associate_verification_key(public, expected, &|cost| work.charge_work(cost))?
117            }
118            None => KeyAssociation::Unproven,
119        };
120        work.charge_work(1)?;
121        Ok(outcome)
122    }
123}
124
125impl<W: Work, Q: Work> SelectedSignature<'_, '_, '_, '_, W, Q> {
126    /// Borrow the original successful-key receipt using this selection's Work.
127    /// A provider unable to report a unique leaf yields an unproven receipt.
128    pub fn with_key_receipt<R, E>(
129        &self,
130        visitor: impl for<'a> FnOnce(SelectedKeyReceipt<'a>) -> Result<R, E>,
131    ) -> Result<R, CatalogueError<E>> {
132        self.session
133            .with_key_receipt_at_in(self.index, self.work, visitor)
134    }
135
136    /// Associate the exact original key decision using this selection's Work.
137    /// A receipt from another operation, generation, Signature or provider is
138    /// rejected before the provider comparison or the caller's callback.
139    /// For a new query execution, use [`VerifiedSession::with_key_association_in`]
140    /// directly, without first performing session-billed Signature selection.
141    pub fn associate_key<R, E>(
142        &self,
143        receipt: &SelectedKeyReceipt<'_>,
144        expected: structured_pkix::path::WorkingKey<'_>,
145        visitor: impl FnOnce(KeyAssociation) -> Result<R, E>,
146    ) -> Result<R, CatalogueError<E>> {
147        self.signature_method()
148            .map_err(CatalogueError::Processing)?;
149        let outcome = self
150            .session
151            .associate_receipt_in(self.index, receipt, expected, self.work)
152            .map_err(CatalogueError::Processing)?;
153        visitor(outcome).map_err(CatalogueError::Visitor)
154    }
155}