use super::{
OcspReferenceError, OcspReferenceIdentity, ReferenceDigest, ReferenceDigestError,
ReferenceIdentifier, ReferenceProperty, ReferenceTarget, ReferenceTime, check_ocsp_reference,
compare_reference_target,
};
use crate::{
binary::{self, BinaryXml},
certificate::Agreement,
digest::StreamingDigest,
properties::CalendarXml,
scalar::TextXml,
xml::{Access, AccessError, Xml},
};
use structured_asn1::{
Reader,
memory::{Execution, MemoryError, Work},
};
use structured_pkix::{
ocsp::{Digest, ResponderMatch, ResponseBytes, SignedResponse},
oid,
};
#[derive(Clone, Copy)]
pub struct OcspReferenceInput<'envelope, 'signed, 'document> {
pub envelope: &'envelope ResponseBytes<'document>,
pub signed: SignedResponse<'signed, 'document>,
}
pub struct OcspEnvelopeReference<'r, 'e, 's, 'd, N, D, G> {
references: &'r ReferenceProperty<N, D, G>,
entry: usize,
actual: OcspReferenceInput<'e, 's, 'd>,
identity: binary::Located<OcspReferenceIdentity, N, D, G>,
digest: Option<ReferenceDigest>,
}
impl<'r, 'e, 's, 'd, N, D, G> OcspEnvelopeReference<'r, 'e, 's, 'd, N, D, G> {
#[must_use]
pub fn references(&self) -> &'r ReferenceProperty<N, D, G> {
self.references
}
#[must_use]
pub fn entry(&self) -> usize {
self.entry
}
#[must_use]
pub fn actual(&self) -> OcspReferenceInput<'e, 's, 'd> {
self.actual
}
#[must_use]
pub fn identity(&self) -> &binary::Located<OcspReferenceIdentity, N, D, G> {
&self.identity
}
#[must_use]
pub fn digest(&self) -> Option<ReferenceDigest> {
self.digest
}
#[must_use]
pub fn identifies_response(&self) -> bool {
if self.identity.value.responder() != Some(ResponderMatch::Matches)
|| self.identity.value.produced_at() != ReferenceTime::Matches
{
return false;
}
matches!(
self.digest,
Some(ReferenceDigest::Compared(Agreement::Matches) | ReferenceDigest::IdentifierOnly)
)
}
}
#[derive(Debug)]
pub enum OcspEnvelopeError<E, KP, DP, N, D, G> {
Access(AccessError<E, N>),
Index {
index: usize,
},
InputMismatch {
node: N,
},
Resource {
node: N,
cause: MemoryError,
},
Identity(OcspReferenceError<E, KP, N, D, G>),
Digest(binary::Error<E, ReferenceDigestError<DP>, N, D, G>),
}
pub type OcspEnvelopeResult<'r, 'e, 's, 'd, X, KP, DP> = Result<
Option<
OcspEnvelopeReference<
'r,
'e,
's,
'd,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
>,
OcspEnvelopeError<
<X as Xml>::Error,
KP,
DP,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
>;
pub(super) fn basic_payload<'a, W: Work>(
envelope: &ResponseBytes<'a>,
work: &W,
) -> Result<Option<&'a [u8]>, MemoryError> {
work.charge_work(1)?;
let kind = envelope.kind().as_bytes();
let expected_kind = oid::OCSP_BASIC.as_bytes();
if kind.len() != expected_kind.len() {
return Ok(None);
}
work.charge_work(u64::try_from(kind.len()).map_err(|_| MemoryError::WorkExceeded)?)?;
if kind != expected_kind {
return Ok(None);
}
Ok(Some(envelope.payload()))
}
pub(super) fn envelope_contains<W: Work>(
envelope: &ResponseBytes<'_>,
authenticated: &[u8],
work: &W,
) -> Result<bool, MemoryError> {
let Some(payload) = basic_payload(envelope, work)? else {
return Ok(false);
};
if payload.len() != authenticated.len() {
return Ok(false);
}
work.charge_work(u64::try_from(payload.len()).map_err(|_| MemoryError::WorkExceeded)?)?;
Ok(payload == authenticated)
}
pub fn check_ocsp_envelope_reference<'r, 'e, 's, 'd, X, K, P, KP>(
xml: &X,
references: &'r ReferenceProperty<X::Id, X::Document, X::Generation>,
index: usize,
actual: OcspReferenceInput<'e, 's, 'd>,
key_digest: &K,
provider: &P,
operation: &Reader<'_>,
) -> OcspEnvelopeResult<'r, 'e, 's, 'd, X, KP, P::Error>
where
X: BinaryXml + CalendarXml + TextXml,
K: for<'work> Digest<Execution<'work>, Error = KP>,
P: StreamingDigest<X>,
{
let work = operation.execution();
let access = Access::new(xml, references.snapshot(), &work);
access
.query(references.node(), |_, _, _| Ok(()))
.map_err(OcspEnvelopeError::Access)?;
let Some(entry) = references.entries().get(index) else {
return Err(OcspEnvelopeError::Index { index });
};
let ReferenceIdentifier::Ocsp { node, .. } = entry.identifier() else {
return Ok(None);
};
let resource = |cause| OcspEnvelopeError::Resource { node, cause };
if !envelope_contains(actual.envelope, actual.signed.response().as_bytes(), &work)
.map_err(resource)?
{
return Err(OcspEnvelopeError::InputMismatch { node });
}
let identity =
check_ocsp_reference(xml, references, index, actual.signed, key_digest, operation)
.map_err(OcspEnvelopeError::Identity)?;
let Some(identity) = identity else {
return Err(OcspEnvelopeError::InputMismatch { node });
};
let digest = if identity.value.responder().is_some() {
Some(
compare_reference_target(
xml,
references,
index,
ReferenceTarget::Ocsp(actual.envelope),
provider,
&work,
)
.map_err(OcspEnvelopeError::Digest)?,
)
} else {
None
};
access
.query(node, |_, _, _| Ok(()))
.map_err(OcspEnvelopeError::Access)?;
Ok(Some(OcspEnvelopeReference {
references,
entry: index,
actual,
identity,
digest,
}))
}