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(
60                    extension,
61                    execution,
62                    "ocspResponse.nonce",
63                )?);
64            } else if extension.kind() == oid::OCSP_EXTENDED_REVOKE {
65                retention::extended_revoke(extension, execution)?;
66                extended_revoke = true;
67            } else if extension.is_critical() {
68                return Ok(Processed {
69                    processing: ExtensionProcessing::Unsupported {
70                        kind: extension.kind(),
71                        span: extension.span(),
72                    },
73                    nonce,
74                    extended_revoke,
75                });
76            }
77        }
78        Ok(Processed {
79            processing: ExtensionProcessing::Understood,
80            nonce,
81            extended_revoke,
82        })
83    }
84}
85
86impl<'a> SingleResponse<'a> {
87    /// Process this assertion's critical demands, independently of unrelated
88    /// assertions. This core supports the archive cutoff; other critical
89    /// demands remain unsupported facts.
90    pub fn check_extensions<W: Work>(&self, execution: &W) -> Result<ExtensionProcessing<'a>> {
91        self.process_extensions(execution)
92            .map(|facts| facts.processing)
93    }
94
95    pub(super) fn process_extensions<W: Work>(&self, execution: &W) -> Result<ProcessedSingle<'a>> {
96        const FIELD: &str = "ocspResponse.singleResponse.extensions";
97        execution
98            .charge_work(1)
99            .map_err(|reason| Error::resource(reason, FIELD, self.span.start))?;
100        let mut archive_cutoff = None;
101        for extension in self.extensions.iter(execution) {
102            let extension = extension?;
103            // The common iterator already admitted the metadata step.
104            execution
105                .charge_work(10)
106                .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
107            if extension.kind() == oid::OCSP_ARCHIVE_CUTOFF {
108                archive_cutoff = Some(retention::archive_cutoff(extension, execution)?);
109            } else if extension.kind() == oid::OCSP_EXTENDED_REVOKE {
110                return Err(retention::misplaced(extension));
111            } else if extension.is_critical() {
112                return Ok(ProcessedSingle {
113                    processing: ExtensionProcessing::Unsupported {
114                        kind: extension.kind(),
115                        span: extension.span(),
116                    },
117                    archive_cutoff,
118                });
119            }
120        }
121        Ok(ProcessedSingle {
122            processing: ExtensionProcessing::Understood,
123            archive_cutoff,
124        })
125    }
126}