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    /// Entire original encoding, including non-DER envelope spelling.
249    #[must_use]
250    pub fn as_bytes(&self) -> &'a [u8] {
251        self.element.as_bytes()
252    }
253    /// Retained document faults; resource refusals never enter this list.
254    #[must_use]
255    pub fn faults(&self) -> &[Error] {
256        &self.faults
257    }
258
259    /// Parsed-field identity for duplicate recovery, not signature acceptance.
260    ///
261    /// BER outer headers and octet segmentation are ignored. Issuer names use
262    /// the common PKIX equality rules; serial bytes, opaque parameters and
263    /// attribute sets retain encoded identity. Two
264    /// present NULL parameters compare by their parsed NULL value, so its
265    /// root length spelling cannot create a different reading. Signed
266    /// attributes therefore remain byte-identical. RFC5754 §2 SHA-2 digest
267    /// absence/NULL defaults share the PKIX comparer; other algorithm defaults
268    /// and selected-provider acceptance remain separate operations.
269    pub fn same_value(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
270        if !self.same_fields(other, parent)? {
271            return Ok(false);
272        }
273        // Resource refusal belongs to this duplicate query, not a prior
274        // retained algorithm's location. Syntax/caller errors keep provenance.
275        let comparison_error = |source| duplicate_comparison_error(source, other.span().start);
276        // RFC5754 §2 requires SHA224/256/384/512 absent/NULL equivalence.
277        // Written identity is checked first so identical opaque malformed
278        // parameters remain inspectable, without claiming algorithm acceptance.
279        // https://www.rfc-editor.org/rfc/rfc5754#section-2
280        if !self
281            .digest_algorithm
282            .same_written_in(&other.digest_algorithm, parent)
283            .map_err(comparison_error)?
284        {
285            let (Some(left), Some(right)) = (
286                self.digest_algorithm.identifier(),
287                other.digest_algorithm.identifier(),
288            ) else {
289                // Malformed parameters have only their original encoded
290                // identity. They cannot acquire valid algorithm defaults.
291                return Ok(false);
292            };
293            if !left
294                .digest_matches_in(&right, parent)
295                .map_err(comparison_error)?
296            {
297                return Ok(false);
298            }
299        }
300        self.signature_algorithm
301            .same_written_in(&other.signature_algorithm, parent)
302            .map_err(comparison_error)
303    }
304
305    // Shared document-field identity for encoded and selected-provider paths.
306    // Provider identities replace only algorithm comparison; original signed
307    // octets and signature/SID value checks cannot be bypassed by either path.
308    pub(crate) fn same_fields(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
309        let field = "signedData.signerInfos";
310        let at = other.span().start;
311        self.require_session(parent, field)?;
312        other.require_session(parent, field)?;
313        parent
314            .execution()
315            .charge_work(1)
316            .map_err(|reason| Error::resource(reason, field, at))?;
317        if self.version != other.version {
318            return Ok(false);
319        }
320        let same_sid = match (&self.sid, &other.sid) {
321            (
322                SignerIdentifier::IssuerAndSerial { issuer, serial },
323                SignerIdentifier::IssuerAndSerial {
324                    issuer: right,
325                    serial: number,
326                },
327            ) => {
328                // RFC5280 §7.1 names denote the same issuer after the shared
329                // directory-string preparation; original signedAttrs stay exact.
330                // https://www.rfc-editor.org/rfc/rfc5280#section-7.1
331                issuer
332                    .matches_in(right, parent)
333                    .map_err(|source| duplicate_comparison_error(source, at))?
334                    && same_bytes(parent, serial.as_bytes(), number.as_bytes(), field, at)?
335            }
336            (
337                SignerIdentifier::SubjectKeyIdentifier(left),
338                SignerIdentifier::SubjectKeyIdentifier(right),
339            ) => left.same_value(right, parent, field, at)?,
340            _ => false,
341        };
342        if !same_sid {
343            return Ok(false);
344        }
345        for (left, right) in [
346            (&self.signed_attributes, &other.signed_attributes),
347            (&self.unsigned_attributes, &other.unsigned_attributes),
348        ] {
349            if !same_optional(
350                parent,
351                left.as_ref().map(|attrs| attrs.element().as_bytes()),
352                right.as_ref().map(|attrs| attrs.element().as_bytes()),
353                field,
354                at,
355            )? {
356                return Ok(false);
357            }
358        }
359        self.signature
360            .same_value(&other.signature, parent, field, at)
361    }
362}
363
364fn read_attributes<'a, W: Work>(
365    parent: &Reader<'a>,
366    element: &Element<'a>,
367    placement: Placement,
368    faults: &mut OwnedVec<Error, W>,
369) -> Result<Attributes<'a>> {
370    let reading = Attributes::read_inspection(parent, element, placement)?;
371    if let Some(fault) = reading.fault() {
372        let field = fault.field();
373        let at = fault.offset();
374        faults
375            .try_push(fault)
376            .map_err(|reason| Error::resource(reason, field, at))?;
377    }
378    Ok(reading)
379}
380
381fn read_algorithm<'a, W: Work>(
382    parent: &Reader<'a>,
383    element: &Element<'a>,
384    field: &'static str,
385    faults: &mut OwnedVec<Error, W>,
386) -> Result<AlgorithmReading<'a>> {
387    // RFC5652 §5.3 carries an AlgorithmIdentifier in each slot. Retaining a
388    // unique malformed parameter is inspection recovery, not conforming BER
389    // or permission to resolve a cryptographic algorithm. PKIX owns that one
390    // structural/parameter kernel; resources and ambiguous syntax still refuse.
391    // https://www.rfc-editor.org/rfc/rfc5652#section-5.3
392    let reading =
393        structured_pkix::algorithm::read_inspection(parent, element, field).map_err(Error::pkix)?;
394    if let Some(fault) = reading.fault() {
395        faults
396            .try_push(Error::pkix(fault.clone()))
397            .map_err(|reason| Error::resource(reason, fault.field(), fault.offset()))?;
398    }
399    Ok(reading)
400}
401
402fn duplicate_comparison_error(source: structured_pkix::Error, at: u64) -> Error {
403    if source.is_resource() {
404        Error::pkix(structured_pkix::Error::new(
405            source.code(),
406            "signedData.signerInfos",
407            at,
408        ))
409    } else {
410        Error::pkix(source)
411    }
412}
413
414pub(crate) fn required<'a>(reader: &mut Reader<'a>, field: &'static str) -> Result<Element<'a>> {
415    reader
416        .next_element()
417        .map_err(|error| Error::encoding(error, field))?
418        .ok_or_else(|| Error::schema(ErrorCode::MissingField, field, reader.offset()))
419}
420
421fn same_bytes(
422    parent: &Reader<'_>,
423    left: &[u8],
424    right: &[u8],
425    field: &'static str,
426    at: u64,
427) -> Result<bool> {
428    let execution = parent.execution();
429    execution
430        .charge_work(1)
431        .map_err(|reason| Error::resource(reason, field, at))?;
432    if left.len() != right.len() {
433        return Ok(false);
434    }
435    execution
436        .charge_work(left.len() as u64)
437        .map_err(|reason| Error::resource(reason, field, at))?;
438    Ok(left == right)
439}
440
441fn same_optional(
442    parent: &Reader<'_>,
443    left: Option<&[u8]>,
444    right: Option<&[u8]>,
445    field: &'static str,
446    at: u64,
447) -> Result<bool> {
448    parent
449        .execution()
450        .charge_work(1)
451        .map_err(|reason| Error::resource(reason, field, at))?;
452    match (left, right) {
453        (None, None) => Ok(true),
454        (Some(left), Some(right)) => same_bytes(parent, left, right, field, at),
455        _ => Ok(false),
456    }
457}