use super::CertId;
use super::ResponderId;
use crate::Certificate;
use crate::algorithm::digest::{self, Algorithm, AsnDigest};
use crate::error::{CallbackError, CallbackResult, Error, ErrorCode};
use crate::revocation::RevocationTarget;
use structured_asn1::digest::{OnceError, Resolution, matches_once};
use structured_asn1::memory::Work;
fn once<E>(error: OnceError<E>, field: &'static str, at: u64) -> CallbackError<E> {
match error {
OnceError::Provider(error) => CallbackError::Callback(error),
OnceError::OutputWidth { .. } => {
CallbackError::Processing(Error::new(ErrorCode::DigestOutput, field, at))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CertMatch {
Matches,
Differs,
MalformedHashWidth,
MalformedHashAlgorithm,
UnsupportedHash,
}
pub(super) enum CertDigest<I> {
Ready(I),
Rejected(CertMatch),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResponderMatch {
Matches,
Differs,
MalformedHashWidth,
UnsupportedHash,
}
impl ResponderId<'_> {
pub fn matches_certificate<'d, D: AsnDigest<'d> + ?Sized, E: Work>(
&self,
certificate: &Certificate<'_>,
digest: &'d D,
execution: &E,
field: &'static str,
at: u64,
) -> CallbackResult<ResponderMatch, D::Error> {
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, field, at))?;
let same = match self {
Self::Name(name) => name.matches(certificate.tbs().subject(), execution)?,
Self::KeyHash(expected) => {
if expected.len() != Algorithm::Sha1.width() {
return Ok(ResponderMatch::MalformedHashWidth);
}
let Resolution::Supported(identity) = digest::supported(digest, Algorithm::Sha1)
else {
return Ok(ResponderMatch::UnsupportedHash);
};
if digest
.width(&identity)
.is_some_and(|width| width != Algorithm::Sha1.width())
{
return Ok(ResponderMatch::MalformedHashWidth);
}
let input = certificate.tbs().public_key().key().as_bytes();
match matches_once(digest, &identity, input, expected, execution)
.map_err(|error| once(error, field, at))?
{
Resolution::Supported(same) => same,
Resolution::Unsupported | Resolution::Malformed => {
return Ok(ResponderMatch::UnsupportedHash);
}
}
}
};
Ok(if same {
ResponderMatch::Matches
} else {
ResponderMatch::Differs
})
}
}
impl CertId<'_> {
pub(super) fn digest_identity<'d, D: AsnDigest<'d> + ?Sized, E: Work>(
&self,
provider: &'d D,
execution: &E,
) -> crate::Result<CertDigest<D::Identity>> {
let source = self.algorithm.query_in(execution);
source
.charge_work(1)
.map_err(|reason| Error::resource(reason, "ocsp.certID", self.span.start))?;
let algorithm = match digest::algorithm(&self.algorithm, execution)? {
Resolution::Supported(algorithm) => algorithm,
Resolution::Malformed => {
return Ok(CertDigest::Rejected(CertMatch::MalformedHashAlgorithm));
}
Resolution::Unsupported => return Ok(CertDigest::Rejected(CertMatch::UnsupportedHash)),
};
if self.issuer_name_hash.len() != algorithm.width()
|| self.issuer_key_hash.len() != algorithm.width()
{
return Ok(CertDigest::Rejected(CertMatch::MalformedHashWidth));
}
let Resolution::Supported(identity) = digest::supported(provider, algorithm) else {
return Ok(CertDigest::Rejected(CertMatch::UnsupportedHash));
};
if provider
.width(&identity)
.is_some_and(|width| width != algorithm.width())
{
return Ok(CertDigest::Rejected(CertMatch::MalformedHashWidth));
}
Ok(CertDigest::Ready(identity))
}
pub fn matches<'t, 'd, T: RevocationTarget<'t>, D: AsnDigest<'d> + ?Sized, E: Work>(
&self,
target: &T,
issuer: &Certificate<'_>,
digest: &'d D,
execution: &E,
) -> CallbackResult<CertMatch, D::Error> {
const FIELD: &str = "ocspResponse.certID";
let at = self.span.start;
let source = self.algorithm.query_in(execution);
let target_work = target.execution_in(execution);
let issuer_work = issuer.execution_in(execution);
let left = self.serial.as_bytes();
source
.charge_work(left.len() as u64 + 1)
.map_err(|reason| Error::resource(reason, FIELD, at))?;
target_work
.charge_work(1)
.map_err(|reason| Error::resource(reason, FIELD, target.span().start))?;
let right = target.serial().as_bytes();
target_work
.charge_work(right.len() as u64)
.map_err(|reason| Error::resource(reason, FIELD, target.span().start))?;
if left != right {
return Ok(CertMatch::Differs);
}
issuer_work
.charge_work(1)
.map_err(|reason| Error::resource(reason, FIELD, issuer.span().start))?;
if !target.issuer().matches_between(
issuer.tbs().subject(),
(&target_work, target.source_parser()),
(&issuer_work, issuer.source_parser()),
)? {
return Ok(CertMatch::Differs);
}
let identity = match self.digest_identity(digest, execution)? {
CertDigest::Ready(identity) => identity,
CertDigest::Rejected(reason) => return Ok(reason),
};
for (input, expected, work) in [
(
target.issuer().as_bytes(),
self.issuer_name_hash,
&target_work,
),
(
issuer.tbs().public_key().key().as_bytes(),
self.issuer_key_hash,
&issuer_work,
),
] {
source
.charge_work(expected.len() as u64)
.map_err(|reason| Error::resource(reason, FIELD, at))?;
match matches_once(digest, &identity, input, expected, work)
.map_err(|error| once(error, FIELD, at))?
{
Resolution::Supported(true) => {}
Resolution::Supported(false) => return Ok(CertMatch::Differs),
Resolution::Unsupported | Resolution::Malformed => {
return Ok(CertMatch::UnsupportedHash);
}
}
}
Ok(CertMatch::Matches)
}
}
#[cfg(test)]
mod tests;