use super::super::digest::CertDigest;
use super::{FIELD, OcspRequest, RequestEntry};
use crate::algorithm::digest::AsnDigest;
use crate::error::{CallbackError, CallbackResult, Error, Result};
use crate::ocsp::{
CertId, CertMatch, ExtensionProcessing, Lookup, NonceBinding, RequestNonce, SignedResponse,
};
use structured_asn1::memory::{OwnedVec, Work};
pub(super) fn equal<L: Work, R: Work>(
left: &[u8],
l: &L,
right: &[u8],
r: &R,
field: &'static str,
left_at: u64,
right_at: u64,
) -> Result<bool> {
l.charge_work(1)
.map_err(|e| Error::resource(e, field, left_at))?;
r.charge_work(1)
.map_err(|e| Error::resource(e, field, right_at))?;
if left.len() != right.len() {
return Ok(false);
}
l.charge_work(left.len() as u64)
.map_err(|e| Error::resource(e, field, left_at))?;
r.charge_work(right.len() as u64)
.map_err(|e| Error::resource(e, field, right_at))?;
Ok(left == right)
}
impl CertId<'_> {
pub fn matches_id<'d, D: AsnDigest<'d> + ?Sized, W: Work>(
&self,
other: &CertId<'_>,
digest: &'d D,
execution: &W,
) -> CallbackResult<CertMatch, D::Error> {
const FIELD: &str = "ocsp.certID";
let left_work = self.algorithm.query_in(execution);
let right_work = other.algorithm.query_in(execution);
if !equal(
self.serial.as_bytes(),
&left_work,
other.serial.as_bytes(),
&right_work,
FIELD,
self.span.start,
other.span.start,
)? {
return Ok(CertMatch::Differs);
}
let left = match self.digest_identity(digest, execution)? {
CertDigest::Ready(id) => id,
CertDigest::Rejected(reason) => return Ok(reason),
};
let right = match other.digest_identity(digest, execution)? {
CertDigest::Ready(id) => id,
CertDigest::Rejected(reason) => return Ok(reason),
};
execution
.charge_work(1)
.map_err(|e| Error::resource(e, FIELD, self.span.start))?;
if !digest
.compare(&left, &right, execution)
.map_err(CallbackError::Callback)?
.is_eq()
{
return Ok(CertMatch::Differs);
}
for (left, right) in [
(self.issuer_name_hash, other.issuer_name_hash),
(self.issuer_key_hash, other.issuer_key_hash),
] {
if !equal(
left,
&left_work,
right,
&right_work,
FIELD,
self.span.start,
other.span.start,
)? {
return Ok(CertMatch::Differs);
}
}
Ok(CertMatch::Matches)
}
}
#[derive(Debug)]
pub struct RequestLookup<'q, 'a, 'r, 'response> {
request: &'q RequestEntry<'a>,
lookup: Lookup<'r, 'response>,
extensions: ExtensionProcessing<'a>,
}
impl<'q, 'a, 'r, 'response> RequestLookup<'q, 'a, 'r, 'response> {
#[must_use]
pub fn request(&self) -> &'q RequestEntry<'a> {
self.request
}
#[must_use]
pub fn lookup(&self) -> Lookup<'r, 'response> {
self.lookup
}
#[must_use]
pub fn extensions(&self) -> ExtensionProcessing<'a> {
self.extensions
}
}
#[derive(Debug)]
pub struct BoundResponse<'q, 'a, 'r, 'response> {
request: &'q OcspRequest<'a>,
response: SignedResponse<'r, 'response>,
nonce: NonceBinding,
extensions: ExtensionProcessing<'a>,
lookups: OwnedVec<RequestLookup<'q, 'a, 'r, 'response>>,
}
impl<'q, 'a, 'r, 'response> BoundResponse<'q, 'a, 'r, 'response> {
#[must_use]
pub fn request(&self) -> &'q OcspRequest<'a> {
self.request
}
#[must_use]
pub fn response(&self) -> SignedResponse<'r, 'response> {
self.response
}
#[must_use]
pub fn nonce(&self) -> NonceBinding {
self.nonce
}
#[must_use]
pub fn extensions(&self) -> ExtensionProcessing<'a> {
self.extensions
}
#[must_use]
pub fn lookups(&self) -> &[RequestLookup<'q, 'a, 'r, 'response>] {
&self.lookups
}
}
impl<'a> OcspRequest<'a> {
pub fn nonce<W: Work>(&self, execution: &W) -> Result<Option<RequestNonce<'a>>> {
let source = self.execution_in(execution);
let Some(extension) = self.extensions.get(crate::oid::OCSP_NONCE, &source)? else {
return Ok(None);
};
let bytes = super::super::nonce::read(extension, &source, "ocspRequest.nonce")?;
RequestNonce::from_sent(bytes).map(Some).map_err(|_| {
Error::new(
crate::ErrorCode::MalformedValue,
"ocspRequest.nonce",
extension.span().start,
)
})
}
pub fn check_extensions<W: Work>(&self, execution: &W) -> Result<ExtensionProcessing<'a>> {
process_extensions(&self.extensions, true, &self.execution_in(execution))
.map(|processed| processed.processing)
}
pub fn bind_response<'q, 'r, 'response, 'd, D: AsnDigest<'d> + ?Sized, W: Work>(
&'q self,
response: SignedResponse<'r, 'response>,
digest: &'d D,
execution: &W,
) -> CallbackResult<BoundResponse<'q, 'a, 'r, 'response>, D::Error> {
let source = self.execution_in(execution);
let processed = process_extensions(&self.extensions, true, &source)?;
let response_nonce = response.response().nonce(execution)?;
let response_work = response
.response()
.signature_algorithm()
.query_in(execution);
let nonce = super::super::nonce::bind(
processed.nonce,
response_nonce,
(&source, "ocspRequest.nonce", self.span.start),
(
&response_work,
"ocspResponse.nonce",
response.response().span().start,
),
)?;
let mut lookups = source.vector();
lookups
.try_reserve_exact(self.entries.len())
.map_err(|e| Error::resource(e, FIELD, self.span.start))?;
for request in &self.entries {
source
.charge_work(1)
.map_err(|e| Error::resource(e, FIELD, request.span.start))?;
let extensions = process_extensions(&request.extensions, false, &source)?.processing;
let lookup = response.find_id(&request.id, digest, execution)?;
lookups
.try_push(RequestLookup {
request,
lookup,
extensions,
})
.map_err(|e| Error::resource(e, FIELD, request.span.start))?;
}
Ok(BoundResponse {
request: self,
response,
nonce,
extensions: processed.processing,
lookups: lookups.into_owned(),
})
}
}
struct Processed<'a> {
processing: ExtensionProcessing<'a>,
nonce: Option<RequestNonce<'a>>,
}
fn process_extensions<'a, W: Work>(
extensions: &crate::Extensions<'a>,
wide: bool,
execution: &W,
) -> Result<Processed<'a>> {
let mut processing = ExtensionProcessing::Understood;
let mut nonce = None;
for extension in extensions.iter(execution) {
let extension = extension?;
execution
.charge_work(10)
.map_err(|e| Error::resource(e, FIELD, extension.span().start))?;
if wide && extension.kind() == crate::oid::OCSP_NONCE {
let bytes = super::super::nonce::read(extension, execution, "ocspRequest.nonce")?;
nonce = Some(RequestNonce::from_sent(bytes).map_err(|_| {
Error::new(
crate::ErrorCode::MalformedValue,
"ocspRequest.nonce",
extension.span().start,
)
})?);
} else if extension.is_critical() && matches!(processing, ExtensionProcessing::Understood) {
processing = ExtensionProcessing::Unsupported {
kind: extension.kind(),
span: extension.span(),
};
}
}
Ok(Processed { processing, nonce })
}