Skip to main content

xml_sec/xmldsig/verify/
capture.rs

1//! Original execution facts retained only by scoped verification.
2
3use super::*;
4use structured_asn1::memory::{MemoryError, OwnedMap, OwnedVec, Work};
5
6/// Original parsed method, not a reconstruction from an algorithm URI.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct SelectedSignatureMethod {
9    /// Original SignatureMethod node, including document generation.
10    pub identity: NodeIdentity,
11    /// Parsed signature algorithm, including effective RSA-PSS parameters.
12    pub algorithm: SignatureAlgorithm,
13}
14
15pub(super) trait VerificationCapture {
16    fn verify(
17        &self,
18        provider: &dyn crate::provider::CryptoProvider,
19        key: &dyn VerifyingKey,
20        input: crate::provider::SignatureVerificationInput<'_>,
21    ) -> Result<bool, DsigError> {
22        provider.verify_with_context(
23            key,
24            input.algorithm,
25            input.context,
26            input.data,
27            input.signature,
28        )
29    }
30    fn method(
31        &self,
32        _view: DocumentView<'_>,
33        _signed_info: Node<'_, '_>,
34        _algorithm: SignatureAlgorithm,
35    ) -> Result<(), MemoryError> {
36        Ok(())
37    }
38    fn admit_digest(
39        &self,
40        _reference: Option<NodeId>,
41        _algorithm: DigestAlgorithm,
42    ) -> Result<(), MemoryError> {
43        Ok(())
44    }
45    fn computed_digest(&self, _reference: Option<NodeId>) -> Result<(), MemoryError> {
46        Ok(())
47    }
48}
49
50impl VerificationCapture for () {}
51
52#[derive(Clone, Copy)]
53pub(super) struct CapturedDigest {
54    pub(super) algorithm: DigestAlgorithm,
55    pub(super) computed: bool,
56}
57
58pub(super) struct ScopedCapture<'work, W: Work> {
59    pub(super) seal: [u8; 1],
60    pub(super) key: RefCell<CapturedKey<W>>,
61    pub(super) method: Cell<Option<SelectedSignatureMethod>>,
62    // Arena ordinals are private to the retained original operation, never
63    // compared across parses or exposed as public provenance.
64    pub(super) digests: RefCell<OwnedMap<usize, CapturedDigest, W>>,
65    pub(super) failure: Cell<Option<MemoryError>>,
66    work: &'work W,
67}
68
69pub(super) struct CapturedKey<W: Work> {
70    pub(super) public: Option<OwnedVec<u8, W>>,
71    pub(super) binding: Option<crate::provider::ProviderBinding>,
72    observations: u8,
73}
74
75impl<'work, W: Work> ScopedCapture<'work, W> {
76    pub(super) fn new(work: &'work W) -> Self {
77        Self {
78            seal: [0],
79            key: RefCell::new(CapturedKey {
80                public: None,
81                binding: None,
82                observations: 0,
83            }),
84            method: Cell::new(None),
85            digests: RefCell::new(work.map()),
86            failure: Cell::new(None),
87            work,
88        }
89    }
90
91    fn retain_failure<T>(&self, result: Result<T, MemoryError>) -> Result<T, MemoryError> {
92        if let Err(error) = result
93            && self.failure.get().is_none()
94        {
95            self.failure.set(Some(error));
96        }
97        result
98    }
99
100    fn copy_public_evidence(&self, public: &[u8]) -> Result<OwnedVec<u8, W>, MemoryError> {
101        let mut bytes = self.work.vector();
102        // OwnedVec admits public.len() copy work before reserving capacity;
103        // its allocator separately admits allocation work before allocation.
104        // A separate byte charge here would bill the same copy twice; retain
105        // its original refusal so candidate recovery cannot erase it.
106        self.retain_failure(bytes.try_extend_from_slice(public))?;
107        Ok(bytes)
108    }
109}
110
111impl<W: Work> VerificationCapture for ScopedCapture<'_, W> {
112    fn verify(
113        &self,
114        provider: &dyn crate::provider::CryptoProvider,
115        key: &dyn VerifyingKey,
116        input: crate::provider::SignatureVerificationInput<'_>,
117    ) -> Result<bool, DsigError> {
118        let result = provider.verify_with_key_receipt(key, input, &mut |event| {
119            if let Some(error) = self.failure.get() {
120                return Err(error.into());
121            }
122            if matches!(event, crate::provider::KeyVerificationEvent::BeforeCandidate { depth }
123                if depth == crate::hard_limits::KEY_CANDIDATE_CEILING)
124            {
125                return self
126                    .retain_failure(Err(MemoryError::WorkExceeded))
127                    .map_err(Into::into);
128            }
129            self.retain_failure(self.work.charge_work(1))?;
130            let crate::provider::KeyVerificationEvent::Verified(key) = event else {
131                return Ok(());
132            };
133            let mut retained = self.key.borrow_mut();
134            if retained.observations != 0 {
135                // A custom composite that continues after success supplies no
136                // unique winning leaf. Never pick an arbitrary success for it.
137                retained.observations = 2;
138                retained.public = None;
139                retained.binding = None;
140                return Ok(());
141            }
142            retained.observations = 1;
143            if let Some(public) = key.association_spki(input.algorithm) {
144                retained.public = Some(self.copy_public_evidence(public)?);
145            }
146            retained.binding = key.provider_binding().cloned();
147            Ok(())
148        });
149        if let Some(error) = self.failure.get() {
150            return Err(error.into());
151        }
152        result
153    }
154    fn method(
155        &self,
156        view: DocumentView<'_>,
157        signed_info: Node<'_, '_>,
158        algorithm: SignatureAlgorithm,
159    ) -> Result<(), MemoryError> {
160        self.retain_failure((|| {
161            for child in signed_info.children() {
162                self.work.charge_work(64)?;
163                if child.has_tag_name((XMLDSIG_NS, "SignatureMethod")) {
164                    self.method.set(Some(SelectedSignatureMethod {
165                        identity: view.node_identity(child),
166                        algorithm,
167                    }));
168                    break;
169                }
170            }
171            Ok(())
172        })())
173    }
174
175    fn admit_digest(
176        &self,
177        reference: Option<NodeId>,
178        algorithm: DigestAlgorithm,
179    ) -> Result<(), MemoryError> {
180        self.retain_failure((|| {
181            if let Some(reference) = reference {
182                self.digests.borrow_mut().try_insert(
183                    reference.get_usize(),
184                    CapturedDigest {
185                        algorithm,
186                        computed: false,
187                    },
188                )?;
189            }
190            Ok(())
191        })())
192    }
193
194    fn computed_digest(&self, reference: Option<NodeId>) -> Result<(), MemoryError> {
195        self.retain_failure((|| {
196            if let Some(reference) = reference {
197                let mut digests = self.digests.borrow_mut();
198                let completed = digests
199                    .get_mut(&reference.get_usize(), self.work)?
200                    .expect("digest admitted before execution");
201                completed.computed = true;
202            }
203            Ok(())
204        })())
205    }
206}
207
208pub(super) struct CapturedOperation<P, B, C> {
209    pub(super) operation: OperationExecutionContext<P, B>,
210    pub(super) capture: C,
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use std::sync::Arc;
217    use structured_asn1::memory::{Memory, MemoryAccount};
218
219    #[test]
220    fn receipt_copy_charges_exact_byte_work_before_capacity_and_stays_terminal() {
221        // The bounded vector, not merely its observer event, pays every copied
222        // byte exactly once. An insufficient budget must leave no retained copy.
223        let public = vec![0x42; 128 * 1024];
224        let exact = public.len() * 2; // One byte-copy pass plus one allocation.
225        for allowance in [public.len() - 1, exact - 1, exact] {
226            let work = Memory::new(Arc::new(MemoryAccount::new(public.len() * 2, allowance)));
227            let capture = ScopedCapture::new(&work);
228            let result = capture.copy_public_evidence(&public);
229            if allowance < exact {
230                assert!(matches!(result, Err(MemoryError::WorkExceeded)));
231                assert_eq!(work.used(), 0);
232                assert_eq!(capture.failure.get(), Some(MemoryError::WorkExceeded));
233                let _ = capture.retain_failure(Err::<(), _>(MemoryError::LimitExceeded));
234                assert_eq!(capture.failure.get(), Some(MemoryError::WorkExceeded));
235            } else {
236                let copied = match result {
237                    Ok(copied) => copied,
238                    Err(error) => panic!("exact admitted copy failed: {error:?}"),
239                };
240                assert_eq!(&*copied, public.as_slice());
241                assert_eq!(work.work_used(), exact);
242                drop(copied);
243                assert_eq!(work.used(), 0);
244            }
245        }
246    }
247
248    #[test]
249    fn first_receipt_resource_failure_stays_terminal() {
250        // Manifest recovery must not replace an earlier allocator/capacity
251        // cause with a later work error while continuing other instructions.
252        let work = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
253        let capture = ScopedCapture::new(&work);
254        assert!(
255            capture
256                .retain_failure(Err::<(), _>(MemoryError::LimitExceeded))
257                .is_err()
258        );
259        assert!(
260            capture
261                .retain_failure(Err::<(), _>(MemoryError::WorkExceeded))
262                .is_err()
263        );
264        assert_eq!(capture.failure.get(), Some(MemoryError::LimitExceeded));
265    }
266}