Skip to main content

structured_pkix/ocsp/
lookup.rs

1//! Applicable assertions are selected without discarding duplicate evidence.
2
3use super::{CertMatch, Digest, SignedResponse, SingleResponse};
4use crate::Certificate;
5use crate::error::{CallbackResult, Error};
6use structured_asn1::memory::Work;
7
8/// Identity lookup within an authenticated response, not a complete verdict.
9#[derive(Debug, Clone, Copy)]
10pub enum Lookup<'r, 'a> {
11    /// No assertion matches the target and actual issuer.
12    Missing,
13    /// One matching original assertion. Time/extensions/authorization still
14    /// have to be checked before interpreting its revocation status.
15    Found(&'r SingleResponse<'a>),
16    /// Multiple matching assertions are retained, including identical repeats.
17    /// Selection must not silently pick first or last.
18    Ambiguous {
19        /// First matching assertion in wire order.
20        first: &'r SingleResponse<'a>,
21        /// Next matching assertion in wire order.
22        second: &'r SingleResponse<'a>,
23    },
24    /// A candidate identity uses a hash capability unavailable to this caller.
25    UnsupportedHash(&'r SingleResponse<'a>),
26    /// Known fixed-output hash width contradicts the original `CertID` bytes.
27    MalformedHashWidth(&'r SingleResponse<'a>),
28}
29
30impl<'r, 'a> SignedResponse<'r, 'a> {
31    /// Locate the target's assertion using actual target/issuer bytes and the
32    /// selected provider. RFC6960 ยง3.2(1) requires binding to the requested
33    /// certificate. The caller binds these inputs to its actual request and
34    /// checks nonce/intended recipient separately; identity alone is insufficient.
35    /// <https://www.rfc-editor.org/rfc/rfc6960#section-3.2>
36    pub fn find<D: Digest<E>, E: Work>(
37        self,
38        target: &Certificate<'_>,
39        issuer: &Certificate<'_>,
40        digest: &D,
41        execution: &E,
42    ) -> CallbackResult<Lookup<'r, 'a>, D::Error> {
43        let mut first = None;
44        for response in self.response().responses() {
45            execution.charge_work(1).map_err(|reason| {
46                Error::resource(reason, "ocspResponse.responses", response.span().start)
47            })?;
48            match response.id().matches(target, issuer, digest, execution)? {
49                CertMatch::Differs => {}
50                CertMatch::UnsupportedHash => return Ok(Lookup::UnsupportedHash(response)),
51                CertMatch::MalformedHashWidth => return Ok(Lookup::MalformedHashWidth(response)),
52                CertMatch::Matches => match first {
53                    None => first = Some(response),
54                    Some(first) => {
55                        return Ok(Lookup::Ambiguous {
56                            first,
57                            second: response,
58                        });
59                    }
60                },
61            }
62        }
63        Ok(match first {
64            Some(response) => Lookup::Found(response),
65            None => Lookup::Missing,
66        })
67    }
68}