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::{AlgorithmIdentifier, Name};
8
9/// The identity actually selected by the document, without certificate selection.
10#[derive(Debug)]
11pub enum SignerIdentifier<'a> {
12    /// The issuer's distinguished name and original certificate serial.
13    IssuerAndSerial {
14        /// Original issuer name, interpreted by the common PKIX name core.
15        issuer: Name<'a>,
16        /// Original minimal two's-complement serial; no certificate policy is inferred.
17        serial: Integer<'a>,
18    },
19    /// Logical identifier octets, preserving any BER segmentation.
20    SubjectKeyIdentifier(Octets<'a>),
21}
22
23impl<'a> SignerIdentifier<'a> {
24    fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
25        const FIELD: &str = "signerInfo.sid";
26        if element.tag() == Tag::context(false, 0) || element.tag() == Tag::context(true, 0) {
27            return Octets::read_identifier(parent, element, FIELD).map(Self::SubjectKeyIdentifier);
28        }
29        if element.tag() != Tag::constructed(universal::SEQUENCE) {
30            return Err(Error::schema(
31                ErrorCode::UnexpectedTag,
32                FIELD,
33                element.span().start,
34            ));
35        }
36        let mut inside = parent
37            .enter(element)
38            .map_err(|error| Error::encoding(error, FIELD))?;
39        let name = inside
40            .expect(Tag::constructed(universal::SEQUENCE))
41            .map_err(|error| Error::encoding(error, FIELD))?;
42        let issuer = structured_pkix::name::read(&inside, &name, FIELD).map_err(Error::pkix)?;
43        let serial = inside
44            .read_integer()
45            .map_err(|error| Error::encoding(error, FIELD))?;
46        if !inside.is_empty() {
47            return Err(Error::schema(
48                ErrorCode::TrailingData,
49                FIELD,
50                inside.offset(),
51            ));
52        }
53        Ok(Self::IssuerAndSerial { issuer, serial })
54    }
55
56    /// The version required by this CHOICE (RFC5652 §5.3).
57    #[must_use]
58    pub fn required_version(&self) -> u64 {
59        match self {
60            Self::IssuerAndSerial { .. } => 1,
61            Self::SubjectKeyIdentifier(_) => 3,
62        }
63    }
64}
65
66/// One parsed CMS signer, retaining original byte identity and diagnostic storage.
67#[derive(Debug)]
68pub struct SignerInfo<'a> {
69    version: u64,
70    sid: SignerIdentifier<'a>,
71    digest_algorithm: AlgorithmIdentifier<'a>,
72    signed_attributes: Option<Attributes<'a>>,
73    signature_algorithm: AlgorithmIdentifier<'a>,
74    signature: Octets<'a>,
75    unsigned_attributes: Option<Attributes<'a>>,
76    encoding: Encoding,
77    element: Element<'a>,
78    faults: OwnedVec<Error>,
79}
80
81impl<'a> SignerInfo<'a> {
82    pub(crate) fn require_session(&self, parent: &Reader<'_>, field: &'static str) -> Result<()> {
83        if !self.digest_algorithm.same_session(parent) {
84            return Err(Error::pkix(structured_pkix::Error::new(
85                structured_pkix::ErrorCode::ForeignReader,
86                field,
87                self.span().start,
88            )));
89        }
90        Ok(())
91    }
92    /// Reads a signer at the original depth with both producing work ceilings.
93    ///
94    /// A uniquely readable version mismatch is retained as a fault, never a
95    /// successful conformance claim. Signed attributes remain DER and their
96    /// original octets are never repaired (RFC5652 §§5.3–5.4).
97    /// <https://www.rfc-editor.org/rfc/rfc5652#section-5.3>
98    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
99        const FIELD: &str = "signerInfo";
100        if element.tag() != Tag::constructed(universal::SEQUENCE) {
101            return Err(Error::schema(
102                ErrorCode::UnexpectedTag,
103                FIELD,
104                element.span().start,
105            ));
106        }
107        let mut inside = parent
108            .enter(element)
109            .map_err(|error| Error::encoding(error, FIELD))?;
110        let execution = parent.execution();
111        let mut faults = execution.vector();
112        let version_element = inside
113            .expect(Tag::primitive(universal::INTEGER))
114            .map_err(|error| Error::encoding(error, "signerInfo.version"))?;
115        let version = inside
116            .integer(&version_element)
117            .map_err(|error| Error::encoding(error, "signerInfo.version"))?
118            .as_u64()
119            .ok_or_else(|| {
120                Error::schema(
121                    ErrorCode::ValueTooLarge,
122                    "signerInfo.version",
123                    version_element.span().start,
124                )
125            })?;
126        let named = required(&mut inside, "signerInfo.sid")?;
127        let sid = SignerIdentifier::read(&inside, &named)?;
128        if version != sid.required_version() {
129            faults
130                .try_push(Error::schema(
131                    ErrorCode::VersionMismatch,
132                    "signerInfo.version",
133                    version_element.span().start,
134                ))
135                .map_err(|reason| {
136                    Error::resource(reason, "signerInfo.version", version_element.span().start)
137                })?;
138        }
139        let digest = required(&mut inside, "signerInfo.digestAlgorithm")?;
140        let digest_algorithm =
141            structured_pkix::algorithm::read(&inside, &digest, "signerInfo.digestAlgorithm")
142                .map_err(Error::pkix)?;
143        let mut next = required(&mut inside, "signerInfo.signatureAlgorithm")?;
144        let signed_attributes = if next.tag() == Tag::context(true, 0) {
145            let attributes = Attributes::read(&inside, &next, Placement::Signed)?;
146            next = required(&mut inside, "signerInfo.signatureAlgorithm")?;
147            Some(attributes)
148        } else {
149            None
150        };
151        let signature_algorithm =
152            structured_pkix::algorithm::read(&inside, &next, "signerInfo.signatureAlgorithm")
153                .map_err(Error::pkix)?;
154        let signature_element = required(&mut inside, "signerInfo.signature")?;
155        let signature = Octets::read(&inside, &signature_element, "signerInfo.signature")?;
156        let unsigned_attributes = match inside
157            .next_element()
158            .map_err(|error| Error::encoding(error, "signerInfo.unsignedAttrs"))?
159        {
160            Some(attributes) => Some(Attributes::read(&inside, &attributes, Placement::Unsigned)?),
161            None => None,
162        };
163        if !inside.is_empty() {
164            return Err(Error::schema(
165                ErrorCode::TrailingData,
166                FIELD,
167                inside.offset(),
168            ));
169        }
170        Ok(Self {
171            version,
172            sid,
173            digest_algorithm,
174            signed_attributes,
175            signature_algorithm,
176            signature,
177            unsigned_attributes,
178            encoding: inside.encoding(),
179            element: *element,
180            faults: faults.into_owned(),
181        })
182    }
183
184    /// Declared numeric version, independently of any retained mismatch.
185    #[must_use]
186    pub fn version(&self) -> u64 {
187        self.version
188    }
189    /// The document's signer identity CHOICE.
190    #[must_use]
191    pub fn sid(&self) -> &SignerIdentifier<'a> {
192        &self.sid
193    }
194    /// Original digest algorithm and opaque parameters.
195    #[must_use]
196    pub fn digest_algorithm(&self) -> AlgorithmIdentifier<'a> {
197        self.digest_algorithm
198    }
199    /// Original signed attributes, when present.
200    #[must_use]
201    pub fn signed_attributes(&self) -> Option<&Attributes<'a>> {
202        self.signed_attributes.as_ref()
203    }
204    /// Original signature algorithm and opaque parameters.
205    #[must_use]
206    pub fn signature_algorithm(&self) -> AlgorithmIdentifier<'a> {
207        self.signature_algorithm
208    }
209    /// Logical signature octets without joining.
210    #[must_use]
211    pub fn signature(&self) -> &Octets<'a> {
212        &self.signature
213    }
214    /// Original attributes outside the signature's coverage.
215    #[must_use]
216    pub fn unsigned_attributes(&self) -> Option<&Attributes<'a>> {
217        self.unsigned_attributes.as_ref()
218    }
219
220    /// Reads direct unsigned countersignatures lazily through original provenance.
221    /// Malformed values remain independent answers; resource refusal stops once.
222    pub fn countersignatures_in<'s>(
223        &'s self,
224        parent: &'s Reader<'a>,
225    ) -> crate::counter::Countersignatures<'s, 'a> {
226        crate::counter::Countersignatures::new(self, parent)
227    }
228    /// Encoding contract of the envelope, not that of the signed attributes.
229    #[must_use]
230    pub fn encoding(&self) -> Encoding {
231        self.encoding
232    }
233    /// Original signer range.
234    #[must_use]
235    pub fn span(&self) -> Span {
236        self.element.span()
237    }
238    /// Entire original encoding, including non-DER envelope spelling.
239    #[must_use]
240    pub fn as_bytes(&self) -> &'a [u8] {
241        self.element.as_bytes()
242    }
243    /// Retained document faults; resource refusals never enter this list.
244    #[must_use]
245    pub fn faults(&self) -> &[Error] {
246        &self.faults
247    }
248
249    /// Parsed-field identity for duplicate recovery, not signature acceptance.
250    ///
251    /// BER outer headers and octet segmentation are ignored. Issuer names use
252    /// the common PKIX equality rules; serial bytes, opaque parameters and
253    /// attribute sets retain encoded identity. Two
254    /// present NULL parameters compare by their parsed NULL value, so its
255    /// root length spelling cannot create a different reading. Signed
256    /// attributes therefore remain byte-identical. RFC5754 §2 SHA-2 digest
257    /// absence/NULL defaults share the PKIX comparer; other algorithm defaults
258    /// and selected-provider acceptance remain separate operations.
259    pub fn same_value(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
260        if !self.same_fields(other, parent)? {
261            return Ok(false);
262        }
263        // Resource refusal belongs to this duplicate query, not a prior
264        // retained algorithm's location. Syntax/caller errors keep provenance.
265        let comparison_error = |source| duplicate_comparison_error(source, other.span().start);
266        // RFC5754 §2 requires SHA224/256/384/512 absent/NULL equivalence.
267        // Written identity is checked first so identical opaque malformed
268        // parameters remain inspectable, without claiming algorithm acceptance.
269        // https://www.rfc-editor.org/rfc/rfc5754#section-2
270        if !self
271            .digest_algorithm
272            .same_written_in(&other.digest_algorithm, parent)
273            .map_err(comparison_error)?
274            && !self
275                .digest_algorithm
276                .digest_matches_in(&other.digest_algorithm, parent)
277                .map_err(comparison_error)?
278        {
279            return Ok(false);
280        }
281        self.signature_algorithm
282            .same_written_in(&other.signature_algorithm, parent)
283            .map_err(comparison_error)
284    }
285
286    // Shared document-field identity for encoded and selected-provider paths.
287    // Provider identities replace only algorithm comparison; original signed
288    // octets and signature/SID value checks cannot be bypassed by either path.
289    pub(crate) fn same_fields(&self, other: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<bool> {
290        let field = "signedData.signerInfos";
291        let at = other.span().start;
292        self.require_session(parent, field)?;
293        other.require_session(parent, field)?;
294        parent
295            .execution()
296            .charge_work(1)
297            .map_err(|reason| Error::resource(reason, field, at))?;
298        if self.version != other.version {
299            return Ok(false);
300        }
301        let same_sid = match (&self.sid, &other.sid) {
302            (
303                SignerIdentifier::IssuerAndSerial { issuer, serial },
304                SignerIdentifier::IssuerAndSerial {
305                    issuer: right,
306                    serial: number,
307                },
308            ) => {
309                // RFC5280 §7.1 names denote the same issuer after the shared
310                // directory-string preparation; original signedAttrs stay exact.
311                // https://www.rfc-editor.org/rfc/rfc5280#section-7.1
312                issuer
313                    .matches_in(right, parent)
314                    .map_err(|source| duplicate_comparison_error(source, at))?
315                    && same_bytes(parent, serial.as_bytes(), number.as_bytes(), field, at)?
316            }
317            (
318                SignerIdentifier::SubjectKeyIdentifier(left),
319                SignerIdentifier::SubjectKeyIdentifier(right),
320            ) => left.same_value(right, parent, field, at)?,
321            _ => false,
322        };
323        if !same_sid {
324            return Ok(false);
325        }
326        for (left, right) in [
327            (&self.signed_attributes, &other.signed_attributes),
328            (&self.unsigned_attributes, &other.unsigned_attributes),
329        ] {
330            if !same_optional(
331                parent,
332                left.as_ref().map(|attrs| attrs.element().as_bytes()),
333                right.as_ref().map(|attrs| attrs.element().as_bytes()),
334                field,
335                at,
336            )? {
337                return Ok(false);
338            }
339        }
340        self.signature
341            .same_value(&other.signature, parent, field, at)
342    }
343}
344
345fn duplicate_comparison_error(source: structured_pkix::Error, at: u64) -> Error {
346    if source.is_resource() {
347        Error::pkix(structured_pkix::Error::new(
348            source.code(),
349            "signedData.signerInfos",
350            at,
351        ))
352    } else {
353        Error::pkix(source)
354    }
355}
356
357pub(crate) fn required<'a>(reader: &mut Reader<'a>, field: &'static str) -> Result<Element<'a>> {
358    reader
359        .next_element()
360        .map_err(|error| Error::encoding(error, field))?
361        .ok_or_else(|| Error::schema(ErrorCode::MissingField, field, reader.offset()))
362}
363
364fn same_bytes(
365    parent: &Reader<'_>,
366    left: &[u8],
367    right: &[u8],
368    field: &'static str,
369    at: u64,
370) -> Result<bool> {
371    let execution = parent.execution();
372    execution
373        .charge_work(1)
374        .map_err(|reason| Error::resource(reason, field, at))?;
375    if left.len() != right.len() {
376        return Ok(false);
377    }
378    execution
379        .charge_work(left.len() as u64)
380        .map_err(|reason| Error::resource(reason, field, at))?;
381    Ok(left == right)
382}
383
384fn same_optional(
385    parent: &Reader<'_>,
386    left: Option<&[u8]>,
387    right: Option<&[u8]>,
388    field: &'static str,
389    at: u64,
390) -> Result<bool> {
391    parent
392        .execution()
393        .charge_work(1)
394        .map_err(|reason| Error::resource(reason, field, at))?;
395    match (left, right) {
396        (None, None) => Ok(true),
397        (Some(left), Some(right)) => same_bytes(parent, left, right, field, at),
398        _ => Ok(false),
399    }
400}