use super::{
AttributeCertificate,
validation::usage::{AttributeUse, Checked},
};
use crate::{
Error, ErrorCode, Result,
attribute::Attribute,
enveloped::{EnvelopedData, oid_is},
signer::required,
};
use structured_asn1::memory::{Execution, OwnedVec, Work};
use structured_asn1::{Element, Encoding, Integer, Oid, Reader, Span, Tag, universal};
use structured_pkix::general_name::{self, GeneralName};
pub const IDENTIFIER: Oid<'static> = Oid::from_bytes(&[43, 6, 1, 5, 5, 7, 10, 6]);
pub const CONTENT_TYPE: Oid<'static> =
Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 1, 14]);
const FIELD: &str = "attributeCertificate.encAttrs";
const CLEAR: &str = "acClearAttrs";
#[derive(Debug)]
pub struct ClearAttrs<'a> {
pub issuer: GeneralName<'a>,
pub serial: Integer<'a>,
pub attributes: OwnedVec<Attribute<'a>>,
element: Element<'a>,
}
impl<'a> ClearAttrs<'a> {
pub fn read(parent: &mut Reader<'a>) -> Result<Self> {
let element = parent
.expect(Tag::constructed(universal::SEQUENCE))
.map_err(|e| Error::encoding(e, CLEAR))?;
let mut der = parent
.reread(&element, Encoding::Der)
.map_err(|e| Error::encoding(e, CLEAR))?;
let element = der
.expect(Tag::constructed(universal::SEQUENCE))
.map_err(|e| Error::encoding(e, CLEAR))?;
let mut inside = der.enter(&element).map_err(|e| Error::encoding(e, CLEAR))?;
let value = required(&mut inside, "acClearAttrs.acIssuer")?;
let issuer =
general_name::read(&inside, &value, "acClearAttrs.acIssuer").map_err(Error::pkix)?;
let serial = inside
.read_integer()
.map_err(|e| Error::encoding(e, "acClearAttrs.acSerial"))?;
let sequence = inside
.expect(Tag::constructed(universal::SEQUENCE))
.map_err(|e| Error::encoding(e, "acClearAttrs.attrs"))?;
let mut members = inside
.enter(&sequence)
.map_err(|e| Error::encoding(e, CLEAR))?;
let work = inside.execution();
let mut attributes = work.vector();
while let Some(element) = members
.next_element()
.map_err(|e| Error::encoding(e, CLEAR))?
{
attributes
.try_reserve(1)
.map_err(|e| Error::resource(e, CLEAR, element.span().start))?;
let value = Attribute::read(&members, &element, "acClearAttrs.attrs")?;
attributes
.try_push(value)
.map_err(|e| Error::resource(e, CLEAR, element.span().start))?;
}
end(&inside)?;
end(parent)?;
Ok(Self {
issuer,
serial,
attributes: attributes.into_owned(),
element,
})
}
#[must_use]
pub fn element(&self) -> &Element<'a> {
&self.element
}
}
pub enum Attempt<T> {
Decrypted(T),
NotRecipient,
Unsupported,
Rejected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rejection {
Decryption,
Issuer,
Serial,
}
#[derive(Debug)]
pub enum Outcome<T> {
Processed(T),
NotRecipient,
Unsupported,
Rejected(Rejection),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlaintextSource {
pub value: usize,
pub envelope: Span,
}
#[derive(Debug)]
pub enum DecryptError<P, V> {
Processing(Error),
Provider {
envelope: Span,
cause: P,
},
Plaintext {
cause: Error,
source: PlaintextSource,
},
Visitor {
cause: V,
source: PlaintextSource,
},
}
pub struct Bound<'s> {
clear: ClearAttrs<'s>,
attributes: OwnedVec<AttributeUse>,
source: PlaintextSource,
}
impl Bound<'_> {
#[must_use]
pub fn clear(&self) -> &ClearAttrs<'_> {
&self.clear
}
#[must_use]
pub fn attributes(&self) -> &[AttributeUse] {
&self.attributes
}
#[must_use]
pub fn source(&self) -> PlaintextSource {
self.source
}
}
pub struct Encrypted<'r, 'a> {
certificate: &'r AttributeCertificate<'a>,
envelopes: OwnedVec<EnvelopedData<'a>>,
}
impl<'r, 'a> Encrypted<'r, 'a> {
pub fn read(
certificate: &'r AttributeCertificate<'a>,
parent: &Reader<'a>,
) -> Result<Option<Self>> {
if !core::ptr::eq(certificate.parser, parent.parser()) {
return Err(Error::pkix(structured_pkix::Error::new(
structured_pkix::ErrorCode::ForeignReader,
FIELD,
certificate.element().span().start,
)));
}
let work = parent.execution();
let mut selected = None;
for attribute in &certificate.information().attributes {
work.charge_work(1)
.map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
if attribute.kind().as_bytes().len() != IDENTIFIER.as_bytes().len() {
continue;
}
work.charge_work(IDENTIFIER.as_bytes().len() as u64)
.map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
if attribute.kind() == IDENTIFIER {
selected = Some(attribute);
break;
}
}
let Some(attribute) = selected else {
return Ok(None);
};
let mut envelopes = work.vector();
envelopes
.try_reserve_exact(attribute.values().len())
.map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
for ordinal in 0..attribute.values().len() {
let mut reader = attribute
.value_reader_in(ordinal, parent, FIELD)?
.ok_or_else(|| {
Error::schema(ErrorCode::InvalidLocation, FIELD, attribute.span().start)
})?;
let envelope = EnvelopedData::read(&mut reader)?;
oid_is(
envelope.content.kind,
CONTENT_TYPE,
&reader,
FIELD,
envelope.element().span().start,
)?;
if envelope.content.ciphertext.is_none() {
return Err(Error::schema(
ErrorCode::DetachedContent,
FIELD,
envelope.element().span().start,
));
}
envelopes
.try_push(envelope)
.map_err(|e| Error::resource(e, FIELD, attribute.span().start))?;
}
Ok(Some(Self {
certificate,
envelopes: envelopes.into_owned(),
}))
}
#[must_use]
pub fn envelopes(&self) -> &[EnvelopedData<'a>] {
&self.envelopes
}
pub fn decrypt_all<'p, 'd, 'c, S, P, V, R>(
&self,
checked: &Checked<'_, 'p, 'd, 'c, S>,
parent: &Reader<'_>,
mut provider: impl FnMut(
&EnvelopedData<'a>,
&Execution<'_>,
) -> core::result::Result<Attempt<OwnedVec<u8>>, P>,
mut visitor: impl for<'scope> FnMut(
&Bound<'scope>,
&Execution<'_>,
) -> core::result::Result<R, V>,
) -> core::result::Result<OwnedVec<Outcome<R>>, DecryptError<P, V>> {
let physical = DecryptError::Processing;
if !core::ptr::eq(self.certificate.parser, parent.parser())
|| !core::ptr::eq(self.certificate, checked.certificate())
{
return Err(physical(Error::schema(
ErrorCode::InvalidLocation,
FIELD,
self.certificate.element().span().start,
)));
}
let work = parent.execution();
let mut results = work.vector();
results
.try_reserve_exact(self.envelopes.len())
.map_err(|e| {
physical(Error::resource(
e,
FIELD,
self.certificate.element().span().start,
))
})?;
for (ordinal, envelope) in self.envelopes.iter().enumerate() {
work.charge_work(1).map_err(|e| {
physical(Error::resource(e, FIELD, envelope.element().span().start))
})?;
let output = provider(envelope, &work).map_err(|cause| DecryptError::Provider {
envelope: envelope.element().span(),
cause,
})?;
let outcome = match output {
Attempt::NotRecipient => Outcome::NotRecipient,
Attempt::Unsupported => Outcome::Unsupported,
Attempt::Rejected => Outcome::Rejected(Rejection::Decryption),
Attempt::Decrypted(bytes) => {
if !bytes.memory().same_account(parent.memory()) {
return Err(physical(Error::schema(
ErrorCode::InvalidLocation,
FIELD,
envelope.element().span().start,
)));
}
self.visit(checked, parent, envelope, ordinal, &bytes, &mut visitor)?
}
};
results.try_push(outcome).map_err(|e| {
physical(Error::resource(e, FIELD, envelope.element().span().start))
})?;
}
Ok(results.into_owned())
}
fn visit<'p, 'd, 'c, S, P, V, R>(
&self,
checked: &Checked<'_, 'p, 'd, 'c, S>,
parent: &Reader<'_>,
envelope: &EnvelopedData<'a>,
ordinal: usize,
bytes: &[u8],
visitor: &mut impl for<'scope> FnMut(
&Bound<'scope>,
&Execution<'_>,
) -> core::result::Result<R, V>,
) -> core::result::Result<Outcome<R>, DecryptError<P, V>> {
let source = PlaintextSource {
value: ordinal,
envelope: envelope.element().span(),
};
let logical = |cause| DecryptError::Plaintext { cause, source };
let ciphertext = envelope
.content
.ciphertext
.as_ref()
.expect("encAttrs requires attached ciphertext");
let mut reader = self
.certificate
.parser
.read_nested_query_in(parent, bytes, 0, Encoding::Der, ciphertext.outer_depth())
.map_err(|e| logical(Error::encoding(e, CLEAR)))?;
let clear = ClearAttrs::read(&mut reader).map_err(logical)?;
let work = reader.execution();
let ac = self.certificate;
work.charge_work(3)
.map_err(|e| logical(Error::resource(e, CLEAR, clear.element.span().start)))?;
let left = clear.serial.as_bytes();
let right = ac.information().serial.as_bytes();
if left.len() != right.len() {
return Ok(Outcome::Rejected(Rejection::Serial));
}
work.charge_work(left.len() as u64 + right.len() as u64)
.map_err(|e| {
logical(Error::resource(
e,
"acClearAttrs.acSerial",
clear.element.span().start,
))
})?;
if left != right {
return Ok(Outcome::Rejected(Rejection::Serial));
}
let Some([GeneralName::Directory(actual)]) = ac.information().issuer.names.as_deref()
else {
unreachable!("authenticated profile restricts issuer to one directoryName")
};
let GeneralName::Directory(claimed) = &clear.issuer else {
return Ok(Outcome::Rejected(Rejection::Issuer));
};
let actual = actual
.ordered(&work)
.map_err(|e| DecryptError::Processing(Error::pkix(e)))?;
let claimed = claimed
.ordered_in(&reader)
.map_err(|e| logical(Error::pkix(e)))?;
if claimed
.order(&actual, &work)
.map_err(|e| logical(Error::pkix(e)))?
!= core::cmp::Ordering::Equal
{
return Ok(Outcome::Rejected(Rejection::Issuer));
}
let mut attributes = work.vector();
attributes
.try_reserve_exact(clear.attributes.len())
.map_err(|e| logical(Error::resource(e, CLEAR, clear.element.span().start)))?;
for (index, attribute) in clear.attributes.iter().enumerate() {
work.charge_work(1)
.map_err(|e| logical(Error::resource(e, CLEAR, attribute.span().start)))?;
let permission = checked
.allows(attribute.kind(), &work)
.map_err(DecryptError::Processing)?;
attributes
.try_push(AttributeUse { index, permission })
.map_err(|e| logical(Error::resource(e, CLEAR, attribute.span().start)))?;
}
let bound = Bound {
clear,
attributes: attributes.into_owned(),
source,
};
let output =
visitor(&bound, &work).map_err(|cause| DecryptError::Visitor { cause, source })?;
Ok(Outcome::Processed(output))
}
}
fn end(parent: &Reader<'_>) -> Result<()> {
if parent.is_empty() {
Ok(())
} else {
Err(Error::schema(
ErrorCode::TrailingData,
CLEAR,
parent.offset(),
))
}
}