use super::aa_path::validated::{ScopedVerifier, VerificationError};
use super::aa_path::{Issued, Permission};
use super::authentication::Authenticated;
use super::digest::BoundObject;
use super::{Check, HolderMethod, Rejection, charge};
use crate::attribute_certificate::controls::{self, AaControls};
use crate::attribute_certificate::{proxy, target};
use crate::{Error, Result};
use structured_asn1::Oid;
use structured_asn1::memory::{Execution, OwnedVec, Work};
use structured_pkix::Certificate;
use structured_pkix::extension::Recognizer;
use structured_pkix::general_name::GeneralName;
use structured_pkix::path::{self, EffectiveAnchor, Interval, Verify, Watch};
use structured_pkix::temporal::{LeapTable, Reading, Uncertainty};
mod status;
pub use status::{Evidence, NoStatus, Status};
#[derive(Debug)]
pub struct HolderPath<'p, 'a> {
certificates: &'p [&'a Certificate<'a>],
facts: path::ValidationFacts<'a>,
anchor: EffectiveAnchor<'a>,
interval: Interval,
}
#[derive(Debug)]
pub enum HolderValidation<T, P> {
Accepted(T),
Rejected(P),
Incomplete(path::Truncation),
}
pub type HolderResult<'p, 'a, V, R> = core::result::Result<
HolderValidation<HolderPath<'p, 'a>, path::Rejection<'a>>,
HolderError<V, R>,
>;
#[derive(Debug)]
pub enum HolderError<V, R> {
Processing(Error),
Verification(V),
Recognition(R),
}
impl<'p, 'a> HolderPath<'p, 'a> {
#[must_use]
pub fn certificates(&self) -> &[&Certificate<'a>] {
self.certificates
}
#[must_use]
pub fn facts(&self) -> &path::ValidationFacts<'a> {
&self.facts
}
#[must_use]
pub fn anchor(&self) -> EffectiveAnchor<'a> {
self.anchor
}
#[must_use]
pub fn interval(&self) -> Interval {
self.interval
}
pub fn validate<V, T, R, W>(
certificates: &'p [&'a Certificate<'a>],
anchor: EffectiveAnchor<'a>,
context: &path::ValidationContext<'a, '_, V, T, R, W>,
) -> HolderResult<'p, 'a, <V as Verify<W>>::Error, R::Error>
where
W: Work,
V: Verify<W>,
for<'op> V: Verify<Execution<'op>, Error = <V as Verify<W>>::Error>,
T: Watch,
R: Recognizer<W>,
{
let verifier = ScopedVerifier(context.verifier);
let scoped = path::ValidationContext {
execution: context.execution,
interval: context.interval,
policy: context.policy,
bounds: context.bounds,
verifier: &verifier,
watch: context.watch,
recognizer: context.recognizer,
};
let checked =
path::validate_effective(certificates, anchor, &scoped).map_err(
|error| match error {
path::BuildError::Processing(e) => HolderError::Processing(Error::pkix(e)),
path::BuildError::Recognition(e) => HolderError::Recognition(e),
path::BuildError::Verification(VerificationError::Callback(e)) => {
HolderError::Verification(e)
}
path::BuildError::Verification(VerificationError::Processing(e)) => {
HolderError::Processing(e)
}
},
)?;
Ok(match checked {
path::Validation::Accepted(facts) => HolderValidation::Accepted(Self {
certificates,
facts,
anchor,
interval: context.interval,
}),
path::Validation::Rejected(error) => HolderValidation::Rejected(error),
path::Validation::Incomplete(reason) => HolderValidation::Incomplete(reason),
})
}
}
#[derive(Debug)]
pub enum Holder<'r, 'p, 'a, 'c> {
Certificate {
path: &'r HolderPath<'p, 'a>,
method: HolderMethod,
},
Object {
binding: &'r BoundObject<'r, 'c, 'a>,
path: Option<&'r HolderPath<'p, 'a>>,
},
}
#[derive(Debug, Clone, Copy)]
pub struct Proxy<'n, 'a> {
pub sender: proxy::Sender<'a, 'n>,
pub intermediates: &'n [&'n [GeneralName<'a>]],
}
#[derive(Debug)]
pub enum Failure {
AuthorityTrust,
Interval,
Time(Uncertainty),
Holder(Rejection),
HolderObject,
HolderPath,
Target(target::Decision),
ProxyContext,
Proxy(proxy::Decision),
Status(Status),
}
#[derive(Debug)]
pub enum Outcome<T> {
Checked(T),
Rejected(Failure),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupRole {
Target,
Proxy(proxy::Subject),
}
pub struct Context<'r, 'p, 'a, 'c, 'op, S: Evidence, W: Work> {
pub holder: Holder<'r, 'p, 'a, 'c>,
pub recipient: &'r [GeneralName<'a>],
pub proxy: Option<Proxy<'r, 'a>>,
pub status: &'r S,
pub now: Reading,
pub utc: &'op LeapTable<'op>,
pub execution: &'op W,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AttributeUse {
pub index: usize,
pub permission: Permission,
}
#[derive(Debug)]
pub enum Authority<'r, 'p, 'a, 'c> {
Direct(&'r Authenticated<'r, 'c, 'a>),
Path(&'r Issued<'r, 'p, 'c, 'a>),
}
impl<'r, 'a, 'c> Authority<'r, '_, 'a, 'c> {
fn facts(&self) -> &'r Authenticated<'r, 'c, 'a> {
match self {
Self::Direct(facts) => facts,
Self::Path(issued) => issued.facts(),
}
}
fn allows<W: Work>(
&self,
kind: Oid<'_>,
controls: Option<&AaControls<'_>>,
work: &W,
) -> Result<Permission> {
match self {
Self::Direct(facts) => {
charge(
&work.execution_for(facts.certificate().parser),
1,
facts.certificate().element().span().start,
)?;
if let Some(control) = controls
&& !control.allows(kind, work)?
{
return Ok(Permission::Excluded {
certificate: 0,
source: control.element.span(),
});
}
Ok(Permission::Permitted)
}
Self::Path(issued) => issued.allows(kind, work),
}
}
}
#[derive(Debug)]
pub struct Checked<'r, 'p, 'a, 'c, S> {
authority: Authority<'r, 'p, 'a, 'c>,
direct_controls: Option<AaControls<'a>>,
holder: Holder<'r, 'p, 'a, 'c>,
status: &'r S,
now: Reading,
target: Option<usize>,
proxy: Option<usize>,
attributes: OwnedVec<AttributeUse>,
}
impl<'r, 'p, 'a, 'c, S> Checked<'r, 'p, 'a, 'c, S> {
#[must_use]
pub fn certificate(&self) -> &'r crate::attribute_certificate::AttributeCertificate<'c> {
self.authority.facts().certificate()
}
#[must_use]
pub fn authority(&self) -> &Authority<'r, 'p, 'a, 'c> {
&self.authority
}
#[must_use]
pub fn holder(&self) -> &Holder<'r, 'p, 'a, 'c> {
&self.holder
}
#[must_use]
pub fn status(&self) -> &'r S {
self.status
}
#[must_use]
pub fn evaluated_at(&self) -> Reading {
self.now
}
#[must_use]
pub fn target(&self) -> Option<usize> {
self.target
}
#[must_use]
pub fn proxy(&self) -> Option<usize> {
self.proxy
}
#[must_use]
pub fn attributes(&self) -> &[AttributeUse] {
&self.attributes
}
pub fn allows<W: Work>(&self, kind: Oid<'_>, work: &W) -> Result<Permission> {
self.authority
.allows(kind, self.direct_controls.as_ref(), work)
}
}
#[derive(Debug)]
pub enum UseError<E> {
Processing(Error),
Membership(target::CheckError<E>),
}
impl<E> From<Error> for UseError<E> {
fn from(error: Error) -> Self {
Self::Processing(error)
}
}
pub fn direct<'r, 'p, 'a, 'c, S: Evidence, W: Work, E>(
facts: &'r Authenticated<'r, 'c, 'a>,
trusted_aa: &Certificate<'_>,
context: Context<'r, 'p, 'a, 'c, '_, S, W>,
membership: impl FnMut(
GroupRole,
&GeneralName<'c>,
&Execution<'_>,
) -> core::result::Result<target::Membership, E>,
) -> core::result::Result<Outcome<Checked<'r, 'p, 'a, 'c, S>>, UseError<E>> {
let work = facts.authority().execution_in(context.execution);
charge(&work, 1, facts.authority().span().start)?;
if !core::ptr::eq(facts.authority(), trusted_aa) {
return Ok(Outcome::Rejected(Failure::AuthorityTrust));
}
let controls = facts
.authority()
.tbs()
.extensions()
.get(controls::IDENTIFIER, &work)
.map_err(Error::pkix)?
.map(|extension| AaControls::read_extension(extension, &work))
.transpose()?;
check(Authority::Direct(facts), controls, context, membership)
}
pub fn issued<'r, 'p, 'a, 'c, S: Evidence, W: Work, E>(
facts: &'r Issued<'r, 'p, 'c, 'a>,
context: Context<'r, 'p, 'a, 'c, '_, S, W>,
membership: impl FnMut(
GroupRole,
&GeneralName<'c>,
&Execution<'_>,
) -> core::result::Result<target::Membership, E>,
) -> core::result::Result<Outcome<Checked<'r, 'p, 'a, 'c, S>>, UseError<E>> {
check(Authority::Path(facts), None, context, membership)
}
fn holder_binding<W: Work>(
ac: &crate::attribute_certificate::AttributeCertificate<'_>,
holder: &Holder<'_, '_, '_, '_>,
inside: impl Fn(Interval) -> bool,
execution: &W,
) -> Result<Option<Failure>> {
match holder {
Holder::Certificate { path, method } => {
if !inside(path.interval) {
return Ok(Some(Failure::Interval));
}
if let Check::Rejected(reason) =
ac.bind_holder(path.certificates[0], *method, execution)?
{
return Ok(Some(Failure::Holder(reason)));
}
}
Holder::Object { binding, path } => {
let original = binding.certificate();
charge(
&execution.execution_for(original.parser),
1,
original.element().span().start,
)?;
if !core::ptr::eq(binding.certificate(), ac) {
return Ok(Some(Failure::HolderObject));
}
if let Some(path) = path
&& !inside(path.interval)
{
return Ok(Some(Failure::Interval));
}
match binding.object() {
super::digest::Object::Certificate(certificate) => {
charge(
&certificate.execution_in(execution),
1,
certificate.span().start,
)?;
if let Some(path) = path {
let actual = path.certificates[0];
charge(&actual.execution_in(execution), 1, actual.span().start)?;
}
if !path.is_some_and(|path| core::ptr::eq(path.certificates[0], certificate)) {
return Ok(Some(Failure::HolderPath));
}
}
super::digest::Object::PublicKey(key) => {
charge(
&key.key().execution_in(execution),
1,
key.key().algorithm().offset(),
)?;
if let Some(path) = path {
let actual = path.facts.key;
charge(
&actual.key().execution_in(execution),
1,
actual.key().algorithm().offset(),
)?;
let parameters = match (actual.parameters(), key.parameters()) {
(None, None) => true,
(Some(left), Some(right)) => core::ptr::eq(left, right),
_ => false,
};
if !core::ptr::eq(actual.key().as_bytes(), key.key().as_bytes())
|| !parameters
{
return Ok(Some(Failure::HolderPath));
}
}
}
}
}
}
Ok(None)
}
fn check<'r, 'p, 'a, 'c, S: Evidence, W: Work, E>(
authority: Authority<'r, 'p, 'a, 'c>,
direct_controls: Option<AaControls<'a>>,
context: Context<'r, 'p, 'a, 'c, '_, S, W>,
mut membership: impl FnMut(
GroupRole,
&GeneralName<'c>,
&Execution<'_>,
) -> core::result::Result<target::Membership, E>,
) -> core::result::Result<Outcome<Checked<'r, 'p, 'a, 'c, S>>, UseError<E>> {
let facts = authority.facts();
let ac = facts.certificate();
let execution = context.execution.execution_for(ac.parser);
let at = ac.element().span().start;
let searches = u64::from(usize::BITS - context.utc.transitions().len().leading_zeros() + 1);
charge(&execution, 6 + 3 * searches + 64, at)?;
let upper = match context.now.upper_bound(context.utc) {
Ok(value) => value,
Err(reason) => return Ok(Outcome::Rejected(Failure::Time(reason))),
};
let inside = |interval: Interval| {
context.now.start() >= interval.from_instant() && upper <= interval.to_instant()
};
if !inside(facts.interval()) {
return Ok(Outcome::Rejected(Failure::Interval));
}
if let Some(reason) = holder_binding(ac, &context.holder, inside, &execution)? {
return Ok(Outcome::Rejected(reason));
}
let target = if let Some(targets) = facts.profile().targeting() {
match targets
.check(context.recipient, &execution, |name, work| {
membership(GroupRole::Target, name, work)
})
.map_err(UseError::Membership)?
{
target::Decision::Matches { target } => Some(target),
other => return Ok(Outcome::Rejected(Failure::Target(other))),
}
} else {
None
};
let proxy = if let Some(proxies) = facts.profile().proxying() {
let Some(proxy) = context.proxy else {
return Ok(Outcome::Rejected(Failure::ProxyContext));
};
match proxies
.check(
proxy.sender,
context.recipient,
proxy.intermediates,
&execution,
|subject, name, work| membership(GroupRole::Proxy(subject), name, work),
)
.map_err(UseError::Membership)?
{
proxy::Decision::Matches { group } => Some(group),
other => return Ok(Outcome::Rejected(Failure::Proxy(other))),
}
} else {
None
};
let status = context
.status
.check(facts.profile(), context.now, &execution)?;
if !matches!(status, Status::Good | Status::NoRevAvailable) {
return Ok(Outcome::Rejected(Failure::Status(status)));
}
let mut attributes = execution.vector();
let original = &ac.information().attributes;
attributes
.try_reserve(original.len())
.map_err(|e| Error::resource(e, super::FIELD, at))?;
for (index, attribute) in original.iter().enumerate() {
charge(&execution, 1, attribute.element().span().start)?;
let permission =
authority.allows(attribute.kind(), direct_controls.as_ref(), &execution)?;
attributes
.try_push(AttributeUse { index, permission })
.map_err(|e| Error::resource(e, super::FIELD, at))?;
}
Ok(Outcome::Checked(Checked {
authority,
direct_controls,
holder: context.holder,
status: context.status,
now: context.now,
target,
proxy,
attributes: attributes.into_owned(),
}))
}