1use super::{
4 Bag, BagValue, DATA, FIELD, Integrity, Pfx, PrivateKey, Safe, ShroudedKey, encoding, end,
5 equal_oid, invalid, sequence,
6};
7use crate::{Error, ErrorCode, Octets, Result, content_source::SourceMap, signer::required};
8use structured_asn1::memory::{OwnedVec, Work};
9use structured_asn1::{Encoding, Reader, Span};
10use zeroize::Zeroize;
11
12pub struct Plaintext(OwnedVec<u8>);
16impl Plaintext {
17 pub fn copy_from(bytes: &[u8], query: &Reader<'_>) -> Result<Self> {
20 let execution = query.execution();
21 execution
22 .charge_work(bytes.len() as u64)
23 .map_err(|e| Error::resource(e, FIELD, query.offset()))?;
24 let mut output = Wiping(Some(execution.vector()));
25 output
26 .bytes()
27 .try_extend_from_slice(bytes)
28 .map_err(|e| Error::resource(e, FIELD, query.offset()))?;
29 Ok(output.into_owned())
30 }
31 pub fn copy_octets(octets: &Octets<'_>, query: &Reader<'_>) -> Result<Self> {
34 if !core::ptr::eq(octets.parser(), query.parser()) {
35 return Err(Error::pkix(structured_pkix::Error::new(
36 structured_pkix::ErrorCode::ForeignReader,
37 FIELD,
38 octets.span().start,
39 )));
40 }
41 let execution = query.execution();
42 let fail = |e| Error::resource(e, FIELD, octets.span().start);
43 execution.charge_work(octets.len() as u64).map_err(fail)?;
44 execution
45 .charge_work(octets.pieces().len() as u64)
46 .map_err(fail)?;
47 let mut output = Wiping(Some(execution.vector()));
48 output
49 .bytes()
50 .try_reserve_exact(octets.len())
51 .map_err(fail)?;
52 for piece in octets.pieces() {
53 output
54 .bytes()
55 .try_extend_from_slice(piece.as_bytes())
56 .map_err(fail)?;
57 }
58 Ok(output.into_owned())
59 }
60 #[must_use]
62 pub fn as_bytes(&self) -> &[u8] {
63 &self.0
64 }
65 pub fn as_mut_bytes(&mut self) -> &mut [u8] {
67 &mut self.0
68 }
69 pub fn truncate(&mut self, length: usize) {
73 if length < self.0.len() {
74 self.0[length..].zeroize();
75 while self.0.len() > length {
76 self.0.pop();
77 }
78 }
79 }
80}
81
82pub fn legacy_password(password: &str, query: &Reader<'_>) -> Result<Plaintext> {
89 let execution = query.execution();
90 let fail = |e| Error::resource(e, FIELD, query.offset());
91 execution.charge_work(password.len() as u64).map_err(fail)?;
93 execution.charge_work(password.len() as u64).map_err(fail)?;
94 let mut count = 1_usize;
95 for ch in password.chars() {
96 if u32::from(ch) > u32::from(u16::MAX) {
97 return Err(Error::schema(
98 ErrorCode::MalformedValue,
99 FIELD,
100 query.offset(),
101 ));
102 }
103 count = count
104 .checked_add(1)
105 .ok_or_else(|| Error::schema(ErrorCode::LengthOverflow, FIELD, query.offset()))?;
106 }
107 let size = count
108 .checked_mul(2)
109 .ok_or_else(|| Error::schema(ErrorCode::LengthOverflow, FIELD, query.offset()))?;
110 execution.charge_work(size as u64).map_err(fail)?;
111 let mut output = Wiping(Some(execution.vector()));
112 output.bytes().try_reserve_exact(size).map_err(fail)?;
113 for ch in password.chars() {
114 debug_assert!(u16::try_from(u32::from(ch)).is_ok());
115 let encoded = u32::from(ch).to_be_bytes();
116 output
117 .bytes()
118 .try_extend_from_slice(&encoded[2..])
119 .map_err(fail)?;
120 }
121 output
122 .bytes()
123 .try_extend_from_slice(&[0, 0])
124 .map_err(fail)?;
125 Ok(output.into_owned())
126}
127impl Drop for Plaintext {
128 fn drop(&mut self) {
129 self.0.zeroize();
130 }
131}
132struct Wiping<W: Work>(Option<OwnedVec<u8, W>>);
133impl<W: Work> Wiping<W> {
134 fn bytes(&mut self) -> &mut OwnedVec<u8, W> {
135 self.0
136 .as_mut()
137 .expect("buffer exists until consuming transfer")
138 }
139 fn into_owned(mut self) -> Plaintext {
140 Plaintext(self.0.take().expect("consuming transfer once").into_owned())
141 }
142}
143impl<W: Work> Drop for Wiping<W> {
144 fn drop(&mut self) {
145 if let Some(bytes) = self.0.as_mut() {
146 bytes.zeroize();
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum Decision {
154 Accept,
156 Reject,
158 Unsupported,
160}
161
162pub enum Source<'a> {
166 Document,
168 Carried {
170 map: &'a Fragments,
172 parent: &'a Source<'a>,
174 },
175 Plaintext {
178 ciphertext: Span,
180 parent: &'a Source<'a>,
182 },
183}
184#[derive(Debug)]
186pub enum Origin {
187 Carried(Fragments),
189 Plaintext(Span),
192}
193
194#[derive(Debug)]
199pub struct Fragments(SourceMap);
200impl Fragments {
201 #[must_use]
203 pub fn enclosure(&self) -> Span {
204 self.0.carrier()
205 }
206 pub fn offset_in_parent(&self, at: u64, work: &impl Work) -> Result<u64> {
210 self.0.project_offset(at, work)
211 }
212 pub fn ranges_in_parent(&self, span: Span, work: &impl Work) -> Result<OwnedVec<Span>> {
215 self.0.project_span(span, work)
216 }
217 pub fn try_copy_in(&self, work: &impl Work) -> Result<Self> {
219 self.0.try_copy_in(work).map(Self)
220 }
221}
222#[derive(Debug)]
226pub struct Failure<E> {
227 pub cause: Cause<E>,
229 pub origins: [Option<Origin>; 4],
232}
233#[derive(Debug)]
235pub enum Cause<E> {
236 Processing(Error),
238 Callback(E),
240}
241impl<E> Failure<E> {
242 fn processing(error: Error) -> Self {
243 Self {
244 cause: Cause::Processing(error),
245 origins: core::array::from_fn(|_| None),
246 }
247 }
248 fn callback(error: E) -> Self {
249 Self {
250 cause: Cause::Callback(error),
251 origins: core::array::from_fn(|_| None),
252 }
253 }
254 fn enclosed(mut self, origin: Origin) -> Self {
255 let slot = self
256 .origins
257 .iter_mut()
258 .find(|item| item.is_none())
259 .expect("four pipeline transformations maximum");
260 *slot = Some(origin);
261 self
262 }
263}
264
265pub trait Backend {
269 type Error;
271 fn authenticate(
276 &mut self,
277 integrity: &Integrity<'_>,
278 query: &Reader<'_>,
279 ) -> core::result::Result<Decision, Self::Error>;
280 fn decrypt_safe(
285 &mut self,
286 safe: &Safe<'_>,
287 query: &Reader<'_>,
288 source: &Source<'_>,
289 ) -> core::result::Result<Plaintext, Self::Error>;
290 fn decrypt_key(
293 &mut self,
294 key: &ShroudedKey<'_>,
295 query: &Reader<'_>,
296 source: &Source<'_>,
297 ) -> core::result::Result<Plaintext, Self::Error>;
298 fn bag(
302 &mut self,
303 bag: &Bag<'_>,
304 query: &Reader<'_>,
305 source: &Source<'_>,
306 ) -> core::result::Result<(), Self::Error>;
307 fn key(
310 &mut self,
311 key: &PrivateKey<'_>,
312 query: &Reader<'_>,
313 source: &Source<'_>,
314 ) -> core::result::Result<(), Self::Error>;
315}
316
317#[expect(
324 clippy::result_large_err,
325 reason = "retain opaque cause and four source maps without allocating on the refusal path"
326)]
327pub fn process<B: Backend>(
328 pfx: &Pfx<'_>,
329 query: &Reader<'_>,
330 backend: &mut B,
331) -> core::result::Result<Decision, Failure<B::Error>> {
332 let original = pfx
333 .original()
334 .reader_in(query)
335 .map_err(Failure::processing)?;
336 original
337 .charge(1)
338 .map_err(encoding)
339 .map_err(Failure::processing)?;
340 let decision = backend
341 .authenticate(&pfx.integrity, &original)
342 .map_err(Failure::callback)?;
343 if decision != Decision::Accept {
344 return Ok(decision);
345 }
346 nested(
347 pfx.safe(),
348 &original,
349 &Source::Document,
350 |reader, source| {
351 let root = required(reader, FIELD).map_err(Failure::processing)?;
352 let mut safes = sequence(reader, &root).map_err(Failure::processing)?;
353 end(reader).map_err(Failure::processing)?;
354 while let Some(element) = safes
355 .next_element()
356 .map_err(encoding)
357 .map_err(Failure::processing)?
358 {
359 let safe = Safe::read(&safes, &element).map_err(Failure::processing)?;
360 safes
361 .charge(1)
362 .map_err(encoding)
363 .map_err(Failure::processing)?;
364 match &safe {
365 Safe::Data(bytes) => nested(bytes, &safes, source, |reader, source| {
366 bags(reader, source, backend)
367 })?,
368 Safe::Encrypted(encrypted) => {
369 check_data(&safes, encrypted.content.kind, element.span())
370 .map_err(Failure::processing)?;
371 let ciphertext =
372 encrypted.content.ciphertext.as_ref().ok_or_else(|| {
373 Failure::processing(invalid(ErrorCode::DetachedContent, &element))
374 })?;
375 let plain = backend
376 .decrypt_safe(&safe, &safes, source)
377 .map_err(Failure::callback)?;
378 decrypted(&plain, &safes, ciphertext, source, |reader, source| {
379 bags(reader, source, backend)
380 })?;
381 }
382 Safe::Enveloped(enveloped) => {
383 check_data(&safes, enveloped.content.kind, element.span())
384 .map_err(Failure::processing)?;
385 let ciphertext =
386 enveloped.content.ciphertext.as_ref().ok_or_else(|| {
387 Failure::processing(invalid(ErrorCode::DetachedContent, &element))
388 })?;
389 let plain = backend
390 .decrypt_safe(&safe, &safes, source)
391 .map_err(Failure::callback)?;
392 decrypted(&plain, &safes, ciphertext, source, |reader, source| {
393 bags(reader, source, backend)
394 })?;
395 }
396 Safe::Other { .. } => {
397 return Err(Failure::processing(invalid(
398 ErrorCode::UnexpectedContentType,
399 &element,
400 )));
401 }
402 }
403 }
404 Ok(())
405 },
406 )?;
407 Ok(decision)
408}
409
410fn check_data(parent: &Reader<'_>, kind: structured_asn1::Oid<'_>, span: Span) -> Result<()> {
411 if !equal_oid(parent, kind, DATA)? {
412 return Err(Error::schema(
413 ErrorCode::UnexpectedContentType,
414 FIELD,
415 span.start,
416 ));
417 }
418 Ok(())
419}
420
421#[expect(
422 clippy::result_large_err,
423 reason = "owned source maps preserve error provenance without refusal-path allocation"
424)]
425fn bags<B: Backend>(
426 parent: &mut Reader<'_>,
427 source: &Source<'_>,
428 backend: &mut B,
429) -> core::result::Result<(), Failure<B::Error>> {
430 let root = required(parent, FIELD).map_err(Failure::processing)?;
431 let reader = sequence(parent, &root).map_err(Failure::processing)?;
432 end(parent).map_err(Failure::processing)?;
433 let execution = parent.execution();
434 let mut frames = execution.vector();
435 frames
436 .try_push(reader)
437 .map_err(|e| Failure::processing(Error::resource(e, FIELD, root.span().start)))?;
438 while let Some(frame) = frames.last_mut() {
439 frame
440 .charge(1)
441 .map_err(encoding)
442 .map_err(Failure::processing)?;
443 let Some(element) = frame
444 .next_element()
445 .map_err(encoding)
446 .map_err(Failure::processing)?
447 else {
448 frames.pop();
449 continue;
450 };
451 let bag = Bag::read(frame, &element).map_err(Failure::processing)?;
452 backend
453 .bag(&bag, frame, source)
454 .map_err(Failure::callback)?;
455 if let BagValue::Contents(value) = &bag.value {
456 let original = value.reader_in(frame).map_err(Failure::processing)?;
459 let reader = sequence(&original, value.element()).map_err(Failure::processing)?;
460 frames.try_push(reader).map_err(|e| {
461 Failure::processing(Error::resource(e, FIELD, element.span().start))
462 })?;
463 } else if let BagValue::Shrouded(key) = &bag.value {
464 let plain = backend
465 .decrypt_key(key, frame, source)
466 .map_err(Failure::callback)?;
467 decrypted(&plain, frame, &key.encrypted, source, |reader, source| {
468 let value = required(reader, FIELD).map_err(Failure::processing)?;
469 let key = PrivateKey::read(reader, &value).map_err(Failure::processing)?;
470 end(reader).map_err(Failure::processing)?;
471 reader
472 .charge(1)
473 .map_err(encoding)
474 .map_err(Failure::processing)?;
475 backend.key(&key, reader, source).map_err(Failure::callback)
476 })?;
477 }
478 }
479 Ok(())
480}
481
482#[expect(
483 clippy::result_large_err,
484 reason = "preserve independently owned source chain with the original callback cause"
485)]
486fn nested<R, E>(
487 octets: &Octets<'_>,
488 parent: &Reader<'_>,
489 origin: &Source<'_>,
490 callback: impl for<'scope> FnOnce(
491 &mut Reader<'scope>,
492 &Source<'_>,
493 ) -> core::result::Result<R, Failure<E>>,
494) -> core::result::Result<R, Failure<E>> {
495 if !core::ptr::eq(octets.parser(), parent.parser()) {
496 return Err(Failure::processing(Error::pkix(
497 structured_pkix::Error::new(
498 structured_pkix::ErrorCode::ForeignReader,
499 FIELD,
500 octets.span().start,
501 ),
502 )));
503 }
504 let map = Fragments(SourceMap::of(octets, parent, FIELD).map_err(Failure::processing)?);
505 let joined;
506 let (bytes, base) = if let [piece] = octets.pieces() {
507 (piece.as_bytes(), piece.span().start)
508 } else {
509 joined = Plaintext::copy_octets(octets, parent).map_err(Failure::processing)?;
510 (&*joined.0, 0)
511 };
512 let reader = parent.parser().read_nested_query_in(
513 parent,
514 bytes,
515 base,
516 Encoding::Ber,
517 octets.outer_depth(),
518 );
519 let result = match reader {
520 Ok(mut reader) => callback(
521 &mut reader,
522 &Source::Carried {
523 map: &map,
524 parent: origin,
525 },
526 ),
527 Err(error) => Err(Failure::processing(encoding(error))),
528 };
529 result.map_err(|e| e.enclosed(Origin::Carried(map)))
530}
531
532#[expect(
533 clippy::result_large_err,
534 reason = "preserve source/opaque cause without another allocation on refusal"
535)]
536fn decrypted<R, E>(
537 plain: &Plaintext,
538 parent: &Reader<'_>,
539 encrypted: &Octets<'_>,
540 origin: &Source<'_>,
541 callback: impl for<'scope> FnOnce(
542 &mut Reader<'scope>,
543 &Source<'_>,
544 ) -> core::result::Result<R, Failure<E>>,
545) -> core::result::Result<R, Failure<E>> {
546 let ciphertext = encrypted.span();
547 if !plain.0.memory().same_account(parent.memory()) {
548 return Err(Failure::processing(Error::schema(
549 ErrorCode::InvalidLocation,
550 FIELD,
551 ciphertext.start,
552 )));
553 }
554 let reader = parent.parser().read_nested_query_in(
555 parent,
556 &plain.0,
557 0,
558 Encoding::Ber,
559 encrypted.outer_depth(),
560 );
561 let result = match reader {
562 Ok(mut reader) => callback(
563 &mut reader,
564 &Source::Plaintext {
565 ciphertext,
566 parent: origin,
567 },
568 ),
569 Err(error) => Err(Failure::processing(encoding(error))),
570 };
571 result.map_err(|e| e.enclosed(Origin::Plaintext(ciphertext)))
572}