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    kind_encoding: &'a [u8],
127    content: Option<Octets<'a>>,
128    span: Span,
129}
130
131impl<'a> Encapsulated<'a> {
132    /// Reads type and optional explicit OCTET STRING under the original parent.
133    ///
134    /// Absence denotes detached content. Empty attached content is a distinct
135    /// value, and no caller-supplied payload silently replaces attached bytes.
136    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
137        const FIELD: &str = "signedData.encapContentInfo";
138        const KIND: &str = "signedData.encapContentInfo.eContentType";
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 kind_element = inside
150            .expect(Tag::primitive(universal::OID))
151            .map_err(|error| Error::encoding(error, KIND))?;
152        let kind = inside
153            .oid(&kind_element)
154            .map_err(|error| Error::encoding(error, KIND))?;
155        let content = if let Some(wrapper) = inside
156            .next_element()
157            .map_err(|error| Error::encoding(error, FIELD))?
158        {
159            const CONTENT: &str = "signedData.encapContentInfo.eContent";
160            if wrapper.tag() != Tag::context(true, 0) {
161                return Err(Error::schema(
162                    ErrorCode::UnexpectedTag,
163                    CONTENT,
164                    wrapper.span().start,
165                ));
166            }
167            let mut explicit = inside
168                .enter(&wrapper)
169                .map_err(|error| Error::encoding(error, CONTENT))?;
170            let octets = explicit
171                .next_element()
172                .map_err(|error| Error::encoding(error, CONTENT))?
173                .ok_or_else(|| {
174                    Error::schema(ErrorCode::MissingField, CONTENT, wrapper.span().start)
175                })?;
176            let content = Octets::read(&explicit, &octets, CONTENT)?;
177            if !explicit.is_empty() {
178                return Err(Error::schema(
179                    ErrorCode::TrailingData,
180                    CONTENT,
181                    explicit.offset(),
182                ));
183            }
184            Some(content)
185        } else {
186            None
187        };
188        if !inside.is_empty() {
189            return Err(Error::schema(
190                ErrorCode::TrailingData,
191                FIELD,
192                inside.offset(),
193            ));
194        }
195        Ok(Self {
196            kind,
197            kind_encoding: kind_element.as_bytes(),
198            content,
199            span: element.span(),
200        })
201    }
202
203    /// The original content type identifier.
204    #[must_use]
205    pub fn kind(&self) -> Oid<'a> {
206        self.kind
207    }
208
209    /// The original `eContentType` encoding, including its tag and length octets.
210    #[must_use]
211    pub fn kind_encoding(&self) -> &'a [u8] {
212        self.kind_encoding
213    }
214
215    /// Attached logical octets, or `None` for detached content.
216    #[must_use]
217    pub fn content(&self) -> Option<&Octets<'a>> {
218        self.content.as_ref()
219    }
220
221    /// Whether the envelope omitted the content field.
222    #[must_use]
223    pub fn is_detached(&self) -> bool {
224        self.content.is_none()
225    }
226
227    /// The encapsulated content-info's original range.
228    #[must_use]
229    pub fn span(&self) -> Span {
230        self.span
231    }
232}