synta-python 0.2.5

Python extension module for the synta ASN.1 library
Documentation
//! Python bindings for PKCS #5 v2.1 (RFC 8018) parameter builders.
//!
//! Exposes two builder classes:
//!
//! - [`PyPbkdf2ParamsBuilder`] — ``Pkcs5Pbkdf2Params`` (§5.2)
//! - [`PyPbes2ParamsBuilder`] — ``Pkcs5Pbes2Params`` (§6.2)
//!
//! Also registers OID constants for the PBKDF2 and PBES2 algorithm identifiers.

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

// ── PyPbkdf2ParamsBuilder ─────────────────────────────────────────────────────

/// Python-facing wrapper for [`synta_certificate::Pbkdf2ParamsBuilder`].
///
/// Builds a DER-encoded ``Pkcs5Pbkdf2Params`` SEQUENCE (RFC 8018 §5.2).
///
/// Required fields: ``salt`` and ``iteration_count``.
/// Optional: ``key_length``, ``prf`` (absent means HMAC-SHA-1 per RFC 8018
/// default).
///
/// Use :meth:`build` to produce the raw ``PBKDF2-params`` DER, or
/// :meth:`build_as_algorithm_identifier` to wrap it in an
/// ``AlgorithmIdentifier`` for use as the ``keyDerivationFunc`` field in
/// :class:`Pbes2ParamsBuilder`.
///
/// Example::
///
///     import synta
///     import synta.pkcs5 as pkcs5
///
///     params_der = (
///         synta.Pbkdf2ParamsBuilder()
///         .salt(b"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08")
///         .iteration_count(10_000)
///         .key_length(32)
///         .prf(list(pkcs5.ID_HMAC_WITH_SHA256.components()))
///         .build()
///     )
#[pyclass(name = "Pbkdf2ParamsBuilder")]
pub struct PyPbkdf2ParamsBuilder {
    inner: synta_certificate::Pbkdf2ParamsBuilder,
    built: bool,
}

#[pymethods]
impl PyPbkdf2ParamsBuilder {
    /// Create a new, empty ``Pbkdf2ParamsBuilder``.
    #[new]
    fn new() -> Self {
        Self {
            inner: synta_certificate::Pbkdf2ParamsBuilder::new(),
            built: false,
        }
    }

    /// Set the ``salt`` OCTET STRING.
    ///
    /// RFC 8018 recommends at least 8 bytes of random salt.
    ///
    /// :param salt: salt bytes.
    fn salt<'py>(slf: Bound<'py, Self>, salt: &[u8]) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::Pbkdf2ParamsBuilder::new(),
            );
            guard.inner = old.salt(salt);
        }
        slf
    }

    /// Set the ``iterationCount``.
    ///
    /// RFC 8018 specifies a minimum of 1000 iterations; modern deployments
    /// use at least 10 000.
    ///
    /// :param count: iteration count (must be at least 1).
    /// :raises ValueError: if ``count`` is 0 or exceeds the DER INTEGER maximum
    ///     (deferred to :meth:`build`).
    fn iteration_count<'py>(slf: Bound<'py, Self>, count: u64) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::Pbkdf2ParamsBuilder::new(),
            );
            guard.inner = old.iteration_count(count);
        }
        slf
    }

    /// Set the optional ``keyLength`` (output key length in bytes).
    ///
    /// :param len: key length in bytes.
    fn key_length<'py>(slf: Bound<'py, Self>, len: u64) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::Pbkdf2ParamsBuilder::new(),
            );
            guard.inner = old.key_length(len);
        }
        slf
    }

    /// Set the optional ``prf`` (pseudo-random function) algorithm OID.
    ///
    /// Use one of the HMAC OID constants from ``synta.pkcs5``, e.g.
    /// ``list(synta.pkcs5.ID_HMAC_WITH_SHA256.components())``.
    /// When absent, the default HMAC-SHA-1 is used per RFC 8018 §5.2.
    ///
    /// :param prf_oid: OID arc components as a list of ints.
    /// :raises ValueError: if the OID is invalid (deferred to :meth:`build`).
    fn prf<'py>(slf: Bound<'py, Self>, prf_oid: Vec<u32>) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::Pbkdf2ParamsBuilder::new(),
            );
            guard.inner = old.prf(&prf_oid);
        }
        slf
    }

    /// Build the DER-encoded ``Pkcs5Pbkdf2Params`` SEQUENCE.
    ///
    /// :returns: DER bytes of the ``PBKDF2-params``.
    /// :raises ValueError: if ``salt`` or ``iteration_count`` are missing,
    ///     DER encoding fails, or if called more than once.
    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
        if self.built {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "build() has already been called; create a new builder",
            ));
        }
        self.built = true;
        let inner = std::mem::replace(
            &mut self.inner,
            synta_certificate::Pbkdf2ParamsBuilder::new(),
        );
        let der = inner
            .build()
            .map_err(pyo3::exceptions::PyValueError::new_err)?;
        Ok(PyBytes::new(py, &der))
    }

    /// Build the ``Pkcs5Pbkdf2Params`` and wrap it as an
    /// ``AlgorithmIdentifier`` with the given algorithm OID.
    ///
    /// This convenience method produces the complete
    /// ``AlgorithmIdentifier { id-PBKDF2, PBKDF2-params }`` DER needed as
    /// the ``keyDerivationFunc`` field in :class:`Pbes2ParamsBuilder`.
    ///
    /// Pass ``list(synta.pkcs5.ID_PBKDF2.components())`` as ``alg_oid``.
    ///
    /// :param alg_oid: OID arc components as a list of ints (use
    ///     ``synta.pkcs5.ID_PBKDF2``).
    /// :returns: DER bytes of the ``AlgorithmIdentifier``.
    /// :raises ValueError: if any required field is missing or encoding fails.
    fn build_as_algorithm_identifier<'py>(
        &mut self,
        py: Python<'py>,
        alg_oid: Vec<u32>,
    ) -> PyResult<Bound<'py, PyBytes>> {
        if self.built {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "build() has already been called; create a new builder",
            ));
        }
        self.built = true;
        let inner = std::mem::replace(
            &mut self.inner,
            synta_certificate::Pbkdf2ParamsBuilder::new(),
        );
        let der = inner
            .build_as_algorithm_identifier(&alg_oid)
            .map_err(pyo3::exceptions::PyValueError::new_err)?;
        Ok(PyBytes::new(py, &der))
    }

    fn __repr__(&self) -> String {
        "Pbkdf2ParamsBuilder()".to_string()
    }
}

