Skip to main content

structured_cms/
signed.rs

1//! `SignedData` catalogues and version facts, without application trust decisions.
2
3use crate::content::{ContentInfo, Encapsulated};
4use crate::signer::{SignerInfo, required};
5use crate::{Error, ErrorCode, Result, oid};
6use structured_asn1::memory::{OwnedVec, Work};
7use structured_asn1::{Element, Encoding, Oid, Parser, Reader, Span, Tag, universal};
8use structured_pkix::{AlgorithmIdentifier, Certificate};
9
10/// One original certificate CHOICE (RFC5652 §10.2.2).
11///
12/// X.509 values live in one flat accounted catalogue, avoiding one Box
13/// allocation per certificate and avoiding a large enum slot for opaque kinds.
14#[derive(Debug, Clone, Copy)]
15pub enum CertificateChoice<'a> {
16    /// Index into this message's [`SignedData::certificates`] catalogue.
17    X509(usize),
18    /// Obsolete PKCS6 extended certificate, preserved without interpreting it.
19    Extended(Element<'a>),
20    /// Obsolete version-1 attribute certificate, preserved as encoded.
21    AttributeV1(Element<'a>),
22    /// Version-2 attribute certificate, preserved as encoded.
23    AttributeV2(Element<'a>),
24    /// Other format with its original format identifier and value.
25    Other(Element<'a>),
26}
27
28/// Original CMS other-revocation format and its untouched ANY value. Decoding
29/// a recognized format establishes no signature, authorization or freshness.
30#[derive(Debug)]
31pub struct OtherRevocationInfo<'a> {
32    reader: Reader<'a>,
33    kind: Oid<'a>,
34    value: Element<'a>,
35}
36
37impl<'a> OtherRevocationInfo<'a> {
38    /// Reads an original context \[1\] IMPLICIT `OtherRevocationInfoFormat` using
39    /// the same field kernel as `SignedData`'s structural catalogue validation.
40    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
41        const FIELD: &str = "signedData.crls.other";
42        if element.tag() != Tag::context(true, 1) {
43            return Err(Error::schema(
44                ErrorCode::UnexpectedTag,
45                FIELD,
46                element.span().start,
47            ));
48        }
49        let (reader, kind, value) = other_fields(parent, element, FIELD)?;
50        Ok(Self {
51            reader,
52            kind,
53            value,
54        })
55    }
56
57    /// Original open-format identifier, without provider/default normalization.
58    #[must_use]
59    pub fn kind(&self) -> Oid<'a> {
60        self.kind
61    }
62
63    /// Whole original ANY element, not its contents or a re-encoded value.
64    #[must_use]
65    pub fn value(&self) -> Element<'a> {
66        self.value
67    }
68
69    /// Decodes the successful whole `OCSPResponse` required by RFC5940 §3.
70    /// An unknown format returns None; a known malformed response or unsigned
71    /// protocol failure returns an error. Unsupported inner response syntax is
72    /// preserved by the shared envelope, rather than promoted to a basic body.
73    /// <https://www.rfc-editor.org/rfc/rfc5940#section-3>
74    pub fn ocsp(self) -> Result<Option<structured_pkix::ocsp::ResponseBytes<'a>>> {
75        const FIELD: &str = "signedData.crls.other";
76        self.reader
77            .charge(9)
78            .map_err(|error| Error::encoding(error, FIELD))?;
79        if self.kind != oid::OCSP_RESPONSE {
80            return Ok(None);
81        }
82        match structured_pkix::ocsp::Response::read_element(&self.reader, &self.value)
83            .map_err(Error::pkix)?
84        {
85            structured_pkix::ocsp::Response::Successful(response) => Ok(Some(response)),
86            structured_pkix::ocsp::Response::Failure { span, .. } => {
87                Err(Error::schema(ErrorCode::MalformedValue, FIELD, span.start))
88            }
89        }
90    }
91}
92
93/// Original CMS `SignedData` with independently accounted metadata and faults.
94#[derive(Debug)]
95pub struct SignedData<'a> {
96    parser: &'a Parser,
97    encoding: Encoding,
98    outer: u32,
99    version: u64,
100    required_version: u64,
101    algorithms: OwnedVec<AlgorithmIdentifier<'a>>,
102    content: Encapsulated<'a>,
103    choices: OwnedVec<CertificateChoice<'a>>,
104    certificates: OwnedVec<Certificate<'a>>,
105    certificate_field: Option<Span>,
106    revocations: OwnedVec<Element<'a>>,
107    revocation_field: Option<Span>,
108    signers: OwnedVec<SignerInfo<'a>>,
109    faults: OwnedVec<Error>,
110    element: Element<'a>,
111}
112
113impl<'a> SignedData<'a> {
114    /// Reads one entire CMS `ContentInfo` and its `SignedData` using the same parser.
115    pub fn read(parser: &'a Parser, encoded: &'a [u8], base: u64) -> Result<Self> {
116        let mut root = parser
117            .read(encoded, base)
118            .map_err(|error| Error::encoding(error, "contentInfo"))?;
119        let (envelope, parent) = ContentInfo::read(&mut root)?;
120        parent
121            .execution()
122            .charge_work(envelope.kind().as_bytes().len() as u64)
123            .map_err(|reason| {
124                Error::resource(reason, "contentInfo.contentType", envelope.span().start)
125            })?;
126        if envelope.kind() != oid::SIGNED_DATA {
127            return Err(Error::schema(
128                ErrorCode::UnexpectedContentType,
129                "contentInfo.contentType",
130                envelope.span().start,
131            ));
132        }
133        Self::read_element(&parent, envelope.content())
134    }
135
136    /// Reads an original embedded `SignedData`, preserving enclosing depth/work.
137    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
138        const FIELD: &str = "signedData";
139        if element.tag() != Tag::constructed(universal::SEQUENCE) {
140            return Err(Error::schema(
141                ErrorCode::UnexpectedTag,
142                FIELD,
143                element.span().start,
144            ));
145        }
146        let mut inside = parent
147            .enter(element)
148            .map_err(|error| Error::encoding(error, FIELD))?;
149        let execution = parent.execution();
150        let (version, version_element) = read_version(&mut inside)?;
151        let set = required(&mut inside, "signedData.digestAlgorithms")?;
152        let algorithms = catalogue(
153            &inside,
154            &set,
155            Tag::constructed(universal::SET),
156            "signedData.digestAlgorithms",
157            |members, entry| {
158                structured_pkix::algorithm::read(members, entry, "signedData.digestAlgorithms")
159                    .map_err(Error::pkix)
160            },
161        )?;
162        let content_element = required(&mut inside, "signedData.encapContentInfo")?;
163        let content = Encapsulated::read(&inside, &content_element)?;
164        execution
165            .charge_work(content.kind().as_bytes().len() as u64)
166            .map_err(|reason| {
167                Error::resource(
168                    reason,
169                    "signedData.encapContentInfo.eContentType",
170                    content.span().start,
171                )
172            })?;
173        // RFC5652 §5.1 ladder, computed during the admitted schema traversal:
174        // 5 (other), 4 (v2 AC), 3 (v1 AC/v3 signer/non-data), otherwise 1.
175        // https://www.rfc-editor.org/rfc/rfc5652#section-5.1
176        let mut required_version = if content.kind() == oid::DATA { 1 } else { 3 };
177        let mut choices = parent.memory().vector();
178        let mut certificates = parent.memory().vector();
179        let mut revocations = parent.memory().vector();
180        let mut certificate_field = None;
181        let mut revocation_field = None;
182        let mut next = required(&mut inside, "signedData.signerInfos")?;
183        if next.tag() == Tag::context(true, 0) {
184            certificate_field = Some(next.span());
185            (choices, certificates) = read_certificates(
186                &inside,
187                &next,
188                &mut required_version,
189                "signedData.certificates",
190            )?;
191            next = required(&mut inside, "signedData.signerInfos")?;
192        }
193        if next.tag() == Tag::context(true, 1) {
194            revocation_field = Some(next.span());
195            revocations =
196                read_revocations(&inside, &next, &mut required_version, "signedData.crls")?;
197            next = required(&mut inside, "signedData.signerInfos")?;
198        }
199        let signers = catalogue(
200            &inside,
201            &next,
202            Tag::constructed(universal::SET),
203            "signedData.signerInfos",
204            |members, entry| {
205                let signer = SignerInfo::read(members, entry)?;
206                if signer.version() == 3 {
207                    required_version = required_version.max(3);
208                }
209                Ok(signer)
210            },
211        )?;
212        if !inside.is_empty() {
213            return Err(Error::schema(
214                ErrorCode::TrailingData,
215                FIELD,
216                inside.offset(),
217            ));
218        }
219        let faults = version_fault(&execution, version, required_version, &version_element)?;
220        Ok(Self {
221            parser: parent.parser(),
222            encoding: parent.encoding(),
223            outer: inside.depth(),
224            version,
225            required_version,
226            algorithms,
227            content,
228            choices,
229            certificates,
230            certificate_field,
231            revocations,
232            revocation_field,
233            signers,
234            faults,
235            element: *element,
236        })
237    }
238
239    /// Declared version.
240    #[must_use]
241    pub fn version(&self) -> u64 {
242        self.version
243    }
244    /// Version independently required by the encoded choices.
245    #[must_use]
246    pub fn required_version(&self) -> u64 {
247        self.required_version
248    }
249    /// Original advertised digest algorithms; provider resolution is separate.
250    /// RFC5652 §§5.1, 5.3 permit zero entries and do not require recipients to
251    /// refuse an unlisted signer digest. These advertisements never replace the
252    /// original signer algorithm; stricter catalogue acceptance is caller policy.
253    /// <https://www.rfc-editor.org/rfc/rfc5652#section-5.1>
254    #[must_use]
255    pub fn digest_algorithms(&self) -> &[AlgorithmIdentifier<'a>] {
256        &self.algorithms
257    }
258    /// Original attached or absent content.
259    #[must_use]
260    pub fn content(&self) -> &Encapsulated<'a> {
261        &self.content
262    }
263    /// Whole original `encapContentInfo` TLV, including BER framing/EOC.
264    /// No element is reopened, normalized or copied. Historical archive inputs
265    /// distinguish this from the logical eContent octets (RFC5126 §6.4.1).
266    #[must_use]
267    pub fn encap_content_encoding(&self) -> &'a [u8] {
268        self.field_encoding(self.content.span())
269    }
270    /// Certificate choices in their original order.
271    #[must_use]
272    pub fn certificate_choices(&self) -> &[CertificateChoice<'a>] {
273        &self.choices
274    }
275    /// Parsed X.509 certificates, in the order referenced by X509 choices.
276    #[must_use]
277    pub fn certificates(&self) -> &[Certificate<'a>] {
278        &self.certificates
279    }
280    /// Original presence of the certificate field, including an empty written set.
281    #[must_use]
282    pub fn certificate_field(&self) -> Option<Span> {
283        self.certificate_field
284    }
285    /// Whole original optional certificate field, including its context tag.
286    /// An absent field differs from a written empty set; bytes are borrowed.
287    #[must_use]
288    pub fn certificate_encoding(&self) -> Option<&'a [u8]> {
289        self.certificate_field.map(|span| self.field_encoding(span))
290    }
291    /// Opens an original non-X.509 certificate CHOICE under the current query.
292    ///
293    /// Preserves the original parser, encoding, location and certificate-set
294    /// depth. The current operation funds new work while original ceilings
295    /// remain active. An absent ordinal or X.509 index returns `None` after
296    /// metadata admission; parsed X.509 certificates have their own accessors.
297    /// No certificate is implicitly trusted or decoded by opening this reader.
298    pub fn certificate_choice_reader_in<'query>(
299        &self,
300        query: &Reader<'query>,
301        ordinal: usize,
302    ) -> Result<Option<Reader<'query>>>
303    where
304        'a: 'query,
305    {
306        const FIELD: &str = "signedData.certificates";
307        let at = self.certificate_field.unwrap_or(self.element.span()).start;
308        query
309            .execution()
310            .charge_work(1)
311            .map_err(|reason| Error::resource(reason, FIELD, at))?;
312        let entry = match self.choices.get(ordinal) {
313            Some(
314                CertificateChoice::Extended(entry)
315                | CertificateChoice::AttributeV1(entry)
316                | CertificateChoice::AttributeV2(entry)
317                | CertificateChoice::Other(entry),
318            ) => entry,
319            Some(CertificateChoice::X509(_)) | None => return Ok(None),
320        };
321        self.parser
322            .read_nested_query_in(
323                query,
324                entry.as_bytes(),
325                entry.span().start,
326                self.encoding,
327                self.outer,
328            )
329            .map(Some)
330            .map_err(|error| Error::encoding(error, FIELD))
331    }
332    /// Original presence of the revocation field, including an empty written set.
333    #[must_use]
334    pub fn revocation_field(&self) -> Option<Span> {
335        self.revocation_field
336    }
337    /// Whole original optional revocation field, not assembled member bytes.
338    #[must_use]
339    pub fn revocation_encoding(&self) -> Option<&'a [u8]> {
340        self.revocation_field.map(|span| self.field_encoding(span))
341    }
342    /// Original revocation choices, not implicitly authenticated or fetched.
343    #[must_use]
344    pub fn revocations(&self) -> &[Element<'a>] {
345        &self.revocations
346    }
347    /// Opens one original revocation CHOICE under the current query operation.
348    ///
349    /// The reader contains the whole original CRL or other-format CHOICE, not
350    /// its contents. Original parser, encoding, location and set-member depth
351    /// are preserved; the query may come from another document and pays all
352    /// new work/storage while both source and operation ceilings remain active.
353    /// No bytes are copied and no new parser or authentication is introduced.
354    /// An absent ordinal returns `None` only after paid metadata admission.
355    pub fn revocation_reader_in<'query>(
356        &self,
357        query: &Reader<'query>,
358        ordinal: usize,
359    ) -> Result<Option<Reader<'query>>>
360    where
361        'a: 'query,
362    {
363        const FIELD: &str = "signedData.crls";
364        let at = self.revocation_field.unwrap_or(self.element.span()).start;
365        query
366            .execution()
367            .charge_work(1)
368            .map_err(|reason| Error::resource(reason, FIELD, at))?;
369        let Some(entry) = self.revocations.get(ordinal) else {
370            return Ok(None);
371        };
372        self.parser
373            .read_nested_query_in(
374                query,
375                entry.as_bytes(),
376                entry.span().start,
377                self.encoding,
378                self.outer,
379            )
380            .map(Some)
381            .map_err(|error| Error::encoding(error, FIELD))
382    }
383    /// Independent signers in their original order, without an all-signers verdict.
384    #[must_use]
385    pub fn signers(&self) -> &[SignerInfo<'a>] {
386        &self.signers
387    }
388    /// Refuses a reader of another parser session.
389    pub fn require_session(&self, parent: &Reader<'_>, field: &'static str) -> Result<()> {
390        if !core::ptr::eq(self.parser, parent.parser()) {
391            return Err(Error::pkix(structured_pkix::Error::new(
392                structured_pkix::ErrorCode::ForeignReader,
393                field,
394                self.element.span().start,
395            )));
396        }
397        Ok(())
398    }
399    /// Refuses a `SignerInfo` that is not one of these signers, by identity.
400    ///
401    /// Constant work: a reference to one of these signers lies inside their
402    /// storage, so an address range test replaces a scan of every signer.
403    pub fn require_signer(&self, signer: &SignerInfo<'_>, field: &'static str) -> Result<()> {
404        let storage = self.signers.as_ptr_range();
405        let address = core::ptr::from_ref(signer).cast::<SignerInfo<'a>>();
406        if !storage.contains(&address) {
407            return Err(Error::schema(
408                ErrorCode::InvalidLocation,
409                field,
410                signer.span().start,
411            ));
412        }
413        Ok(())
414    }
415    /// Readable document faults, retaining their allocation lifetime.
416    #[must_use]
417    pub fn faults(&self) -> &[Error] {
418        &self.faults
419    }
420    /// Original `SignedData` source range.
421    #[must_use]
422    pub fn span(&self) -> Span {
423        self.element.span()
424    }
425    /// Original encoding without serialization.
426    #[must_use]
427    pub fn as_bytes(&self) -> &'a [u8] {
428        self.element.as_bytes()
429    }
430
431    fn field_encoding(&self, span: Span) -> &'a [u8] {
432        // Each saved field was produced inside this exact element. Its
433        // differences therefore fit the borrowed encoding's usize length.
434        let start = usize::try_from(span.start - self.element.span().start)
435            .expect("original field starts inside SignedData");
436        let length = usize::try_from(span.length).expect("original field length fits SignedData");
437        &self.element.as_bytes()[start..start + length]
438    }
439}
440
441fn version_fault(
442    execution: &impl Work,
443    version: u64,
444    required: u64,
445    element: &Element<'_>,
446) -> Result<OwnedVec<Error>> {
447    let mut faults = execution.vector();
448    if version != required {
449        faults
450            .try_push(Error::schema(
451                ErrorCode::VersionMismatch,
452                "signedData.version",
453                element.span().start,
454            ))
455            .map_err(|reason| {
456                Error::resource(reason, "signedData.version", element.span().start)
457            })?;
458    }
459    Ok(faults.into_owned())
460}
461
462fn ordered<'a>(
463    parent: &Reader<'a>,
464    previous: &mut Option<&'a [u8]>,
465    element: &Element<'a>,
466    field: &'static str,
467) -> Result<()> {
468    if parent.encoding().is_der() {
469        if let Some(earlier) = *previous
470            && !structured_pkix::der::set_ordered(parent, earlier, element, field)
471                .map_err(Error::pkix)?
472        {
473            return Err(Error::schema(
474                ErrorCode::SetOrder,
475                field,
476                element.span().start,
477            ));
478        }
479        *previous = Some(element.as_bytes());
480    }
481    Ok(())
482}
483
484fn other_format(parent: &Reader<'_>, element: &Element<'_>, field: &'static str) -> Result<()> {
485    other_fields(parent, element, field).map(|_| ())
486}
487
488fn other_fields<'a>(
489    parent: &Reader<'a>,
490    element: &Element<'a>,
491    field: &'static str,
492) -> Result<(Reader<'a>, Oid<'a>, Element<'a>)> {
493    let mut inside = parent
494        .enter(element)
495        .map_err(|error| Error::encoding(error, field))?;
496    let kind = inside
497        .read_oid()
498        .map_err(|error| Error::encoding(error, field))?;
499    let value = required(&mut inside, field)?;
500    if !inside.is_empty() {
501        return Err(Error::schema(
502            ErrorCode::TrailingData,
503            field,
504            inside.offset(),
505        ));
506    }
507    Ok((inside, kind, value))
508}
509
510pub(crate) fn catalogue<'a, T>(
511    parent: &Reader<'a>,
512    set: &Element<'a>,
513    tag: Tag,
514    field: &'static str,
515    mut read: impl FnMut(&Reader<'a>, &Element<'a>) -> Result<T>,
516) -> Result<OwnedVec<T>> {
517    if set.tag() != tag {
518        return Err(Error::schema(
519            ErrorCode::UnexpectedTag,
520            field,
521            set.span().start,
522        ));
523    }
524    let mut members = parent
525        .enter(set)
526        .map_err(|error| Error::encoding(error, field))?;
527    let execution = parent.execution();
528    let mut values = execution.vector();
529    let mut previous = None;
530    while let Some(entry) = members
531        .next_element()
532        .map_err(|error| Error::encoding(error, field))?
533    {
534        ordered(&members, &mut previous, &entry, field)?;
535        values
536            .try_reserve(1)
537            .map_err(|reason| Error::resource(reason, field, entry.span().start))?;
538        let value = read(&members, &entry)?;
539        values
540            .try_push(value)
541            .map_err(|reason| Error::resource(reason, field, entry.span().start))?;
542    }
543    Ok(values.into_owned())
544}
545
546pub(crate) fn read_certificates<'a>(
547    parent: &Reader<'a>,
548    set: &Element<'a>,
549    version: &mut u64,
550    field: &'static str,
551) -> Result<(OwnedVec<CertificateChoice<'a>>, OwnedVec<Certificate<'a>>)> {
552    let execution = parent.execution();
553    let mut certificates = execution.vector();
554    let choices = catalogue(
555        parent,
556        set,
557        Tag::context(true, 0),
558        field,
559        |members, entry| {
560            Ok(match entry.tag() {
561                tag if tag == Tag::constructed(universal::SEQUENCE) => {
562                    certificates
563                        .try_reserve(1)
564                        .map_err(|reason| Error::resource(reason, field, entry.span().start))?;
565                    let certificate =
566                        Certificate::read_element(members, entry).map_err(Error::pkix)?;
567                    let index = certificates.len();
568                    certificates
569                        .try_push(certificate)
570                        .map_err(|reason| Error::resource(reason, field, entry.span().start))?;
571                    CertificateChoice::X509(index)
572                }
573                tag if tag == Tag::context(true, 0) => CertificateChoice::Extended(*entry),
574                tag if tag == Tag::context(true, 1) => {
575                    *version = (*version).max(3);
576                    CertificateChoice::AttributeV1(*entry)
577                }
578                tag if tag == Tag::context(true, 2) => {
579                    *version = (*version).max(4);
580                    CertificateChoice::AttributeV2(*entry)
581                }
582                tag if tag == Tag::context(true, 3) => {
583                    other_format(members, entry, field)?;
584                    *version = 5;
585                    CertificateChoice::Other(*entry)
586                }
587                _ => {
588                    return Err(Error::schema(
589                        ErrorCode::UnexpectedTag,
590                        field,
591                        entry.span().start,
592                    ));
593                }
594            })
595        },
596    )?;
597    Ok((choices, certificates.into_owned()))
598}
599
600fn read_version<'a>(inside: &mut Reader<'a>) -> Result<(u64, Element<'a>)> {
601    const FIELD: &str = "signedData.version";
602    let element = inside
603        .expect(Tag::primitive(universal::INTEGER))
604        .map_err(|error| Error::encoding(error, FIELD))?;
605    let version = inside
606        .integer(&element)
607        .map_err(|error| Error::encoding(error, FIELD))?
608        .as_u64()
609        .ok_or_else(|| Error::schema(ErrorCode::ValueTooLarge, FIELD, element.span().start))?;
610    Ok((version, element))
611}
612
613pub(crate) fn read_revocations<'a>(
614    parent: &Reader<'a>,
615    set: &Element<'a>,
616    version: &mut u64,
617    field: &'static str,
618) -> Result<OwnedVec<Element<'a>>> {
619    catalogue(
620        parent,
621        set,
622        Tag::context(true, 1),
623        field,
624        |members, entry| {
625            if entry.tag() == Tag::context(true, 1) {
626                other_format(members, entry, field)?;
627                *version = 5;
628            } else if entry.tag() != Tag::constructed(universal::SEQUENCE) {
629                return Err(Error::schema(
630                    ErrorCode::UnexpectedTag,
631                    field,
632                    entry.span().start,
633                ));
634            }
635            Ok(*entry)
636        },
637    )
638}