use super::{
Bag, BagValue, DATA, FIELD, Integrity, Pfx, PrivateKey, Safe, ShroudedKey, encoding, end,
equal_oid, invalid, sequence,
};
use crate::{Error, ErrorCode, Octets, Result, content_source::SourceMap, signer::required};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Encoding, Reader, Span};
use zeroize::Zeroize;
pub struct Plaintext(OwnedVec<u8>);
impl Plaintext {
pub fn copy_from(bytes: &[u8], query: &Reader<'_>) -> Result<Self> {
let execution = query.execution();
execution
.charge_work(bytes.len() as u64)
.map_err(|e| Error::resource(e, FIELD, query.offset()))?;
let mut output = Wiping(Some(execution.vector()));
output
.bytes()
.try_extend_from_slice(bytes)
.map_err(|e| Error::resource(e, FIELD, query.offset()))?;
Ok(output.into_owned())
}
pub fn copy_octets(octets: &Octets<'_>, query: &Reader<'_>) -> Result<Self> {
if !core::ptr::eq(octets.parser(), query.parser()) {
return Err(Error::pkix(structured_pkix::Error::new(
structured_pkix::ErrorCode::ForeignReader,
FIELD,
octets.span().start,
)));
}
let execution = query.execution();
let fail = |e| Error::resource(e, FIELD, octets.span().start);
execution.charge_work(octets.len() as u64).map_err(fail)?;
execution
.charge_work(octets.pieces().len() as u64)
.map_err(fail)?;
let mut output = Wiping(Some(execution.vector()));
output
.bytes()
.try_reserve_exact(octets.len())
.map_err(fail)?;
for piece in octets.pieces() {
output
.bytes()
.try_extend_from_slice(piece.as_bytes())
.map_err(fail)?;
}
Ok(output.into_owned())
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn as_mut_bytes(&mut self) -> &mut [u8] {
&mut self.0
}
pub fn truncate(&mut self, length: usize) {
if length < self.0.len() {
self.0[length..].zeroize();
while self.0.len() > length {
self.0.pop();
}
}
}
}
pub fn legacy_password(password: &str, query: &Reader<'_>) -> Result<Plaintext> {
let execution = query.execution();
let fail = |e| Error::resource(e, FIELD, query.offset());
execution.charge_work(password.len() as u64).map_err(fail)?;
execution.charge_work(password.len() as u64).map_err(fail)?;
let mut count = 1_usize;
for ch in password.chars() {
if u32::from(ch) > u32::from(u16::MAX) {
return Err(Error::schema(
ErrorCode::MalformedValue,
FIELD,
query.offset(),
));
}
count = count
.checked_add(1)
.ok_or_else(|| Error::schema(ErrorCode::LengthOverflow, FIELD, query.offset()))?;
}
let size = count
.checked_mul(2)
.ok_or_else(|| Error::schema(ErrorCode::LengthOverflow, FIELD, query.offset()))?;
execution.charge_work(size as u64).map_err(fail)?;
let mut output = Wiping(Some(execution.vector()));
output.bytes().try_reserve_exact(size).map_err(fail)?;
for ch in password.chars() {
debug_assert!(u16::try_from(u32::from(ch)).is_ok());
let encoded = u32::from(ch).to_be_bytes();
output
.bytes()
.try_extend_from_slice(&encoded[2..])
.map_err(fail)?;
}
output
.bytes()
.try_extend_from_slice(&[0, 0])
.map_err(fail)?;
Ok(output.into_owned())
}
impl Drop for Plaintext {
fn drop(&mut self) {
self.0.zeroize();
}
}
struct Wiping<W: Work>(Option<OwnedVec<u8, W>>);
impl<W: Work> Wiping<W> {
fn bytes(&mut self) -> &mut OwnedVec<u8, W> {
self.0
.as_mut()
.expect("buffer exists until consuming transfer")
}
fn into_owned(mut self) -> Plaintext {
Plaintext(self.0.take().expect("consuming transfer once").into_owned())
}
}
impl<W: Work> Drop for Wiping<W> {
fn drop(&mut self) {
if let Some(bytes) = self.0.as_mut() {
bytes.zeroize();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Decision {
Accept,
Reject,
Unsupported,
}
pub enum Source<'a> {
Document,
Carried {
map: &'a Fragments,
parent: &'a Source<'a>,
},
Plaintext {
ciphertext: Span,
parent: &'a Source<'a>,
},
}
#[derive(Debug)]
pub enum Origin {
Carried(Fragments),
Plaintext(Span),
}
#[derive(Debug)]
pub struct Fragments(SourceMap);
impl Fragments {
#[must_use]
pub fn enclosure(&self) -> Span {
self.0.carrier()
}
pub fn offset_in_parent(&self, at: u64, work: &impl Work) -> Result<u64> {
self.0.project_offset(at, work)
}
pub fn ranges_in_parent(&self, span: Span, work: &impl Work) -> Result<OwnedVec<Span>> {
self.0.project_span(span, work)
}
pub fn try_copy_in(&self, work: &impl Work) -> Result<Self> {
self.0.try_copy_in(work).map(Self)
}
}
#[derive(Debug)]
pub struct Failure<E> {
pub cause: Cause<E>,
pub origins: [Option<Origin>; 4],
}
#[derive(Debug)]
pub enum Cause<E> {
Processing(Error),
Callback(E),
}
impl<E> Failure<E> {
fn processing(error: Error) -> Self {
Self {
cause: Cause::Processing(error),
origins: core::array::from_fn(|_| None),
}
}
fn callback(error: E) -> Self {
Self {
cause: Cause::Callback(error),
origins: core::array::from_fn(|_| None),
}
}
fn enclosed(mut self, origin: Origin) -> Self {
let slot = self
.origins
.iter_mut()
.find(|item| item.is_none())
.expect("four pipeline transformations maximum");
*slot = Some(origin);
self
}
}
pub trait Backend {
type Error;
fn authenticate(
&mut self,
integrity: &Integrity<'_>,
query: &Reader<'_>,
) -> core::result::Result<Decision, Self::Error>;
fn decrypt_safe(
&mut self,
safe: &Safe<'_>,
query: &Reader<'_>,
source: &Source<'_>,
) -> core::result::Result<Plaintext, Self::Error>;
fn decrypt_key(
&mut self,
key: &ShroudedKey<'_>,
query: &Reader<'_>,
source: &Source<'_>,
) -> core::result::Result<Plaintext, Self::Error>;
fn bag(
&mut self,
bag: &Bag<'_>,
query: &Reader<'_>,
source: &Source<'_>,
) -> core::result::Result<(), Self::Error>;
fn key(
&mut self,
key: &PrivateKey<'_>,
query: &Reader<'_>,
source: &Source<'_>,
) -> core::result::Result<(), Self::Error>;
}
#[expect(
clippy::result_large_err,
reason = "retain opaque cause and four source maps without allocating on the refusal path"
)]
pub fn process<B: Backend>(
pfx: &Pfx<'_>,
query: &Reader<'_>,
backend: &mut B,
) -> core::result::Result<Decision, Failure<B::Error>> {
let original = pfx
.original()
.reader_in(query)
.map_err(Failure::processing)?;
original
.charge(1)
.map_err(encoding)
.map_err(Failure::processing)?;
let decision = backend
.authenticate(&pfx.integrity, &original)
.map_err(Failure::callback)?;
if decision != Decision::Accept {
return Ok(decision);
}
nested(
pfx.safe(),
&original,
&Source::Document,
|reader, source| {
let root = required(reader, FIELD).map_err(Failure::processing)?;
let mut safes = sequence(reader, &root).map_err(Failure::processing)?;
end(reader).map_err(Failure::processing)?;
while let Some(element) = safes
.next_element()
.map_err(encoding)
.map_err(Failure::processing)?
{
let safe = Safe::read(&safes, &element).map_err(Failure::processing)?;
safes
.charge(1)
.map_err(encoding)
.map_err(Failure::processing)?;
match &safe {
Safe::Data(bytes) => nested(bytes, &safes, source, |reader, source| {
bags(reader, source, backend)
})?,
Safe::Encrypted(encrypted) => {
check_data(&safes, encrypted.content.kind, element.span())
.map_err(Failure::processing)?;
let ciphertext =
encrypted.content.ciphertext.as_ref().ok_or_else(|| {
Failure::processing(invalid(ErrorCode::DetachedContent, &element))
})?;
let plain = backend
.decrypt_safe(&safe, &safes, source)
.map_err(Failure::callback)?;
decrypted(&plain, &safes, ciphertext, source, |reader, source| {
bags(reader, source, backend)
})?;
}
Safe::Enveloped(enveloped) => {
check_data(&safes, enveloped.content.kind, element.span())
.map_err(Failure::processing)?;
let ciphertext =
enveloped.content.ciphertext.as_ref().ok_or_else(|| {
Failure::processing(invalid(ErrorCode::DetachedContent, &element))
})?;
let plain = backend
.decrypt_safe(&safe, &safes, source)
.map_err(Failure::callback)?;
decrypted(&plain, &safes, ciphertext, source, |reader, source| {
bags(reader, source, backend)
})?;
}
Safe::Other { .. } => {
return Err(Failure::processing(invalid(
ErrorCode::UnexpectedContentType,
&element,
)));
}
}
}
Ok(())
},
)?;
Ok(decision)
}
fn check_data(parent: &Reader<'_>, kind: structured_asn1::Oid<'_>, span: Span) -> Result<()> {
if !equal_oid(parent, kind, DATA)? {
return Err(Error::schema(
ErrorCode::UnexpectedContentType,
FIELD,
span.start,
));
}
Ok(())
}
#[expect(
clippy::result_large_err,
reason = "owned source maps preserve error provenance without refusal-path allocation"
)]
fn bags<B: Backend>(
parent: &mut Reader<'_>,
source: &Source<'_>,
backend: &mut B,
) -> core::result::Result<(), Failure<B::Error>> {
let root = required(parent, FIELD).map_err(Failure::processing)?;
let reader = sequence(parent, &root).map_err(Failure::processing)?;
end(parent).map_err(Failure::processing)?;
let execution = parent.execution();
let mut frames = execution.vector();
frames
.try_push(reader)
.map_err(|e| Failure::processing(Error::resource(e, FIELD, root.span().start)))?;
while let Some(frame) = frames.last_mut() {
frame
.charge(1)
.map_err(encoding)
.map_err(Failure::processing)?;
let Some(element) = frame
.next_element()
.map_err(encoding)
.map_err(Failure::processing)?
else {
frames.pop();
continue;
};
let bag = Bag::read(frame, &element).map_err(Failure::processing)?;
backend
.bag(&bag, frame, source)
.map_err(Failure::callback)?;
if let BagValue::Contents(value) = &bag.value {
let original = value.reader_in(frame).map_err(Failure::processing)?;
let reader = sequence(&original, value.element()).map_err(Failure::processing)?;
frames.try_push(reader).map_err(|e| {
Failure::processing(Error::resource(e, FIELD, element.span().start))
})?;
} else if let BagValue::Shrouded(key) = &bag.value {
let plain = backend
.decrypt_key(key, frame, source)
.map_err(Failure::callback)?;
decrypted(&plain, frame, &key.encrypted, source, |reader, source| {
let value = required(reader, FIELD).map_err(Failure::processing)?;
let key = PrivateKey::read(reader, &value).map_err(Failure::processing)?;
end(reader).map_err(Failure::processing)?;
reader
.charge(1)
.map_err(encoding)
.map_err(Failure::processing)?;
backend.key(&key, reader, source).map_err(Failure::callback)
})?;
}
}
Ok(())
}
#[expect(
clippy::result_large_err,
reason = "preserve independently owned source chain with the original callback cause"
)]
fn nested<R, E>(
octets: &Octets<'_>,
parent: &Reader<'_>,
origin: &Source<'_>,
callback: impl for<'scope> FnOnce(
&mut Reader<'scope>,
&Source<'_>,
) -> core::result::Result<R, Failure<E>>,
) -> core::result::Result<R, Failure<E>> {
if !core::ptr::eq(octets.parser(), parent.parser()) {
return Err(Failure::processing(Error::pkix(
structured_pkix::Error::new(
structured_pkix::ErrorCode::ForeignReader,
FIELD,
octets.span().start,
),
)));
}
let map = Fragments(SourceMap::of(octets, parent, FIELD).map_err(Failure::processing)?);
let joined;
let (bytes, base) = if let [piece] = octets.pieces() {
(piece.as_bytes(), piece.span().start)
} else {
joined = Plaintext::copy_octets(octets, parent).map_err(Failure::processing)?;
(&*joined.0, 0)
};
let reader = parent.parser().read_nested_query_in(
parent,
bytes,
base,
Encoding::Ber,
octets.outer_depth(),
);
let result = match reader {
Ok(mut reader) => callback(
&mut reader,
&Source::Carried {
map: &map,
parent: origin,
},
),
Err(error) => Err(Failure::processing(encoding(error))),
};
result.map_err(|e| e.enclosed(Origin::Carried(map)))
}
#[expect(
clippy::result_large_err,
reason = "preserve source/opaque cause without another allocation on refusal"
)]
fn decrypted<R, E>(
plain: &Plaintext,
parent: &Reader<'_>,
encrypted: &Octets<'_>,
origin: &Source<'_>,
callback: impl for<'scope> FnOnce(
&mut Reader<'scope>,
&Source<'_>,
) -> core::result::Result<R, Failure<E>>,
) -> core::result::Result<R, Failure<E>> {
let ciphertext = encrypted.span();
if !plain.0.memory().same_account(parent.memory()) {
return Err(Failure::processing(Error::schema(
ErrorCode::InvalidLocation,
FIELD,
ciphertext.start,
)));
}
let reader = parent.parser().read_nested_query_in(
parent,
&plain.0,
0,
Encoding::Ber,
encrypted.outer_depth(),
);
let result = match reader {
Ok(mut reader) => callback(
&mut reader,
&Source::Plaintext {
ciphertext,
parent: origin,
},
),
Err(error) => Err(Failure::processing(encoding(error))),
};
result.map_err(|e| e.enclosed(Origin::Plaintext(ciphertext)))
}