use super::super::{AttributeCertificate, DigestedObject};
use crate::Error;
use structured_asn1::digest::Resolution;
use structured_asn1::memory::Work;
use structured_pkix::Certificate;
use structured_pkix::algorithm::digest::{self as algorithms, AsnDigest};
use structured_pkix::key::effective::{EffectiveSpki, VisitError};
use structured_pkix::path::WorkingKey;
const FIELD: &str = "attributeCertificate.holder.objectDigestInfo";
pub trait ObjectDigestProvider<'p>: AsnDigest<'p> {
fn validate_public_key<W: Work>(
&self,
key: WorkingKey<'_>,
work: &W,
) -> Result<Resolution<()>, Self::Error>;
}
#[derive(Debug, Clone, Copy)]
pub enum Object<'c, 'a> {
Certificate(&'c Certificate<'a>),
PublicKey(WorkingKey<'a>),
}
#[derive(Debug)]
pub struct BoundObject<'r, 'a, 's> {
certificate: &'r AttributeCertificate<'a>,
object: Object<'r, 's>,
}
impl<'r, 'a, 's> BoundObject<'r, 'a, 's> {
#[must_use]
pub fn certificate(&self) -> &'r AttributeCertificate<'a> {
self.certificate
}
#[must_use]
pub fn object(&self) -> Object<'r, 's> {
self.object
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Failure {
Absent,
ObjectType,
OtherObject,
Algorithm,
Hash,
KeyEncoding,
Differs,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Capability {
Digest,
Key,
}
#[derive(Debug)]
pub enum Binding<'r, 'a, 's> {
Bound(BoundObject<'r, 'a, 's>),
Rejected(Failure),
Unsupported(Capability),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
KeyEncoding,
Start,
CertificateInput,
Finish,
Comparison,
}
#[derive(Debug)]
pub enum BindingError<E> {
Processing(Error),
Callback {
phase: Phase,
source: E,
},
KeyInput(VisitError<E>),
OutputWidth {
expected: usize,
actual: usize,
},
}
impl<E> From<Error> for BindingError<E> {
fn from(error: Error) -> Self {
Self::Processing(error)
}
}
impl<'a> AttributeCertificate<'a> {
pub fn bind_holder_digest<'r, 's, 'p, P: ObjectDigestProvider<'p>, W: Work>(
&'r self,
object: Object<'r, 's>,
provider: &'p P,
work: &W,
) -> Result<Binding<'r, 'a, 's>, BindingError<P::Error>> {
let execution = work.execution_for(self.parser);
let at = self.signed_element().span().start;
execution
.charge_work(4)
.map_err(|e| Error::resource(e, FIELD, at))?;
let Some(assertion) = &self.information.holder.object_digest else {
return Ok(Binding::Rejected(Failure::Absent));
};
if assertion.object_type == DigestedObject::Other || assertion.other_type.is_some() {
return Ok(Binding::Rejected(Failure::OtherObject));
}
if !matches!(
(&assertion.object_type, object),
(DigestedObject::PublicKey, Object::PublicKey(_))
| (DigestedObject::PublicKeyCertificate, Object::Certificate(_))
) {
return Ok(Binding::Rejected(Failure::ObjectType));
}
let algorithm =
match algorithms::algorithm(&assertion.algorithm, &execution).map_err(Error::pkix)? {
Resolution::Supported(value) => value,
Resolution::Unsupported => return Ok(Binding::Unsupported(Capability::Digest)),
Resolution::Malformed => return Ok(Binding::Rejected(Failure::Algorithm)),
};
let Some(expected) = assertion
.digest
.octets()
.filter(|bytes| bytes.len() == algorithm.width())
else {
return Ok(Binding::Rejected(Failure::Hash));
};
let Some(identity) = provider.identity(algorithm) else {
return Ok(Binding::Unsupported(Capability::Digest));
};
check_width(provider.width(&identity), algorithm.width())?;
let calculation = match prepare(object, provider, &execution, |input| {
hash(
input,
provider,
&identity,
algorithm.width(),
expected,
&execution,
)
})? {
Resolution::Supported(calculation) => calculation,
Resolution::Unsupported => return Ok(Binding::Unsupported(Capability::Key)),
Resolution::Malformed => return Ok(Binding::Rejected(Failure::KeyEncoding)),
};
let matches = match calculation {
Resolution::Supported(matches) => matches,
Resolution::Unsupported => return Ok(Binding::Unsupported(Capability::Digest)),
Resolution::Malformed => return Ok(Binding::Rejected(Failure::Algorithm)),
};
Ok(if matches {
Binding::Bound(BoundObject {
certificate: self,
object,
})
} else {
Binding::Rejected(Failure::Differs)
})
}
}
fn check_width<E>(actual: Option<usize>, expected: usize) -> Result<(), BindingError<E>> {
if let Some(actual) = actual
&& actual != expected
{
return Err(BindingError::OutputWidth { expected, actual });
}
Ok(())
}
#[derive(Clone, Copy)]
enum Input<'v, 's> {
Certificate(&'v Certificate<'s>),
Key(&'v EffectiveSpki<'s>),
}
fn prepare<'s, 'p, P: ObjectDigestProvider<'p>, W: Work, T>(
object: Object<'_, 's>,
provider: &'p P,
work: &W,
visit: impl FnOnce(Input<'_, 's>) -> Result<T, BindingError<P::Error>>,
) -> Result<Resolution<T>, BindingError<P::Error>> {
let key = match object {
Object::Certificate(certificate) => {
return visit(Input::Certificate(certificate)).map(Resolution::Supported);
}
Object::PublicKey(key) => key,
};
let encoded = EffectiveSpki::read(key, work).map_err(Error::pkix)?;
Ok(
match provider
.validate_public_key(key, &encoded.execution_in(work))
.map_err(|source| BindingError::Callback {
phase: Phase::KeyEncoding,
source,
})? {
Resolution::Supported(()) => {
return visit(Input::Key(&encoded)).map(Resolution::Supported);
}
Resolution::Unsupported => Resolution::Unsupported,
Resolution::Malformed => Resolution::Malformed,
},
)
}
fn hash<'p, P: ObjectDigestProvider<'p>, W: Work>(
input: Input<'_, '_>,
provider: &'p P,
identity: &P::Identity,
width: usize,
expected: &[u8],
work: &W,
) -> Result<Resolution<bool>, BindingError<P::Error>> {
let mut state =
match provider
.start(identity, work)
.map_err(|source| BindingError::Callback {
phase: Phase::Start,
source,
})? {
Resolution::Supported(state) => state,
Resolution::Unsupported => return Ok(Resolution::Unsupported),
Resolution::Malformed => return Ok(Resolution::Malformed),
};
match input {
Input::Certificate(certificate) => {
let execution = certificate.execution_in(work);
execution.charge_work(1).map_err(|e| {
Error::resource(
e,
"attributeCertificate.objectDigestInfo.certificateInput",
certificate.span().start,
)
})?;
provider
.update(&mut state, certificate.as_bytes(), &execution)
.map_err(|source| BindingError::Callback {
phase: Phase::CertificateInput,
source,
})?;
}
Input::Key(encoded) => encoded
.visit(work, |_, bytes, source| {
provider.update(&mut state, bytes, source)
})
.map_err(BindingError::KeyInput)?,
}
let output = provider
.finish(state, work)
.map_err(|source| BindingError::Callback {
phase: Phase::Finish,
source,
})?;
check_width(Some(output.as_ref().len()), width)?;
provider
.matches(identity, &output, expected, work)
.map(Resolution::Supported)
.map_err(|source| BindingError::Callback {
phase: Phase::Comparison,
source,
})
}