Skip to main content

_synta/certificate/
pkixalgs.rs

1//! Python bindings for RFC 3279 algorithm parameter types.
2//!
3//! Exposes ``DssParms``, ``DssSigValue``, ``EcdsaSigValue``, and ``ECParameters``
4//! as Python classes, along with the OID constants from the ``PKIXAlgs`` module.
5//! All types are installed into the ``synta.pkixalgs`` submodule.
6
7use std::sync::OnceLock;
8
9use pyo3::prelude::*;
10use pyo3::types::PyBytes;
11
12use synta::{Decoder, Encoding};
13
14use crate::error::SyntaErr;
15use crate::types::PyObjectIdentifier;
16
17// ── PyDssParms ────────────────────────────────────────────────────────────────
18
19/// DSA domain parameters (RFC 3279 §2.3.2).
20///
21/// Carries the prime modulus ``p``, prime divisor ``q``, and generator ``g``
22/// parameters for a DSA public key.  Decoded from the ``parameters`` field
23/// of an ``AlgorithmIdentifier`` whose OID is ``id-dsa``.
24///
25/// ```python,ignore
26/// import synta.pkixalgs as pa
27/// parms = pa.DssParms.from_der(alg_id_params_der)
28/// print(len(parms.p))  # byte length of p
29/// ```
30#[pyclass(frozen, name = "DssParms")]
31pub struct PyDssParms {
32    inner: synta_certificate::pkixalgs_types::DssParms,
33}
34
35#[pymethods]
36impl PyDssParms {
37    /// Parse a DER-encoded ``Dss-Parms`` SEQUENCE.
38    ///
39    /// :param data: DER bytes of the ``Dss-Parms`` SEQUENCE.
40    /// :raises ValueError: if the bytes cannot be decoded.
41    #[staticmethod]
42    fn from_der(data: &[u8]) -> PyResult<Self> {
43        let mut dec = Decoder::new(data, Encoding::Der);
44        let inner = dec
45            .decode::<synta_certificate::pkixalgs_types::DssParms>()
46            .map_err(SyntaErr)?;
47        Ok(Self { inner })
48    }
49
50    /// Return the DER encoding of this ``Dss-Parms`` SEQUENCE.
51    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
52        Ok(PyBytes::new(py, &self.inner.to_der().map_err(SyntaErr)?))
53    }
54
55    /// Prime modulus ``p`` (big-endian two's-complement bytes).
56    #[getter]
57    fn p<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
58        PyBytes::new(py, self.inner.p.as_bytes())
59    }
60
61    /// Prime divisor ``q`` (big-endian two's-complement bytes).
62    #[getter]
63    fn q<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
64        PyBytes::new(py, self.inner.q.as_bytes())
65    }
66
67    /// Generator ``g`` (big-endian two's-complement bytes).
68    #[getter]
69    fn g<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
70        PyBytes::new(py, self.inner.g.as_bytes())
71    }
72
73    fn __repr__(&self) -> String {
74        format!(
75            "DssParms(p=<{} bytes>, q=<{} bytes>, g=<{} bytes>)",
76            self.inner.p.as_bytes().len(),
77            self.inner.q.as_bytes().len(),
78            self.inner.g.as_bytes().len(),
79        )
80    }
81}
82
83// ── PyDssSigValue ─────────────────────────────────────────────────────────────
84
85/// DSA signature value (RFC 3279 §2.2.2).
86///
87/// Contains the integer pair ``(r, s)`` produced by the DSA signing operation.
88#[pyclass(frozen, name = "DssSigValue")]
89pub struct PyDssSigValue {
90    inner: synta_certificate::pkixalgs_types::DssSigValue,
91}
92
93#[pymethods]
94impl PyDssSigValue {
95    /// Parse a DER-encoded ``Dss-Sig-Value`` SEQUENCE.
96    #[staticmethod]
97    fn from_der(data: &[u8]) -> PyResult<Self> {
98        let mut dec = Decoder::new(data, Encoding::Der);
99        let inner = dec
100            .decode::<synta_certificate::pkixalgs_types::DssSigValue>()
101            .map_err(SyntaErr)?;
102        Ok(Self { inner })
103    }
104
105    /// Return the DER encoding of this ``Dss-Sig-Value`` SEQUENCE.
106    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
107        Ok(PyBytes::new(py, &self.inner.to_der().map_err(SyntaErr)?))
108    }
109
110    /// Signature integer ``r`` (big-endian two's-complement bytes).
111    #[getter]
112    fn r<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
113        PyBytes::new(py, self.inner.r.as_bytes())
114    }
115
116    /// Signature integer ``s`` (big-endian two's-complement bytes).
117    #[getter]
118    fn s<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
119        PyBytes::new(py, self.inner.s.as_bytes())
120    }
121
122    fn __repr__(&self) -> String {
123        format!(
124            "DssSigValue(r=<{} bytes>, s=<{} bytes>)",
125            self.inner.r.as_bytes().len(),
126            self.inner.s.as_bytes().len(),
127        )
128    }
129}
130
131// ── PyEcdsaSigValue ───────────────────────────────────────────────────────────
132
133/// ECDSA signature value (RFC 3279 §2.2.3, X9.62).
134///
135/// Contains the integer pair ``(r, s)`` produced by the ECDSA signing
136/// operation.  Typically found as the ``subjectPublicKey`` bit-string content
137/// inside an X.509 certificate's ``AlgorithmIdentifier`` for ECDSA.
138///
139/// ```python,ignore
140/// import synta.pkixalgs as pa
141/// sig = pa.EcdsaSigValue.from_der(signature_bytes)
142/// r_bytes, s_bytes = sig.r, sig.s
143/// ```
144#[pyclass(frozen, name = "EcdsaSigValue")]
145pub struct PyEcdsaSigValue {
146    inner: synta_certificate::pkixalgs_types::EcdsaSigValue,
147}
148
149#[pymethods]
150impl PyEcdsaSigValue {
151    /// Parse a DER-encoded ``ECDSA-Sig-Value`` SEQUENCE.
152    #[staticmethod]
153    fn from_der(data: &[u8]) -> PyResult<Self> {
154        let mut dec = Decoder::new(data, Encoding::Der);
155        let inner = dec
156            .decode::<synta_certificate::pkixalgs_types::EcdsaSigValue>()
157            .map_err(SyntaErr)?;
158        Ok(Self { inner })
159    }
160
161    /// Return the DER encoding of this ``ECDSA-Sig-Value`` SEQUENCE.
162    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
163        Ok(PyBytes::new(py, &self.inner.to_der().map_err(SyntaErr)?))
164    }
165
166    /// Signature integer ``r`` (big-endian two's-complement bytes).
167    #[getter]
168    fn r<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
169        PyBytes::new(py, self.inner.r.as_bytes())
170    }
171
172    /// Signature integer ``s`` (big-endian two's-complement bytes).
173    #[getter]
174    fn s<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
175        PyBytes::new(py, self.inner.s.as_bytes())
176    }
177
178    fn __repr__(&self) -> String {
179        format!(
180            "EcdsaSigValue(r=<{} bytes>, s=<{} bytes>)",
181            self.inner.r.as_bytes().len(),
182            self.inner.s.as_bytes().len(),
183        )
184    }
185}
186
187// ── PyECParameters ────────────────────────────────────────────────────────────
188
189/// EC domain parameters (RFC 3279 §2.3.5, X9.62).
190///
191/// A CHOICE with three alternatives:
192///
193/// * ``namedCurve`` — an OID identifying a well-known curve (most common in X.509)
194/// * ``ecParameters`` — explicit ``SpecifiedECDomain`` (rarely used in PKI)
195/// * ``implicitlyCA`` — NULL (inherit parameters from the CA certificate)
196///
197/// Use :attr:`arm` to determine which alternative is present, and
198/// :attr:`named_curve_oid` to obtain the OID for the ``namedCurve`` arm.
199///
200/// ```python,ignore
201/// import synta.pkixalgs as pa
202/// params = pa.ECParameters.from_der(alg_params_der)
203/// if params.arm == "namedCurve":
204///     print(params.named_curve_oid)
205/// ```
206#[pyclass(frozen, name = "ECParameters")]
207pub struct PyECParameters {
208    _data: Py<PyBytes>,
209    raw: &'static [u8],
210    inner: OnceLock<Box<synta_certificate::pkixalgs_types::ECParameters<'static>>>,
211}
212
213impl PyECParameters {
214    fn params(&self) -> PyResult<&synta_certificate::pkixalgs_types::ECParameters<'static>> {
215        if let Some(v) = self.inner.get() {
216            return Ok(v.as_ref());
217        }
218        let mut dec = Decoder::new(self.raw, Encoding::Der);
219        let decoded = dec
220            .decode::<synta_certificate::pkixalgs_types::ECParameters<'static>>()
221            .map_err(SyntaErr)?;
222        let _ = self.inner.set(Box::new(decoded));
223        Ok(self.inner.get().unwrap().as_ref())
224    }
225}
226
227#[pymethods]
228impl PyECParameters {
229    /// Parse a DER-encoded ``ECParameters`` CHOICE.
230    #[staticmethod]
231    fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
232        let py_bytes = data.unbind();
233        // Validate before storing
234        {
235            let raw = py_bytes.as_bytes(py);
236            Decoder::new(raw, Encoding::Der)
237                .decode::<synta_certificate::pkixalgs_types::ECParameters<'_>>()
238                .map_err(SyntaErr)?;
239        }
240        let raw: &'static [u8] = unsafe {
241            let s = py_bytes.bind(py).as_bytes();
242            std::slice::from_raw_parts(s.as_ptr(), s.len())
243        };
244        Ok(Self {
245            _data: py_bytes,
246            raw,
247            inner: OnceLock::new(),
248        })
249    }
250
251    /// Return the DER encoding of this ``ECParameters`` value.
252    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
253        Ok(PyBytes::new(
254            py,
255            &self.params()?.to_der().map_err(SyntaErr)?,
256        ))
257    }
258
259    /// Which CHOICE arm is present: ``"namedCurve"``, ``"ecParameters"``, or
260    /// ``"implicitlyCA"``.
261    #[getter]
262    fn arm(&self) -> PyResult<&'static str> {
263        use synta_certificate::pkixalgs_types::ECParameters::*;
264        Ok(match self.params()? {
265            NamedCurve(_) => "namedCurve",
266            EcParameters(_) => "ecParameters",
267            ImplicitlyCA(_) => "implicitlyCA",
268        })
269    }
270
271    /// The named-curve OID, or ``None`` if the arm is not ``namedCurve``.
272    #[getter]
273    fn named_curve_oid(&self, py: Python<'_>) -> PyResult<Option<Py<PyObjectIdentifier>>> {
274        use synta_certificate::pkixalgs_types::ECParameters::*;
275        match self.params()? {
276            NamedCurve(oid) => {
277                let obj = Py::new(py, PyObjectIdentifier::from_oid(oid.clone()))?;
278                Ok(Some(obj))
279            }
280            _ => Ok(None),
281        }
282    }
283
284    fn __repr__(&self) -> PyResult<String> {
285        Ok(format!("ECParameters(arm={})", self.arm()?))
286    }
287}
288
289// ── PyAlgorithmIdentifier ─────────────────────────────────────────────────────
290
291/// An X.509 ``AlgorithmIdentifier`` SEQUENCE (RFC 5280 §4.1.1.2).
292///
293/// Wraps an algorithm OID and optional parameters as a pre-encoded DER
294/// SEQUENCE.  Use :meth:`to_der` to obtain bytes ready for passing to builder
295/// APIs such as :meth:`~synta.CertificateListBuilder.signature_algorithm`.
296///
297/// Two constructors handle the two conventions used in practice:
298///
299/// * :meth:`from_oid` — RSA-family algorithms; encodes ``SEQUENCE { oid, NULL }``
300/// * :meth:`from_oid_no_params` — ECDSA/EdDSA; encodes ``SEQUENCE { oid }``
301///
302/// ```python,ignore
303/// import synta
304/// alg = synta.AlgorithmIdentifier.from_oid(synta.oids.SHA256_WITH_RSA)
305/// crl_builder.signature_algorithm(alg.to_der())
306/// ```
307#[pyclass(frozen, name = "AlgorithmIdentifier")]
308pub struct PyAlgorithmIdentifier {
309    der: Vec<u8>,
310    oid: synta::ObjectIdentifier,
311}
312
313impl PyAlgorithmIdentifier {
314    fn build(oid: synta::ObjectIdentifier, with_null: bool) -> PyResult<Self> {
315        // Encode inner content: OID [+ NULL]
316        let mut inner_enc = synta::Encoder::new(Encoding::Der);
317        inner_enc.encode(&oid).map_err(SyntaErr)?;
318        if with_null {
319            inner_enc.encode(&synta::Null).map_err(SyntaErr)?;
320        }
321        let inner = inner_enc.finish().map_err(SyntaErr)?;
322
323        // Wrap in SEQUENCE { ... }
324        let mut outer_enc = synta::Encoder::new(Encoding::Der);
325        outer_enc
326            .write_tag(synta::Tag::universal_constructed(synta::tag::TAG_SEQUENCE))
327            .map_err(SyntaErr)?;
328        outer_enc.write_length(inner.len()).map_err(SyntaErr)?;
329        outer_enc.write_bytes(&inner);
330        let der = outer_enc.finish().map_err(SyntaErr)?;
331
332        Ok(Self { der, oid })
333    }
334}
335
336#[pymethods]
337impl PyAlgorithmIdentifier {
338    /// Construct an ``AlgorithmIdentifier`` with an explicit ``NULL`` parameters field.
339    ///
340    /// Required for RSA-family signature algorithms (``sha256WithRSAEncryption``,
341    /// ``sha384WithRSAEncryption``, etc.) per RFC 3279 §2.2.1.
342    ///
343    /// :param oid: Algorithm :class:`~synta.ObjectIdentifier`.
344    #[staticmethod]
345    fn from_oid(oid: &PyObjectIdentifier) -> PyResult<Self> {
346        Self::build(oid.inner.clone(), true)
347    }
348
349    /// Construct an ``AlgorithmIdentifier`` without a parameters field.
350    ///
351    /// Used for ECDSA and EdDSA algorithms, which omit the parameters
352    /// field entirely per RFC 3279 §2.2.3 and RFC 8410.
353    ///
354    /// :param oid: Algorithm :class:`~synta.ObjectIdentifier`.
355    #[staticmethod]
356    fn from_oid_no_params(oid: &PyObjectIdentifier) -> PyResult<Self> {
357        Self::build(oid.inner.clone(), false)
358    }
359
360    /// Return the DER encoding of the complete ``AlgorithmIdentifier`` SEQUENCE.
361    ///
362    /// :returns: DER bytes suitable for
363    ///     :meth:`~synta.CertificateListBuilder.signature_algorithm` and
364    ///     similar builder methods.
365    fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
366        PyBytes::new(py, &self.der)
367    }
368
369    /// The algorithm OID.
370    #[getter]
371    fn oid(&self, py: Python<'_>) -> PyResult<Py<PyObjectIdentifier>> {
372        Py::new(py, PyObjectIdentifier::from_oid(self.oid.clone()))
373    }
374
375    fn __repr__(&self) -> String {
376        format!("AlgorithmIdentifier({})", self.oid)
377    }
378}
379
380// ── register_pkixalgs_submodule ───────────────────────────────────────────────
381
382/// Build and register the ``synta.pkixalgs`` submodule.
383pub(super) fn register_pkixalgs_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
384    let py = parent.py();
385    let m = PyModule::new(py, "pkixalgs")?;
386
387    m.add_class::<PyAlgorithmIdentifier>()?;
388    m.add_class::<PyDssParms>()?;
389    m.add_class::<PyDssSigValue>()?;
390    m.add_class::<PyEcdsaSigValue>()?;
391    m.add_class::<PyECParameters>()?;
392
393    // ── DSA / DH OIDs ────────────────────────────────────────────────────────
394    m.add(
395        "ID_DSA",
396        super::oid_const(py, synta_certificate::pkixalgs_types::ID_DSA),
397    )?;
398    m.add(
399        "ID_DSA_WITH_SHA1",
400        super::oid_const(py, synta_certificate::pkixalgs_types::ID_DSA_WITH_SHA1),
401    )?;
402    m.add(
403        "DHPUBLICNUMBER",
404        super::oid_const(py, synta_certificate::pkixalgs_types::DHPUBLICNUMBER),
405    )?;
406
407    // ── EC / ECDSA OIDs ──────────────────────────────────────────────────────
408    m.add(
409        "ID_EC_PUBLIC_KEY",
410        super::oid_const(py, synta_certificate::pkixalgs_types::ID_EC_PUBLIC_KEY),
411    )?;
412    m.add(
413        "ECDSA_WITH_SHA1",
414        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA1),
415    )?;
416    m.add(
417        "ECDSA_WITH_SHA256",
418        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA256),
419    )?;
420    m.add(
421        "ECDSA_WITH_SHA384",
422        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA384),
423    )?;
424    m.add(
425        "ECDSA_WITH_SHA512",
426        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA512),
427    )?;
428
429    // ── Named curve OIDs ─────────────────────────────────────────────────────
430    m.add(
431        "PRIME192V1",
432        super::oid_const(py, synta_certificate::pkixalgs_types::PRIME192V1),
433    )?;
434    m.add(
435        "PRIME256V1",
436        super::oid_const(py, synta_certificate::pkixalgs_types::PRIME256V1),
437    )?;
438    m.add(
439        "SECP224R1",
440        super::oid_const(py, synta_certificate::pkixalgs_types::SECP224R1),
441    )?;
442    m.add(
443        "SECP384R1",
444        super::oid_const(py, synta_certificate::pkixalgs_types::SECP384R1),
445    )?;
446    m.add(
447        "SECP521R1",
448        super::oid_const(py, synta_certificate::pkixalgs_types::SECP521R1),
449    )?;
450
451    crate::install_submodule(
452        parent,
453        &m,
454        "synta.pkixalgs",
455        Some(concat!(
456            "synta.pkixalgs — RFC 3279 algorithm parameter types.\n\n",
457            "Provides DssParms, DssSigValue, EcdsaSigValue, and ECParameters\n",
458            "for decoding DSA/DH domain parameters and DSA/ECDSA signature values,\n",
459            "along with OID constants for DSA, DH, EC, and named-curve algorithms.",
460        )),
461    )
462}