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, ScopedReader, 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: 'operation, 'operation, V, E>(
103    parent: &ScopedReader<'a, 'operation>,
104    element: &Element<'a>,
105    field: &'static str,
106    read_value: impl FnOnce(
107        &ExtensionHeader<'a>,
108        &ScopedReader<'a, 'operation>,
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 ScopedExtensionFields<'a: 'operation, 'operation> {
121    header: ExtensionHeader<'a>,
122    inside: ScopedReader<'a, 'operation>,
123    pending: ValueState<'a>,
124    field: &'static str,
125}
126
127/// Header/value cursor when source and operation have the same lifetime.
128pub type ExtensionFields<'a> = ScopedExtensionFields<'a, 'a>;
129
130enum ValueState<'a> {
131    Read(Option<Element<'a>>),
132    Deferred,
133}
134
135impl<'a: 'operation, 'operation> ScopedExtensionFields<'a, 'operation> {
136    /// Original header facts, before deferred extnValue parsing.
137    #[must_use]
138    pub fn header(&self) -> ExtensionHeader<'a> {
139        self.header
140    }
141
142    /// Producing execution/depth, for header diagnostic admission.
143    #[must_use]
144    pub fn reader(&self) -> &ScopedReader<'a, 'operation> {
145        &self.inside
146    }
147
148    /// Reads the one original universal OCTET STRING, then decodes its value.
149    /// The callback sees the reader just after that element, so remaining
150    /// bytes are trailing structural data. A strict callback can refuse them
151    /// before allocation; inspection can retain the returned trailing span.
152    pub fn read_with<V, E>(
153        mut self,
154        read_value: impl FnOnce(
155            &ExtensionHeader<'a>,
156            &ScopedReader<'a, 'operation>,
157            &Element<'a>,
158        ) -> core::result::Result<V, E>,
159    ) -> CallbackResult<ExtensionValue<'a, V>, E> {
160        let value = match self.pending {
161            ValueState::Read(value) => value,
162            ValueState::Deferred => self.inside.next_element().field(self.field)?,
163        }
164        .ok_or_else(|| Error::new(ErrorCode::MissingField, self.field, self.header.span.start))?;
165        if value.tag() != Tag::primitive(universal::OCTET_STRING)
166            && value.tag() != Tag::constructed(universal::OCTET_STRING)
167        {
168            return Err(Error::new(
169                ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
170                self.field,
171                value.span().start,
172            )
173            .into());
174        }
175        let trailing_span = if self.inside.is_empty() {
176            None
177        } else {
178            Some(Span {
179                start: self.inside.offset(),
180                length: self.inside.remaining().len() as u64,
181            })
182        };
183        let decoded =
184            read_value(&self.header, &self.inside, &value).map_err(CallbackError::Callback)?;
185        Ok(ExtensionValue {
186            header: self.header,
187            value: decoded,
188            trailing_span,
189        })
190    }
191}
192
193/// Reads Extension's OID/optional BOOLEAN, deferring extnValue after a flag.
194/// This is the same kernel used by [`read_with`]. Applications needing header
195/// diagnostics before payload allocation use this two-stage form.
196pub fn open<'a: 'operation, 'operation>(
197    parent: &ScopedReader<'a, 'operation>,
198    element: &Element<'a>,
199    field: &'static str,
200) -> Result<ScopedExtensionFields<'a, 'operation>> {
201    let fail = |code, offset| Error::new(code, field, offset);
202    if element.tag() != Tag::constructed(universal::SEQUENCE) {
203        return Err(fail(
204            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
205            element.span().start,
206        ));
207    }
208    let mut inside = parent.enter(element).field(field)?;
209    let kind = inside.read_oid().field(field)?;
210    let value = inside.next_element().field(field)?;
211    let mut pending = ValueState::Read(value);
212    let mut critical_span = None;
213    let mut critical = false;
214    if let Some(flag) = value
215        && flag.tag() == Tag::primitive(universal::BOOLEAN)
216    {
217        critical = inside.boolean(&flag).field(field)?;
218        critical_span = Some(flag.span());
219        // Defer even reading this value's header until the caller has had
220        // the opportunity to admit a written-default diagnostic.
221        pending = ValueState::Deferred;
222    }
223    Ok(ScopedExtensionFields {
224        header: ExtensionHeader {
225            kind,
226            critical,
227            critical_span,
228            span: element.span(),
229        },
230        inside,
231        pending,
232        field,
233    })
234}