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
7const SIGNED_SINGLETONS: [Oid<'static>; 4] = [
8    crate::oid::CONTENT_TYPE,
9    crate::oid::MESSAGE_DIGEST,
10    crate::oid::SIGNING_TIME,
11    crate::oid::ALGORITHM_PROTECTION,
12];
13
14/// The schema placement of an attribute set, not deployment policy.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Placement {
17    /// `[0] IMPLICIT SignedAttributes`, required to be DER.
18    Signed,
19    /// `[1] IMPLICIT UnsignedAttributes`, permitted to use BER.
20    Unsigned,
21}
22
23impl Placement {
24    fn field(self) -> &'static str {
25        match self {
26            Self::Signed => "signerInfo.signedAttrs",
27            Self::Unsigned => "signerInfo.unsignedAttrs",
28        }
29    }
30    fn tag(self) -> Tag {
31        Tag::context(
32            true,
33            match self {
34                Self::Signed => 0,
35                Self::Unsigned => 1,
36            },
37        )
38    }
39}
40
41/// One original attribute, preserving all values without interpreting unknown syntax.
42#[derive(Debug)]
43pub struct Attribute<'a> {
44    kind: Oid<'a>,
45    values: OwnedVec<Element<'a>>,
46    element: Element<'a>,
47    parser: &'a Parser,
48    depth: u32,
49    encoding: Encoding,
50}
51
52impl<'a> Attribute<'a> {
53    fn read(parent: &Reader<'a>, element: &Element<'a>, field: &'static str) -> Result<Self> {
54        if element.tag() != Tag::constructed(universal::SEQUENCE) {
55            return Err(Error::schema(
56                ErrorCode::UnexpectedTag,
57                field,
58                element.span().start,
59            ));
60        }
61        let mut inside = parent
62            .enter(element)
63            .map_err(|error| Error::encoding(error, field))?;
64        let kind = inside
65            .read_oid()
66            .map_err(|error| Error::encoding(error, field))?;
67        let set = inside
68            .expect(Tag::constructed(universal::SET))
69            .map_err(|error| Error::encoding(error, field))?;
70        let mut members = inside
71            .enter(&set)
72            .map_err(|error| Error::encoding(error, field))?;
73        let execution = parent.execution();
74        let mut values = execution.vector();
75        let mut previous = None;
76        // RFC5652 §§5.3/12.1: attrValues is SET OF AttributeValue without SIZE.
77        // The nonempty bound belongs to SignedAttributes/UnsignedAttributes,
78        // not this inner set. Registered attribute rules impose their own
79        // cardinality in binding/counter/ESS processing; unknown syntax cannot
80        // inherit a blanket lower bound from the outer collection.
81        // https://www.rfc-editor.org/rfc/rfc5652#section-5.3
82        while let Some(value) = members
83            .next_element()
84            .map_err(|error| Error::encoding(error, field))?
85        {
86            if members.encoding().is_der() {
87                structured_pkix::der::conforms(&members, &value, field).map_err(Error::pkix)?;
88                if let Some(earlier) = previous
89                    && !structured_pkix::der::set_ordered(&members, earlier, &value, field)
90                        .map_err(Error::pkix)?
91                {
92                    return Err(Error::schema(
93                        ErrorCode::AttributeOrder,
94                        field,
95                        value.span().start,
96                    ));
97                }
98                previous = Some(value.as_bytes());
99            }
100            values
101                .try_push(value)
102                .map_err(|reason| Error::resource(reason, field, value.span().start))?;
103        }
104        if !inside.is_empty() {
105            return Err(Error::schema(
106                ErrorCode::TrailingData,
107                field,
108                inside.offset(),
109            ));
110        }
111        Ok(Self {
112            kind,
113            values: values.into_owned(),
114            element: *element,
115            parser: members.parser(),
116            depth: members.depth(),
117            encoding: members.encoding(),
118        })
119    }
120
121    /// The original attribute identifier.
122    #[must_use]
123    pub fn kind(&self) -> Oid<'a> {
124        self.kind
125    }
126
127    /// Every original value, including unknown or generically empty value sets.
128    #[must_use]
129    pub fn values(&self) -> &[Element<'a>] {
130        &self.values
131    }
132
133    /// The original attribute source range.
134    #[must_use]
135    pub fn span(&self) -> Span {
136        self.element.span()
137    }
138
139    /// The original encoded attribute without normalization.
140    #[must_use]
141    pub fn as_bytes(&self) -> &'a [u8] {
142        self.element.as_bytes()
143    }
144
145    /// Opens one original value in the current account and inherited parser depth.
146    ///
147    /// The index selects this attribute's catalogue, so a foreign element cannot
148    /// be rebound to its provenance. No serialized bridge or replacement parser
149    /// is used. Missing indices are absence; resource refusal remains an error.
150    pub fn value_reader(
151        &self,
152        index: usize,
153        execution: &Memory,
154        field: &'static str,
155    ) -> Result<Option<Reader<'a>>> {
156        let Some(value) = self.selected_value(index, execution, field)? else {
157            return Ok(None);
158        };
159        debug_assert!(self.depth > 0);
160        self.parser
161            .read_nested_in(
162                value.as_bytes(),
163                value.span().start,
164                self.encoding,
165                self.depth - 1,
166                execution,
167            )
168            .map(Some)
169            .map_err(|error| Error::encoding(error, field))
170    }
171
172    /// Opens one original value with producing local and current aggregate work.
173    /// A foreign parser session refuses before lookup/allocation; it cannot
174    /// silently replace the current producing ceiling with this value's old one.
175    pub fn value_reader_in<'query>(
176        &self,
177        index: usize,
178        parent: &Reader<'query>,
179        field: &'static str,
180    ) -> Result<Option<Reader<'query>>>
181    where
182        'a: 'query,
183    {
184        if !core::ptr::eq(self.parser, parent.parser()) {
185            return Err(Error::pkix(structured_pkix::Error::new(
186                structured_pkix::ErrorCode::ForeignReader,
187                field,
188                self.element.span().start,
189            )));
190        }
191        let Some(value) = self.selected_value(index, &parent.execution(), field)? else {
192            return Ok(None);
193        };
194        debug_assert!(self.depth > 0);
195        self.parser
196            .read_nested_query_in(
197                parent,
198                value.as_bytes(),
199                value.span().start,
200                self.encoding,
201                self.depth - 1,
202            )
203            .map(Some)
204            .map_err(|error| Error::encoding(error, field))
205    }
206
207    fn selected_value<W: Work>(
208        &self,
209        index: usize,
210        execution: &W,
211        field: &'static str,
212    ) -> Result<Option<Element<'a>>> {
213        execution
214            .charge_work(1)
215            .map_err(|reason| Error::resource(reason, field, self.element.span().start))?;
216        Ok(self.values.get(index).copied())
217    }
218
219    /// Requires a singleton for attribute types whose specific schema demands it.
220    pub fn single(&self, field: &'static str) -> Result<&Element<'a>> {
221        match &*self.values {
222            [value] => Ok(value),
223            _ => Err(Error::schema(
224                ErrorCode::AttributeCardinality,
225                field,
226                self.element.span().start,
227            )),
228        }
229    }
230}
231
232/// An original attribute set with independently retained catalogue storage.
233#[derive(Debug)]
234pub struct Attributes<'a> {
235    items: OwnedVec<Attribute<'a>>,
236    element: Element<'a>,
237    placement: Placement,
238    parser: &'a Parser,
239}
240
241/// Inspection selection among original byte-identical attribute instances.
242///
243/// Repetition is a conformance violation for a registered singleton, not a
244/// successful strict binding. Signer inspection retains its located diagnostics
245/// independently; this borrowed selection does not allocate another report.
246#[derive(Debug)]
247#[must_use]
248pub struct SingletonReading<'s, 'a> {
249    attribute: Option<&'s Attribute<'a>>,
250    repeats: usize,
251}
252
253impl<'s, 'a> SingletonReading<'s, 'a> {
254    /// The original first instance, or absence after the complete paid query.
255    #[must_use]
256    pub fn attribute(&self) -> Option<&'s Attribute<'a>> {
257        self.attribute
258    }
259
260    /// Number of later byte-identical instances, excluding the first one.
261    ///
262    /// Both absence and one instance have zero repeats. This describes
263    /// instances, not the cardinality of values inside one Attribute.
264    #[must_use]
265    pub fn repeats(&self) -> usize {
266        self.repeats
267    }
268}
269
270impl<'a> Attributes<'a> {
271    /// Reads nonempty attributes in their trusted schema placement.
272    ///
273    /// RFC5652 §5.3 requires nonempty Signed/UnsignedAttributes. Signed sets
274    /// are reread under DER through the same parser, not repaired or re-encoded.
275    /// Generic attributes preserve unknown instances; per-type cardinality and
276    /// placement checks are separate (§§11.1–11.4, RFC6211 §2).
277    /// <https://www.rfc-editor.org/rfc/rfc5652#section-5.3>
278    pub fn read(parent: &Reader<'a>, element: &Element<'a>, placement: Placement) -> Result<Self> {
279        let reading = Self::read_inspection(parent, element, placement)?;
280        if let Some(fault) = reading.fault() {
281            return Err(fault);
282        }
283        Ok(reading)
284    }
285
286    /// Reads original attributes while retaining a uniquely bounded empty set.
287    ///
288    /// An empty outer set violates RFC5652 §5.3 and is exposed by [`Self::fault`].
289    /// This inspection recovery does not relax signed DER, member syntax or
290    /// binding requirements. It preserves presence rather than treating an
291    /// invalid written set as absent.
292    pub fn read_inspection(
293        parent: &Reader<'a>,
294        element: &Element<'a>,
295        placement: Placement,
296    ) -> Result<Self> {
297        if element.tag() != placement.tag() {
298            return Err(Error::schema(
299                ErrorCode::UnexpectedTag,
300                placement.field(),
301                element.span().start,
302            ));
303        }
304        if placement == Placement::Signed {
305            let mut strict = parent
306                .reread(element, Encoding::Der)
307                .map_err(|error| Error::encoding(error, placement.field()))?;
308            let original = strict
309                .expect(placement.tag())
310                .map_err(|error| Error::encoding(error, placement.field()))?;
311            Self::read_set(&strict, &original, placement)
312        } else {
313            Self::read_set(parent, element, placement)
314        }
315    }
316
317    fn read_set(parent: &Reader<'a>, element: &Element<'a>, placement: Placement) -> Result<Self> {
318        let field = placement.field();
319        let mut inside = parent
320            .enter(element)
321            .map_err(|error| Error::encoding(error, field))?;
322        let execution = parent.execution();
323        let mut items = execution.vector();
324        let mut previous = None;
325        while let Some(member) = inside
326            .next_element()
327            .map_err(|error| Error::encoding(error, field))?
328        {
329            if inside.encoding().is_der() {
330                if let Some(earlier) = previous
331                    && !structured_pkix::der::set_ordered(&inside, earlier, &member, field)
332                        .map_err(Error::pkix)?
333                {
334                    return Err(Error::schema(
335                        ErrorCode::AttributeOrder,
336                        field,
337                        member.span().start,
338                    ));
339                }
340                previous = Some(member.as_bytes());
341            }
342            let attribute = Attribute::read(&inside, &member, field)?;
343            items
344                .try_push(attribute)
345                .map_err(|reason| Error::resource(reason, field, member.span().start))?;
346        }
347        Ok(Self {
348            items: items.into_owned(),
349            element: *element,
350            placement,
351            parser: parent.parser(),
352        })
353    }
354
355    /// The located nonempty-bound violation, without allocating diagnostics.
356    ///
357    /// Original signed bytes remain available for inspection; this fault must
358    /// be refused before preparing a cryptographic operation.
359    #[must_use]
360    pub fn fault(&self) -> Option<Error> {
361        if self.items.is_empty() {
362            return Some(Error::schema(
363                ErrorCode::EmptyAttributes,
364                self.placement.field(),
365                self.element.span().start,
366            ));
367        }
368        None
369    }
370
371    /// The original attribute catalogue in encoded order.
372    #[must_use]
373    pub fn items(&self) -> &[Attribute<'a>] {
374        &self.items
375    }
376
377    /// The exact range and placement supplied by the schema.
378    #[must_use]
379    pub fn element(&self) -> &Element<'a> {
380        &self.element
381    }
382
383    /// Searches every instance with the current retained-query account.
384    ///
385    /// Lookup errors are yielded once, then iteration stops. Absence is returned
386    /// only after the whole admitted search, including its terminal step.
387    pub fn all<'s>(&'s self, kind: Oid<'s>, execution: &Memory) -> Matching<'s, 'a> {
388        Matching {
389            attributes: self,
390            kind,
391            execution: execution.clone(),
392            next: 0,
393            stopped: false,
394        }
395    }
396
397    /// Searches through the producing reader's local and aggregate ceilings.
398    pub fn all_in<'s>(
399        &'s self,
400        kind: Oid<'s>,
401        parent: &'s Reader<'_>,
402    ) -> Matching<'s, 'a, Execution<'s>> {
403        Matching {
404            attributes: self,
405            kind,
406            execution: parent.execution(),
407            next: 0,
408            stopped: false,
409        }
410    }
411
412    /// Requires at most one instance for a specifically singleton attribute type.
413    ///
414    /// Unknown generic types must use [`Self::all_in`] instead: RFC5652 §5.3
415    /// does not forbid every repeated attribute identifier.
416    pub fn unique_in<'s>(
417        &'s self,
418        kind: Oid<'s>,
419        parent: &'s Reader<'_>,
420    ) -> Result<Option<&'s Attribute<'a>>> {
421        let mut found = None;
422        for result in self.all_in(kind, parent) {
423            let attribute = result?;
424            if found.is_some() {
425                return Err(Error::schema(
426                    ErrorCode::DuplicateAttribute,
427                    self.placement.field(),
428                    attribute.span().start,
429                ));
430            }
431            found = Some(attribute);
432        }
433        Ok(found)
434    }
435
436    /// Selects one readable original singleton while reporting copied instances.
437    ///
438    /// Only byte-identical copies share one inspection reading. Conflicts
439    /// refuse rather than choosing a claim; original signed bytes are never
440    /// normalized. This does not validate the type's values or relax strict
441    /// [`Self::unique_in`] / binding. Located registered-type violations are
442    /// retained by [`crate::signer::SignerInfo::faults`].
443    ///
444    /// The producing session is checked before query work. Scanning and byte
445    /// comparisons use both its local and aggregate current ceilings. The
446    /// returned borrowing facts retain no query execution or scratch storage.
447    pub fn singleton_inspection_in<'s>(
448        &'s self,
449        kind: Oid<'_>,
450        parent: &Reader<'_>,
451    ) -> Result<SingletonReading<'s, 'a>> {
452        self.require_session(parent)?;
453        let mut first: Option<&Attribute<'a>> = None;
454        let mut repeats = 0;
455        let mut next = 0;
456        let mut stopped = false;
457        let execution = parent.execution();
458        while let Some(result) = self.next_matching(kind, &execution, &mut next, &mut stopped) {
459            let attribute = result?;
460            if let Some(original) = first {
461                if !same_encoding(
462                    parent,
463                    original.as_bytes(),
464                    attribute.as_bytes(),
465                    self.placement.field(),
466                    attribute.span().start,
467                )? {
468                    return Err(Error::schema(
469                        ErrorCode::DuplicateAttribute,
470                        self.placement.field(),
471                        attribute.span().start,
472                    ));
473                }
474                debug_assert!(repeats < self.items.len());
475                repeats += 1;
476            } else {
477                first = Some(attribute);
478            }
479        }
480        Ok(SingletonReading {
481            attribute: first,
482            repeats,
483        })
484    }
485
486    fn require_session(&self, parent: &Reader<'_>) -> Result<()> {
487        if !core::ptr::eq(self.parser, parent.parser()) {
488            return Err(Error::pkix(structured_pkix::Error::new(
489                structured_pkix::ErrorCode::ForeignReader,
490                self.placement.field(),
491                self.element.span().start,
492            )));
493        }
494        Ok(())
495    }
496
497    pub(crate) fn placement_faults_in(
498        &self,
499        parent: &Reader<'_>,
500        mut report: impl FnMut(Error) -> Result<()>,
501    ) -> Result<()> {
502        self.require_session(parent)?;
503        if self.items.is_empty() {
504            return Ok(());
505        }
506        // One placement kernel serves retained inspection and strict binding.
507        // RFC5652 §§11.1–11.4 and RFC6211 §2 constrain these schema roles.
508        // https://www.rfc-editor.org/rfc/rfc5652#section-11
509        // https://www.rfc-editor.org/rfc/rfc6211#section-2
510        if self.placement == Placement::Unsigned {
511            for kind in SIGNED_SINGLETONS {
512                for result in self.all_in(kind, parent) {
513                    let attribute = result?;
514                    report(Error::schema(
515                        ErrorCode::AttributePlacement,
516                        self.placement.field(),
517                        attribute.span().start,
518                    ))?;
519                }
520            }
521        } else {
522            // A signed counter is misplaced even if its child cannot be read.
523            // Unsigned repetition remains legal in the generic CMS schema.
524            for result in self.all_in(crate::oid::COUNTERSIGNATURE, parent) {
525                let attribute = result?;
526                report(Error::schema(
527                    ErrorCode::AttributePlacement,
528                    "signerInfo.signedAttrs.counterSignature",
529                    attribute.span().start,
530                ))?;
531            }
532        }
533        Ok(())
534    }
535
536    pub(crate) fn inspect_registered<W: Work>(
537        &self,
538        parent: &Reader<'_>,
539        faults: &mut OwnedVec<Error, W>,
540    ) -> Result<()> {
541        self.placement_faults_in(parent, |fault| retain(faults, fault))?;
542        if self.placement != Placement::Signed || self.items.is_empty() {
543            return Ok(());
544        }
545        // Registered CMS rules, not a blanket ban on generic repeated OIDs.
546        // RFC5652 §§11.1–11.3; RFC6211 §2. ESS retains its independent profile.
547        // https://www.rfc-editor.org/rfc/rfc5652#section-11
548        // https://www.rfc-editor.org/rfc/rfc6211#section-2
549        for kind in SIGNED_SINGLETONS {
550            let mut seen = false;
551            for result in self.all_in(kind, parent) {
552                let attribute = result?;
553                if !seen {
554                    seen = true;
555                    continue;
556                }
557                retain(
558                    faults,
559                    Error::schema(
560                        ErrorCode::DuplicateAttribute,
561                        self.placement.field(),
562                        attribute.span().start,
563                    ),
564                )?;
565            }
566        }
567        Ok(())
568    }
569
570    /// Original signed octets with only the implicit identifier replaced by SET.
571    ///
572    /// RFC5652 §5.4 requires the SET OF identifier, preserving original DER
573    /// length/content. Unsigned sets have no such signed range.
574    #[must_use]
575    pub fn signed_bytes(&self) -> Option<[&'a [u8]; 2]> {
576        if self.placement != Placement::Signed {
577            return None;
578        }
579        Some([&[0x31], &self.element.as_bytes()[1..]])
580    }
581}
582
583fn retain<W: Work>(faults: &mut OwnedVec<Error, W>, fault: Error) -> Result<()> {
584    let field = fault.field();
585    let at = fault.offset();
586    faults
587        .try_push(fault)
588        .map_err(|reason| Error::resource(reason, field, at))
589}
590
591pub(crate) fn same_encoding(
592    parent: &Reader<'_>,
593    left: &[u8],
594    right: &[u8],
595    field: &'static str,
596    at: u64,
597) -> Result<bool> {
598    let execution = parent.execution();
599    execution
600        .charge_work(1)
601        .map_err(|reason| Error::resource(reason, field, at))?;
602    if left.len() != right.len() {
603        return Ok(false);
604    }
605    execution
606        .charge_work(left.len() as u64)
607        .map_err(|reason| Error::resource(reason, field, at))?;
608    Ok(left == right)
609}
610
611/// Accounted matching instances, with one terminal resource refusal.
612pub struct Matching<'s, 'a, W: Work = Memory> {
613    attributes: &'s Attributes<'a>,
614    kind: Oid<'s>,
615    execution: W,
616    next: usize,
617    stopped: bool,
618}
619
620impl<'s, 'a, W: Work> Iterator for Matching<'s, 'a, W> {
621    type Item = Result<&'s Attribute<'a>>;
622
623    fn next(&mut self) -> Option<Self::Item> {
624        self.attributes.next_matching(
625            self.kind,
626            &self.execution,
627            &mut self.next,
628            &mut self.stopped,
629        )
630    }
631}
632
633impl<'a> Attributes<'a> {
634    pub(crate) fn next_matching<'s>(
635        &'s self,
636        kind: Oid<'_>,
637        execution: &impl Work,
638        next: &mut usize,
639        stopped: &mut bool,
640    ) -> Option<Result<&'s Attribute<'a>>> {
641        if *stopped {
642            return None;
643        }
644        let field = self.placement.field();
645        loop {
646            if let Err(reason) = execution.charge_work(1) {
647                *stopped = true;
648                let at = self
649                    .items
650                    .get(*next)
651                    .map_or(self.element.span().start, |attribute| {
652                        attribute.span().start
653                    });
654                return Some(Err(Error::resource(reason, field, at)));
655            }
656            let Some(attribute) = self.items.get(*next) else {
657                *stopped = true;
658                return None;
659            };
660            *next += 1;
661            if attribute.kind.as_bytes().len() != kind.as_bytes().len() {
662                continue;
663            }
664            if let Err(reason) = execution.charge_work(attribute.kind.as_bytes().len() as u64) {
665                *stopped = true;
666                return Some(Err(Error::resource(reason, field, attribute.span().start)));
667            }
668            if attribute.kind == kind {
669                return Some(Ok(attribute));
670            }
671        }
672    }
673}
674
675impl<W: Work> core::iter::FusedIterator for Matching<'_, '_, W> {}
676
677#[cfg(test)]
678mod tests {
679    use super::*;
680    use alloc::{sync::Arc, vec};
681
682    #[test]
683    fn placement_kernel_preserves_collecting_strict_and_callback_refusal() {
684        // Both consumers must see the same placement facts; a sink's own
685        // resource refusal stops immediately and retains its original cause.
686        let mut attribute = vec![0x30, 15, 6, 9];
687        attribute.extend(crate::oid::COUNTERSIGNATURE.as_bytes());
688        attribute.extend([0x31, 2, 5, 0]);
689        let mut bytes = vec![0xa0, 34];
690        bytes.extend(attribute.repeat(2));
691        let memory = Memory::new(Arc::new(structured_asn1::memory::MemoryAccount::new(
692            65536, 65536,
693        )));
694        let parser = Parser::ber(memory);
695        let mut parent = parser.read(&bytes, 700).unwrap();
696        let element = parent.next_element().unwrap().unwrap();
697        let attributes = Attributes::read(&parent, &element, Placement::Signed).unwrap();
698        let first = attributes.items()[0].span().start;
699        let mut count = 0;
700        attributes
701            .placement_faults_in(&parent, |fault| {
702                assert_eq!(fault.code(), ErrorCode::AttributePlacement);
703                assert_eq!(fault.offset(), attributes.items()[count].span().start);
704                count += 1;
705                Ok(())
706            })
707            .unwrap();
708        assert_eq!(count, 2);
709        let mut calls = 0;
710        let error = attributes
711            .placement_faults_in(&parent, |fault| {
712                calls += 1;
713                Err(fault)
714            })
715            .unwrap_err();
716        assert_eq!(calls, 1);
717        assert_eq!(error.code(), ErrorCode::AttributePlacement);
718        assert_eq!(error.offset(), first);
719        let error = attributes
720            .placement_faults_in(&parent, |_| {
721                Err(Error::resource(
722                    structured_asn1::memory::MemoryError::WorkExceeded,
723                    "report",
724                    first,
725                ))
726            })
727            .unwrap_err();
728        assert!(error.is_resource());
729        assert_eq!(error.field(), "report");
730        assert_eq!(error.offset(), first);
731    }
732}