use super::{
Digest, ExtensionProcessing, Lookup, NonceBinding, RequestNonce, ScopedResponse,
SingleResponse, UpdateWindow,
};
use crate::error::{CallbackResult, Result};
use crate::temporal::{LeapTable, Reading};
use crate::{Certificate, Error};
use structured_asn1::memory::{Memory, MemoryError, Work};
use structured_asn1::{Oid, Span};
pub struct Request<'p, 'a> {
pub target: &'p Certificate<'a>,
pub nonce: Option<RequestNonce<'p>>,
pub intended_signer: Option<&'p Certificate<'a>>,
}
pub struct Evaluation<'p, E: Work = Memory> {
pub now: Reading,
pub utc: &'p LeapTable<'p>,
pub execution: &'p E,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecipientBinding {
NotSpecified,
Matches,
}
#[derive(Debug, Clone, Copy)]
pub struct Evidence<'r, 'a> {
source: ScopedResponse<'r, 'a>,
assertion: &'r SingleResponse<'a>,
window: UpdateWindow,
nonce: NonceBinding,
recipient: RecipientBinding,
revocation_time: Option<crate::time::Relation>,
}
impl<'r, 'a> Evidence<'r, 'a> {
#[must_use]
pub fn source(self) -> ScopedResponse<'r, 'a> {
self.source
}
#[must_use]
pub fn assertion(self) -> &'r SingleResponse<'a> {
self.assertion
}
#[must_use]
pub fn window(self) -> UpdateWindow {
self.window
}
#[must_use]
pub fn nonce(self) -> NonceBinding {
self.nonce
}
#[must_use]
pub fn recipient(self) -> RecipientBinding {
self.recipient
}
#[must_use]
pub fn revocation_time(self) -> Option<crate::time::Relation> {
self.revocation_time
}
}
#[derive(Debug, Clone, Copy)]
#[expect(
clippy::large_enum_variant,
reason = "retain source/key/time facts by value rather than allocate a box per target merely to discriminate the result"
)]
pub enum TargetEvidence<'r, 'a> {
Evidence(Evidence<'r, 'a>),
UnexpectedSigner,
Lookup(Lookup<'r, 'a>),
UnsupportedCritical {
kind: Oid<'a>,
span: Span,
},
}
#[derive(Debug)]
pub struct TargetReading<'target, 'request, 'response, 'source> {
target: &'target Certificate<'request>,
evaluated_at: Reading,
outcome: TargetEvidence<'response, 'source>,
}
impl<'target, 'request, 'response, 'source> TargetReading<'target, 'request, 'response, 'source> {
#[must_use]
pub fn target(&self) -> &'target Certificate<'request> {
self.target
}
#[must_use]
pub fn evaluated_at(&self) -> Reading {
self.evaluated_at
}
#[must_use]
pub fn outcome(&self) -> &TargetEvidence<'response, 'source> {
&self.outcome
}
#[must_use]
pub fn evidence(&self) -> Option<BoundEvidence<'target, 'request, 'response, 'source>> {
match self.outcome {
TargetEvidence::Evidence(evidence) => Some(BoundEvidence {
target: self.target,
evaluated_at: self.evaluated_at,
evidence,
}),
_ => None,
}
}
#[must_use]
pub fn into_outcome(self) -> TargetEvidence<'response, 'source> {
self.outcome
}
}
#[derive(Debug, Clone, Copy)]
pub struct BoundEvidence<'target, 'request, 'response, 'source> {
target: &'target Certificate<'request>,
evaluated_at: Reading,
evidence: Evidence<'response, 'source>,
}
impl<'target, 'request, 'response, 'source> BoundEvidence<'target, 'request, 'response, 'source> {
#[must_use]
pub fn target(self) -> &'target Certificate<'request> {
self.target
}
#[must_use]
pub fn evaluated_at(self) -> Reading {
self.evaluated_at
}
#[must_use]
pub fn evidence(self) -> Evidence<'response, 'source> {
self.evidence
}
}
fn exact_signer<E: Work>(
left: &Certificate<'_>,
right: &Certificate<'_>,
execution: &E,
at: u64,
) -> Result<bool> {
let left = left.as_bytes();
let right = right.as_bytes();
let work = u64::try_from(left.len().max(right.len()))
.ok()
.and_then(|n| n.checked_mul(2))
.and_then(|n| n.checked_add(1))
.ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.recipient", at))?;
execution
.charge_work(work)
.map_err(|reason| Error::resource(reason, "ocspResponse.recipient", at))?;
Ok(left == right)
}
impl<'r, 'a> ScopedResponse<'r, 'a> {
pub fn evaluate_with_target<'target, 'request, D: Digest<E>, E: Work>(
self,
request: &Request<'target, 'request>,
time: &Evaluation<'_, E>,
digest: &D,
) -> CallbackResult<TargetReading<'target, 'request, 'r, 'a>, D::Error> {
let outcome = self.evaluate(request, time, digest)?;
Ok(TargetReading {
target: request.target,
evaluated_at: time.now,
outcome,
})
}
pub fn evaluate<D: Digest<E>, E: Work>(
self,
request: &Request<'_, '_>,
time: &Evaluation<'_, E>,
digest: &D,
) -> CallbackResult<TargetEvidence<'r, 'a>, D::Error> {
let execution = time.execution;
let response = self.signed().response();
let recipient = match request.intended_signer {
None => RecipientBinding::NotSpecified,
Some(expected) => {
if !exact_signer(
self.signed().signer(),
expected,
execution,
response.span().start,
)? {
return Ok(TargetEvidence::UnexpectedSigner);
}
RecipientBinding::Matches
}
};
let processed = response.process_extensions(execution)?;
if let ExtensionProcessing::Unsupported { kind, span } = processed.processing {
return Ok(TargetEvidence::UnsupportedCritical { kind, span });
}
let assertion = match self.find(request.target, digest, execution)? {
Lookup::Found(assertion) => assertion,
outcome => return Ok(TargetEvidence::Lookup(outcome)),
};
if let ExtensionProcessing::Unsupported { kind, span } =
assertion.check_extensions(execution)?
{
return Ok(TargetEvidence::UnsupportedCritical { kind, span });
}
let nonce = super::nonce::bind(
request.nonce,
processed.nonce,
execution,
response.span().start,
)?;
let window = assertion.update_window(time.now, time.utc, execution)?;
let revocation_time = assertion.revocation_relation(time.now, time.utc, execution)?;
Ok(TargetEvidence::Evidence(Evidence {
source: self,
assertion,
nonce,
window,
recipient,
revocation_time,
}))
}
}
#[cfg(test)]
mod tests;