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