Skip to main content

_synta/certificate/cms/
mod.rs

1//! Python bindings for CMS content types (RFC 5652 / RFC 9629).
2//!
3//! Split into focused sub-files:
4//! - `kem`       — `KEMRecipientInfo` + `CMSORIforKEMOtherInfo` (RFC 9629)
5//! - `container` — `ContentInfo` + `IssuerAndSerialNumber`
6//! - `signed`    — `SignedData` + `SignerInfo`
7//! - `enveloped` — `EnvelopedData` + `EncryptedData`
8//! - `digest`    — `DigestedData` + `AuthenticatedData`
9//! - `builder`   — `EnvelopedDataBuilder`
10
11mod builder;
12mod container;
13mod digest;
14mod enveloped;
15pub(super) mod kem;
16mod signed;
17mod signed_builder;
18
19use builder::PyEnvelopedDataBuilder;
20use container::{PyContentInfo, PyIssuerAndSerialNumber};
21use digest::{PyAuthenticatedData, PyDigestedData};
22use enveloped::{PyEncryptedData, PyEnvelopedData};
23use kem::{PyCMSORIforKEMOtherInfo, PyKEMRecipientInfo};
24use signed::{PySignedData, PySignerInfo};
25use signed_builder::PySignedDataBuilder;
26
27use pyo3::prelude::*;
28use pyo3::types::PyBytes;
29
30use synta::traits::Encode;
31use synta::Encoding;
32
33/// Re-encode an ASN.1 `Element` to DER bytes, returning `None` for a missing
34/// optional field.  Used by the algorithm-parameter getters across all CMS types.
35fn encode_element_opt<'py>(
36    py: Python<'py>,
37    elem: Option<&synta::Element<'_>>,
38) -> PyResult<Option<Bound<'py, PyBytes>>> {
39    match elem {
40        None => Ok(None),
41        Some(e) => {
42            let mut encoder = synta::Encoder::new(Encoding::Der);
43            e.encode(&mut encoder)
44                .map_err(|err| pyo3::exceptions::PyValueError::new_err(format!("{err}")))?;
45            let bytes = encoder
46                .finish()
47                .map_err(|err| pyo3::exceptions::PyValueError::new_err(format!("{err}")))?;
48            Ok(Some(PyBytes::new(py, &bytes)))
49        }
50    }
51}
52
53/// Exposes CMS (RFC 5652) content types, CMS-KEM (RFC 9629) types, and OID
54/// constants.  Installs the module into ``sys.modules`` so that
55/// ``from synta.cms import ContentInfo`` works.
56pub(super) fn register_cms_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
57    let py = parent.py();
58    let m = PyModule::new(py, "cms")?;
59
60    m.add_class::<PyContentInfo>()?;
61    m.add_class::<PySignedData>()?;
62    m.add_class::<PySignerInfo>()?;
63    m.add_class::<PyEnvelopedData>()?;
64    m.add_class::<PyEnvelopedDataBuilder>()?;
65    m.add_class::<PySignedDataBuilder>()?;
66    m.add_class::<PyEncryptedData>()?;
67    m.add_class::<PyDigestedData>()?;
68    m.add_class::<PyAuthenticatedData>()?;
69    m.add_class::<PyIssuerAndSerialNumber>()?;
70    m.add_class::<PyKEMRecipientInfo>()?;
71    m.add_class::<PyCMSORIforKEMOtherInfo>()?;
72
73    // Content-type OIDs (RFC 5652 §14)
74    m.add(
75        "ID_DATA",
76        super::oid_const(py, synta_certificate::pkcs7_types::ID_DATA),
77    )?;
78    m.add(
79        "ID_SIGNED_DATA",
80        super::oid_const(py, synta_certificate::pkcs7_types::ID_SIGNED_DATA),
81    )?;
82    m.add(
83        "ID_ENVELOPED_DATA",
84        super::oid_const(py, synta_certificate::pkcs7_types::ID_ENVELOPED_DATA),
85    )?;
86    m.add(
87        "ID_ENCRYPTED_DATA",
88        super::oid_const(py, synta_certificate::pkcs7_types::ID_ENCRYPTED_DATA),
89    )?;
90    m.add(
91        "ID_DIGESTED_DATA",
92        super::oid_const(py, synta_certificate::oids::CMS_DIGESTED_DATA),
93    )?;
94    m.add(
95        "ID_CT_AUTH_DATA",
96        super::oid_const(py, synta_certificate::oids::CMS_AUTH_DATA),
97    )?;
98    // OtherRecipientInfo OIDs (RFC 9629 §6.2)
99    m.add(
100        "ID_ORI",
101        super::oid_const(py, synta_certificate::cms_kem_types::ID_ORI),
102    )?;
103    m.add(
104        "ID_ORI_KEM",
105        super::oid_const(py, synta_certificate::cms_kem_types::ID_ORI_KEM),
106    )?;
107    // Content-encryption algorithm OIDs (NIST AES, RFC 3565)
108    m.add(
109        "ID_AES128_CBC",
110        super::oid_const(py, synta_certificate::pkcs12_types::ID_AES128_CBC),
111    )?;
112    m.add(
113        "ID_AES192_CBC",
114        super::oid_const(py, synta_certificate::pkcs12_types::ID_AES192_CBC),
115    )?;
116    m.add(
117        "ID_AES256_CBC",
118        super::oid_const(py, synta_certificate::pkcs12_types::ID_AES256_CBC),
119    )?;
120    // Key-transport algorithm OIDs (RFC 8017)
121    m.add(
122        "ID_RSAES_OAEP",
123        super::oid_const(py, synta_certificate::oids::RSAES_OAEP),
124    )?;
125    m.add(
126        "ID_RSA_ENCRYPTION",
127        super::oid_const(py, synta_certificate::oids::RSA_ENCRYPTION),
128    )?;
129
130    crate::install_submodule(
131        parent,
132        &m,
133        "synta.cms",
134        Some(
135            "synta.cms — CMS (RFC 5652) and CMS-KEM (RFC 9629) types.\n\
136             \n\
137             Provides ContentInfo, SignedData, SignerInfo, EnvelopedData,\n\
138             EnvelopedDataBuilder, EncryptedData, DigestedData, AuthenticatedData,\n\
139             IssuerAndSerialNumber, KEMRecipientInfo, CMSORIforKEMOtherInfo,\n\
140             along with content-type and OtherRecipientInfo OID constants.",
141        ),
142    )
143}