// ── PyPbes2ParamsBuilder ──────────────────────────────────────────────────────

/// Python-facing wrapper for [`synta_certificate::Pbes2ParamsBuilder`].
///
/// Builds a DER-encoded ``Pkcs5Pbes2Params`` SEQUENCE (RFC 8018 §6.2).
///
/// Both ``key_derivation_func`` and ``encryption_scheme`` are required.
///
/// Typical usage:
///
/// 1. Build the PBKDF2 ``AlgorithmIdentifier`` using
///    :meth:`Pbkdf2ParamsBuilder.build_as_algorithm_identifier`.
/// 2. Set the encryption scheme using :meth:`encryption_scheme_from_oid`.
/// 3. Call :meth:`build`.
///
/// Example::
///
///     import synta
///     import synta.pkcs5 as pkcs5
///
///     salt = b"\\x00" * 8
///     iv   = b"\\x00" * 16
///
///     kdf_der = (
///         synta.Pbkdf2ParamsBuilder()
///         .salt(salt)
///         .iteration_count(10_000)
///         .prf(list(pkcs5.ID_HMAC_WITH_SHA256.components()))
///         .build_as_algorithm_identifier(list(pkcs5.ID_PBKDF2.components()))
///     )
///
///     pbes2_der = (
///         synta.Pbes2ParamsBuilder()
///         .key_derivation_func(kdf_der)
///         .encryption_scheme_from_oid(list(pkcs5.AES256_CBC_PAD.components()), iv)
///         .build()
///     )
#[pyclass(name = "Pbes2ParamsBuilder")]
pub struct PyPbes2ParamsBuilder {
    inner: synta_certificate::Pbes2ParamsBuilder,
    built: bool,
}

#[pymethods]
impl PyPbes2ParamsBuilder {
    /// Create a new, empty ``Pbes2ParamsBuilder``.
    #[new]
    fn new() -> Self {
        Self {
            inner: synta_certificate::Pbes2ParamsBuilder::new(),
            built: false,
        }
    }

