_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}