Skip to main content

structured_pkix/ocsp/
authentication.rs

1//! Actual signer binding and response signature authentication.
2
3use super::{BasicResponse, ResponderMatch};
4use crate::algorithm::digest::AsnDigest;
5use crate::error::CallbackError;
6use crate::path::{Verified, Verify, WorkingKey};
7use crate::{Certificate, Error};
8use core::fmt;
9use structured_asn1::memory::{MemoryError, Work};
10
11/// Failure to execute authentication, retaining the originating capability.
12#[derive(Debug)]
13pub enum SignatureError<V, D> {
14    /// Located shared processing/resource failure.
15    Processing(Error),
16    /// Signature provider failure, including its resource or policy error.
17    Verification(V),
18    /// Digest provider failure, including its resource or policy error.
19    Digest(D),
20}
21
22impl<V, D> From<Error> for SignatureError<V, D> {
23    fn from(error: Error) -> Self {
24        Self::Processing(error)
25    }
26}
27
28impl<V: fmt::Display, D: fmt::Display> fmt::Display for SignatureError<V, D> {
29    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            Self::Processing(error) => error.fmt(formatter),
32            Self::Verification(error) => write!(formatter, "signature verifier: {error}"),
33            Self::Digest(error) => write!(formatter, "digest provider: {error}"),
34        }
35    }
36}
37
38#[cfg(feature = "std")]
39impl<V: std::error::Error + 'static, D: std::error::Error + 'static> std::error::Error
40    for SignatureError<V, D>
41{
42    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
43        match self {
44            Self::Processing(error) => Some(error),
45            Self::Verification(error) => Some(error),
46            Self::Digest(error) => Some(error),
47        }
48    }
49}
50
51/// Completed negative binding/signature result, not an execution failure.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum SignatureFailure {
54    /// Effective key does not belong to the supplied signer's original SPKI.
55    KeyBinding,
56    /// `ResponderID` does not identify the actual supplied signer.
57    ResponderIdentity,
58    /// Provider resolved protocol-imposed SHA-1 with a contradicting width.
59    DigestWidth,
60    /// The completed signature verification rejected the signature.
61    Signature,
62}
63
64/// Original response authenticated by its actual signer. This proves neither
65/// signer authorization for a target issuer nor freshness or request binding.
66#[derive(Debug, Clone, Copy)]
67pub struct SignedResponse<'r, 'a> {
68    response: &'r BasicResponse<'a>,
69    signer: &'r Certificate<'a>,
70    key: WorkingKey<'a>,
71}
72
73impl<'r, 'a> SignedResponse<'r, 'a> {
74    /// Original signed response, retaining every assertion and extension.
75    #[must_use]
76    pub fn response(self) -> &'r BasicResponse<'a> {
77        self.response
78    }
79    /// Actual certificate whose subject/key matched `ResponderID`.
80    #[must_use]
81    pub fn signer(self) -> &'r Certificate<'a> {
82        self.signer
83    }
84    /// Effective key bound to that certificate's original SPKI.
85    #[must_use]
86    pub fn key(self) -> WorkingKey<'a> {
87        self.key
88    }
89}
90
91/// Signature authentication outcome with unsupported capabilities separate.
92#[derive(Debug)]
93pub enum SignatureAuthentication<'r, 'a> {
94    /// Signer identity and signature established; authorization remains separate.
95    Signed(SignedResponse<'r, 'a>),
96    /// Completed semantic rejection.
97    Rejected(SignatureFailure),
98    /// Protocol-imposed SHA-1 cannot be supplied by this provider/policy.
99    UnsupportedDigest,
100    /// Signature algorithm/effective key is unsupported by this verifier.
101    UnsupportedSignature,
102}
103
104impl<'a> BasicResponse<'a> {
105    /// Authenticate exact `ResponseData` with the actual signer's bound key.
106    /// Included certificates are candidates only. RFC6960 §§4.2.2.2–4.2.2.3
107    /// require authorization and status validity beyond this signature fact.
108    /// <https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2>
109    pub fn authenticate_signature<'r, 'd, V: Verify<E>, D: AsnDigest<'d> + ?Sized, E: Work>(
110        &'r self,
111        signer: &'r Certificate<'a>,
112        key: WorkingKey<'a>,
113        verifier: &V,
114        digest: &'d D,
115        execution: &E,
116    ) -> core::result::Result<SignatureAuthentication<'r, 'a>, SignatureError<V::Error, D::Error>>
117    {
118        const FIELD: &str = "ocspResponse.signature";
119        let charge = |work| {
120            execution
121                .charge_work(work)
122                .map_err(|reason| Error::resource(reason, FIELD, self.span.start))
123        };
124        let encoded = key.key().as_bytes();
125        let actual = signer.tbs().public_key().as_bytes();
126        let work = u64::try_from(encoded.len().max(actual.len()))
127            .ok()
128            .and_then(|n| n.checked_mul(2))
129            .and_then(|n| n.checked_add(1))
130            .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, FIELD, self.span.start))?;
131        charge(work)?;
132        if encoded != actual {
133            return Ok(SignatureAuthentication::Rejected(
134                SignatureFailure::KeyBinding,
135            ));
136        }
137        let identity = self
138            .responder
139            .matches_certificate(signer, digest, execution, FIELD, self.span.start)
140            .map_err(|cause| match cause {
141                CallbackError::Processing(cause) => SignatureError::Processing(cause),
142                CallbackError::Callback(cause) => SignatureError::Digest(cause),
143            })?;
144        match identity {
145            ResponderMatch::Matches => {}
146            ResponderMatch::Differs => {
147                return Ok(SignatureAuthentication::Rejected(
148                    SignatureFailure::ResponderIdentity,
149                ));
150            }
151            ResponderMatch::MalformedHashWidth => {
152                return Ok(SignatureAuthentication::Rejected(
153                    SignatureFailure::DigestWidth,
154                ));
155            }
156            ResponderMatch::UnsupportedHash => {
157                return Ok(SignatureAuthentication::UnsupportedDigest);
158            }
159        }
160        charge(1)?;
161        match verifier
162            .verify(&self.algorithm, key, self.signed, self.signature, execution)
163            .map_err(SignatureError::Verification)?
164        {
165            Verified::Yes => Ok(SignatureAuthentication::Signed(SignedResponse {
166                response: self,
167                signer,
168                key,
169            })),
170            Verified::No => Ok(SignatureAuthentication::Rejected(
171                SignatureFailure::Signature,
172            )),
173            Verified::Unsupported => Ok(SignatureAuthentication::UnsupportedSignature),
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests;