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(
312 &mut self,
313 key: &PrivateKey<'_>,
314 query: &Reader<'_>,
315 source: &Source<'_>,
316 ) -> core::result::Result<(), Self::Error>;
317 fn retain_key(&mut self, plaintext: Plaintext) -> core::result::Result<(), Self::Error>;
322}
323
324#[expect(
331 clippy::result_large_err,
332 reason = "retain opaque cause and four source maps without allocating on the refusal path"
333)]
334pub fn process<B: Backend>(
335 pfx: &Pfx<'_>,
336 query: &Reader<'_>,
337 backend: &mut B,
338) -> core::result::Result<Decision, Failure<B::Error>> {
339 let original = pfx
340 .original()
341 .reader_in(query)
342 .map_err(Failure::processing)?;
343 original
344 .charge(1)
345 .map_err(encoding)
346 .map_err(Failure::processing)?;
347 let decision = backend
348 .authenticate(&pfx.integrity, &original)
349 .map_err(Failure::callback)?;
350 if decision != Decision::Accept {
351 return Ok(decision);
352 }
353 nested(
354 pfx.safe(),
355 &original,
356 &Source::Document,
357 |reader, source| {
358 let root = required(reader, FIELD).map_err(Failure::processing)?;
359 let mut safes = sequence(reader, &root).map_err(Failure::processing)?;
360 end(reader).map_err(Failure::processing)?;
361 while let Some(element) = safes
362 .next_element()
363 .map_err(encoding)
364 .map_err(Failure::processing)?
365 {
366 let safe = Safe::read(&safes, &element).map_err(Failure::processing)?;
367 safes
368 .charge(1)
369 .map_err(encoding)
370 .map_err(Failure::processing)?;
371 match &safe {
372 Safe::Data(bytes) => nested(bytes, &safes, source, |reader, source| {
373 bags(reader, source, backend)
374 })?,
375 Safe::Encrypted(encrypted) => {
376 check_data(&safes, encrypted.content.kind, element.span())
377 .map_err(Failure::processing)?;
378 let ciphertext =
379 encrypted.content.ciphertext.as_ref().ok_or_else(|| {
380 Failure::processing(invalid(ErrorCode::DetachedContent, &element))
381 })?;
382 let plain = backend
383 .decrypt_safe(&safe, &safes, source)
384 .map_err(Failure::callback)?;
385 decrypted(&plain, &safes, ciphertext, source, |reader, source| {
386 bags(reader, source, backend)
387 })?;
388 }
389 Safe::Enveloped(enveloped) => {
390 check_data(&safes, enveloped.content.kind, element.span())
391 .map_err(Failure::processing)?;
392 let ciphertext =
393 enveloped.content.ciphertext.as_ref().ok_or_else(|| {
394 Failure::processing(invalid(ErrorCode::DetachedContent, &element))
395 })?;
396 let plain = backend
397 .decrypt_safe(&safe, &safes, source)
398 .map_err(Failure::callback)?;
399 decrypted(&plain, &safes, ciphertext, source, |reader, source| {
400 bags(reader, source, backend)
401 })?;
402 }
403 Safe::Other { .. } => {
404 return Err(Failure::processing(invalid(
405 ErrorCode::UnexpectedContentType,
406 &element,
407 )));
408 }
409 }
410 }
411 Ok(())
412 },
413 )?;
414 Ok(decision)
415}
416
417fn check_data(parent: &Reader<'_>, kind: structured_asn1::Oid<'_>, span: Span) -> Result<()> {
418 if !equal_oid(parent, kind, DATA)? {
419 return Err(Error::schema(
420 ErrorCode::UnexpectedContentType,
421 FIELD,
422 span.start,
423 ));
424 }
425 Ok(())
426}
427
428#[expect(
429 clippy::result_large_err,
430 reason = "owned source maps preserve error provenance without refusal-path allocation"
431)]
432fn bags<B: Backend>(
433 parent: &mut Reader<'_>,
434 source: &Source<'_>,
435 backend: &mut B,
436) -> core::result::Result<(), Failure<B::Error>> {
437 let root = required(parent, FIELD).map_err(Failure::processing)?;
438 let reader = sequence(parent, &root).map_err(Failure::processing)?;
439 end(parent).map_err(Failure::processing)?;
440 let execution = parent.execution();
441 let mut frames = execution.vector();
442 frames
443 .try_push(reader)
444 .map_err(|e| Failure::processing(Error::resource(e, FIELD, root.span().start)))?;
445 while let Some(frame) = frames.last_mut() {
446 frame
447 .charge(1)
448 .map_err(encoding)
449 .map_err(Failure::processing)?;
450 let Some(element) = frame
451 .next_element()
452 .map_err(encoding)
453 .map_err(Failure::processing)?
454 else {
455 frames.pop();
456 continue;
457 };
458 let bag = Bag::read(frame, &element).map_err(Failure::processing)?;
459 backend
460 .bag(&bag, frame, source)
461 .map_err(Failure::callback)?;
462 if let BagValue::Contents(value) = &bag.value {
463 let original = value.reader_in(frame).map_err(Failure::processing)?;
466 let reader = sequence(&original, value.element()).map_err(Failure::processing)?;
467 frames.try_push(reader).map_err(|e| {
468 Failure::processing(Error::resource(e, FIELD, element.span().start))
469 })?;
470 } else if let BagValue::Shrouded(key) = &bag.value {
471 let plain = backend
472 .decrypt_key(key, frame, source)
473 .map_err(Failure::callback)?;
474 decrypted(&plain, frame, &key.encrypted, source, |reader, source| {
475 let value = required(reader, FIELD).map_err(Failure::processing)?;
476 let key = PrivateKey::read(reader, &value).map_err(Failure::processing)?;
477 end(reader).map_err(Failure::processing)?;
478 reader
479 .charge(1)
480 .map_err(encoding)
481 .map_err(Failure::processing)?;
482 backend.key(&key, reader, source).map_err(Failure::callback)
483 })?;
484 backend.retain_key(plain).map_err(|error| {
485 Failure::callback(error).enclosed(Origin::Plaintext(key.encrypted.span()))
486 })?;
487 }
488 }
489 Ok(())
490}
491
492#[expect(
493 clippy::result_large_err,
494 reason = "preserve independently owned source chain with the original callback cause"
495)]
496fn nested<R, E>(
497 octets: &Octets<'_>,
498 parent: &Reader<'_>,
499 origin: &Source<'_>,
500 callback: impl for<'scope> FnOnce(
501 &mut Reader<'scope>,
502 &Source<'_>,
503 ) -> core::result::Result<R, Failure<E>>,
504) -> core::result::Result<R, Failure<E>> {
505 if !core::ptr::eq(octets.parser(), parent.parser()) {
506 return Err(Failure::processing(Error::pkix(
507 structured_pkix::Error::new(
508 structured_pkix::ErrorCode::ForeignReader,
509 FIELD,
510 octets.span().start,
511 ),
512 )));
513 }
514 let map = Fragments(SourceMap::of(octets, parent, FIELD).map_err(Failure::processing)?);
515 let joined;
516 let (bytes, base) = if let [piece] = octets.pieces() {
517 (piece.as_bytes(), piece.span().start)
518 } else {
519 joined = Plaintext::copy_octets(octets, parent).map_err(Failure::processing)?;
520 (&*joined.0, 0)
521 };
522 let reader = parent.parser().read_nested_query_in(
523 parent,
524 bytes,
525 base,
526 Encoding::Ber,
527 octets.outer_depth(),
528 );
529 let result = match reader {
530 Ok(mut reader) => callback(
531 &mut reader,
532 &Source::Carried {
533 map: &map,
534 parent: origin,
535 },
536 ),
537 Err(error) => Err(Failure::processing(encoding(error))),
538 };
539 result.map_err(|e| e.enclosed(Origin::Carried(map)))
540}
541
542#[expect(
543 clippy::result_large_err,
544 reason = "preserve source/opaque cause without another allocation on refusal"
545)]
546fn decrypted<R, E>(
547 plain: &Plaintext,
548 parent: &Reader<'_>,
549 encrypted: &Octets<'_>,
550 origin: &Source<'_>,
551 callback: impl for<'scope> FnOnce(
552 &mut Reader<'scope>,
553 &Source<'_>,
554 ) -> core::result::Result<R, Failure<E>>,
555) -> core::result::Result<R, Failure<E>> {
556 let ciphertext = encrypted.span();
557 if !plain.0.memory().same_account(parent.memory()) {
558 return Err(Failure::processing(Error::schema(
559 ErrorCode::InvalidLocation,
560 FIELD,
561 ciphertext.start,
562 )));
563 }
564 let reader = parent.parser().read_nested_query_in(
565 parent,
566 &plain.0,
567 0,
568 Encoding::Ber,
569 encrypted.outer_depth(),
570 );
571 let result = match reader {
572 Ok(mut reader) => callback(
573 &mut reader,
574 &Source::Plaintext {
575 ciphertext,
576 parent: origin,
577 },
578 ),
579 Err(error) => Err(Failure::processing(encoding(error))),
580 };
581 result.map_err(|e| e.enclosed(Origin::Plaintext(ciphertext)))
582}