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