use super::{IssuerSerial, ObjectDigestInfo, end, issuer_serial, object_digest, sequence, tag};
use crate::signer::required;
use crate::{Error, ErrorCode, Result};
use core::ops::Range;
use structured_asn1::memory::{Execution, OwnedVec, Work};
use structured_asn1::{Class, Element, Oid, Parser, ScopedReader, Span, Tag};
use structured_pkix::Extension;
use structured_pkix::general_name::{self, GeneralName};
pub(super) const FIELD: &str = "attributeCertificate.targetInformation";
pub const IDENTIFIER: Oid<'static> = Oid::from_bytes(&[0x55, 0x1d, 55]);
#[derive(Debug)]
pub struct Group<'a> {
pub element: Element<'a>,
pub targets: Range<usize>,
}
#[derive(Debug)]
pub struct Target<'a> {
pub element: Element<'a>,
pub group: usize,
pub value: Value<'a>,
}
#[derive(Debug)]
pub enum Value<'a> {
Name(GeneralName<'a>),
Group(GeneralName<'a>),
Certificate(usize),
}
#[derive(Debug)]
pub struct TargetCertificate<'a> {
pub target: usize,
pub element: Element<'a>,
pub certificate: IssuerSerial<'a>,
pub name: Option<GeneralName<'a>>,
pub digest: Option<ObjectDigestInfo<'a>>,
}
#[derive(Debug)]
pub struct TargetInformation<'a> {
parser: &'a Parser,
element: Element<'a>,
critical: bool,
groups: OwnedVec<Group<'a>>,
targets: OwnedVec<Target<'a>>,
certificates: OwnedVec<TargetCertificate<'a>>,
}
impl<'a> TargetInformation<'a> {
pub fn read<W: Work>(extension: &Extension<'a>, work: &W) -> Result<Self> {
Self::read_as(extension, work, IDENTIFIER, FIELD)
}
pub(super) fn read_as<W: Work>(
extension: &Extension<'a>,
work: &W,
identifier: Oid<'_>,
field: &'static str,
) -> Result<Self> {
let execution = work.execution_for(extension.parser());
execution
.charge_work(identifier.as_bytes().len() as u64 + 1)
.map_err(|e| Error::resource(e, field, extension.span().start))?;
if extension.kind() != identifier {
return Err(Error::schema(
ErrorCode::UnexpectedContentType,
field,
extension.span().start,
));
}
let mut reader = extension
.reader_scoped(&execution)
.map_err(|e| Error::encoding(e, field))?;
let element = required(&mut reader, field)?;
tag(&element, sequence(), field)?;
end(&reader, field)?;
let mut inside = reader
.enter(&element)
.map_err(|e| Error::encoding(e, field))?;
let mut groups = execution.vector();
let mut targets = execution.vector();
let mut certificates = execution.vector();
while let Some(group) = inside
.next_element()
.map_err(|e| Error::encoding(e, field))?
{
tag(&group, sequence(), field)?;
groups
.try_reserve(1)
.map_err(|e| Error::resource(e, field, group.span().start))?;
let start = targets.len();
let mut entries = inside
.enter(&group)
.map_err(|e| Error::encoding(e, field))?;
while let Some(entry) = entries
.next_element()
.map_err(|e| Error::encoding(e, field))?
{
let at = entry.span().start;
targets
.try_reserve(1)
.map_err(|e| Error::resource(e, field, at))?;
let value = if entry.tag() == Tag::context(true, 0)
|| entry.tag() == Tag::context(true, 1)
{
let mut name = entries
.enter(&entry)
.map_err(|e| Error::encoding(e, field))?;
let encoded = required(&mut name, field)?;
let decoded =
general_name::read(&name, &encoded, field).map_err(Error::pkix)?;
end(&name, field)?;
if entry.tag() == Tag::context(true, 0) {
Value::Name(decoded)
} else {
Value::Group(decoded)
}
} else if entry.tag() == Tag::context(true, 2) {
certificates
.try_reserve(1)
.map_err(|e| Error::resource(e, field, at))?;
let decoded = certificate(&entries, &entry, targets.len(), field)?;
let index = certificates.len();
certificates
.try_push(decoded)
.map_err(|e| Error::resource(e, field, at))?;
Value::Certificate(index)
} else {
return Err(Error::schema(ErrorCode::UnexpectedTag, field, at));
};
targets
.try_push(Target {
element: entry,
group: groups.len(),
value,
})
.map_err(|e| Error::resource(e, field, at))?;
}
groups
.try_push(Group {
element: group,
targets: start..targets.len(),
})
.map_err(|e| Error::resource(e, field, group.span().start))?;
}
Ok(Self {
parser: extension.parser(),
element,
critical: extension.is_critical(),
groups: groups.into_owned(),
targets: targets.into_owned(),
certificates: certificates.into_owned(),
})
}
#[must_use]
pub fn element(&self) -> Element<'a> {
self.element
}
#[must_use]
pub fn is_critical(&self) -> bool {
self.critical
}
#[must_use]
pub fn groups(&self) -> &[Group<'a>] {
&self.groups
}
#[must_use]
pub fn targets(&self) -> &[Target<'a>] {
&self.targets
}
#[must_use]
pub fn certificates(&self) -> &[TargetCertificate<'a>] {
&self.certificates
}
pub fn check<W: Work, E>(
&self,
recipients: &[GeneralName<'_>],
work: &W,
mut membership: impl FnMut(
&GeneralName<'a>,
&Execution<'_>,
) -> core::result::Result<Membership, E>,
) -> core::result::Result<Decision, CheckError<E>> {
let execution = work.execution_for(self.parser);
let pay = |at| {
execution
.charge_work(1)
.map_err(|e| CheckError::Processing(Error::resource(e, FIELD, at)))
};
pay(self.element.span().start)?;
if !self.critical {
return Ok(Decision::NonCritical);
}
if let Some(certificate) = self.certificates.first() {
return Ok(Decision::ProhibitedTargetCertificate {
target: certificate.target,
});
}
let mut unknown = false;
for (index, target) in self.targets.iter().enumerate() {
match matches_target(target, recipients, &execution, FIELD, &mut membership)? {
Membership::Member => return Ok(Decision::Matches { target: index }),
Membership::NotMember => {}
Membership::Unknown => unknown = true,
}
}
Ok(if unknown {
Decision::Unproven
} else {
Decision::Differs
})
}
}
pub(super) fn opaque(name: &GeneralName<'_>) -> bool {
matches!(
name,
GeneralName::Other { .. } | GeneralName::X400(_) | GeneralName::EdiParty(_)
)
}
pub(super) fn matches_target<'a, E>(
target: &Target<'a>,
recipients: &[GeneralName<'_>],
execution: &Execution<'_>,
field: &'static str,
membership: &mut impl FnMut(&GeneralName<'a>, &Execution<'_>) -> core::result::Result<Membership, E>,
) -> core::result::Result<Membership, CheckError<E>> {
let at = target.element.span().start;
let pay = || {
execution
.charge_work(1)
.map_err(|e| CheckError::Processing(Error::resource(e, field, at)))
};
pay()?;
match &target.value {
Value::Name(name) => {
if opaque(name) {
return Ok(Membership::Unknown);
}
for recipient in recipients {
pay()?;
if general_name::matches_at(name, recipient, execution, at, field)
.map_err(|e| CheckError::Processing(Error::pkix(e)))?
{
return Ok(Membership::Member);
}
}
Ok(Membership::NotMember)
}
Value::Group(name) => membership(name, execution).map_err(|source| CheckError::Callback {
target: target.element.span(),
source,
}),
Value::Certificate(_) => unreachable!("complete certificate preflight"),
}
}
fn certificate<'a>(
parent: &ScopedReader<'a, '_>,
element: &Element<'a>,
target: usize,
field: &'static str,
) -> Result<TargetCertificate<'a>> {
let mut inside = parent
.enter(element)
.map_err(|e| Error::encoding(e, field))?;
let issuer = required(&mut inside, field)?;
tag(&issuer, sequence(), field)?;
let certificate = issuer_serial(&inside, &issuer, field)?;
let mut next = inside
.next_element()
.map_err(|e| Error::encoding(e, field))?;
let name = if next
.as_ref()
.is_some_and(|e| e.tag().class() == Class::ContextSpecific)
{
let decoded = general_name::read(&inside, next.as_ref().expect("present name"), field)
.map_err(Error::pkix)?;
next = inside
.next_element()
.map_err(|e| Error::encoding(e, field))?;
Some(decoded)
} else {
None
};
let digest = if let Some(value) = next {
tag(&value, sequence(), field)?;
Some(object_digest(&inside, &value, field)?)
} else {
None
};
end(&inside, field)?;
Ok(TargetCertificate {
target,
element: *element,
certificate,
name,
digest,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Membership {
Member,
NotMember,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Decision {
Matches {
target: usize,
},
Differs,
Unproven,
NonCritical,
ProhibitedTargetCertificate {
target: usize,
},
}
#[derive(Debug)]
pub enum CheckError<E> {
Processing(Error),
Callback {
target: Span,
source: E,
},
}