Skip to main content

structured_cms/
content.rs

1//! CMS content envelopes, preserving original elements and producing readers.
2
3use crate::{Error, ErrorCode, Octets, Result};
4use structured_asn1::{Element, Oid, Reader, Span, Tag, universal};
5
6/// The CMS `ContentInfo` envelope (RFC 5652 §3).
7///
8/// Its explicit content field is mandatory. Detached `SignedData` instead omits
9/// the inner [`Encapsulated`] `eContent` (RFC 5652 §5.2). This type does not use
10/// the older PKCS#7 optional outer-content grammar from RFC 2315 §7.
11#[derive(Debug)]
12pub struct ContentInfo<'a> {
13    kind: Oid<'a>,
14    content: Element<'a>,
15    span: Span,
16}
17
18impl<'a> ContentInfo<'a> {
19    /// Reads one complete envelope, refusing bytes after that envelope.
20    ///
21    /// The returned reader produced the content element and retains the same
22    /// parser budget, execution account, diagnostic path and inherited depth.
23    /// It is used directly by the selected content schema; no encode/reparse
24    /// adapter or replacement parser is created.
25    pub fn read(reader: &mut Reader<'a>) -> Result<(Self, Reader<'a>)> {
26        let outer = reader
27            .expect(Tag::constructed(universal::SEQUENCE))
28            .map_err(|error| Error::encoding(error, "contentInfo"))?;
29        let result = Self::read_element(reader, &outer)?;
30        if !reader.is_empty() {
31            return Err(Error::schema(
32                ErrorCode::TrailingData,
33                "contentInfo",
34                reader.offset(),
35            ));
36        }
37        Ok(result)
38    }
39
40    /// Reads an embedded envelope from its original producing parent.
41    ///
42    /// Unlike [`Self::read`], surrounding siblings belong to the enclosing
43    /// schema and are not refused. Envelope components themselves remain exact.
44    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<(Self, Reader<'a>)> {
45        const FIELD: &str = "contentInfo";
46        if element.tag() != Tag::constructed(universal::SEQUENCE) {
47            return Err(Error::schema(
48                ErrorCode::UnexpectedTag,
49                FIELD,
50                element.span().start,
51            ));
52        }
53        let mut inside = parent
54            .enter(element)
55            .map_err(|error| Error::encoding(error, FIELD))?;
56        let kind = inside
57            .read_oid()
58            .map_err(|error| Error::encoding(error, "contentInfo.contentType"))?;
59        // RFC5652 §§3/12.1 require outer content (no OPTIONAL), unlike PKCS#7
60        // RFC2315 §7. Detached CMS omits only EncapsulatedContentInfo.eContent
61        // (§5.2); it does not omit this wrapper or its single inner value.
62        // https://www.rfc-editor.org/rfc/rfc5652#section-3
63        let wrapper = inside
64            .expect(Tag::context(true, 0))
65            .map_err(|error| Error::encoding(error, "contentInfo.content"))?;
66        let mut explicit = inside
67            .enter(&wrapper)
68            .map_err(|error| Error::encoding(error, "contentInfo.content"))?;
69        let content = explicit
70            .next_element()
71            .map_err(|error| Error::encoding(error, "contentInfo.content"))?
72            .ok_or_else(|| {
73                Error::schema(
74                    ErrorCode::MissingField,
75                    "contentInfo.content",
76                    wrapper.span().start,
77                )
78            })?;
79        if !explicit.is_empty() {
80            return Err(Error::schema(
81                ErrorCode::TrailingData,
82                "contentInfo.content",
83                explicit.offset(),
84            ));
85        }
86        if !inside.is_empty() {
87            return Err(Error::schema(
88                ErrorCode::TrailingData,
89                FIELD,
90                inside.offset(),
91            ));
92        }
93        Ok((
94            Self {
95                kind,
96                content,
97                span: element.span(),
98            },
99            explicit,
100        ))
101    }
102
103    /// The original content type identifier, without an application interpretation.
104    #[must_use]
105    pub fn kind(&self) -> Oid<'a> {
106        self.kind
107    }
108
109    /// The original embedded content element, including its encoding.
110    #[must_use]
111    pub fn content(&self) -> &Element<'a> {
112        &self.content
113    }
114
115    /// The envelope's original source range.
116    #[must_use]
117    pub fn span(&self) -> Span {
118        self.span
119    }
120}
121
122/// Attached or absent encapsulated content (RFC 5652 §5.2).
123#[derive(Debug)]
124pub struct Encapsulated<'a> {
125    kind: Oid<'a>,
126    content: Option<Octets<'a>>,
127    span: Span,
128}
129
130impl<'a> Encapsulated<'a> {
131    /// Reads type and optional explicit OCTET STRING under the original parent.
132    ///
133    /// Absence denotes detached content. Empty attached content is a distinct
134    /// value, and no caller-supplied payload silently replaces attached bytes.
135    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
136        const FIELD: &str = "signedData.encapContentInfo";
137        if element.tag() != Tag::constructed(universal::SEQUENCE) {
138            return Err(Error::schema(
139                ErrorCode::UnexpectedTag,
140                FIELD,
141                element.span().start,
142            ));
143        }
144        let mut inside = parent
145            .enter(element)
146            .map_err(|error| Error::encoding(error, FIELD))?;
147        let kind = inside
148            .read_oid()
149            .map_err(|error| Error::encoding(error, "signedData.encapContentInfo.eContentType"))?;
150        let content = if let Some(wrapper) = inside
151            .next_element()
152            .map_err(|error| Error::encoding(error, FIELD))?
153        {
154            const CONTENT: &str = "signedData.encapContentInfo.eContent";
155            if wrapper.tag() != Tag::context(true, 0) {
156                return Err(Error::schema(
157                    ErrorCode::UnexpectedTag,
158                    CONTENT,
159                    wrapper.span().start,
160                ));
161            }
162            let mut explicit = inside
163                .enter(&wrapper)
164                .map_err(|error| Error::encoding(error, CONTENT))?;
165            let octets = explicit
166                .next_element()
167                .map_err(|error| Error::encoding(error, CONTENT))?
168                .ok_or_else(|| {
169                    Error::schema(ErrorCode::MissingField, CONTENT, wrapper.span().start)
170                })?;
171            let content = Octets::read(&explicit, &octets, CONTENT)?;
172            if !explicit.is_empty() {
173                return Err(Error::schema(
174                    ErrorCode::TrailingData,
175                    CONTENT,
176                    explicit.offset(),
177                ));
178            }
179            Some(content)
180        } else {
181            None
182        };
183        if !inside.is_empty() {
184            return Err(Error::schema(
185                ErrorCode::TrailingData,
186                FIELD,
187                inside.offset(),
188            ));
189        }
190        Ok(Self {
191            kind,
192            content,
193            span: element.span(),
194        })
195    }
196
197    /// The original content type identifier.
198    #[must_use]
199    pub fn kind(&self) -> Oid<'a> {
200        self.kind
201    }
202
203    /// Attached logical octets, or `None` for detached content.
204    #[must_use]
205    pub fn content(&self) -> Option<&Octets<'a>> {
206        self.content.as_ref()
207    }
208
209    /// Whether the envelope omitted the content field.
210    #[must_use]
211    pub fn is_detached(&self) -> bool {
212        self.content.is_none()
213    }
214
215    /// The encapsulated content-info's original range.
216    #[must_use]
217    pub fn span(&self) -> Span {
218        self.span
219    }
220}