use super::{CertificateId, IssuerAgreement, SigningAttributes};
use crate::digest::{DigestBatch, DigestError, PassError};
use crate::provider::Finished;
use crate::stream::RangesError;
use crate::{Error, Octets};
use structured_asn1::digest::{Digest, Resolution};
use structured_asn1::memory::{Execution, OwnedVec, Work};
use structured_asn1::{Reader, Span};
use structured_pkix::Certificate;
use structured_pkix::algorithm::digest::AsnDigest;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Binding {
Matches,
HashDiffers,
IssuerDiffers,
IssuerUnreadable,
MalformedHash,
MalformedAlgorithm,
Unsupported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Bindings {
pub v1: Option<Binding>,
pub v2: Option<Binding>,
}
#[derive(Debug)]
pub enum BindingError<E> {
Processing(Error),
Callback {
field: &'static str,
span: Span,
source: E,
},
Digest {
span: Span,
source: DigestError<E>,
},
Pass {
span: Span,
source: PassError<RangesError, E>,
},
LostRequest(usize),
}
enum Expected<'a, W: Work> {
Borrowed(&'a [u8]),
Joined(OwnedVec<u8, W>),
}
impl<W: Work> Expected<'_, W> {
fn as_bytes(&self) -> &[u8] {
match self {
Self::Borrowed(bytes) => bytes,
Self::Joined(bytes) => bytes,
}
}
}
struct Queued<'s, 'a, W: Work> {
identifier: &'s CertificateId<'a>,
expected: Expected<'a, W>,
ordinal: usize,
}
enum Request<I, H> {
Answer(Binding),
Compute { identity: I, expected: H },
}
type PreparedRequest<'a, 'p, P> = Result<
Request<<P as Digest<'a>>::Identity, Expected<'a, Execution<'p>>>,
BindingError<<P as Digest<'a>>::Error>,
>;
impl<'a> SigningAttributes<'a> {
pub fn bind_certificate<P: AsnDigest<'a>>(
&self,
provider: &'a P,
certificate: &Certificate<'_>,
parent: &Reader<'a>,
) -> Result<Bindings, BindingError<P::Error>> {
let execution = parent.execution();
let identifiers = [
self.v1().map(super::SigningCertificate::signer),
self.v2().map(super::SigningCertificate::signer),
];
let mut answers = [None, None];
let mut queued = [None, None];
let mut identities = None;
for (index, identifier) in identifiers.iter().enumerate() {
let Some(identifier) = identifier else {
continue;
};
let at = identifier.element().span().start;
let field = identifier.field;
match prepare_request(provider, identifier, parent)? {
Request::Answer(answer) => answers[index] = Some(answer),
Request::Compute { identity, expected } => {
let identities = match identities.as_mut() {
Some(identities) => identities,
None => identities.insert(execution.vector()),
};
let ordinal = identities.len();
identities.try_push(identity).map_err(|reason| {
BindingError::Processing(Error::resource(reason, field, at))
})?;
queued[index] = Some(Queued {
identifier,
expected,
ordinal,
});
}
}
}
let Some(identities) = identities else {
return Ok(Bindings {
v1: answers[0],
v2: answers[1],
});
};
let source_at = certificate.span().start;
let batch = DigestBatch::prepare(
provider,
identities.into_owned(),
&execution,
"ess.certificate",
source_at,
)
.map_err(|source| BindingError::Digest {
span: certificate.span(),
source,
})?;
let mut ranges = [certificate.as_bytes()];
let memo = batch
.run(&mut ranges, &execution)
.map_err(|source| BindingError::Pass {
span: certificate.span(),
source,
})?;
let mut issuer = None;
for (index, request) in queued.into_iter().enumerate() {
let Some(request) = request else {
continue;
};
let Some(digest) = memo
.for_request(request.ordinal, &execution)
.map_err(|source| BindingError::Digest {
span: certificate.span(),
source,
})?
else {
return Err(BindingError::LostRequest(request.ordinal));
};
answers[index] = Some(compare_binding(
provider,
request.identifier,
request.expected.as_bytes(),
digest.finished(),
certificate,
parent,
&mut issuer,
)?);
}
Ok(Bindings {
v1: answers[0],
v2: answers[1],
})
}
}
fn prepare_request<'a, 'p, P: AsnDigest<'a>>(
provider: &'a P,
identifier: &CertificateId<'a>,
parent: &'p Reader<'a>,
) -> PreparedRequest<'a, 'p, P> {
crate::provider::require_instance(
provider,
identifier.field,
identifier.element().span().start,
)
.map_err(BindingError::Processing)?;
let execution = parent.execution();
let at = identifier.element().span().start;
let field = identifier.field;
execution
.charge_work(1)
.map_err(|reason| BindingError::Processing(Error::resource(reason, field, at)))?;
let resolved = crate::provider::resolve_digest(provider, identifier.digest_algorithm(), parent)
.map_err(BindingError::Processing)?;
match resolved {
Resolution::Unsupported => Ok(Request::Answer(Binding::Unsupported)),
Resolution::Malformed => Ok(Request::Answer(Binding::MalformedAlgorithm)),
Resolution::Supported(identity) => {
if let Some(width) = provider.width(&identity)
&& width != identifier.hashed().len()
{
return Ok(Request::Answer(Binding::MalformedHash));
}
let expected = prepare_expected(identifier.hashed(), &execution, field, at)?;
Ok(Request::Compute { identity, expected })
}
}
}
fn prepare_expected<'a, W: Work, E>(
octets: &Octets<'a>,
execution: &W,
field: &'static str,
at: u64,
) -> Result<Expected<'a, W>, BindingError<E>> {
if let Some(bytes) = octets.as_one() {
return Ok(Expected::Borrowed(bytes));
}
let mut joined = execution.vector();
joined
.try_reserve_exact(octets.len())
.map_err(|reason| BindingError::Processing(Error::resource(reason, field, at)))?;
for piece in octets.pieces() {
execution.charge_work(1).map_err(|reason| {
BindingError::Processing(Error::resource(reason, field, piece.span().start))
})?;
joined
.try_extend_from_slice(piece.as_bytes())
.map_err(|reason| {
BindingError::Processing(Error::resource(reason, field, piece.span().start))
})?;
}
Ok(Expected::Joined(joined))
}
fn compare_binding<'a, 'c, 'p, P: Digest<'a>>(
provider: &P,
identifier: &CertificateId<'a>,
expected: &[u8],
digest: Finished<'_, P, P::Identity, P::Output>,
certificate: &Certificate<'c>,
parent: &'p Reader<'_>,
issuer: &mut Option<structured_pkix::Ordered<'c, Execution<'p>>>,
) -> Result<Binding, BindingError<P::Error>> {
if expected.len() != digest.output().as_ref().len() {
return Ok(Binding::MalformedHash);
}
let matches = provider
.matches(
digest.identity(),
digest.output(),
expected,
&parent.execution(),
)
.map_err(|source| BindingError::Callback {
field: identifier.field,
span: identifier.element().span(),
source,
})?;
if !matches {
return Ok(Binding::HashDiffers);
}
match identifier
.matches_issuer_with(certificate, parent, issuer)
.map_err(BindingError::Processing)?
{
IssuerAgreement::Unstated | IssuerAgreement::Matches => Ok(Binding::Matches),
IssuerAgreement::Differs => Ok(Binding::IssuerDiffers),
IssuerAgreement::Unreadable => Ok(Binding::IssuerUnreadable),
}
}