Skip to main content

structured_pkix/extension/
entry.rs

1//! Shared Extension grammar; protocol-specific conformance and duplicate policy.
2
3use crate::error::{CallbackError, CallbackResult, Error, ErrorCode, Located, Result};
4use structured_asn1::{Element, Oid, Reader, Span, Tag, universal};
5
6/// Original scalar header facts, available before payload decoding/allocation.
7///
8/// A written DEFAULT FALSE has one readable field value. Its position is
9/// available before value parsing; trailing bytes are retained separately by
10/// [`ExtensionValue`]. Strict profiles refuse these facts, while inspection
11/// profiles can admit and retain diagnostics before decoding.
12/// No duplicate-extension or application recognition decision is made here.
13#[derive(Debug, Clone, Copy)]
14pub struct ExtensionHeader<'a> {
15    kind: Oid<'a>,
16    critical: bool,
17    critical_span: Option<Span>,
18    span: Span,
19}
20
21impl<'a> ExtensionHeader<'a> {
22    /// Original extnID, validated by the producing reader.
23    #[must_use]
24    pub fn kind(self) -> Oid<'a> {
25        self.kind
26    }
27
28    /// Written critical value, or the ASN.1 DEFAULT FALSE when absent.
29    #[must_use]
30    pub fn is_critical(self) -> bool {
31        self.critical
32    }
33
34    /// Original complete BOOLEAN TLV span, absent when the flag was omitted.
35    #[must_use]
36    pub fn critical_span(self) -> Option<Span> {
37        self.critical_span
38    }
39
40    /// Original complete extension SEQUENCE span.
41    #[must_use]
42    pub fn span(self) -> Span {
43        self.span
44    }
45
46    /// A FALSE value was written explicitly (DER must omit it: X.690 §11.5).
47    #[must_use]
48    pub fn default_written(self) -> bool {
49        self.critical_span.is_some() && !self.critical
50    }
51}
52
53/// One structural extension and the caller's interpretation of its octets.
54#[derive(Debug)]
55pub struct ExtensionValue<'a, V> {
56    header: ExtensionHeader<'a>,
57    value: V,
58    trailing_span: Option<Span>,
59}
60
61impl<'a, V> ExtensionValue<'a, V> {
62    /// Scalar original header, including readable structural faults.
63    #[must_use]
64    pub fn header(&self) -> ExtensionHeader<'a> {
65        self.header
66    }
67
68    /// Borrowed decoded value; accessing it performs no new parsing.
69    #[must_use]
70    pub fn value(&self) -> &V {
71        &self.value
72    }
73
74    /// Original remaining range after extnValue, without interpreting it.
75    #[must_use]
76    pub fn trailing_span(&self) -> Option<Span> {
77        self.trailing_span
78    }
79
80    /// Moves the decoded value, preserving any owned allocation charge.
81    #[must_use]
82    pub fn into_value(self) -> V {
83        self.value
84    }
85}
86
87/// Decodes the common Extension grammar before invoking a payload callback.
88///
89/// RFC5280 §4.1 defines SEQUENCE { OID, BOOLEAN DEFAULT FALSE, OCTET STRING }.
90/// X.690 §8.7 permits constructed BER octets; the selected profile/callback
91/// decides whether to retain that reading or refuse it. The callback receives
92/// header metadata before any payload decoding, the original producing Reader
93/// and the original extnValue Element (whole and contents spans stay distinct).
94/// It must admit its work/allocations through that reader's Execution.
95///
96/// Structural grammar errors refuse before the callback. Readable default/
97/// trailing facts are metadata, not an implicit permissive certificate mode.
98/// Callback failures retain their original cause and are never converted to
99/// a malformed-value answer. This function performs no mandatory allocation
100/// other than the producing reader's already-accounted path operations.
101/// <https://www.rfc-editor.org/rfc/rfc5280#section-4.1>
102pub fn read_with<'a, V, E>(
103    parent: &Reader<'a>,
104    element: &Element<'a>,
105    field: &'static str,
106    read_value: impl FnOnce(
107        &ExtensionHeader<'a>,
108        &Reader<'a>,
109        &Element<'a>,
110    ) -> core::result::Result<V, E>,
111) -> CallbackResult<ExtensionValue<'a, V>, E> {
112    open(parent, element, field)?.read_with(read_value)
113}
114
115/// Header and deferred original value reader, without decoding extnValue yet.
116///
117/// Inspection callers can admit a DEFAULT diagnostic before even reading the
118/// next value header. Missing/malformed values then cannot erase that prior
119/// header fault. No borrowed callback state or duplicate policy is imposed.
120pub struct ExtensionFields<'a> {
121    header: ExtensionHeader<'a>,
122    inside: Reader<'a>,
123    pending: ValueState<'a>,
124    field: &'static str,
125}
126
127enum ValueState<'a> {
128    Read(Option<Element<'a>>),
129    Deferred,
130}
131
132impl<'a> ExtensionFields<'a> {
133    /// Original header facts, before deferred extnValue parsing.
134    #[must_use]
135    pub fn header(&self) -> ExtensionHeader<'a> {
136        self.header
137    }
138
139    /// Producing execution/depth, for header diagnostic admission.
140    #[must_use]
141    pub fn reader(&self) -> &Reader<'a> {
142        &self.inside
143    }
144
145    /// Reads the one original universal OCTET STRING, then decodes its value.
146    /// The callback sees the reader just after that element, so remaining
147    /// bytes are trailing structural data. A strict callback can refuse them
148    /// before allocation; inspection can retain the returned trailing span.
149    pub fn read_with<V, E>(
150        mut self,
151        read_value: impl FnOnce(
152            &ExtensionHeader<'a>,
153            &Reader<'a>,
154            &Element<'a>,
155        ) -> core::result::Result<V, E>,
156    ) -> CallbackResult<ExtensionValue<'a, V>, E> {
157        let value = match self.pending {
158            ValueState::Read(value) => value,
159            ValueState::Deferred => self.inside.next_element().field(self.field)?,
160        }
161        .ok_or_else(|| Error::new(ErrorCode::MissingField, self.field, self.header.span.start))?;
162        if value.tag() != Tag::primitive(universal::OCTET_STRING)
163            && value.tag() != Tag::constructed(universal::OCTET_STRING)
164        {
165            return Err(Error::new(
166                ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
167                self.field,
168                value.span().start,
169            )
170            .into());
171        }
172        let trailing_span = if self.inside.is_empty() {
173            None
174        } else {
175            Some(Span {
176                start: self.inside.offset(),
177                length: self.inside.remaining().len() as u64,
178            })
179        };
180        let decoded =
181            read_value(&self.header, &self.inside, &value).map_err(CallbackError::Callback)?;
182        Ok(ExtensionValue {
183            header: self.header,
184            value: decoded,
185            trailing_span,
186        })
187    }
188}
189
190/// Reads Extension's OID/optional BOOLEAN, deferring extnValue after a flag.
191/// This is the same kernel used by [`read_with`]. Applications needing header
192/// diagnostics before payload allocation use this two-stage form.
193pub fn open<'a>(
194    parent: &Reader<'a>,
195    element: &Element<'a>,
196    field: &'static str,
197) -> Result<ExtensionFields<'a>> {
198    let fail = |code, offset| Error::new(code, field, offset);
199    if element.tag() != Tag::constructed(universal::SEQUENCE) {
200        return Err(fail(
201            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
202            element.span().start,
203        ));
204    }
205    let mut inside = parent.enter(element).field(field)?;
206    let kind = inside.read_oid().field(field)?;
207    let value = inside.next_element().field(field)?;
208    let mut pending = ValueState::Read(value);
209    let mut critical_span = None;
210    let mut critical = false;
211    if let Some(flag) = value
212        && flag.tag() == Tag::primitive(universal::BOOLEAN)
213    {
214        critical = inside.boolean(&flag).field(field)?;
215        critical_span = Some(flag.span());
216        // Defer even reading this value's header until the caller has had
217        // the opportunity to admit a written-default diagnostic.
218        pending = ValueState::Deferred;
219    }
220    Ok(ExtensionFields {
221        header: ExtensionHeader {
222            kind,
223            critical,
224            critical_span,
225            span: element.span(),
226        },
227        inside,
228        pending,
229        field,
230    })
231}