    /// Set the ``keyDerivationFunc`` from a pre-encoded ``AlgorithmIdentifier``
    /// DER blob.
    ///
    /// Use :meth:`Pbkdf2ParamsBuilder.build_as_algorithm_identifier` to
    /// produce this DER blob.
    ///
    /// :param alg_id_der: complete ``AlgorithmIdentifier`` SEQUENCE TLV bytes.
    fn key_derivation_func<'py>(slf: Bound<'py, Self>, alg_id_der: &[u8]) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::Pbes2ParamsBuilder::new(),
            );
            guard.inner = old.key_derivation_func(alg_id_der);
        }
        slf
    }

    /// Set the ``encryptionScheme`` from a pre-encoded ``AlgorithmIdentifier``
    /// DER blob.
    ///
    /// :param alg_id_der: complete ``AlgorithmIdentifier`` SEQUENCE TLV bytes.
    fn encryption_scheme<'py>(slf: Bound<'py, Self>, alg_id_der: &[u8]) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::Pbes2ParamsBuilder::new(),
            );
            guard.inner = old.encryption_scheme(alg_id_der);
        }
        slf
    }

    /// Set the ``encryptionScheme`` from an OID component slice and an IV.
    ///
    /// This convenience method builds the
    /// ``AlgorithmIdentifier { oid, OCTET STRING iv }`` used by AES-CBC-PAD.
    ///
    /// :param enc_oid: encryption algorithm OID arc components (e.g.
    ///     ``list(synta.pkcs5.AES256_CBC_PAD.components())``).
    /// :param iv: 16-byte initialization vector.
    /// :raises ValueError: if the OID is invalid or encoding fails
    ///     (deferred to :meth:`build`).
    fn encryption_scheme_from_oid<'py>(
        slf: Bound<'py, Self>,
        enc_oid: Vec<u32>,
        iv: &[u8],
    ) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::Pbes2ParamsBuilder::new(),
            );
            guard.inner = old.encryption_scheme_from_oid(&enc_oid, iv);
        }
        slf
    }

    /// Build the DER-encoded ``Pkcs5Pbes2Params`` SEQUENCE.
    ///
    /// :returns: DER bytes of the ``PBES2-params``.
    /// :raises ValueError: if either field was not set, DER encoding fails,
    ///     or if called more than once.
    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
        if self.built {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "build() has already been called; create a new builder",
            ));
        }
        self.built = true;
        let inner = std::mem::replace(
            &mut self.inner,
            synta_certificate::Pbes2ParamsBuilder::new(),
        );
        let der = inner
            .build()
            .map_err(pyo3::exceptions::PyValueError::new_err)?;
        Ok(PyBytes::new(py, &der))
    }

    fn __repr__(&self) -> String {
        "Pbes2ParamsBuilder()".to_string()
    }
}

// ── register ──────────────────────────────────────────────────────────────────

/// Register PKCS#5 builder classes and OID constants into ``synta.pkcs5``.
pub(super) fn register_pkcs5_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
    let py = parent.py();
    let m = pyo3::types::PyModule::new(py, "pkcs5")?;

    m.add_class::<PyPbkdf2ParamsBuilder>()?;
    m.add_class::<PyPbes2ParamsBuilder>()?;

    // ── PKCS#5 algorithm OIDs ─────────────────────────────────────────────────
    m.add(
        "ID_PBKDF2",
        super::oid_const(py, synta_certificate::pkcs5_types::ID_PBKDF2),
    )?;
    m.add(
        "ID_PBES2",
        super::oid_const(py, synta_certificate::pkcs5_types::ID_PBES2),
    )?;
    m.add(
        "ID_HMAC_WITH_SHA1",
        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA1_P5),
    )?;
    m.add(
        "ID_HMAC_WITH_SHA224",
        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA224_P5),
    )?;
    m.add(
        "ID_HMAC_WITH_SHA256",
        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA256_P5),
    )?;
    m.add(
        "ID_HMAC_WITH_SHA384",
        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA384_P5),
    )?;
    m.add(
        "ID_HMAC_WITH_SHA512",
        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA512_P5),
    )?;
    m.add(
        "AES128_CBC_PAD",
        super::oid_const(py, synta_certificate::pkcs5_types::AES128_CBC_PAD),
    )?;
    m.add(
        "AES192_CBC_PAD",
        super::oid_const(py, synta_certificate::pkcs5_types::AES192_CBC_PAD),
    )?;
    m.add(
        "AES256_CBC_PAD",
        super::oid_const(py, synta_certificate::pkcs5_types::AES256_CBC_PAD),
    )?;

    crate::install_submodule(
        parent,
        &m,
        "synta.pkcs5",
        Some(concat!(
            "synta.pkcs5 — RFC 8018 PKCS #5 v2.1 parameter builders.\n\n",
            "Provides Pbkdf2ParamsBuilder and Pbes2ParamsBuilder for constructing\n",
            "DER-encoded PBKDF2 and PBES2 parameter structures, along with OID\n",
            "constants for key derivation and encryption scheme algorithms.",
        )),
    )
}