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        if element.tag() != placement.tag() {
243            return Err(Error::schema(
244                ErrorCode::UnexpectedTag,
245                placement.field(),
246                element.span().start,
247            ));
248        }
249        if placement == Placement::Signed {
250            let mut strict = parent
251                .reread(element, Encoding::Der)
252                .map_err(|error| Error::encoding(error, placement.field()))?;
253            let original = strict
254                .expect(placement.tag())
255                .map_err(|error| Error::encoding(error, placement.field()))?;
256            Self::read_set(&strict, &original, placement)
257        } else {
258            Self::read_set(parent, element, placement)
259        }
260    }
261
262    fn read_set(parent: &Reader<'a>, element: &Element<'a>, placement: Placement) -> Result<Self> {
263        let field = placement.field();
264        let mut inside = parent
265            .enter(element)
266            .map_err(|error| Error::encoding(error, field))?;
267        let execution = parent.execution();
268        let mut items = execution.vector();
269        let mut previous = None;
270        while let Some(member) = inside
271            .next_element()
272            .map_err(|error| Error::encoding(error, field))?
273        {
274            if inside.encoding().is_der() {
275                if let Some(earlier) = previous
276                    && !structured_pkix::der::set_ordered(&inside, earlier, &member, field)
277                        .map_err(Error::pkix)?
278                {
279                    return Err(Error::schema(
280                        ErrorCode::AttributeOrder,
281                        field,
282                        member.span().start,
283                    ));
284                }
285                previous = Some(member.as_bytes());
286            }
287            let attribute = Attribute::read(&inside, &member, field)?;
288            items
289                .try_push(attribute)
290                .map_err(|reason| Error::resource(reason, field, member.span().start))?;
291        }
292        if items.is_empty() {
293            return Err(Error::schema(
294                ErrorCode::EmptyAttributes,
295                field,
296                element.span().start,
297            ));
298        }
299        Ok(Self {
300            items: items.into_owned(),
301            element: *element,
302            placement,
303        })
304    }
305
306    /// The original attribute catalogue in encoded order.
307    #[must_use]
308    pub fn items(&self) -> &[Attribute<'a>] {
309        &self.items
310    }
311
312    /// The exact range and placement supplied by the schema.
313    #[must_use]
314    pub fn element(&self) -> &Element<'a> {
315        &self.element
316    }
317
318    /// Searches every instance with the current retained-query account.
319    ///
320    /// Lookup errors are yielded once, then iteration stops. Absence is returned
321    /// only after the whole admitted search, including its terminal step.
322    pub fn all<'s>(&'s self, kind: Oid<'s>, execution: &Memory) -> Matching<'s, 'a> {
323        Matching {
324            attributes: self,
325            kind,
326            execution: execution.clone(),
327            next: 0,
328            stopped: false,
329        }
330    }
331
332    /// Searches through the producing reader's local and aggregate ceilings.
333    pub fn all_in<'s>(
334        &'s self,
335        kind: Oid<'s>,
336        parent: &'s Reader<'_>,
337    ) -> Matching<'s, 'a, Execution<'s>> {
338        Matching {
339            attributes: self,
340            kind,
341            execution: parent.execution(),
342            next: 0,
343            stopped: false,
344        }
345    }
346
347    /// Requires at most one instance for a specifically singleton attribute type.
348    ///
349    /// Unknown generic types must use [`Self::all_in`] instead: RFC5652 §5.3
350    /// does not forbid every repeated attribute identifier.
351    pub fn unique_in<'s>(
352        &'s self,
353        kind: Oid<'s>,
354        parent: &'s Reader<'_>,
355    ) -> Result<Option<&'s Attribute<'a>>> {
356        let mut found = None;
357        for result in self.all_in(kind, parent) {
358            let attribute = result?;
359            if found.is_some() {
360                return Err(Error::schema(
361                    ErrorCode::AttributeCardinality,
362                    self.placement.field(),
363                    attribute.span().start,
364                ));
365            }
366            found = Some(attribute);
367        }
368        Ok(found)
369    }
370
371    /// Original signed octets with only the implicit identifier replaced by SET.
372    ///
373    /// RFC5652 §5.4 requires the SET OF identifier, preserving original DER
374    /// length/content. Unsigned sets have no such signed range.
375    #[must_use]
376    pub fn signed_bytes(&self) -> Option<[&'a [u8]; 2]> {
377        if self.placement != Placement::Signed {
378            return None;
379        }
380        Some([&[0x31], &self.element.as_bytes()[1..]])
381    }
382}
383
384/// Accounted matching instances, with one terminal resource refusal.
385pub struct Matching<'s, 'a, W: Work = Memory> {
386    attributes: &'s Attributes<'a>,
387    kind: Oid<'s>,
388    execution: W,
389    next: usize,
390    stopped: bool,
391}
392
393impl<'s, 'a, W: Work> Iterator for Matching<'s, 'a, W> {
394    type Item = Result<&'s Attribute<'a>>;
395
396    fn next(&mut self) -> Option<Self::Item> {
397        self.attributes.next_matching(
398            self.kind,
399            &self.execution,
400            &mut self.next,
401            &mut self.stopped,
402        )
403    }
404}
405
406impl<'a> Attributes<'a> {
407    pub(crate) fn next_matching<'s>(
408        &'s self,
409        kind: Oid<'_>,
410        execution: &impl Work,
411        next: &mut usize,
412        stopped: &mut bool,
413    ) -> Option<Result<&'s Attribute<'a>>> {
414        if *stopped {
415            return None;
416        }
417        let field = self.placement.field();
418        loop {
419            if let Err(reason) = execution.charge_work(1) {
420                *stopped = true;
421                let at = self
422                    .items
423                    .get(*next)
424                    .map_or(self.element.span().start, |attribute| {
425                        attribute.span().start
426                    });
427                return Some(Err(Error::resource(reason, field, at)));
428            }
429            let Some(attribute) = self.items.get(*next) else {
430                *stopped = true;
431                return None;
432            };
433            *next += 1;
434            if attribute.kind.as_bytes().len() != kind.as_bytes().len() {
435                continue;
436            }
437            if let Err(reason) = execution.charge_work(attribute.kind.as_bytes().len() as u64) {
438                *stopped = true;
439                return Some(Err(Error::resource(reason, field, attribute.span().start)));
440            }
441            if attribute.kind == kind {
442                return Some(Ok(attribute));
443            }
444        }
445    }
446}
447
448impl<W: Work> core::iter::FusedIterator for Matching<'_, '_, W> {}