synta-python 0.3.0

Python extension module for the synta ASN.1 library
Documentation
//! Python bindings for CMS content types (RFC 5652 / RFC 9629).
//!
//! Split into focused sub-files:
//! - `kem`       — `KEMRecipientInfo` + `CMSORIforKEMOtherInfo` (RFC 9629)
//! - `container` — `ContentInfo` + `IssuerAndSerialNumber`
//! - `signed`    — `SignedData` + `SignerInfo`
//! - `enveloped` — `EnvelopedData` + `EncryptedData`
//! - `digest`    — `DigestedData` + `AuthenticatedData`
//! - `builder`   — `EnvelopedDataBuilder`

mod builder;
mod container;
mod digest;
mod enveloped;
pub(super) mod kem;
mod signed;
mod signed_builder;

use builder::PyEnvelopedDataBuilder;
use container::{PyContentInfo, PyIssuerAndSerialNumber};
use digest::{PyAuthenticatedData, PyDigestedData};
use enveloped::{PyEncryptedData, PyEnvelopedData};
use kem::{PyCMSORIforKEMOtherInfo, PyKEMRecipientInfo};
use signed::{PySignedData, PySignerInfo};
use signed_builder::PySignedDataBuilder;

use pyo3::prelude::*;
use pyo3::types::PyBytes;

use synta::traits::Encode;
use synta::Encoding;

/// Re-encode an ASN.1 `Element` to DER bytes, returning `None` for a missing
/// optional field.  Used by the algorithm-parameter getters across all CMS types.
fn encode_element_opt<'py>(
    py: Python<'py>,
    elem: Option<&synta::Element<'_>>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
    match elem {
        None => Ok(None),
        Some(e) => {
            let mut encoder = synta::Encoder::new(Encoding::Der);
            e.encode(&mut encoder)
                .map_err(|err| pyo3::exceptions::PyValueError::new_err(format!("{err}")))?;
            let bytes = encoder
                .finish()
                .map_err(|err| pyo3::exceptions::PyValueError::new_err(format!("{err}")))?;
            Ok(Some(PyBytes::new(py, &bytes)))
        }
    }
}

/// Exposes CMS (RFC 5652) content types, CMS-KEM (RFC 9629) types, and OID
/// constants.  Installs the module into ``sys.modules`` so that
/// ``from synta.cms import ContentInfo`` works.
pub(super) fn register_cms_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
    let py = parent.py();
    let m = PyModule::new(py, "cms")?;

    m.add_class::<PyContentInfo>()?;
    m.add_class::<PySignedData>()?;
    m.add_class::<PySignerInfo>()?;
    m.add_class::<PyEnvelopedData>()?;
    m.add_class::<PyEnvelopedDataBuilder>()?;
    m.add_class::<PySignedDataBuilder>()?;
    m.add_class::<PyEncryptedData>()?;
    m.add_class::<PyDigestedData>()?;
    m.add_class::<PyAuthenticatedData>()?;
    m.add_class::<PyIssuerAndSerialNumber>()?;
    m.add_class::<PyKEMRecipientInfo>()?;
    m.add_class::<PyCMSORIforKEMOtherInfo>()?;

    // Content-type OIDs (RFC 5652 §14)
    m.add(
        "ID_DATA",
        super::oid_const(py, synta_certificate::pkcs7_types::ID_DATA),
    )?;
    m.add(
        "ID_SIGNED_DATA",
        super::oid_const(py, synta_certificate::pkcs7_types::ID_SIGNED_DATA),
    )?;
    m.add(
        "ID_ENVELOPED_DATA",
        super::oid_const(py, synta_certificate::pkcs7_types::ID_ENVELOPED_DATA),
    )?;
    m.add(
        "ID_ENCRYPTED_DATA",
        super::oid_const(py, synta_certificate::pkcs7_types::ID_ENCRYPTED_DATA),
    )?;
    m.add(
        "ID_DIGESTED_DATA",
        super::oid_const(py, synta_certificate::oids::CMS_DIGESTED_DATA),
    )?;
    m.add(
        "ID_CT_AUTH_DATA",
        super::oid_const(py, synta_certificate::oids::CMS_AUTH_DATA),
    )?;
    // OtherRecipientInfo OIDs (RFC 9629 §6.2)
    m.add(
        "ID_ORI",
        super::oid_const(py, synta_certificate::cms_kem_types::ID_ORI),
    )?;
    m.add(
        "ID_ORI_KEM",
        super::oid_const(py, synta_certificate::cms_kem_types::ID_ORI_KEM),
    )?;
    // Content-encryption algorithm OIDs (NIST AES, RFC 3565)
    m.add(
        "ID_AES128_CBC",
        super::oid_const(py, synta_certificate::pkcs12_types::ID_AES128_CBC),
    )?;
    m.add(
        "ID_AES192_CBC",
        super::oid_const(py, synta_certificate::pkcs12_types::ID_AES192_CBC),
    )?;
    m.add(
        "ID_AES256_CBC",
        super::oid_const(py, synta_certificate::pkcs12_types::ID_AES256_CBC),
    )?;
    // Key-transport algorithm OIDs (RFC 8017)
    m.add(
        "ID_RSAES_OAEP",
        super::oid_const(py, synta_certificate::oids::RSAES_OAEP),
    )?;
    m.add(
        "ID_RSA_ENCRYPTION",
        super::oid_const(py, synta_certificate::oids::RSA_ENCRYPTION),
    )?;

    crate::install_submodule(
        parent,
        &m,
        "synta.cms",
        Some(
            "synta.cms — CMS (RFC 5652) and CMS-KEM (RFC 9629) types.\n\
             \n\
             Provides ContentInfo, SignedData, SignerInfo, EnvelopedData,\n\
             EnvelopedDataBuilder, EncryptedData, DigestedData, AuthenticatedData,\n\
             IssuerAndSerialNumber, KEMRecipientInfo, CMSORIforKEMOtherInfo,\n\
             along with content-type and OtherRecipientInfo OID constants.",
        ),
    )
}