Skip to main content

_synta/certificate/
pkcs5.rs

1//! Python bindings for PKCS #5 v2.1 (RFC 8018) parameter builders.
2//!
3//! Exposes two builder classes:
4//!
5//! - [`PyPbkdf2ParamsBuilder`] — ``Pkcs5Pbkdf2Params`` (§5.2)
6//! - [`PyPbes2ParamsBuilder`] — ``Pkcs5Pbes2Params`` (§6.2)
7//!
8//! Also registers OID constants for the PBKDF2 and PBES2 algorithm identifiers.
9
10use pyo3::prelude::*;
11use pyo3::types::PyBytes;
12
13// ── PyPbkdf2ParamsBuilder ─────────────────────────────────────────────────────
14
15/// Python-facing wrapper for [`synta_certificate::Pbkdf2ParamsBuilder`].
16///
17/// Builds a DER-encoded ``Pkcs5Pbkdf2Params`` SEQUENCE (RFC 8018 §5.2).
18///
19/// Required fields: ``salt`` and ``iteration_count``.
20/// Optional: ``key_length``, ``prf`` (absent means HMAC-SHA-1 per RFC 8018
21/// default).
22///
23/// Use :meth:`build` to produce the raw ``PBKDF2-params`` DER, or
24/// :meth:`build_as_algorithm_identifier` to wrap it in an
25/// ``AlgorithmIdentifier`` for use as the ``keyDerivationFunc`` field in
26/// :class:`Pbes2ParamsBuilder`.
27///
28/// Example::
29///
30///     import synta
31///     import synta.pkcs5 as pkcs5
32///
33///     params_der = (
34///         synta.Pbkdf2ParamsBuilder()
35///         .salt(b"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08")
36///         .iteration_count(10_000)
37///         .key_length(32)
38///         .prf(list(pkcs5.ID_HMAC_WITH_SHA256.components()))
39///         .build()
40///     )
41#[pyclass(name = "Pbkdf2ParamsBuilder")]
42pub struct PyPbkdf2ParamsBuilder {
43    inner: synta_certificate::Pbkdf2ParamsBuilder,
44    built: bool,
45}
46
47#[pymethods]
48impl PyPbkdf2ParamsBuilder {
49    /// Create a new, empty ``Pbkdf2ParamsBuilder``.
50    #[new]
51    fn new() -> Self {
52        Self {
53            inner: synta_certificate::Pbkdf2ParamsBuilder::new(),
54            built: false,
55        }
56    }
57
58    /// Set the ``salt`` OCTET STRING.
59    ///
60    /// RFC 8018 recommends at least 8 bytes of random salt.
61    ///
62    /// :param salt: salt bytes.
63    fn salt<'py>(slf: Bound<'py, Self>, salt: &[u8]) -> Bound<'py, Self> {
64        {
65            let mut guard = slf.borrow_mut();
66            let old = std::mem::replace(
67                &mut guard.inner,
68                synta_certificate::Pbkdf2ParamsBuilder::new(),
69            );
70            guard.inner = old.salt(salt);
71        }
72        slf
73    }
74
75    /// Set the ``iterationCount``.
76    ///
77    /// RFC 8018 specifies a minimum of 1000 iterations; modern deployments
78    /// use at least 10 000.
79    ///
80    /// :param count: iteration count (must be at least 1).
81    /// :raises ValueError: if ``count`` is 0 or exceeds the DER INTEGER maximum
82    ///     (deferred to :meth:`build`).
83    fn iteration_count<'py>(slf: Bound<'py, Self>, count: u64) -> Bound<'py, Self> {
84        {
85            let mut guard = slf.borrow_mut();
86            let old = std::mem::replace(
87                &mut guard.inner,
88                synta_certificate::Pbkdf2ParamsBuilder::new(),
89            );
90            guard.inner = old.iteration_count(count);
91        }
92        slf
93    }
94
95    /// Set the optional ``keyLength`` (output key length in bytes).
96    ///
97    /// :param len: key length in bytes.
98    fn key_length<'py>(slf: Bound<'py, Self>, len: u64) -> Bound<'py, Self> {
99        {
100            let mut guard = slf.borrow_mut();
101            let old = std::mem::replace(
102                &mut guard.inner,
103                synta_certificate::Pbkdf2ParamsBuilder::new(),
104            );
105            guard.inner = old.key_length(len);
106        }
107        slf
108    }
109
110    /// Set the optional ``prf`` (pseudo-random function) algorithm OID.
111    ///
112    /// Use one of the HMAC OID constants from ``synta.pkcs5``, e.g.
113    /// ``list(synta.pkcs5.ID_HMAC_WITH_SHA256.components())``.
114    /// When absent, the default HMAC-SHA-1 is used per RFC 8018 §5.2.
115    ///
116    /// :param prf_oid: OID arc components as a list of ints.
117    /// :raises ValueError: if the OID is invalid (deferred to :meth:`build`).
118    fn prf<'py>(slf: Bound<'py, Self>, prf_oid: Vec<u32>) -> Bound<'py, Self> {
119        {
120            let mut guard = slf.borrow_mut();
121            let old = std::mem::replace(
122                &mut guard.inner,
123                synta_certificate::Pbkdf2ParamsBuilder::new(),
124            );
125            guard.inner = old.prf(&prf_oid);
126        }
127        slf
128    }
129
130    /// Build the DER-encoded ``Pkcs5Pbkdf2Params`` SEQUENCE.
131    ///
132    /// :returns: DER bytes of the ``PBKDF2-params``.
133    /// :raises ValueError: if ``salt`` or ``iteration_count`` are missing,
134    ///     DER encoding fails, or if called more than once.
135    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
136        if self.built {
137            return Err(pyo3::exceptions::PyValueError::new_err(
138                "build() has already been called; create a new builder",
139            ));
140        }
141        self.built = true;
142        let inner = std::mem::replace(
143            &mut self.inner,
144            synta_certificate::Pbkdf2ParamsBuilder::new(),
145        );
146        let der = inner
147            .build()
148            .map_err(pyo3::exceptions::PyValueError::new_err)?;
149        Ok(PyBytes::new(py, &der))
150    }
151
152    /// Build the ``Pkcs5Pbkdf2Params`` and wrap it as an
153    /// ``AlgorithmIdentifier`` with the given algorithm OID.
154    ///
155    /// This convenience method produces the complete
156    /// ``AlgorithmIdentifier { id-PBKDF2, PBKDF2-params }`` DER needed as
157    /// the ``keyDerivationFunc`` field in :class:`Pbes2ParamsBuilder`.
158    ///
159    /// Pass ``list(synta.pkcs5.ID_PBKDF2.components())`` as ``alg_oid``.
160    ///
161    /// :param alg_oid: OID arc components as a list of ints (use
162    ///     ``synta.pkcs5.ID_PBKDF2``).
163    /// :returns: DER bytes of the ``AlgorithmIdentifier``.
164    /// :raises ValueError: if any required field is missing or encoding fails.
165    fn build_as_algorithm_identifier<'py>(
166        &mut self,
167        py: Python<'py>,
168        alg_oid: Vec<u32>,
169    ) -> PyResult<Bound<'py, PyBytes>> {
170        if self.built {
171            return Err(pyo3::exceptions::PyValueError::new_err(
172                "build() has already been called; create a new builder",
173            ));
174        }
175        self.built = true;
176        let inner = std::mem::replace(
177            &mut self.inner,
178            synta_certificate::Pbkdf2ParamsBuilder::new(),
179        );
180        let der = inner
181            .build_as_algorithm_identifier(&alg_oid)
182            .map_err(pyo3::exceptions::PyValueError::new_err)?;
183        Ok(PyBytes::new(py, &der))
184    }
185
186    fn __repr__(&self) -> String {
187        "Pbkdf2ParamsBuilder()".to_string()
188    }
189}
190
191// ── PyPbes2ParamsBuilder ──────────────────────────────────────────────────────
192
193/// Python-facing wrapper for [`synta_certificate::Pbes2ParamsBuilder`].
194///
195/// Builds a DER-encoded ``Pkcs5Pbes2Params`` SEQUENCE (RFC 8018 §6.2).
196///
197/// Both ``key_derivation_func`` and ``encryption_scheme`` are required.
198///
199/// Typical usage:
200///
201/// 1. Build the PBKDF2 ``AlgorithmIdentifier`` using
202///    :meth:`Pbkdf2ParamsBuilder.build_as_algorithm_identifier`.
203/// 2. Set the encryption scheme using :meth:`encryption_scheme_from_oid`.
204/// 3. Call :meth:`build`.
205///
206/// Example::
207///
208///     import synta
209///     import synta.pkcs5 as pkcs5
210///
211///     salt = b"\\x00" * 8
212///     iv   = b"\\x00" * 16
213///
214///     kdf_der = (
215///         synta.Pbkdf2ParamsBuilder()
216///         .salt(salt)
217///         .iteration_count(10_000)
218///         .prf(list(pkcs5.ID_HMAC_WITH_SHA256.components()))
219///         .build_as_algorithm_identifier(list(pkcs5.ID_PBKDF2.components()))
220///     )
221///
222///     pbes2_der = (
223///         synta.Pbes2ParamsBuilder()
224///         .key_derivation_func(kdf_der)
225///         .encryption_scheme_from_oid(list(pkcs5.AES256_CBC_PAD.components()), iv)
226///         .build()
227///     )
228#[pyclass(name = "Pbes2ParamsBuilder")]
229pub struct PyPbes2ParamsBuilder {
230    inner: synta_certificate::Pbes2ParamsBuilder,
231    built: bool,
232}
233
234#[pymethods]
235impl PyPbes2ParamsBuilder {
236    /// Create a new, empty ``Pbes2ParamsBuilder``.
237    #[new]
238    fn new() -> Self {
239        Self {
240            inner: synta_certificate::Pbes2ParamsBuilder::new(),
241            built: false,
242        }
243    }
244
245    /// Set the ``keyDerivationFunc`` from a pre-encoded ``AlgorithmIdentifier``
246    /// DER blob.
247    ///
248    /// Use :meth:`Pbkdf2ParamsBuilder.build_as_algorithm_identifier` to
249    /// produce this DER blob.
250    ///
251    /// :param alg_id_der: complete ``AlgorithmIdentifier`` SEQUENCE TLV bytes.
252    fn key_derivation_func<'py>(slf: Bound<'py, Self>, alg_id_der: &[u8]) -> Bound<'py, Self> {
253        {
254            let mut guard = slf.borrow_mut();
255            let old = std::mem::replace(
256                &mut guard.inner,
257                synta_certificate::Pbes2ParamsBuilder::new(),
258            );
259            guard.inner = old.key_derivation_func(alg_id_der);
260        }
261        slf
262    }
263
264    /// Set the ``encryptionScheme`` from a pre-encoded ``AlgorithmIdentifier``
265    /// DER blob.
266    ///
267    /// :param alg_id_der: complete ``AlgorithmIdentifier`` SEQUENCE TLV bytes.
268    fn encryption_scheme<'py>(slf: Bound<'py, Self>, alg_id_der: &[u8]) -> Bound<'py, Self> {
269        {
270            let mut guard = slf.borrow_mut();
271            let old = std::mem::replace(
272                &mut guard.inner,
273                synta_certificate::Pbes2ParamsBuilder::new(),
274            );
275            guard.inner = old.encryption_scheme(alg_id_der);
276        }
277        slf
278    }
279
280    /// Set the ``encryptionScheme`` from an OID component slice and an IV.
281    ///
282    /// This convenience method builds the
283    /// ``AlgorithmIdentifier { oid, OCTET STRING iv }`` used by AES-CBC-PAD.
284    ///
285    /// :param enc_oid: encryption algorithm OID arc components (e.g.
286    ///     ``list(synta.pkcs5.AES256_CBC_PAD.components())``).
287    /// :param iv: 16-byte initialization vector.
288    /// :raises ValueError: if the OID is invalid or encoding fails
289    ///     (deferred to :meth:`build`).
290    fn encryption_scheme_from_oid<'py>(
291        slf: Bound<'py, Self>,
292        enc_oid: Vec<u32>,
293        iv: &[u8],
294    ) -> Bound<'py, Self> {
295        {
296            let mut guard = slf.borrow_mut();
297            let old = std::mem::replace(
298                &mut guard.inner,
299                synta_certificate::Pbes2ParamsBuilder::new(),
300            );
301            guard.inner = old.encryption_scheme_from_oid(&enc_oid, iv);
302        }
303        slf
304    }
305
306    /// Build the DER-encoded ``Pkcs5Pbes2Params`` SEQUENCE.
307    ///
308    /// :returns: DER bytes of the ``PBES2-params``.
309    /// :raises ValueError: if either field was not set, DER encoding fails,
310    ///     or if called more than once.
311    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
312        if self.built {
313            return Err(pyo3::exceptions::PyValueError::new_err(
314                "build() has already been called; create a new builder",
315            ));
316        }
317        self.built = true;
318        let inner = std::mem::replace(
319            &mut self.inner,
320            synta_certificate::Pbes2ParamsBuilder::new(),
321        );
322        let der = inner
323            .build()
324            .map_err(pyo3::exceptions::PyValueError::new_err)?;
325        Ok(PyBytes::new(py, &der))
326    }
327
328    fn __repr__(&self) -> String {
329        "Pbes2ParamsBuilder()".to_string()
330    }
331}
332
333// ── register ──────────────────────────────────────────────────────────────────
334
335/// Register PKCS#5 builder classes and OID constants into ``synta.pkcs5``.
336pub(super) fn register_pkcs5_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
337    let py = parent.py();
338    let m = pyo3::types::PyModule::new(py, "pkcs5")?;
339
340    m.add_class::<PyPbkdf2ParamsBuilder>()?;
341    m.add_class::<PyPbes2ParamsBuilder>()?;
342
343    // ── PKCS#5 algorithm OIDs ─────────────────────────────────────────────────
344    m.add(
345        "ID_PBKDF2",
346        super::oid_const(py, synta_certificate::pkcs5_types::ID_PBKDF2),
347    )?;
348    m.add(
349        "ID_PBES2",
350        super::oid_const(py, synta_certificate::pkcs5_types::ID_PBES2),
351    )?;
352    m.add(
353        "ID_HMAC_WITH_SHA1",
354        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA1_P5),
355    )?;
356    m.add(
357        "ID_HMAC_WITH_SHA224",
358        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA224_P5),
359    )?;
360    m.add(
361        "ID_HMAC_WITH_SHA256",
362        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA256_P5),
363    )?;
364    m.add(
365        "ID_HMAC_WITH_SHA384",
366        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA384_P5),
367    )?;
368    m.add(
369        "ID_HMAC_WITH_SHA512",
370        super::oid_const(py, synta_certificate::pkcs5_types::ID_HMAC_WITH_SHA512_P5),
371    )?;
372    m.add(
373        "AES128_CBC_PAD",
374        super::oid_const(py, synta_certificate::pkcs5_types::AES128_CBC_PAD),
375    )?;
376    m.add(
377        "AES192_CBC_PAD",
378        super::oid_const(py, synta_certificate::pkcs5_types::AES192_CBC_PAD),
379    )?;
380    m.add(
381        "AES256_CBC_PAD",
382        super::oid_const(py, synta_certificate::pkcs5_types::AES256_CBC_PAD),
383    )?;
384
385    crate::install_submodule(
386        parent,
387        &m,
388        "synta.pkcs5",
389        Some(concat!(
390            "synta.pkcs5 — RFC 8018 PKCS #5 v2.1 parameter builders.\n\n",
391            "Provides Pbkdf2ParamsBuilder and Pbes2ParamsBuilder for constructing\n",
392            "DER-encoded PBKDF2 and PBES2 parameter structures, along with OID\n",
393            "constants for key derivation and encryption scheme algorithms.",
394        )),
395    )
396}