Skip to main content

structured_pkix/ocsp/
authentication.rs

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