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, 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        const FIELD: &str = "signerInfo.sid";
27        if element.tag() == Tag::context(false, 0) || element.tag() == Tag::context(true, 0) {
28            return Octets::read_identifier(parent, element, FIELD).map(Self::SubjectKeyIdentifier);
29        }
30        if element.tag() != Tag::constructed(universal::SEQUENCE) {
31            return Err(Error::schema(
32                ErrorCode::UnexpectedTag,
33                FIELD,
34                element.span().start,
35            ));
36        }
37        let mut inside = parent
38            .enter(element)
39            .map_err(|error| Error::encoding(error, FIELD))?;
40        let name = inside
41            .expect(Tag::constructed(universal::SEQUENCE))
42            .map_err(|error| Error::encoding(error, FIELD))?;
43        let issuer = structured_pkix::name::read(&inside, &name, FIELD).map_err(Error::pkix)?;
44        let serial = inside
45            .read_integer()
46            .map_err(|error| Error::encoding(error, FIELD))?;
47        if !inside.is_empty() {
48            return Err(Error::schema(
49                ErrorCode::TrailingData,
50                FIELD,
51                inside.offset(),
52            ));
53        }
54        Ok(Self::IssuerAndSerial { issuer, serial })
55    }
56
57    /// The version required by this CHOICE (RFC5652 §5.3).
58    #[must_use]
59    pub fn required_version(&self) -> u64 {
60        match self {
61            Self::IssuerAndSerial { .. } => 1,
62            Self::SubjectKeyIdentifier(_) => 3,
63        }
64    }
65}
66
67/// One parsed CMS signer, retaining original byte identity and diagnostic storage.
68#[derive(Debug)]
69pub struct SignerInfo<'a> {
70    version: u64,
71    sid: SignerIdentifier<'a>,
72    digest_algorithm: AlgorithmReading<'a>,
73    signed_attributes: Option<Attributes<'a>>,
74    signature_algorithm: AlgorithmReading<'a>,
75    signature: Octets<'a>,
76    signed_fields: &'a [u8],
77    unsigned_attributes: Option<Attributes<'a>>,
78    encoding: Encoding,
79    element: Element<'a>,
80    faults: OwnedVec<Error>,
81}
82
83impl<'a> SignerInfo<'a> {
84    pub(crate) fn require_session(&self, parent: &Reader<'_>, field: &'static str) -> Result<()> {
85        if !self.digest_algorithm.same_session(parent) {
86            return Err(Error::pkix(structured_pkix::Error::new(
87                structured_pkix::ErrorCode::ForeignReader,
88                field,
89                self.span().start,
90            )));
91        }
92        Ok(())
93    }
94    /// Reads a signer at the original depth with both producing work ceilings.
95    ///
96    /// A uniquely readable version mismatch is retained as a fault, never a
97    /// successful conformance claim. Signed attributes remain DER and their
98    /// original octets are never repaired (RFC5652 §§5.3–5.4).
99    /// Each algorithm uses the PKIX inspection reader: a uniquely bounded
100    /// malformed parameter stays visible with its located diagnostic, while
101    /// missing/ambiguous fields and resource/processing refusals remain errors.
102    /// Provider preparation refuses either malformed algorithm before callbacks.
103    /// <https://www.rfc-editor.org/rfc/rfc5652#section-5.3>
104    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
105        const FIELD: &str = "signerInfo";
106        if element.tag() != Tag::constructed(universal::SEQUENCE) {
107            return Err(Error::schema(
108                ErrorCode::UnexpectedTag,
109                FIELD,
110                element.span().start,
111            ));
112        }
113        let mut inside = parent
114            .enter(element)
115            .map_err(|error| Error::encoding(error, FIELD))?;
116        let execution = parent.execution();
117        let mut faults = execution.vector();
118        let version_element = inside
119            .expect(Tag::primitive(universal::INTEGER))
120            .map_err(|error| Error::encoding(error, "signerInfo.version"))?;
121        let version = inside
122            .integer(&version_element)
123            .map_err(|error| Error::encoding(error, "signerInfo.version"))?
124            .as_u64()
125            .ok_or_else(|| {
126                Error::schema(
127                    ErrorCode::ValueTooLarge,
128                    "signerInfo.version",
129                    version_element.span().start,
130                )
131            })?;
132        let named = required(&mut inside, "signerInfo.sid")?;
133        let sid = SignerIdentifier::read(&inside, &named)?;
134        if version != sid.required_version() {
135            faults
136                .try_push(Error::schema(
137                    ErrorCode::VersionMismatch,
138                    "signerInfo.version",
139                    version_element.span().start,
140                ))
141                .map_err(|reason| {
142                    Error::resource(reason, "signerInfo.version", version_element.span().start)
143                })?;
144        }
145        let digest = required(&mut inside, "signerInfo.digestAlgorithm")?;
146        let digest_algorithm =
147            read_algorithm(&inside, &digest, "signerInfo.digestAlgorithm", &mut faults)?;
148        let mut next = required(&mut inside, "signerInfo.signatureAlgorithm")?;
149        let signed_attributes = if next.tag() == Tag::context(true, 0) {
150            let attributes = read_attributes(&inside, &next, Placement::Signed, &mut faults)?;
151            next = required(&mut inside, "signerInfo.signatureAlgorithm")?;
152            Some(attributes)
153        } else {
154            None
155        };
156        let signature_algorithm =
157            read_algorithm(&inside, &next, "signerInfo.signatureAlgorithm", &mut faults)?;
158        let signature_element = required(&mut inside, "signerInfo.signature")?;
159        let signature = Octets::read(&inside, &signature_element, "signerInfo.signature")?;
160        let signed_fields = field_range(element, &version_element, &signature_element);
161        let unsigned_attributes = match inside
162            .next_element()
163            .map_err(|error| Error::encoding(error, "signerInfo.unsignedAttrs"))?
164        {
165            Some(attributes) => Some(read_attributes(
166                &inside,
167                &attributes,
168                Placement::Unsigned,
169                &mut faults,
170            )?),
171            None => None,
172        };
173        if !inside.is_empty() {
174            return Err(Error::schema(
175                ErrorCode::TrailingData,
176                FIELD,
177                inside.offset(),
178            ));
179        }
180        Ok(Self {
181            version,
182            sid,
183            digest_algorithm,
184            signed_attributes,
185            signature_algorithm,
186            signature,
187            signed_fields,
188            unsigned_attributes,
189            encoding: inside.encoding(),
190            element: *element,
191            faults: faults.into_owned(),
192        })
193    }
194
195    /// Declared numeric version, independently of any retained mismatch.
196    #[must_use]
197    pub fn version(&self) -> u64 {
198        self.version
199    }
200    /// The document's signer identity CHOICE.
201    #[must_use]
202    pub fn sid(&self) -> &SignerIdentifier<'a> {
203        &self.sid
204    }
205    /// Original digest inspection, including any parameter syntax fault.
206    /// Only the reading's `identifier()` exposes a validated provider input.
207    #[must_use]
208    pub fn digest_algorithm(&self) -> &AlgorithmReading<'a> {
209        &self.digest_algorithm
210    }
211    /// Original signed attributes, when present.
212    #[must_use]
213    pub fn signed_attributes(&self) -> Option<&Attributes<'a>> {
214        self.signed_attributes.as_ref()
215    }
216    /// Original signature inspection, including any parameter syntax fault.
217    /// A readable malformed algorithm is never a validated provider input.
218    #[must_use]
219    pub fn signature_algorithm(&self) -> &AlgorithmReading<'a> {
220        &self.signature_algorithm
221    }
222    /// Logical signature octets without joining.
223    #[must_use]
224    pub fn signature(&self) -> &Octets<'a> {
225        &self.signature
226    }
227    /// Original attributes outside the signature's coverage.
228    #[must_use]
229    pub fn unsigned_attributes(&self) -> Option<&Attributes<'a>> {
230        self.unsigned_attributes.as_ref()
231    }
232
233    /// The original encodings of `version` through `signature`, in their order
234    /// of appearance and without the unsigned attributes that follow them.
235    #[must_use]
236    pub fn signed_fields(&self) -> &'a [u8] {
237        self.signed_fields
238    }
239
240    /// Reads direct unsigned countersignatures lazily through original provenance.
241    /// Malformed values remain independent answers; resource refusal stops once.
242    pub fn countersignatures_in<'s>(
243        &'s self,
244        parent: &'s Reader<'a>,
245    ) -> crate::counter::Countersignatures<'s, 'a> {
246        crate::counter::Countersignatures::new(self, parent)
247    }
248    /// Encoding contract of the envelope, not that of the signed attributes.
249    #[must_use]
250    pub fn encoding(&self) -> Encoding {
251        self.encoding
252    }
253    /// Original signer range.
254    #[must_use]
255    pub fn span(&self) -> Span {
256        self.element.span()
257    }
258    /// Original whole element, including its index in the source container.
259    ///
260    /// This is not a global signer ordinal: nested counter values retain
261    /// their own original container positions. Ranges use the producing
262    /// reader's coordinates; joined-source projection remains with its source map.
263    #[must_use]
264    pub fn element(&self) -> &Element<'a> {
265        &self.element
266    }
267    /// Entire original encoding, including non-DER envelope spelling.
268    #[must_use]
269    pub fn as_bytes(&self) -> &'a [u8] {
270        self.element.as_bytes()
271    }
272    /// Retained document faults; resource refusals never enter this list.
273    ///
274    /// Registered CMS instance/placement violations are retained independently
275    /// of certificate selection. Unknown generic attribute repetition is not
276    /// a blanket fault; value syntax and context-specific binding stay separate.
277    #[must_use]
278    pub fn faults(&self) -> &[Error] {
279        &self.faults
280    }
281
282    /// Parsed-field identity for duplicate recovery, not signature acceptance.
283    ///
284    /// BER outer headers and octet segmentation are ignored. Issuer names use
285    /// the common PKIX equality rules; serial bytes, opaque parameters and
286    /// attribute sets retain encoded identity. Two
287    /// present NULL parameters compare by their parsed NULL value, so its
288    /// root length spelling cannot create a different reading. Signed
289    /// attributes therefore remain byte-identical. RFC5754 §2 SHA-2 digest
290    /// absence/NULL defaults share the PKIX comparer; other algorithm defaults
291    /// and selected-provider acceptance remain separate operations.
292    pub fn same_value(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
293        if !self.same_fields(other, parent)? {
294            return Ok(false);
295        }
296        // Resource refusal belongs to this duplicate query, not a prior
297        // retained algorithm's location. Syntax/caller errors keep provenance.
298        let comparison_error = |source| duplicate_comparison_error(source, other.span().start);
299        // RFC5754 §2 requires SHA224/256/384/512 absent/NULL equivalence.
300        // Written identity is checked first so identical opaque malformed
301        // parameters remain inspectable, without claiming algorithm acceptance.
302        // https://www.rfc-editor.org/rfc/rfc5754#section-2
303        if !self
304            .digest_algorithm
305            .same_written_in(&other.digest_algorithm, parent)
306            .map_err(comparison_error)?
307        {
308            let (Some(left), Some(right)) = (
309                self.digest_algorithm.identifier(),
310                other.digest_algorithm.identifier(),
311            ) else {
312                // Malformed parameters have only their original encoded
313                // identity. They cannot acquire valid algorithm defaults.
314                return Ok(false);
315            };
316            if !left
317                .digest_matches_in(&right, parent)
318                .map_err(comparison_error)?
319            {
320                return Ok(false);
321            }
322        }
323        self.signature_algorithm
324            .same_written_in(&other.signature_algorithm, parent)
325            .map_err(comparison_error)
326    }
327
328    // Shared document-field identity for encoded and selected-provider paths.
329    // Provider identities replace only algorithm comparison; original signed
330    // octets and signature/SID value checks cannot be bypassed by either path.
331    pub(crate) fn same_fields(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
332        let field = "signedData.signerInfos";
333        let at = other.span().start;
334        self.require_session(parent, field)?;
335        other.require_session(parent, field)?;
336        parent
337            .execution()
338            .charge_work(1)
339            .map_err(|reason| Error::resource(reason, field, at))?;
340        if self.version != other.version {
341            return Ok(false);
342        }
343        let same_sid = match (&self.sid, &other.sid) {
344            (
345                SignerIdentifier::IssuerAndSerial { issuer, serial },
346                SignerIdentifier::IssuerAndSerial {
347                    issuer: right,
348                    serial: number,
349                },
350            ) => {
351                // RFC5280 §7.1 names denote the same issuer after the shared
352                // directory-string preparation; original signedAttrs stay exact.
353                // https://www.rfc-editor.org/rfc/rfc5280#section-7.1
354                issuer
355                    .matches_in(right, parent)
356                    .map_err(|source| duplicate_comparison_error(source, at))?
357                    && same_bytes(parent, serial.as_bytes(), number.as_bytes(), field, at)?
358            }
359            (
360                SignerIdentifier::SubjectKeyIdentifier(left),
361                SignerIdentifier::SubjectKeyIdentifier(right),
362            ) => left.same_value(right, parent, field, at)?,
363            _ => false,
364        };
365        if !same_sid {
366            return Ok(false);
367        }
368        for (left, right) in [
369            (&self.signed_attributes, &other.signed_attributes),
370            (&self.unsigned_attributes, &other.unsigned_attributes),
371        ] {
372            if !same_optional(
373                parent,
374                left.as_ref().map(|attrs| attrs.element().as_bytes()),
375                right.as_ref().map(|attrs| attrs.element().as_bytes()),
376                field,
377                at,
378            )? {
379                return Ok(false);
380            }
381        }
382        self.signature
383            .same_value(&other.signature, parent, field, at)
384    }
385}
386
387fn read_attributes<'a, W: Work>(
388    parent: &Reader<'a>,
389    element: &Element<'a>,
390    placement: Placement,
391    faults: &mut OwnedVec<Error, W>,
392) -> Result<Attributes<'a>> {
393    let reading = Attributes::read_inspection(parent, element, placement)?;
394    if let Some(fault) = reading.fault() {
395        let field = fault.field();
396        let at = fault.offset();
397        faults
398            .try_push(fault)
399            .map_err(|reason| Error::resource(reason, field, at))?;
400    }
401    reading.inspect_registered(parent, faults)?;
402    Ok(reading)
403}
404
405fn read_algorithm<'a, W: Work>(
406    parent: &Reader<'a>,
407    element: &Element<'a>,
408    field: &'static str,
409    faults: &mut OwnedVec<Error, W>,
410) -> Result<AlgorithmReading<'a>> {
411    // RFC5652 §5.3 carries an AlgorithmIdentifier in each slot. Retaining a
412    // unique malformed parameter is inspection recovery, not conforming BER
413    // or permission to resolve a cryptographic algorithm. PKIX owns that one
414    // structural/parameter kernel; resources and ambiguous syntax still refuse.
415    // https://www.rfc-editor.org/rfc/rfc5652#section-5.3
416    let reading =
417        structured_pkix::algorithm::read_inspection(parent, element, field).map_err(Error::pkix)?;
418    if let Some(fault) = reading.fault() {
419        faults
420            .try_push(Error::pkix(fault.clone()))
421            .map_err(|reason| Error::resource(reason, fault.field(), fault.offset()))?;
422    }
423    Ok(reading)
424}
425
426fn duplicate_comparison_error(source: structured_pkix::Error, at: u64) -> Error {
427    if source.is_resource() {
428        Error::pkix(structured_pkix::Error::new(
429            source.code(),
430            "signedData.signerInfos",
431            at,
432        ))
433    } else {
434        Error::pkix(source)
435    }
436}
437
438// The fields are consecutive members of the signer, so they are one range of
439// its own encoding: no element starts before the first or ends after the last.
440fn field_range<'a>(signer: &Element<'a>, first: &Element<'a>, last: &Element<'a>) -> &'a [u8] {
441    let origin = signer.span().start;
442    let start = first.span().start - origin;
443    let end = last.span().start + last.span().length - origin;
444    let bytes = signer.as_bytes();
445    // Both members lie inside the signer, whose encoding holds every offset.
446    let start = usize::try_from(start).expect("member offset fits the signer encoding");
447    let end = usize::try_from(end).expect("member end fits the signer encoding");
448    &bytes[start..end]
449}
450
451pub(crate) fn required<'a>(reader: &mut Reader<'a>, field: &'static str) -> Result<Element<'a>> {
452    reader
453        .next_element()
454        .map_err(|error| Error::encoding(error, field))?
455        .ok_or_else(|| Error::schema(ErrorCode::MissingField, field, reader.offset()))
456}
457
458fn same_bytes(
459    parent: &Reader<'_>,
460    left: &[u8],
461    right: &[u8],
462    field: &'static str,
463    at: u64,
464) -> Result<bool> {
465    let execution = parent.execution();
466    execution
467        .charge_work(1)
468        .map_err(|reason| Error::resource(reason, field, at))?;
469    if left.len() != right.len() {
470        return Ok(false);
471    }
472    execution
473        .charge_work(left.len() as u64)
474        .map_err(|reason| Error::resource(reason, field, at))?;
475    Ok(left == right)
476}
477
478fn same_optional(
479    parent: &Reader<'_>,
480    left: Option<&[u8]>,
481    right: Option<&[u8]>,
482    field: &'static str,
483    at: u64,
484) -> Result<bool> {
485    parent
486        .execution()
487        .charge_work(1)
488        .map_err(|reason| Error::resource(reason, field, at))?;
489    match (left, right) {
490        (None, None) => Ok(true),
491        (Some(left), Some(right)) => same_bytes(parent, left, right, field, at),
492        _ => Ok(false),
493    }
494}