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