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