1use super::*;
4use structured_asn1::memory::{MemoryError, OwnedMap, OwnedVec, Work};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct SelectedSignatureMethod {
9 pub identity: NodeIdentity,
11 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 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 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 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 let public = vec![0x42; 128 * 1024];
224 let exact = public.len() * 2; 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 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}