Skip to main content

structured_cms/
attribute.rs

1//! Original CMS attribute values and the exact retagged signed range.
2
3use crate::{Error, ErrorCode, Result};
4use structured_asn1::memory::{Execution, Memory, OwnedVec, Work};
5use structured_asn1::{Element, Encoding, Oid, Parser, Reader, Span, Tag, universal};
6
7/// The schema placement of an attribute set, not deployment policy.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Placement {
10    /// `[0] IMPLICIT SignedAttributes`, required to be DER.
11    Signed,
12    /// `[1] IMPLICIT UnsignedAttributes`, permitted to use BER.
13    Unsigned,
14}
15
16impl Placement {
17    fn field(self) -> &'static str {
18        match self {
19            Self::Signed => "signerInfo.signedAttrs",
20            Self::Unsigned => "signerInfo.unsignedAttrs",
21        }
22    }
23    fn tag(self) -> Tag {
24        Tag::context(
25            true,
26            match self {
27                Self::Signed => 0,
28                Self::Unsigned => 1,
29            },
30        )
31    }
32}
33
34/// One original attribute, preserving all values without interpreting unknown syntax.
35#[derive(Debug)]
36pub struct Attribute<'a> {
37    kind: Oid<'a>,
38    values: OwnedVec<Element<'a>>,
39    element: Element<'a>,
40    parser: &'a Parser,
41    depth: u32,
42    encoding: Encoding,
43}
44
45impl<'a> Attribute<'a> {
46    fn read(parent: &Reader<'a>, element: &Element<'a>, field: &'static str) -> Result<Self> {
47        if element.tag() != Tag::constructed(universal::SEQUENCE) {
48            return Err(Error::schema(
49                ErrorCode::UnexpectedTag,
50                field,
51                element.span().start,
52            ));
53        }
54        let mut inside = parent
55            .enter(element)
56            .map_err(|error| Error::encoding(error, field))?;
57        let kind = inside
58            .read_oid()
59            .map_err(|error| Error::encoding(error, field))?;
60        let set = inside
61            .expect(Tag::constructed(universal::SET))
62            .map_err(|error| Error::encoding(error, field))?;
63        let mut members = inside
64            .enter(&set)
65            .map_err(|error| Error::encoding(error, field))?;
66        let execution = parent.execution();
67        let mut values = execution.vector();
68        let mut previous = None;
69        // RFC5652 §§5.3/12.1: attrValues is SET OF AttributeValue without SIZE.
70        // The nonempty bound belongs to SignedAttributes/UnsignedAttributes,
71        // not this inner set. Registered attribute rules impose their own
72        // cardinality in binding/counter/ESS processing; unknown syntax cannot
73        // inherit a blanket lower bound from the outer collection.
74        // https://www.rfc-editor.org/rfc/rfc5652#section-5.3
75        while let Some(value) = members
76            .next_element()
77            .map_err(|error| Error::encoding(error, field))?
78        {
79            if members.encoding().is_der() {
80                structured_pkix::der::conforms(&members, &value, field).map_err(Error::pkix)?;
81                if let Some(earlier) = previous
82                    && !structured_pkix::der::set_ordered(&members, earlier, &value, field)
83                        .map_err(Error::pkix)?
84                {
85                    return Err(Error::schema(
86                        ErrorCode::AttributeOrder,
87                        field,
88                        value.span().start,
89                    ));
90                }
91                previous = Some(value.as_bytes());
92            }
93            values
94                .try_push(value)
95                .map_err(|reason| Error::resource(reason, field, value.span().start))?;
96        }
97        if !inside.is_empty() {
98            return Err(Error::schema(
99                ErrorCode::TrailingData,
100                field,
101                inside.offset(),
102            ));
103        }
104        Ok(Self {
105            kind,
106            values: values.into_owned(),
107            element: *element,
108            parser: members.parser(),
109            depth: members.depth(),
110            encoding: members.encoding(),
111        })
112    }
113
114    /// The original attribute identifier.
115    #[must_use]
116    pub fn kind(&self) -> Oid<'a> {
117        self.kind
118    }
119
120    /// Every original value, including unknown or generically empty value sets.
121    #[must_use]
122    pub fn values(&self) -> &[Element<'a>] {
123        &self.values
124    }
125
126    /// The original attribute source range.
127    #[must_use]
128    pub fn span(&self) -> Span {
129        self.element.span()
130    }
131
132    /// The original encoded attribute without normalization.
133    #[must_use]
134    pub fn as_bytes(&self) -> &'a [u8] {
135        self.element.as_bytes()
136    }
137
138    /// Opens one original value in the current account and inherited parser depth.
139    ///
140    /// The index selects this attribute's catalogue, so a foreign element cannot
141    /// be rebound to its provenance. No serialized bridge or replacement parser
142    /// is used. Missing indices are absence; resource refusal remains an error.
143    pub fn value_reader(
144        &self,
145        index: usize,
146        execution: &Memory,
147        field: &'static str,
148    ) -> Result<Option<Reader<'a>>> {
149        let Some(value) = self.selected_value(index, execution, field)? else {
150            return Ok(None);
151        };
152        debug_assert!(self.depth > 0);
153        self.parser
154            .read_nested_in(
155                value.as_bytes(),
156                value.span().start,
157                self.encoding,
158                self.depth - 1,
159                execution,
160            )
161            .map(Some)
162            .map_err(|error| Error::encoding(error, field))
163    }
164
165    /// Opens one original value with producing local and current aggregate work.
166    /// A foreign parser session refuses before lookup/allocation; it cannot
167    /// silently replace the current producing ceiling with this value's old one.
168    pub fn value_reader_in<'query>(
169        &self,
170        index: usize,
171        parent: &Reader<'query>,
172        field: &'static str,
173    ) -> Result<Option<Reader<'query>>>
174    where
175        'a: 'query,
176    {
177        if !core::ptr::eq(self.parser, parent.parser()) {
178            return Err(Error::pkix(structured_pkix::Error::new(
179                structured_pkix::ErrorCode::ForeignReader,
180                field,
181                self.element.span().start,
182            )));
183        }
184        let Some(value) = self.selected_value(index, &parent.execution(), field)? else {
185            return Ok(None);
186        };
187        debug_assert!(self.depth > 0);
188        self.parser
189            .read_nested_query_in(
190                parent,
191                value.as_bytes(),
192                value.span().start,
193                self.encoding,
194                self.depth - 1,
195            )
196            .map(Some)
197            .map_err(|error| Error::encoding(error, field))
198    }
199
200    fn selected_value<W: Work>(
201        &self,
202        index: usize,
203        execution: &W,
204        field: &'static str,
205    ) -> Result<Option<Element<'a>>> {
206        execution
207            .charge_work(1)
208            .map_err(|reason| Error::resource(reason, field, self.element.span().start))?;
209        Ok(self.values.get(index).copied())
210    }
211
212    /// Requires a singleton for attribute types whose specific schema demands it.
213    pub fn single(&self, field: &'static str) -> Result<&Element<'a>> {
214        match &*self.values {
215            [value] => Ok(value),
216            _ => Err(Error::schema(
217                ErrorCode::AttributeCardinality,
218                field,
219                self.element.span().start,
220            )),
221        }
222    }
223}
224
225/// An original attribute set with independently retained catalogue storage.
226#[derive(Debug)]
227pub struct Attributes<'a> {
228    items: OwnedVec<Attribute<'a>>,
229    element: Element<'a>,
230    placement: Placement,
231}
232
233impl<'a> Attributes<'a> {
234    /// Reads nonempty attributes in their trusted schema placement.
235    ///
236    /// RFC5652 §5.3 requires nonempty Signed/UnsignedAttributes. Signed sets
237    /// are reread under DER through the same parser, not repaired or re-encoded.
238    /// Generic attributes preserve unknown instances; per-type cardinality and
239    /// placement checks are separate (§§11.1–11.4, RFC6211 §2).
240    /// <https://www.rfc-editor.org/rfc/rfc5652#section-5.3>
241    pub fn read(parent: &Reader<'a>, element: &Element<'a>, placement: Placement) -> Result<Self> {
242        let reading = Self::read_inspection(parent, element, placement)?;
243        if let Some(fault) = reading.fault() {
244            return Err(fault);
245        }
246        Ok(reading)
247    }
248
249    /// Reads original attributes while retaining a uniquely bounded empty set.
250    ///
251    /// An empty outer set violates RFC5652 §5.3 and is exposed by [`Self::fault`].
252    /// This inspection recovery does not relax signed DER, member syntax or
253    /// binding requirements. It preserves presence rather than treating an
254    /// invalid written set as absent.
255    pub fn read_inspection(
256        parent: &Reader<'a>,
257        element: &Element<'a>,
258        placement: Placement,
259    ) -> Result<Self> {
260        if element.tag() != placement.tag() {
261            return Err(Error::schema(
262                ErrorCode::UnexpectedTag,
263                placement.field(),
264                element.span().start,
265            ));
266        }
267        if placement == Placement::Signed {
268            let mut strict = parent
269                .reread(element, Encoding::Der)
270                .map_err(|error| Error::encoding(error, placement.field()))?;
271            let original = strict
272                .expect(placement.tag())
273                .map_err(|error| Error::encoding(error, placement.field()))?;
274            Self::read_set(&strict, &original, placement)
275        } else {
276            Self::read_set(parent, element, placement)
277        }
278    }
279
280    fn read_set(parent: &Reader<'a>, element: &Element<'a>, placement: Placement) -> Result<Self> {
281        let field = placement.field();
282        let mut inside = parent
283            .enter(element)
284            .map_err(|error| Error::encoding(error, field))?;
285        let execution = parent.execution();
286        let mut items = execution.vector();
287        let mut previous = None;
288        while let Some(member) = inside
289            .next_element()
290            .map_err(|error| Error::encoding(error, field))?
291        {
292            if inside.encoding().is_der() {
293                if let Some(earlier) = previous
294                    && !structured_pkix::der::set_ordered(&inside, earlier, &member, field)
295                        .map_err(Error::pkix)?
296                {
297                    return Err(Error::schema(
298                        ErrorCode::AttributeOrder,
299                        field,
300                        member.span().start,
301                    ));
302                }
303                previous = Some(member.as_bytes());
304            }
305            let attribute = Attribute::read(&inside, &member, field)?;
306            items
307                .try_push(attribute)
308                .map_err(|reason| Error::resource(reason, field, member.span().start))?;
309        }
310        Ok(Self {
311            items: items.into_owned(),
312            element: *element,
313            placement,
314        })
315    }
316
317    /// The located nonempty-bound violation, without allocating diagnostics.
318    ///
319    /// Original signed bytes remain available for inspection; this fault must
320    /// be refused before preparing a cryptographic operation.
321    #[must_use]
322    pub fn fault(&self) -> Option<Error> {
323        if self.items.is_empty() {
324            return Some(Error::schema(
325                ErrorCode::EmptyAttributes,
326                self.placement.field(),
327                self.element.span().start,
328            ));
329        }
330        None
331    }
332
333    /// The original attribute catalogue in encoded order.
334    #[must_use]
335    pub fn items(&self) -> &[Attribute<'a>] {
336        &self.items
337    }
338
339    /// The exact range and placement supplied by the schema.
340    #[must_use]
341    pub fn element(&self) -> &Element<'a> {
342        &self.element
343    }
344
345    /// Searches every instance with the current retained-query account.
346    ///
347    /// Lookup errors are yielded once, then iteration stops. Absence is returned
348    /// only after the whole admitted search, including its terminal step.
349    pub fn all<'s>(&'s self, kind: Oid<'s>, execution: &Memory) -> Matching<'s, 'a> {
350        Matching {
351            attributes: self,
352            kind,
353            execution: execution.clone(),
354            next: 0,
355            stopped: false,
356        }
357    }
358
359    /// Searches through the producing reader's local and aggregate ceilings.
360    pub fn all_in<'s>(
361        &'s self,
362        kind: Oid<'s>,
363        parent: &'s Reader<'_>,
364    ) -> Matching<'s, 'a, Execution<'s>> {
365        Matching {
366            attributes: self,
367            kind,
368            execution: parent.execution(),
369            next: 0,
370            stopped: false,
371        }
372    }
373
374    /// Requires at most one instance for a specifically singleton attribute type.
375    ///
376    /// Unknown generic types must use [`Self::all_in`] instead: RFC5652 §5.3
377    /// does not forbid every repeated attribute identifier.
378    pub fn unique_in<'s>(
379        &'s self,
380        kind: Oid<'s>,
381        parent: &'s Reader<'_>,
382    ) -> Result<Option<&'s Attribute<'a>>> {
383        let mut found = None;
384        for result in self.all_in(kind, parent) {
385            let attribute = result?;
386            if found.is_some() {
387                return Err(Error::schema(
388                    ErrorCode::AttributeCardinality,
389                    self.placement.field(),
390                    attribute.span().start,
391                ));
392            }
393            found = Some(attribute);
394        }
395        Ok(found)
396    }
397
398    /// Original signed octets with only the implicit identifier replaced by SET.
399    ///
400    /// RFC5652 §5.4 requires the SET OF identifier, preserving original DER
401    /// length/content. Unsigned sets have no such signed range.
402    #[must_use]
403    pub fn signed_bytes(&self) -> Option<[&'a [u8]; 2]> {
404        if self.placement != Placement::Signed {
405            return None;
406        }
407        Some([&[0x31], &self.element.as_bytes()[1..]])
408    }
409}
410
411/// Accounted matching instances, with one terminal resource refusal.
412pub struct Matching<'s, 'a, W: Work = Memory> {
413    attributes: &'s Attributes<'a>,
414    kind: Oid<'s>,
415    execution: W,
416    next: usize,
417    stopped: bool,
418}
419
420impl<'s, 'a, W: Work> Iterator for Matching<'s, 'a, W> {
421    type Item = Result<&'s Attribute<'a>>;
422
423    fn next(&mut self) -> Option<Self::Item> {
424        self.attributes.next_matching(
425            self.kind,
426            &self.execution,
427            &mut self.next,
428            &mut self.stopped,
429        )
430    }
431}
432
433impl<'a> Attributes<'a> {
434    pub(crate) fn next_matching<'s>(
435        &'s self,
436        kind: Oid<'_>,
437        execution: &impl Work,
438        next: &mut usize,
439        stopped: &mut bool,
440    ) -> Option<Result<&'s Attribute<'a>>> {
441        if *stopped {
442            return None;
443        }
444        let field = self.placement.field();
445        loop {
446            if let Err(reason) = execution.charge_work(1) {
447                *stopped = true;
448                let at = self
449                    .items
450                    .get(*next)
451                    .map_or(self.element.span().start, |attribute| {
452                        attribute.span().start
453                    });
454                return Some(Err(Error::resource(reason, field, at)));
455            }
456            let Some(attribute) = self.items.get(*next) else {
457                *stopped = true;
458                return None;
459            };
460            *next += 1;
461            if attribute.kind.as_bytes().len() != kind.as_bytes().len() {
462                continue;
463            }
464            if let Err(reason) = execution.charge_work(attribute.kind.as_bytes().len() as u64) {
465                *stopped = true;
466                return Some(Err(Error::resource(reason, field, attribute.span().start)));
467            }
468            if attribute.kind == kind {
469                return Some(Ok(attribute));
470            }
471        }
472    }
473}
474
475impl<W: Work> core::iter::FusedIterator for Matching<'_, '_, W> {}