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