Skip to main content

structured_pkix/ocsp/
lookup.rs

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