use super::{Check, Profile, Rejection as ProfileRejection, charge};
use crate::attribute_certificate::AttributeCertificate;
use crate::{Error, Result};
use structured_asn1::Span;
use structured_asn1::digest::Resolution;
use structured_asn1::memory::{Execution, Work};
use structured_pkix::path::{Interval, Verified, Verify, WorkingKey};
use structured_pkix::{AlgorithmIdentifier, Certificate};
pub trait AlgorithmAgreement {
type Error;
fn algorithms<W: Work>(
&self,
signed: AlgorithmIdentifier<'_>,
outer: AlgorithmIdentifier<'_>,
work: &W,
) -> core::result::Result<Resolution<bool>, Self::Error>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
Algorithms,
Signature,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Failure {
Profile(ProfileRejection),
Authority(ProfileRejection),
KeyBinding,
AcValidity,
AuthorityValidity,
AlgorithmSyntax,
AlgorithmMismatch,
SignatureEncoding,
Signature,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rejection {
pub reason: Failure,
pub source: Span,
}
#[derive(Debug)]
pub enum Outcome<T> {
Authenticated(T),
Rejected(Rejection),
Unsupported(Stage),
}
#[derive(Debug)]
pub enum AuthenticationError<E> {
Processing(Error),
UnknownAnswer,
Callback {
stage: Stage,
context: Span,
source: E,
},
}
impl<E> From<Error> for AuthenticationError<E> {
fn from(value: Error) -> Self {
Self::Processing(value)
}
}
#[derive(Debug)]
pub struct Authenticated<'r, 'a, 's> {
profile: Profile<'r, 'a>,
authority: &'r Certificate<'s>,
key: WorkingKey<'s>,
interval: Interval,
}
impl<'r, 'a, 's> Authenticated<'r, 'a, 's> {
#[must_use]
pub fn certificate(&self) -> &AttributeCertificate<'a> {
self.profile.certificate()
}
#[must_use]
pub fn profile(&self) -> &Profile<'r, 'a> {
&self.profile
}
#[must_use]
pub fn authority(&self) -> &'r Certificate<'s> {
self.authority
}
#[must_use]
pub fn key(&self) -> WorkingKey<'s> {
self.key
}
#[must_use]
pub fn interval(&self) -> Interval {
self.interval
}
}
fn rejected<T>(reason: Failure, source: Span) -> Outcome<T> {
Outcome::Rejected(Rejection { reason, source })
}
fn key_matches<W: Work>(
authority: &Certificate<'_>,
key: WorkingKey<'_>,
work: &W,
) -> Result<bool> {
let original = key.key().algorithm();
let key_work = key.key().execution_in(work);
let authority_work = authority.execution_in(work);
charge(&key_work, 1, original.offset())?;
charge(&authority_work, 1, authority.span().start)?;
let supplied = key.key().as_bytes();
let actual = authority.tbs().public_key().as_bytes();
if supplied.len() != actual.len() {
return Ok(false);
}
if core::ptr::eq(supplied, actual) {
return Ok(true);
}
charge(&key_work, supplied.len() as u64, original.offset())?;
charge(&authority_work, actual.len() as u64, authority.span().start)?;
Ok(supplied == actual)
}
impl<'a> AttributeCertificate<'a> {
pub fn authenticate<'r, 's, V, W: Work>(
&'r self,
authority: &'r Certificate<'s>,
key: WorkingKey<'s>,
interval: Interval,
verifier: &V,
work: &W,
) -> core::result::Result<
Outcome<Authenticated<'r, 'a, 's>>,
AuthenticationError<<V as AlgorithmAgreement>::Error>,
>
where
V: AlgorithmAgreement,
for<'op> V: Verify<Execution<'op>, Error = <V as AlgorithmAgreement>::Error>,
{
let execution = work.execution_for(self.parser);
let at = self.signed_element().span();
let profile = match self.profile(&execution)? {
Check::Accepted(profile) => profile,
Check::Rejected(cause) => return Ok(rejected(Failure::Profile(cause), at)),
};
if let Check::Rejected(cause) = self.bind_authority(authority, &execution)? {
return Ok(rejected(Failure::Authority(cause), authority.span()));
}
if !key_matches(authority, key, &execution)? {
return Ok(rejected(Failure::KeyBinding, authority.span()));
}
charge(&execution, 4, at.start)?;
let info = self.information();
if interval.from_instant() < info.not_before.instant()
|| interval.to_instant() > info.not_after.instant()
{
return Ok(rejected(Failure::AcValidity, at));
}
let authority_work = authority.execution_in(&execution);
charge(&authority_work, 4, authority.span().start)?;
let validity = authority.tbs().validity();
if interval.from_instant() < validity.not_before().instant()
|| interval.to_instant() > validity.not_after().instant()
{
return Ok(rejected(Failure::AuthorityValidity, authority.span()));
}
charge(&execution, 1, at.start)?;
if self.signature_value().octets().is_none() {
return Ok(rejected(
Failure::SignatureEncoding,
self.signature_value().span(),
));
}
match verifier
.algorithms(info.signature, self.signature_algorithm(), &execution)
.map_err(|source| AuthenticationError::Callback {
stage: Stage::Algorithms,
context: at,
source,
})? {
Resolution::Supported(true) => {}
Resolution::Supported(false) => return Ok(rejected(Failure::AlgorithmMismatch, at)),
Resolution::Malformed => return Ok(rejected(Failure::AlgorithmSyntax, at)),
Resolution::Unsupported => return Ok(Outcome::Unsupported(Stage::Algorithms)),
}
charge(&execution, 1, at.start)?;
match verifier
.verify(
&info.signature,
key,
self.signed_bytes(),
self.signature_value(),
&execution,
)
.map_err(|source| AuthenticationError::Callback {
stage: Stage::Signature,
context: at,
source,
})? {
Verified::Yes => Ok(Outcome::Authenticated(Authenticated {
profile,
authority,
key,
interval,
})),
Verified::No => Ok(rejected(Failure::Signature, self.signature_value().span())),
Verified::Unsupported => Ok(Outcome::Unsupported(Stage::Signature)),
_ => Err(AuthenticationError::UnknownAnswer),
}
}
}