Skip to main content

structured_pkix/ocsp/
authentication.rs

1//! Actual signer binding and response signature authentication.
2
3use super::{BasicResponse, Digest, DigestAlgorithm, ResponderId};
4use crate::path::{Verified, Verify, WorkingKey};
5use crate::{Certificate, Error};
6use core::fmt;
7use structured_asn1::memory::{MemoryError, Work};
8
9/// Failure to execute authentication, retaining the originating capability.
10#[derive(Debug)]
11pub enum SignatureError<V, D> {
12    /// Located shared processing/resource failure.
13    Processing(Error),
14    /// Signature provider failure, including its resource or policy error.
15    Verification(V),
16    /// Digest provider failure, including its resource or policy error.
17    Digest(D),
18}
19
20impl<V, D> From<Error> for SignatureError<V, D> {
21    fn from(error: Error) -> Self {
22        Self::Processing(error)
23    }
24}
25
26impl<V: fmt::Display, D: fmt::Display> fmt::Display for SignatureError<V, D> {
27    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            Self::Processing(error) => error.fmt(formatter),
30            Self::Verification(error) => write!(formatter, "signature verifier: {error}"),
31            Self::Digest(error) => write!(formatter, "digest provider: {error}"),
32        }
33    }
34}
35
36#[cfg(feature = "std")]
37impl<V: std::error::Error + 'static, D: std::error::Error + 'static> std::error::Error
38    for SignatureError<V, D>
39{
40    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
41        match self {
42            Self::Processing(error) => Some(error),
43            Self::Verification(error) => Some(error),
44            Self::Digest(error) => Some(error),
45        }
46    }
47}
48
49/// Completed negative binding/signature result, not an execution failure.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum SignatureFailure {
52    /// Effective key does not belong to the supplied signer's original SPKI.
53    KeyBinding,
54    /// `ResponderID` does not identify the actual supplied signer.
55    ResponderIdentity,
56    /// Provider resolved protocol-imposed SHA-1 with a contradicting width.
57    DigestWidth,
58    /// The completed signature verification rejected the signature.
59    Signature,
60}
61
62/// Original response authenticated by its actual signer. This proves neither
63/// signer authorization for a target issuer nor freshness or request binding.
64#[derive(Debug, Clone, Copy)]
65pub struct SignedResponse<'r, 'a> {
66    response: &'r BasicResponse<'a>,
67    signer: &'r Certificate<'a>,
68    key: WorkingKey<'a>,
69}
70
71impl<'r, 'a> SignedResponse<'r, 'a> {
72    /// Original signed response, retaining every assertion and extension.
73    #[must_use]
74    pub fn response(self) -> &'r BasicResponse<'a> {
75        self.response
76    }
77    /// Actual certificate whose subject/key matched `ResponderID`.
78    #[must_use]
79    pub fn signer(self) -> &'r Certificate<'a> {
80        self.signer
81    }
82    /// Effective key bound to that certificate's original SPKI.
83    #[must_use]
84    pub fn key(self) -> WorkingKey<'a> {
85        self.key
86    }
87}
88
89/// Signature authentication outcome with unsupported capabilities separate.
90#[derive(Debug)]
91pub enum SignatureAuthentication<'r, 'a> {
92    /// Signer identity and signature established; authorization remains separate.
93    Signed(SignedResponse<'r, 'a>),
94    /// Completed semantic rejection.
95    Rejected(SignatureFailure),
96    /// Protocol-imposed SHA-1 cannot be supplied by this provider/policy.
97    UnsupportedDigest,
98    /// Signature algorithm/effective key is unsupported by this verifier.
99    UnsupportedSignature,
100}
101
102impl<'a> BasicResponse<'a> {
103    /// Authenticate exact `ResponseData` with the actual signer's bound key.
104    /// Included certificates are candidates only. RFC6960 §§4.2.2.2–4.2.2.3
105    /// require authorization and status validity beyond this signature fact.
106    /// <https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2>
107    pub fn authenticate_signature<'r, V: Verify<E>, D: Digest<E>, E: Work>(
108        &'r self,
109        signer: &'r Certificate<'a>,
110        key: WorkingKey<'a>,
111        verifier: &V,
112        digest: &D,
113        execution: &E,
114    ) -> core::result::Result<SignatureAuthentication<'r, 'a>, SignatureError<V::Error, D::Error>>
115    {
116        const FIELD: &str = "ocspResponse.signature";
117        let charge = |work| {
118            execution
119                .charge_work(work)
120                .map_err(|reason| Error::resource(reason, FIELD, self.span.start))
121        };
122        let encoded = key.key().as_bytes();
123        let actual = signer.tbs().public_key().as_bytes();
124        let work = u64::try_from(encoded.len().max(actual.len()))
125            .ok()
126            .and_then(|n| n.checked_mul(2))
127            .and_then(|n| n.checked_add(1))
128            .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, FIELD, self.span.start))?;
129        charge(work)?;
130        if encoded != actual {
131            return Ok(SignatureAuthentication::Rejected(
132                SignatureFailure::KeyBinding,
133            ));
134        }
135        let matches = match &self.responder {
136            ResponderId::Name(name) => name.matches(signer.tbs().subject(), execution)?,
137            ResponderId::KeyHash(expected) => {
138                charge(1)?;
139                let Some(resolved) = digest
140                    .resolve(DigestAlgorithm::Sha1, execution)
141                    .map_err(SignatureError::Digest)?
142                else {
143                    return Ok(SignatureAuthentication::UnsupportedDigest);
144                };
145                // RFC6960 §4.2.2.2 imposes SHA-1 and exactly 20 bytes here.
146                // Never hash malformed width or substitute a signature hash.
147                if resolved.width.get() != 20 || expected.len() != 20 {
148                    return Ok(SignatureAuthentication::Rejected(
149                        SignatureFailure::DigestWidth,
150                    ));
151                }
152                digest
153                    .matches(
154                        resolved.handle,
155                        key.key().key().as_bytes(),
156                        expected,
157                        execution,
158                    )
159                    .map_err(SignatureError::Digest)?
160            }
161        };
162        if !matches {
163            return Ok(SignatureAuthentication::Rejected(
164                SignatureFailure::ResponderIdentity,
165            ));
166        }
167        charge(1)?;
168        match verifier
169            .verify(&self.algorithm, key, self.signed, self.signature, execution)
170            .map_err(SignatureError::Verification)?
171        {
172            Verified::Yes => Ok(SignatureAuthentication::Signed(SignedResponse {
173                response: self,
174                signer,
175                key,
176            })),
177            Verified::No => Ok(SignatureAuthentication::Rejected(
178                SignatureFailure::Signature,
179            )),
180            Verified::Unsupported => Ok(SignatureAuthentication::UnsupportedSignature),
181        }
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::fixtures::{CertificateParts, bits, context, extension, octets, seq, tlv};
189    use core::{cell::Cell, num::NonZeroUsize};
190    use structured_asn1::memory::Memory;
191    use structured_asn1::{BitString, Parser};
192
193    struct Provider<'a> {
194        key: &'a [u8],
195        signed: &'a [u8],
196        hashes: Cell<usize>,
197        signatures: Cell<usize>,
198        width: usize,
199        digest_answer: Option<bool>,
200        signature_answer: Verified,
201        failure: bool,
202    }
203    impl Digest for Provider<'_> {
204        type Handle = ();
205        type Error = &'static str;
206        fn resolve(
207            &self,
208            algorithm: DigestAlgorithm<'_>,
209            _: &Memory,
210        ) -> Result<Option<super::super::ResolvedDigest<()>>, Self::Error> {
211            assert!(matches!(algorithm, DigestAlgorithm::Sha1));
212            if self.failure {
213                return Err("digest refusal");
214            }
215            Ok(self.digest_answer.map(|_| super::super::ResolvedDigest {
216                handle: (),
217                width: NonZeroUsize::new(self.width).unwrap(),
218            }))
219        }
220        fn matches(
221            &self,
222            (): (),
223            input: &[u8],
224            expected: &[u8],
225            _: &Memory,
226        ) -> Result<bool, Self::Error> {
227            self.hashes.set(self.hashes.get() + 1);
228            assert_eq!(input.as_ptr(), self.key.as_ptr());
229            assert_eq!(input, self.key);
230            assert_eq!(expected, &[0x33; 20]);
231            Ok(self.digest_answer.unwrap())
232        }
233    }
234    impl Verify for Provider<'_> {
235        type Error = &'static str;
236        fn verify(
237            &self,
238            _: &crate::AlgorithmIdentifier<'_>,
239            _: WorkingKey<'_>,
240            signed: &[u8],
241            _: BitString<'_>,
242            _: &Memory,
243        ) -> Result<Verified, Self::Error> {
244            self.signatures.set(self.signatures.get() + 1);
245            assert_eq!(signed.as_ptr(), self.signed.as_ptr());
246            assert_eq!(signed, self.signed);
247            Ok(self.signature_answer)
248        }
249    }
250
251    #[test]
252    fn issuer_binding_enforces_object_signing_key_usage() {
253        // RFC5280 4.2.1.3: certificate/CRL signing bits cannot authorize an
254        // OCSP object signature; either object-signing bit is sufficient.
255        for (usage, permitted) in [
256            (None, true),
257            (Some([7, 0x80]), true),
258            (Some([6, 0x40]), true),
259            (Some([1, 0x06]), false),
260            (Some([5, 0x20]), false),
261        ] {
262            let certificate_bytes = CertificateParts {
263                extensions: usage
264                    .map(|value| seq(&[extension(crate::oid::KEY_USAGE, true, &tlv(3, &value))])),
265                ..CertificateParts::default()
266            }
267            .build();
268            let execution = crate::fixtures::memory();
269            let parser = Parser::der(execution.clone());
270            let issuer = Certificate::read(&parser, &certificate_bytes, 100).unwrap();
271            let algorithm = issuer.signature_algorithm().as_bytes();
272            let timestamp = tlv(24, b"20261012120000Z");
273            let id = seq(&[
274                algorithm.to_vec(),
275                octets(&[0x11; 20]),
276                octets(&[0x22; 20]),
277                tlv(2, &[1]),
278            ]);
279            let single = seq(&[id, tlv(0x80, &[]), timestamp.clone()]);
280            let data = seq(&[
281                context(2, &[octets(&[0x33; 20])]),
282                timestamp,
283                seq(&[single]),
284            ]);
285            let bytes = seq(&[data, algorithm.to_vec(), bits(&[0xab])]);
286            let mut reader = parser.read(&bytes, 700).unwrap();
287            let root = reader.next_element().unwrap().unwrap();
288            let response = super::super::read_basic(&reader, &root).unwrap();
289            let key = WorkingKey::standing(issuer.tbs().public_key());
290            let provider = Provider {
291                key: issuer.tbs().public_key().key().as_bytes(),
292                signed: response.signed_bytes(),
293                hashes: Cell::new(0),
294                signatures: Cell::new(0),
295                width: 20,
296                digest_answer: Some(true),
297                signature_answer: Verified::Yes,
298                failure: false,
299            };
300            let SignatureAuthentication::Signed(signed) = response
301                .authenticate_signature(&issuer, key, &provider, &provider, &execution)
302                .unwrap()
303            else {
304                panic!("signature fixture rejected");
305            };
306            let result = signed.bind_issuer(&issuer, key, &execution).unwrap();
307            assert_eq!(
308                matches!(result, super::super::AuthorityBinding::Bound(_)),
309                permitted
310            );
311            if !permitted {
312                assert!(matches!(
313                    result,
314                    super::super::AuthorityBinding::Rejected(
315                        super::super::AuthorityFailure::KeyUsage
316                    )
317                ));
318            }
319            assert_eq!(provider.signatures.get(), 1);
320        }
321    }
322
323    #[test]
324    fn actual_signer_precedes_hash_and_original_response_data_verification() {
325        // This observes dispatch/provenance only, not cryptographic correctness.
326        // Wrong SPKI, responder identity or SHA1 width must stop before verify.
327        let certificate_bytes = CertificateParts::default().build();
328        let execution = crate::fixtures::memory();
329        let parser = Parser::der(execution.clone());
330        let signer = Certificate::read(&parser, &certificate_bytes, 100).unwrap();
331        let algorithm = signer.signature_algorithm().as_bytes();
332        let timestamp = tlv(24, b"20261012120000Z");
333        let id = seq(&[
334            algorithm.to_vec(),
335            octets(&[0x11; 20]),
336            octets(&[0x22; 20]),
337            tlv(2, &[1]),
338        ]);
339        let single = seq(&[id, tlv(0x80, &[]), timestamp.clone()]);
340        let data = seq(&[
341            context(2, &[octets(&[0x33; 20])]),
342            timestamp,
343            seq(&[single]),
344        ]);
345        let bytes = seq(&[data, algorithm.to_vec(), bits(&[0xab])]);
346        let mut reader = parser.read(&bytes, 700).unwrap();
347        let element = reader.next_element().unwrap().unwrap();
348        let response = super::super::read_basic(&reader, &element).unwrap();
349        let key = WorkingKey::standing(signer.tbs().public_key());
350        let mut provider = Provider {
351            key: signer.tbs().public_key().key().as_bytes(),
352            signed: response.signed_bytes(),
353            hashes: Cell::new(0),
354            signatures: Cell::new(0),
355            width: 20,
356            digest_answer: Some(true),
357            signature_answer: Verified::Yes,
358            failure: false,
359        };
360        let retained = execution.used();
361        let SignatureAuthentication::Signed(proof) = response
362            .authenticate_signature(&signer, key, &provider, &provider, &execution)
363            .unwrap()
364        else {
365            panic!("signature not authenticated");
366        };
367        assert!(core::ptr::eq(proof.response(), &raw const response));
368        assert!(core::ptr::eq(proof.signer(), &raw const signer));
369        assert_eq!(provider.signatures.get(), 1);
370        assert_eq!(execution.used(), retained);
371        provider.signatures.set(0);
372        provider.width = 19;
373        assert!(matches!(
374            response
375                .authenticate_signature(&signer, key, &provider, &provider, &execution)
376                .unwrap(),
377            SignatureAuthentication::Rejected(SignatureFailure::DigestWidth)
378        ));
379        assert_eq!(provider.signatures.get(), 0);
380        provider.width = 20;
381        provider.digest_answer = Some(false);
382        assert!(matches!(
383            response
384                .authenticate_signature(&signer, key, &provider, &provider, &execution)
385                .unwrap(),
386            SignatureAuthentication::Rejected(SignatureFailure::ResponderIdentity)
387        ));
388        assert_eq!(provider.signatures.get(), 0);
389        provider.digest_answer = None;
390        assert!(matches!(
391            response
392                .authenticate_signature(&signer, key, &provider, &provider, &execution)
393                .unwrap(),
394            SignatureAuthentication::UnsupportedDigest
395        ));
396        provider.failure = true;
397        assert!(matches!(
398            response.authenticate_signature(&signer, key, &provider, &provider, &execution),
399            Err(SignatureError::Digest("digest refusal"))
400        ));
401        let unfunded = Memory::new(alloc::sync::Arc::new(
402            structured_asn1::memory::MemoryAccount::new(0, 0),
403        ));
404        assert!(
405            matches!(response.authenticate_signature(&signer, key, &provider, &provider, &unfunded), Err(SignatureError::Processing(error)) if error.is_resource() && error.offset() == 700)
406        );
407    }
408}