Skip to main content

structured_cms/
enveloped.rs

1//! Borrowed CMS digital envelopes, with explicit provider/key selection.
2//!
3//! RFC5652 §§6.1–6.4 define this schema, not a cryptographic implementation.
4//! Reading never unwraps a key, decrypts content or trusts originator material.
5//! Every original algorithm and encrypted fragment remains available to the
6//! selected provider. Unsupported algorithms/ORI formats remain its capability
7//! result; malformed structures and resources remain processing refusals.
8//! <https://www.rfc-editor.org/rfc/rfc5652#section-6>
9
10use crate::signed::{CertificateChoice, catalogue, read_certificates, read_revocations};
11use crate::{
12    ContentInfo, Error, ErrorCode, Octets, Result, attribute::Attribute, signer::required,
13};
14use structured_asn1::memory::{OwnedVec, Work};
15use structured_asn1::{Element, Encoding, Oid, Parser, Reader, Tag, universal};
16use structured_pkix::{AlgorithmIdentifier, Certificate};
17
18mod recipient;
19pub use recipient::{AgreementIdentifier, KeyIdentifier, Originator, Recipient, RecipientKey};
20
21/// CMS id-envelopedData, RFC5652 §6.1.
22pub const IDENTIFIER: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 3]);
23const FIELD: &str = "envelopedData";
24
25/// Original open field with its actual decoding position. A selected format
26/// owner can read it without reconstructing the envelope grammar or resetting
27/// depth, encoding, source work or the current operation.
28#[derive(Debug, Clone, Copy)]
29pub struct EncodedValue<'a> {
30    element: Element<'a>,
31    parser: &'a Parser,
32    encoding: Encoding,
33    depth: u32,
34}
35impl<'a> EncodedValue<'a> {
36    pub(crate) fn new(parent: &Reader<'a>, element: Element<'a>) -> Self {
37        Self {
38            element,
39            parser: parent.parser(),
40            encoding: parent.encoding(),
41            depth: parent.depth(),
42        }
43    }
44    /// Whole original element, including its physical source span.
45    #[must_use]
46    pub fn element(&self) -> &Element<'a> {
47        &self.element
48    }
49    /// Read the whole original field under original/current ceilings. The
50    /// cursor includes its header and retains the exact producing depth.
51    pub fn reader_in<'op>(&self, query: &Reader<'op>) -> Result<Reader<'op>>
52    where
53        'a: 'op,
54    {
55        self.parser
56            .read_query_in(
57                query,
58                self.element.as_bytes(),
59                self.element.span().start,
60                self.encoding,
61                self.depth,
62            )
63            .map_err(|e| Error::encoding(e, FIELD))
64    }
65}
66
67/// Original optional originator material, neither trusted nor authenticated.
68#[derive(Debug)]
69pub struct OriginatorInfo<'a> {
70    parser: &'a Parser,
71    encoding: Encoding,
72    outer: u32,
73    at: u64,
74    choices: OwnedVec<CertificateChoice<'a>>,
75    certificates: OwnedVec<Certificate<'a>>,
76    revocations: OwnedVec<Element<'a>>,
77}
78
79impl<'a> OriginatorInfo<'a> {
80    /// Original certificate alternatives; X509 ordinals index `certificates`.
81    #[must_use]
82    pub fn choices(&self) -> &[CertificateChoice<'a>] {
83        &self.choices
84    }
85    /// Original public-key certificates, using the shared `SignedData` kernel.
86    #[must_use]
87    pub fn certificates(&self) -> &[Certificate<'a>] {
88        &self.certificates
89    }
90    /// Original whole CRL/other alternatives, not implicitly authenticated.
91    #[must_use]
92    pub fn revocations(&self) -> &[Element<'a>] {
93        &self.revocations
94    }
95    /// Reopen a retained non-X509 certificate alternative at its actual source
96    /// depth under current/original ceilings. Parsed X509 entries return None.
97    pub fn certificate_reader_in<'op>(
98        &self,
99        query: &Reader<'op>,
100        ordinal: usize,
101    ) -> Result<Option<Reader<'op>>>
102    where
103        'a: 'op,
104    {
105        self.admit(query)?;
106        let Some(
107            CertificateChoice::Extended(entry)
108            | CertificateChoice::AttributeV1(entry)
109            | CertificateChoice::AttributeV2(entry)
110            | CertificateChoice::Other(entry),
111        ) = self.choices.get(ordinal)
112        else {
113            return Ok(None);
114        };
115        self.reopen(query, entry).map(Some)
116    }
117    /// Reopen the original whole CRL/other alternative, without a parser reset,
118    /// copy or format interpretation. Missing indices are paid absence.
119    pub fn revocation_reader_in<'op>(
120        &self,
121        query: &Reader<'op>,
122        ordinal: usize,
123    ) -> Result<Option<Reader<'op>>>
124    where
125        'a: 'op,
126    {
127        self.admit(query)?;
128        self.revocations
129            .get(ordinal)
130            .map(|entry| self.reopen(query, entry))
131            .transpose()
132    }
133    fn admit(&self, query: &Reader<'_>) -> Result<()> {
134        query
135            .execution()
136            .execution_for(self.parser)
137            .charge_work(1)
138            .map_err(|e| Error::resource(e, "envelopedData.originatorInfo", self.at))
139    }
140    fn reopen<'op>(&self, query: &Reader<'op>, entry: &Element<'a>) -> Result<Reader<'op>>
141    where
142        'a: 'op,
143    {
144        // `outer` is the containing OriginatorInfo level. The set itself adds
145        // one level; the returned reader is positioned at its member element.
146        self.parser
147            .read_nested_query_in(
148                query,
149                entry.as_bytes(),
150                entry.span().start,
151                self.encoding,
152                self.outer,
153            )
154            .map_err(|e| Error::encoding(e, "envelopedData.originatorInfo"))
155    }
156}
157
158/// Original content descriptor and optional ciphertext, not plaintext.
159#[derive(Debug)]
160pub struct EncryptedContent<'a> {
161    /// The actual plaintext format selected by the originator.
162    pub kind: Oid<'a>,
163    /// Original content algorithm and parameter source context.
164    pub algorithm: AlgorithmIdentifier<'a>,
165    /// None is detached; attached empty ciphertext is a distinct value.
166    pub ciphertext: Option<Octets<'a>>,
167}
168
169/// Complete `EnvelopedData` schema with original nested decoding context.
170#[derive(Debug)]
171pub struct EnvelopedData<'a> {
172    parser: &'a Parser,
173    element: Element<'a>,
174    depth: u32,
175    encoding: Encoding,
176    /// Declared version, checked against all encoded choices before return.
177    pub version: u64,
178    /// Optional originator certificates and revocation material.
179    pub originator: Option<OriginatorInfo<'a>>,
180    /// Every original recipient, in source SET order.
181    pub recipients: OwnedVec<Recipient<'a>>,
182    /// Original plaintext type, encryption parameters and ciphertext ranges.
183    pub content: EncryptedContent<'a>,
184    /// Unauthenticated/unprotected attributes, never promoted to signed facts.
185    pub unprotected: Option<OwnedVec<Attribute<'a>>>,
186}
187
188impl<'a> EnvelopedData<'a> {
189    /// Read one complete `ContentInfo` with the enveloped-data content type.
190    pub fn read(parent: &mut Reader<'a>) -> Result<Self> {
191        let (wrapper, reader) = ContentInfo::read(parent)?;
192        Self::from_content_info(&wrapper, &reader)
193    }
194    /// Read one original embedded `ContentInfo` without rebuilding its encoding.
195    pub fn read_content_info(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
196        let (wrapper, reader) = ContentInfo::read_element(parent, element)?;
197        Self::from_content_info(&wrapper, &reader)
198    }
199    fn from_content_info(wrapper: &ContentInfo<'a>, parent: &Reader<'a>) -> Result<Self> {
200        oid_is(
201            wrapper.kind(),
202            IDENTIFIER,
203            parent,
204            "contentInfo.contentType",
205            wrapper.span().start,
206        )?;
207        Self::read_element(parent, wrapper.content())
208    }
209    /// Read the original `EnvelopedData` SEQUENCE, retaining its source/depth.
210    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
211        tag(element, Tag::constructed(universal::SEQUENCE), FIELD)?;
212        let mut inside = enter(parent, element, FIELD)?;
213        let (version, at) = number(&mut inside, "envelopedData.version")?;
214        let mut next = required(&mut inside, FIELD)?;
215        let mut material_version = 0;
216        let originator = if next.tag() == Tag::context(true, 0) {
217            let original = originator_info(&inside, &next, &mut material_version)?;
218            next = required(&mut inside, FIELD)?;
219            Some(original)
220        } else {
221            None
222        };
223        let mut all_zero = true;
224        let mut advanced = false;
225        let recipients = catalogue(
226            &inside,
227            &next,
228            Tag::constructed(universal::SET),
229            "envelopedData.recipientInfos",
230            |reader, entry| {
231                let recipient = Recipient::read(reader, entry)?;
232                all_zero &= recipient.is_transport_zero();
233                advanced |= matches!(
234                    recipient,
235                    Recipient::Password { .. } | Recipient::Other { .. }
236                );
237                Ok(recipient)
238            },
239        )?;
240        if recipients.is_empty() {
241            return Err(Error::schema(
242                ErrorCode::MissingField,
243                "envelopedData.recipientInfos",
244                next.span().start,
245            ));
246        }
247        let value = required(&mut inside, "envelopedData.encryptedContentInfo")?;
248        let content = encrypted_content(&inside, &value)?;
249        let unprotected = inside
250            .next_element()
251            .map_err(|e| Error::encoding(e, FIELD))?
252            .map(|set| {
253                let values = catalogue(
254                    &inside,
255                    &set,
256                    Tag::context(true, 1),
257                    "envelopedData.unprotectedAttrs",
258                    |r, e| Attribute::read(r, e, "envelopedData.unprotectedAttrs"),
259                )?;
260                if values.is_empty() {
261                    return Err(Error::schema(
262                        ErrorCode::EmptyAttributes,
263                        "envelopedData.unprotectedAttrs",
264                        set.span().start,
265                    ));
266                }
267                Ok(values)
268            })
269            .transpose()?;
270        end(&inside, FIELD)?;
271        // RFC5652 §6.1's ladder differs from SignedData: other originator
272        // formats ->4; v2 AC/pwri/ori ->3; pure transport v0 ->0; otherwise2.
273        // https://www.rfc-editor.org/rfc/rfc5652#section-6.1
274        let expected = if material_version == 5 {
275            4
276        } else if material_version >= 4 || advanced {
277            3
278        } else if originator.is_none() && unprotected.is_none() && all_zero {
279            0
280        } else {
281            2
282        };
283        if version != expected {
284            return Err(Error::schema(
285                ErrorCode::VersionMismatch,
286                "envelopedData.version",
287                at,
288            ));
289        }
290        Ok(Self {
291            parser: parent.parser(),
292            element: *element,
293            depth: parent.depth(),
294            encoding: parent.encoding(),
295            version,
296            originator,
297            recipients,
298            content,
299            unprotected,
300        })
301    }
302    /// Whole original encoding, not reconstructed plaintext or trusted content.
303    #[must_use]
304    pub fn element(&self) -> &Element<'a> {
305        &self.element
306    }
307    /// Original/current execution for a selected provider's new work.
308    pub fn execution_in<'op, W: Work>(
309        &self,
310        current: &'op W,
311    ) -> structured_asn1::memory::Execution<'op>
312    where
313        'a: 'op,
314    {
315        current.execution_for(self.parser)
316    }
317    /// Reopen this whole original envelope at its exact original depth under
318    /// the current operation. Descendants' own reader APIs preserve field depth.
319    pub fn reader_in<'op>(&self, current: &Reader<'op>) -> Result<Reader<'op>>
320    where
321        'a: 'op,
322    {
323        let outer = self.element.span();
324        self.parser
325            .read_query_in(
326                current,
327                self.element.as_bytes(),
328                outer.start,
329                self.encoding,
330                self.depth,
331            )
332            .map_err(|e| Error::encoding(e, FIELD))
333    }
334}
335
336fn originator_info<'a>(
337    parent: &Reader<'a>,
338    element: &Element<'a>,
339    version: &mut u64,
340) -> Result<OriginatorInfo<'a>> {
341    let field = "envelopedData.originatorInfo";
342    let mut inside = enter(parent, element, field)?;
343    let work = parent.execution();
344    let mut choices = work.vector().into_owned();
345    let mut certificates = work.vector().into_owned();
346    let mut revocations = work.vector().into_owned();
347    let mut next = inside
348        .next_element()
349        .map_err(|e| Error::encoding(e, field))?;
350    if let Some(set) = next.as_ref().filter(|e| e.tag() == Tag::context(true, 0)) {
351        (choices, certificates) =
352            read_certificates(&inside, set, version, "envelopedData.originatorInfo.certs")?;
353        next = inside
354            .next_element()
355            .map_err(|e| Error::encoding(e, field))?;
356    }
357    if let Some(set) = next.as_ref().filter(|e| e.tag() == Tag::context(true, 1)) {
358        revocations = read_revocations(&inside, set, version, "envelopedData.originatorInfo.crls")?;
359        next = inside
360            .next_element()
361            .map_err(|e| Error::encoding(e, field))?;
362    }
363    if let Some(extra) = next {
364        return Err(Error::schema(
365            ErrorCode::UnknownField,
366            field,
367            extra.span().start,
368        ));
369    }
370    Ok(OriginatorInfo {
371        parser: parent.parser(),
372        encoding: parent.encoding(),
373        outer: inside.depth(),
374        at: element.span().start,
375        choices,
376        certificates,
377        revocations,
378    })
379}
380pub(crate) fn encrypted_content<'a>(
381    parent: &Reader<'a>,
382    element: &Element<'a>,
383) -> Result<EncryptedContent<'a>> {
384    let field = "envelopedData.encryptedContentInfo";
385    tag(element, Tag::constructed(universal::SEQUENCE), field)?;
386    let mut inside = enter(parent, element, field)?;
387    let kind = inside.read_oid().map_err(|e| Error::encoding(e, field))?;
388    let value = required(&mut inside, field)?;
389    let algorithm = algorithm(&inside, &value, field)?;
390    let ciphertext = inside
391        .next_element()
392        .map_err(|e| Error::encoding(e, field))?
393        .map(|value| Octets::read_identifier(&inside, &value, field))
394        .transpose()?;
395    end(&inside, field)?;
396    Ok(EncryptedContent {
397        kind,
398        algorithm,
399        ciphertext,
400    })
401}
402fn enter<'a>(
403    parent: &Reader<'a>,
404    element: &Element<'a>,
405    field: &'static str,
406) -> Result<Reader<'a>> {
407    parent.enter(element).map_err(|e| Error::encoding(e, field))
408}
409fn end(parent: &Reader<'_>, field: &'static str) -> Result<()> {
410    if !parent.is_empty() {
411        return Err(Error::schema(
412            ErrorCode::TrailingData,
413            field,
414            parent.offset(),
415        ));
416    }
417    Ok(())
418}
419fn tag(element: &Element<'_>, expected: Tag, field: &'static str) -> Result<()> {
420    if element.tag() != expected {
421        return Err(Error::schema(
422            ErrorCode::UnexpectedTag,
423            field,
424            element.span().start,
425        ));
426    }
427    Ok(())
428}
429fn number(parent: &mut Reader<'_>, field: &'static str) -> Result<(u64, u64)> {
430    let element = parent
431        .expect(Tag::primitive(universal::INTEGER))
432        .map_err(|e| Error::encoding(e, field))?;
433    let value = parent
434        .integer(&element)
435        .map_err(|e| Error::encoding(e, field))?
436        .as_u64()
437        .ok_or_else(|| Error::schema(ErrorCode::ValueTooLarge, field, element.span().start))?;
438    Ok((value, element.span().start))
439}
440fn version(parent: &mut Reader<'_>, expected: u64, field: &'static str) -> Result<()> {
441    let (value, at) = number(parent, field)?;
442    if value != expected {
443        return Err(Error::schema(ErrorCode::UnsupportedVersion, field, at));
444    }
445    Ok(())
446}
447fn algorithm<'a>(
448    parent: &Reader<'a>,
449    element: &Element<'a>,
450    field: &'static str,
451) -> Result<AlgorithmIdentifier<'a>> {
452    structured_pkix::algorithm::read(parent, element, field).map_err(Error::pkix)
453}
454pub(crate) fn oid_is(
455    value: Oid<'_>,
456    expected: Oid<'_>,
457    parent: &Reader<'_>,
458    field: &'static str,
459    at: u64,
460) -> Result<()> {
461    parent.charge(1).map_err(|e| Error::encoding(e, field))?;
462    if value.as_bytes().len() == expected.as_bytes().len() {
463        parent
464            .charge(value.as_bytes().len() as u64)
465            .map_err(|e| Error::encoding(e, field))?;
466        if value == expected {
467            return Ok(());
468        }
469    }
470    Err(Error::schema(ErrorCode::UnexpectedContentType, field, at))
471}