Skip to main content

structured_cms/
signer.rs

1//! Original signer identities, algorithms, attributes and signature ranges.
2
3use crate::attribute::{Attributes, Placement};
4use crate::{Error, ErrorCode, Octets, Result};
5use structured_asn1::memory::{OwnedVec, Work};
6use structured_asn1::{Element, Encoding, Integer, Reader, ScopedReader, Span, Tag, universal};
7use structured_pkix::Name;
8use structured_pkix::algorithm::AlgorithmReading;
9
10/// The identity actually selected by the document, without certificate selection.
11#[derive(Debug)]
12pub enum SignerIdentifier<'a> {
13    /// The issuer's distinguished name and original certificate serial.
14    IssuerAndSerial {
15        /// Original issuer name, interpreted by the common PKIX name core.
16        issuer: Name<'a>,
17        /// Original minimal two's-complement serial; no certificate policy is inferred.
18        serial: Integer<'a>,
19    },
20    /// Logical identifier octets, preserving any BER segmentation.
21    SubjectKeyIdentifier(Octets<'a>),
22}
23
24impl<'a> SignerIdentifier<'a> {
25    fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
26        Self::read_as(parent, element, "signerInfo.sid")
27    }
28
29    pub(crate) fn read_as(
30        parent: &Reader<'a>,
31        element: &Element<'a>,
32        field: &'static str,
33    ) -> Result<Self> {
34        if element.tag() == Tag::context(false, 0) || element.tag() == Tag::context(true, 0) {
35            return Octets::read_identifier(parent, element, field).map(Self::SubjectKeyIdentifier);
36        }
37        if element.tag() != Tag::constructed(universal::SEQUENCE) {
38            return Err(Error::schema(
39                ErrorCode::UnexpectedTag,
40                field,
41                element.span().start,
42            ));
43        }
44        let mut inside = parent
45            .enter(element)
46            .map_err(|error| Error::encoding(error, field))?;
47        let name = inside
48            .expect(Tag::constructed(universal::SEQUENCE))
49            .map_err(|error| Error::encoding(error, field))?;
50        let issuer = structured_pkix::name::read(&inside, &name, field).map_err(Error::pkix)?;
51        let serial = inside
52            .read_integer()
53            .map_err(|error| Error::encoding(error, field))?;
54        if !inside.is_empty() {
55            return Err(Error::schema(
56                ErrorCode::TrailingData,
57                field,
58                inside.offset(),
59            ));
60        }
61        Ok(Self::IssuerAndSerial { issuer, serial })
62    }
63
64    /// The version required by this CHOICE (RFC5652 §5.3).
65    #[must_use]
66    pub fn required_version(&self) -> u64 {
67        match self {
68            Self::IssuerAndSerial { .. } => 1,
69            Self::SubjectKeyIdentifier(_) => 3,
70        }
71    }
72}
73
74/// Original fields required by TS101733 §6.5.1's schema-selected DER recipe.
75/// These are the elements already read by the signer kernel, never a new
76/// positional parser or caller-created algorithm identifier.
77#[derive(Debug, Clone, Copy)]
78pub struct Fields<'d> {
79    pub(crate) parser: &'d structured_asn1::Parser,
80    pub(crate) depth: u32,
81    /// Original version, signer identifier and digest algorithm, in that order.
82    pub identity: [Element<'d>; 3],
83    /// Original signature algorithm and signature OCTET STRING.
84    pub signature: [Element<'d>; 2],
85}
86
87/// One parsed CMS signer, retaining original byte identity and diagnostic storage.
88#[derive(Debug)]
89pub struct SignerInfo<'a> {
90    parser: &'a structured_asn1::Parser,
91    depth: u32,
92    fields: Fields<'a>,
93    version: u64,
94    sid: SignerIdentifier<'a>,
95    digest_algorithm: AlgorithmReading<'a>,
96    signed_attributes: Option<Attributes<'a>>,
97    signature_algorithm: AlgorithmReading<'a>,
98    signature: Octets<'a>,
99    signed_fields: &'a [u8],
100    unsigned_attributes: Option<Attributes<'a>>,
101    encoding: Encoding,
102    element: Element<'a>,
103    faults: OwnedVec<Error>,
104}
105
106impl<'a> SignerInfo<'a> {
107    /// Already parsed original component elements, with no traversal/copy.
108    #[must_use]
109    pub fn fields(&self) -> Fields<'a> {
110        self.fields
111    }
112    /// Requires the original producing parser, before a protocol layer reads
113    /// retained fields. Equal accounts or equal encodings are not session identity.
114    pub fn require_session(&self, parent: &Reader<'_>, field: &'static str) -> Result<()> {
115        if !self.digest_algorithm.same_session(parent) {
116            return Err(Error::pkix(structured_pkix::Error::new(
117                structured_pkix::ErrorCode::ForeignReader,
118                field,
119                self.span().start,
120            )));
121        }
122        Ok(())
123    }
124    /// Reads a signer at the original depth with both producing work ceilings.
125    ///
126    /// A uniquely readable version mismatch is retained as a fault, never a
127    /// successful conformance claim. Signed attributes remain DER and their
128    /// original octets are never repaired (RFC5652 §§5.3–5.4).
129    /// Each algorithm uses the PKIX inspection reader: a uniquely bounded
130    /// malformed parameter stays visible with its located diagnostic, while
131    /// missing/ambiguous fields and resource/processing refusals remain errors.
132    /// Provider preparation refuses either malformed algorithm before callbacks.
133    /// <https://www.rfc-editor.org/rfc/rfc5652#section-5.3>
134    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
135        const FIELD: &str = "signerInfo";
136        if element.tag() != Tag::constructed(universal::SEQUENCE) {
137            return Err(Error::schema(
138                ErrorCode::UnexpectedTag,
139                FIELD,
140                element.span().start,
141            ));
142        }
143        let mut inside = parent
144            .enter(element)
145            .map_err(|error| Error::encoding(error, FIELD))?;
146        let execution = parent.execution();
147        let mut faults = execution.vector();
148        let version_element = inside
149            .expect(Tag::primitive(universal::INTEGER))
150            .map_err(|error| Error::encoding(error, "signerInfo.version"))?;
151        let version = inside
152            .integer(&version_element)
153            .map_err(|error| Error::encoding(error, "signerInfo.version"))?
154            .as_u64()
155            .ok_or_else(|| {
156                Error::schema(
157                    ErrorCode::ValueTooLarge,
158                    "signerInfo.version",
159                    version_element.span().start,
160                )
161            })?;
162        let named = required(&mut inside, "signerInfo.sid")?;
163        let sid = SignerIdentifier::read(&inside, &named)?;
164        if version != sid.required_version() {
165            faults
166                .try_push(Error::schema(
167                    ErrorCode::VersionMismatch,
168                    "signerInfo.version",
169                    version_element.span().start,
170                ))
171                .map_err(|reason| {
172                    Error::resource(reason, "signerInfo.version", version_element.span().start)
173                })?;
174        }
175        let digest = required(&mut inside, "signerInfo.digestAlgorithm")?;
176        let digest_algorithm =
177            read_algorithm(&inside, &digest, "signerInfo.digestAlgorithm", &mut faults)?;
178        let mut next = required(&mut inside, "signerInfo.signatureAlgorithm")?;
179        let signed_attributes = if next.tag() == Tag::context(true, 0) {
180            let attributes = read_attributes(&inside, &next, Placement::Signed, &mut faults)?;
181            next = required(&mut inside, "signerInfo.signatureAlgorithm")?;
182            Some(attributes)
183        } else {
184            None
185        };
186        let signature_algorithm_element = next;
187        let signature_algorithm =
188            read_algorithm(&inside, &next, "signerInfo.signatureAlgorithm", &mut faults)?;
189        let signature_element = required(&mut inside, "signerInfo.signature")?;
190        let signature = Octets::read(&inside, &signature_element, "signerInfo.signature")?;
191        let signed_fields = field_range(element, &version_element, &signature_element);
192        let unsigned_attributes = match inside
193            .next_element()
194            .map_err(|error| Error::encoding(error, "signerInfo.unsignedAttrs"))?
195        {
196            Some(attributes) => Some(read_attributes(
197                &inside,
198                &attributes,
199                Placement::Unsigned,
200                &mut faults,
201            )?),
202            None => None,
203        };
204        if !inside.is_empty() {
205            return Err(Error::schema(
206                ErrorCode::TrailingData,
207                FIELD,
208                inside.offset(),
209            ));
210        }
211        Ok(Self {
212            parser: parent.parser(),
213            depth: inside.depth(),
214            fields: Fields {
215                parser: parent.parser(),
216                depth: inside.depth(),
217                identity: [version_element, named, digest],
218                signature: [signature_algorithm_element, signature_element],
219            },
220            version,
221            sid,
222            digest_algorithm,
223            signed_attributes,
224            signature_algorithm,
225            signature,
226            signed_fields,
227            unsigned_attributes,
228            encoding: inside.encoding(),
229            element: *element,
230            faults: faults.into_owned(),
231        })
232    }
233
234    /// Declared numeric version, independently of any retained mismatch.
235    #[must_use]
236    pub fn version(&self) -> u64 {
237        self.version
238    }
239    /// The document's signer identity CHOICE.
240    #[must_use]
241    pub fn sid(&self) -> &SignerIdentifier<'a> {
242        &self.sid
243    }
244    /// Original digest inspection, including any parameter syntax fault.
245    /// Only the reading's `identifier()` exposes a validated provider input.
246    #[must_use]
247    pub fn digest_algorithm(&self) -> &AlgorithmReading<'a> {
248        &self.digest_algorithm
249    }
250    /// Original signed attributes, when present.
251    #[must_use]
252    pub fn signed_attributes(&self) -> Option<&Attributes<'a>> {
253        self.signed_attributes.as_ref()
254    }
255    /// Original signature inspection, including any parameter syntax fault.
256    /// A readable malformed algorithm is never a validated provider input.
257    #[must_use]
258    pub fn signature_algorithm(&self) -> &AlgorithmReading<'a> {
259        &self.signature_algorithm
260    }
261    /// Logical signature octets without joining.
262    #[must_use]
263    pub fn signature(&self) -> &Octets<'a> {
264        &self.signature
265    }
266    /// Original attributes outside the signature's coverage.
267    #[must_use]
268    pub fn unsigned_attributes(&self) -> Option<&Attributes<'a>> {
269        self.unsigned_attributes.as_ref()
270    }
271
272    /// The original encodings of `version` through `signature`, in their order
273    /// of appearance and without the unsigned attributes that follow them.
274    #[must_use]
275    pub fn signed_fields(&self) -> &'a [u8] {
276        self.signed_fields
277    }
278
279    /// Reads direct unsigned countersignatures lazily through original provenance.
280    /// Malformed values remain independent answers; resource refusal stops once.
281    pub fn countersignatures_in<'s>(
282        &'s self,
283        parent: &'s Reader<'a>,
284    ) -> crate::counter::Countersignatures<'s, 'a> {
285        crate::counter::Countersignatures::new(self, parent)
286    }
287    /// Encoding contract of the envelope, not that of the signed attributes.
288    #[must_use]
289    pub fn encoding(&self) -> Encoding {
290        self.encoding
291    }
292    /// Original signer range.
293    #[must_use]
294    pub fn span(&self) -> Span {
295        self.element.span()
296    }
297    /// Original whole element, including its index in the source container.
298    ///
299    /// This is not a global signer ordinal: nested counter values retain
300    /// their own original container positions. Ranges use the producing
301    /// reader's coordinates; joined-source projection remains with its source map.
302    #[must_use]
303    pub fn element(&self) -> &Element<'a> {
304        &self.element
305    }
306    /// Retains the actual original field cursor context without retaining this
307    /// movable parsed signer. Counter/PoE inputs can later decode fields under
308    /// current Work without a new parser, guessed nesting depth or payload copy.
309    #[must_use]
310    pub fn fields_source(&self) -> FieldsSource<'a> {
311        FieldsSource {
312            parser: self.parser,
313            data: self.element.contents(),
314            base: self.element.contents_span().start,
315            encoding: self.encoding,
316            depth: self.depth,
317            field: "signerInfo",
318        }
319    }
320    /// Entire original encoding, including non-DER envelope spelling.
321    #[must_use]
322    pub fn as_bytes(&self) -> &'a [u8] {
323        self.element.as_bytes()
324    }
325    /// Retained document faults; resource refusals never enter this list.
326    ///
327    /// Registered CMS instance/placement violations are retained independently
328    /// of certificate selection. Unknown generic attribute repetition is not
329    /// a blanket fault; value syntax and context-specific binding stay separate.
330    #[must_use]
331    pub fn faults(&self) -> &[Error] {
332        &self.faults
333    }
334
335    /// Parsed-field identity for duplicate recovery, not signature acceptance.
336    ///
337    /// BER outer headers and octet segmentation are ignored. Issuer names use
338    /// the common PKIX equality rules; serial bytes, opaque parameters and
339    /// attribute sets retain encoded identity. Two
340    /// present NULL parameters compare by their parsed NULL value, so its
341    /// root length spelling cannot create a different reading. Signed
342    /// attributes therefore remain byte-identical. RFC5754 §2 SHA-2 digest
343    /// absence/NULL defaults share the PKIX comparer; other algorithm defaults
344    /// and selected-provider acceptance remain separate operations.
345    pub fn same_value(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
346        if !self.same_fields(other, parent)? {
347            return Ok(false);
348        }
349        // Resource refusal belongs to this duplicate query, not a prior
350        // retained algorithm's location. Syntax/caller errors keep provenance.
351        let comparison_error = |source| duplicate_comparison_error(source, other.span().start);
352        // RFC5754 §2 requires SHA224/256/384/512 absent/NULL equivalence.
353        // Written identity is checked first so identical opaque malformed
354        // parameters remain inspectable, without claiming algorithm acceptance.
355        // https://www.rfc-editor.org/rfc/rfc5754#section-2
356        if !self
357            .digest_algorithm
358            .same_written_in(&other.digest_algorithm, parent)
359            .map_err(comparison_error)?
360        {
361            let (Some(left), Some(right)) = (
362                self.digest_algorithm.identifier(),
363                other.digest_algorithm.identifier(),
364            ) else {
365                // Malformed parameters have only their original encoded
366                // identity. They cannot acquire valid algorithm defaults.
367                return Ok(false);
368            };
369            if !left
370                .digest_matches_in(&right, parent)
371                .map_err(comparison_error)?
372            {
373                return Ok(false);
374            }
375        }
376        self.signature_algorithm
377            .same_written_in(&other.signature_algorithm, parent)
378            .map_err(comparison_error)
379    }
380
381    // Shared document-field identity for encoded and selected-provider paths.
382    // Provider identities replace only algorithm comparison; original signed
383    // octets and signature/SID value checks cannot be bypassed by either path.
384    pub(crate) fn same_fields(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
385        let field = "signedData.signerInfos";
386        let at = other.span().start;
387        self.require_session(parent, field)?;
388        other.require_session(parent, field)?;
389        parent
390            .execution()
391            .charge_work(1)
392            .map_err(|reason| Error::resource(reason, field, at))?;
393        if self.version != other.version {
394            return Ok(false);
395        }
396        let same_sid = match (&self.sid, &other.sid) {
397            (
398                SignerIdentifier::IssuerAndSerial { issuer, serial },
399                SignerIdentifier::IssuerAndSerial {
400                    issuer: right,
401                    serial: number,
402                },
403            ) => {
404                // RFC5280 §7.1 names denote the same issuer after the shared
405                // directory-string preparation; original signedAttrs stay exact.
406                // https://www.rfc-editor.org/rfc/rfc5280#section-7.1
407                issuer
408                    .matches_in(right, parent)
409                    .map_err(|source| duplicate_comparison_error(source, at))?
410                    && same_bytes(parent, serial.as_bytes(), number.as_bytes(), field, at)?
411            }
412            (
413                SignerIdentifier::SubjectKeyIdentifier(left),
414                SignerIdentifier::SubjectKeyIdentifier(right),
415            ) => left.same_value(right, parent, field, at)?,
416            _ => false,
417        };
418        if !same_sid {
419            return Ok(false);
420        }
421        for (left, right) in [
422            (&self.signed_attributes, &other.signed_attributes),
423            (&self.unsigned_attributes, &other.unsigned_attributes),
424        ] {
425            if !same_optional(
426                parent,
427                left.as_ref().map(|attrs| attrs.element().as_bytes()),
428                right.as_ref().map(|attrs| attrs.element().as_bytes()),
429                field,
430                at,
431            )? {
432                return Ok(false);
433            }
434        }
435        self.signature
436            .same_value(&other.signature, parent, field, at)
437    }
438}
439
440/// Original schema-selected CONTENTS with their producing field depth.
441/// This small borrowed context survives a counter-walk frame's parsed model
442/// and is also shared by CMS certificate/revocation field readers.
443#[derive(Debug, Clone, Copy)]
444pub struct FieldsSource<'d> {
445    pub(crate) parser: &'d structured_asn1::Parser,
446    pub(crate) data: &'d [u8],
447    pub(crate) base: u64,
448    pub(crate) encoding: Encoding,
449    pub(crate) depth: u32,
450    pub(crate) field: &'static str,
451}
452impl<'d> FieldsSource<'d> {
453    // Called ONLY after the shared field kernel successfully entered this
454    // actual container. Its checked enter establishes the exact +1 depth and
455    // rules out overflow; this is not a fresh/default parser approximation.
456    pub(crate) fn entered(parent: &Reader<'d>, element: Element<'d>, field: &'static str) -> Self {
457        debug_assert!(element.tag().is_constructed());
458        Self {
459            parser: parent.parser(),
460            data: element.contents(),
461            base: element.contents_span().start,
462            encoding: parent.encoding(),
463            depth: parent.depth() + 1,
464            field,
465        }
466    }
467    /// Opens original fields, not a fabricated outer SEQUENCE. All current
468    /// operation and original session/depth ceilings apply before decoding.
469    pub fn reader_in<'operation, W: Work>(
470        &self,
471        work: &'operation W,
472    ) -> Result<structured_asn1::ScopedReader<'d, 'operation>>
473    where
474        'd: 'operation,
475    {
476        work.read_scoped_source(self.parser, self.data, self.base, self.encoding, self.depth)
477            .map_err(|cause| Error::encoding(cause, self.field))
478    }
479    /// Opens a producing-reader query for models retaining original source
480    /// readers. Unlike a callback-local Work borrow, the query's source budget
481    /// already has the document lifetime. Current operation funding and the
482    /// actual original depth/session remain enforced by the SAME bridge.
483    pub fn query_in<'query>(&self, operation: &Reader<'query>) -> Result<Reader<'query>>
484    where
485        'd: 'query,
486    {
487        self.parser
488            .read_query_in(operation, self.data, self.base, self.encoding, self.depth)
489            .map_err(|cause| Error::encoding(cause, self.field))
490    }
491    /// Original field octets, without a synthesized SEQUENCE header.
492    #[must_use]
493    pub fn as_bytes(self) -> &'d [u8] {
494        self.data
495    }
496    /// Actual original field depth, not an outer-reader approximation.
497    #[must_use]
498    pub fn depth(self) -> u32 {
499        self.depth
500    }
501}
502
503fn read_attributes<'a, W: Work>(
504    parent: &Reader<'a>,
505    element: &Element<'a>,
506    placement: Placement,
507    faults: &mut OwnedVec<Error, W>,
508) -> Result<Attributes<'a>> {
509    let reading = Attributes::read_inspection(parent, element, placement)?;
510    if let Some(fault) = reading.fault() {
511        let field = fault.field();
512        let at = fault.offset();
513        faults
514            .try_push(fault)
515            .map_err(|reason| Error::resource(reason, field, at))?;
516    }
517    reading.inspect_registered(parent, faults)?;
518    Ok(reading)
519}
520
521fn read_algorithm<'a, W: Work>(
522    parent: &Reader<'a>,
523    element: &Element<'a>,
524    field: &'static str,
525    faults: &mut OwnedVec<Error, W>,
526) -> Result<AlgorithmReading<'a>> {
527    // RFC5652 §5.3 carries an AlgorithmIdentifier in each slot. Retaining a
528    // unique malformed parameter is inspection recovery, not conforming BER
529    // or permission to resolve a cryptographic algorithm. PKIX owns that one
530    // structural/parameter kernel; resources and ambiguous syntax still refuse.
531    // https://www.rfc-editor.org/rfc/rfc5652#section-5.3
532    let reading =
533        structured_pkix::algorithm::read_inspection(parent, element, field).map_err(Error::pkix)?;
534    if let Some(fault) = reading.fault() {
535        faults
536            .try_push(Error::pkix(fault.clone()))
537            .map_err(|reason| Error::resource(reason, fault.field(), fault.offset()))?;
538    }
539    Ok(reading)
540}
541
542fn duplicate_comparison_error(source: structured_pkix::Error, at: u64) -> Error {
543    if source.is_resource() {
544        Error::pkix(structured_pkix::Error::new(
545            source.code(),
546            "signedData.signerInfos",
547            at,
548        ))
549    } else {
550        Error::pkix(source)
551    }
552}
553
554// The fields are consecutive members of the signer, so they are one range of
555// its own encoding: no element starts before the first or ends after the last.
556fn field_range<'a>(signer: &Element<'a>, first: &Element<'a>, last: &Element<'a>) -> &'a [u8] {
557    let origin = signer.span().start;
558    let start = first.span().start - origin;
559    let end = last.span().start + last.span().length - origin;
560    let bytes = signer.as_bytes();
561    // Both members lie inside the signer, whose encoding holds every offset.
562    let start = usize::try_from(start).expect("member offset fits the signer encoding");
563    let end = usize::try_from(end).expect("member end fits the signer encoding");
564    &bytes[start..end]
565}
566
567pub(crate) fn required<'a>(
568    reader: &mut ScopedReader<'a, '_>,
569    field: &'static str,
570) -> Result<Element<'a>> {
571    reader
572        .next_element()
573        .map_err(|error| Error::encoding(error, field))?
574        .ok_or_else(|| Error::schema(ErrorCode::MissingField, field, reader.offset()))
575}
576
577fn same_bytes(
578    parent: &Reader<'_>,
579    left: &[u8],
580    right: &[u8],
581    field: &'static str,
582    at: u64,
583) -> Result<bool> {
584    let execution = parent.execution();
585    execution
586        .charge_work(1)
587        .map_err(|reason| Error::resource(reason, field, at))?;
588    if left.len() != right.len() {
589        return Ok(false);
590    }
591    execution
592        .charge_work(left.len() as u64)
593        .map_err(|reason| Error::resource(reason, field, at))?;
594    Ok(left == right)
595}
596
597fn same_optional(
598    parent: &Reader<'_>,
599    left: Option<&[u8]>,
600    right: Option<&[u8]>,
601    field: &'static str,
602    at: u64,
603) -> Result<bool> {
604    parent
605        .execution()
606        .charge_work(1)
607        .map_err(|reason| Error::resource(reason, field, at))?;
608    match (left, right) {
609        (None, None) => Ok(true),
610        (Some(left), Some(right)) => same_bytes(parent, left, right, field, at),
611        _ => Ok(false),
612    }
613}