Skip to main content

structured_pkix/ocsp/
extensions.rs

1//! Critical demands have response-wide or selected-assertion scope.
2
3use super::{BasicResponse, SingleResponse, retention};
4use crate::temporal::Reading;
5use crate::{Error, Result, oid};
6use structured_asn1::memory::Work;
7use structured_asn1::{Oid, Span};
8
9/// Supported extension processing, separate from application acceptance.
10#[derive(Debug, Clone, Copy)]
11pub enum ExtensionProcessing<'a> {
12    /// All critical demands in this scope were understood. Unsupported optional
13    /// noncritical extensions remain available in the original model.
14    Understood,
15    /// A critical demand is unsupported, with its original identifier/location.
16    Unsupported {
17        /// Original extension identifier.
18        kind: Oid<'a>,
19        /// Original extension location.
20        span: Span,
21    },
22}
23
24pub(super) struct Processed<'a> {
25    pub(super) processing: ExtensionProcessing<'a>,
26    pub(super) nonce: Option<&'a [u8]>,
27    pub(super) extended_revoke: bool,
28}
29
30pub(super) struct ProcessedSingle<'a> {
31    pub(super) processing: ExtensionProcessing<'a>,
32    pub(super) archive_cutoff: Option<Reading>,
33}
34
35impl<'a> BasicResponse<'a> {
36    /// Process response-wide critical demands before using any assertion.
37    /// RFC6960 ยง4.4 makes optional extension support explicit. This core
38    /// supports nonce and the extended revoked definition; other optional
39    /// noncritical values remain opaque.
40    /// <https://www.rfc-editor.org/rfc/rfc6960#section-4.4>
41    pub fn check_extensions<W: Work>(&self, execution: &W) -> Result<ExtensionProcessing<'a>> {
42        self.process_extensions(execution)
43            .map(|facts| facts.processing)
44    }
45
46    pub(super) fn process_extensions<W: Work>(&self, execution: &W) -> Result<Processed<'a>> {
47        execution.charge_work(1).map_err(|reason| {
48            Error::resource(reason, "ocspResponse.extensions", self.span.start)
49        })?;
50        let mut nonce = None;
51        let mut extended_revoke = false;
52        for extension in self.extensions.iter(execution) {
53            let extension = extension?;
54            // The common iterator already admitted the metadata step.
55            execution.charge_work(10).map_err(|reason| {
56                Error::resource(reason, "ocspResponse.extensions", extension.span().start)
57            })?;
58            if extension.kind() == oid::OCSP_NONCE {
59                nonce = Some(super::nonce::read(extension, execution)?);
60            } else if extension.kind() == oid::OCSP_EXTENDED_REVOKE {
61                retention::extended_revoke(extension, execution)?;
62                extended_revoke = true;
63            } else if extension.is_critical() {
64                return Ok(Processed {
65                    processing: ExtensionProcessing::Unsupported {
66                        kind: extension.kind(),
67                        span: extension.span(),
68                    },
69                    nonce,
70                    extended_revoke,
71                });
72            }
73        }
74        Ok(Processed {
75            processing: ExtensionProcessing::Understood,
76            nonce,
77            extended_revoke,
78        })
79    }
80}
81
82impl<'a> SingleResponse<'a> {
83    /// Process this assertion's critical demands, independently of unrelated
84    /// assertions. This core supports the archive cutoff; other critical
85    /// demands remain unsupported facts.
86    pub fn check_extensions<W: Work>(&self, execution: &W) -> Result<ExtensionProcessing<'a>> {
87        self.process_extensions(execution)
88            .map(|facts| facts.processing)
89    }
90
91    pub(super) fn process_extensions<W: Work>(&self, execution: &W) -> Result<ProcessedSingle<'a>> {
92        const FIELD: &str = "ocspResponse.singleResponse.extensions";
93        execution
94            .charge_work(1)
95            .map_err(|reason| Error::resource(reason, FIELD, self.span.start))?;
96        let mut archive_cutoff = None;
97        for extension in self.extensions.iter(execution) {
98            let extension = extension?;
99            // The common iterator already admitted the metadata step.
100            execution
101                .charge_work(10)
102                .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
103            if extension.kind() == oid::OCSP_ARCHIVE_CUTOFF {
104                archive_cutoff = Some(retention::archive_cutoff(extension, execution)?);
105            } else if extension.kind() == oid::OCSP_EXTENDED_REVOKE {
106                return Err(retention::misplaced(extension));
107            } else if extension.is_critical() {
108                return Ok(ProcessedSingle {
109                    processing: ExtensionProcessing::Unsupported {
110                        kind: extension.kind(),
111                        span: extension.span(),
112                    },
113                    archive_cutoff,
114                });
115            }
116        }
117        Ok(ProcessedSingle {
118            processing: ExtensionProcessing::Understood,
119            archive_cutoff,
120        })
121    }
122}