use super::{CheckedPath, exact};
use structured_asn1::memory::{MemoryError, Work};
use structured_pkix::{
ocsp::{AuthoritySource, BoundEvidence},
path::WorkingKey,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OcspBindingFailure {
Target,
Issuer,
EffectiveKey,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OcspBindingError {
MemberIndex {
index: usize,
},
Resource(MemoryError),
IssuerKeyUnstated,
}
pub struct OcspTarget<'view, 'certificate, 'document> {
path: &'view CheckedPath<'document>,
member: usize,
evidence: &'view BoundEvidence<'certificate, 'document, 'certificate, 'document>,
}
impl<'view, 'certificate, 'document> OcspTarget<'view, 'certificate, 'document> {
#[must_use]
pub fn path(&self) -> &'view CheckedPath<'document> {
self.path
}
#[must_use]
pub fn member(&self) -> usize {
self.member
}
#[must_use]
pub fn evidence(
&self,
) -> &'view BoundEvidence<'certificate, 'document, 'certificate, 'document> {
self.evidence
}
#[must_use]
pub fn authority(&self) -> AuthoritySource {
self.evidence.evidence().source().source()
}
}
pub enum OcspBinding<'view, 'certificate, 'document> {
Bound(OcspTarget<'view, 'certificate, 'document>),
Rejected(OcspBindingFailure),
}
impl<'document> CheckedPath<'document> {
pub fn bind_ocsp<'view, 'certificate, W: Work>(
&'view self,
member: usize,
evidence: &'view BoundEvidence<'certificate, 'document, 'certificate, 'document>,
execution: &W,
) -> Result<OcspBinding<'view, 'certificate, 'document>, OcspBindingError> {
let resource = OcspBindingError::Resource;
execution.charge_work(1).map_err(resource)?;
let Some(target) = self.members.get(member) else {
return Err(OcspBindingError::MemberIndex { index: member });
};
if !exact(
target.certificate().as_bytes(),
evidence.target().as_bytes(),
execution,
)
.map_err(resource)?
{
return Ok(OcspBinding::Rejected(OcspBindingFailure::Target));
}
execution.charge_work(1).map_err(resource)?;
let expected = if member == 0 {
WorkingKey::standing(self.anchor.key())
} else {
self.members[member - 1].key()
};
let source = evidence.evidence().source();
if !exact(
expected.key().as_bytes(),
source.issuer().tbs().public_key().as_bytes(),
execution,
)
.map_err(resource)?
{
return Ok(OcspBinding::Rejected(OcspBindingFailure::Issuer));
}
if let Some(actual) = source.issuing_key() {
let same = match (expected.parameters(), actual.parameters()) {
(None, None) => true,
(Some(left), Some(right)) => exact(left, right, execution).map_err(resource)?,
_ => false,
};
if !same {
return Ok(OcspBinding::Rejected(OcspBindingFailure::EffectiveKey));
}
} else if !matches!(source.source(), AuthoritySource::Configured) {
return Err(OcspBindingError::IssuerKeyUnstated);
}
Ok(OcspBinding::Bound(OcspTarget {
path: self,
member,
evidence,
}))
}
}