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::traits::Encode;
13use synta::{Decoder, Encoding};
14
15use crate::error::SyntaErr;
16use crate::types::PyObjectIdentifier;
17
18// ── PyDssParms ────────────────────────────────────────────────────────────────
19
20/// DSA domain parameters (RFC 3279 §2.3.2).
21///
22/// Carries the prime modulus ``p``, prime divisor ``q``, and generator ``g``
23/// parameters for a DSA public key.  Decoded from the ``parameters`` field
24/// of an ``AlgorithmIdentifier`` whose OID is ``id-dsa``.
25///
26/// ```python,ignore
27/// import synta.pkixalgs as pa
28/// parms = pa.DssParms.from_der(alg_id_params_der)
29/// print(len(parms.p))  # byte length of p
30/// ```
31#[pyclass(frozen, name = "DssParms")]
32pub struct PyDssParms {
33    inner: synta_certificate::pkixalgs_types::DssParms,
34}
35
36#[pymethods]
37impl PyDssParms {
38    /// Parse a DER-encoded ``Dss-Parms`` SEQUENCE.
39    ///
40    /// :param data: DER bytes of the ``Dss-Parms`` SEQUENCE.
41    /// :raises ValueError: if the bytes cannot be decoded.
42    #[staticmethod]
43    fn from_der(data: &[u8]) -> PyResult<Self> {
44        let mut dec = Decoder::new(data, Encoding::Der);
45        let inner = dec
46            .decode::<synta_certificate::pkixalgs_types::DssParms>()
47            .map_err(SyntaErr)?;
48        Ok(Self { inner })
49    }
50
51    /// Return the DER encoding of this ``Dss-Parms`` SEQUENCE.
52    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
53        let mut enc = synta::Encoder::new(Encoding::Der);
54        self.inner.encode(&mut enc).map_err(SyntaErr)?;
55        Ok(PyBytes::new(py, &enc.finish().map_err(SyntaErr)?))
56    }
57
58    /// Prime modulus ``p`` (big-endian two's-complement bytes).
59    #[getter]
60    fn p<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
61        PyBytes::new(py, self.inner.p.as_bytes())
62    }
63
64    /// Prime divisor ``q`` (big-endian two's-complement bytes).
65    #[getter]
66    fn q<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
67        PyBytes::new(py, self.inner.q.as_bytes())
68    }
69
70    /// Generator ``g`` (big-endian two's-complement bytes).
71    #[getter]
72    fn g<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
73        PyBytes::new(py, self.inner.g.as_bytes())
74    }
75
76    fn __repr__(&self) -> String {
77        format!(
78            "DssParms(p=<{} bytes>, q=<{} bytes>, g=<{} bytes>)",
79            self.inner.p.as_bytes().len(),
80            self.inner.q.as_bytes().len(),
81            self.inner.g.as_bytes().len(),
82        )
83    }
84}
85
86// ── PyDssSigValue ─────────────────────────────────────────────────────────────
87
88/// DSA signature value (RFC 3279 §2.2.2).
89///
90/// Contains the integer pair ``(r, s)`` produced by the DSA signing operation.
91#[pyclass(frozen, name = "DssSigValue")]
92pub struct PyDssSigValue {
93    inner: synta_certificate::pkixalgs_types::DssSigValue,
94}
95
96#[pymethods]
97impl PyDssSigValue {
98    /// Parse a DER-encoded ``Dss-Sig-Value`` SEQUENCE.
99    #[staticmethod]
100    fn from_der(data: &[u8]) -> PyResult<Self> {
101        let mut dec = Decoder::new(data, Encoding::Der);
102        let inner = dec
103            .decode::<synta_certificate::pkixalgs_types::DssSigValue>()
104            .map_err(SyntaErr)?;
105        Ok(Self { inner })
106    }
107
108    /// Return the DER encoding of this ``Dss-Sig-Value`` SEQUENCE.
109    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
110        let mut enc = synta::Encoder::new(Encoding::Der);
111        self.inner.encode(&mut enc).map_err(SyntaErr)?;
112        Ok(PyBytes::new(py, &enc.finish().map_err(SyntaErr)?))
113    }
114
115    /// Signature integer ``r`` (big-endian two's-complement bytes).
116    #[getter]
117    fn r<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
118        PyBytes::new(py, self.inner.r.as_bytes())
119    }
120
121    /// Signature integer ``s`` (big-endian two's-complement bytes).
122    #[getter]
123    fn s<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
124        PyBytes::new(py, self.inner.s.as_bytes())
125    }
126
127    fn __repr__(&self) -> String {
128        format!(
129            "DssSigValue(r=<{} bytes>, s=<{} bytes>)",
130            self.inner.r.as_bytes().len(),
131            self.inner.s.as_bytes().len(),
132        )
133    }
134}
135
136// ── PyEcdsaSigValue ───────────────────────────────────────────────────────────
137
138/// ECDSA signature value (RFC 3279 §2.2.3, X9.62).
139///
140/// Contains the integer pair ``(r, s)`` produced by the ECDSA signing
141/// operation.  Typically found as the ``subjectPublicKey`` bit-string content
142/// inside an X.509 certificate's ``AlgorithmIdentifier`` for ECDSA.
143///
144/// ```python,ignore
145/// import synta.pkixalgs as pa
146/// sig = pa.EcdsaSigValue.from_der(signature_bytes)
147/// r_bytes, s_bytes = sig.r, sig.s
148/// ```
149#[pyclass(frozen, name = "EcdsaSigValue")]
150pub struct PyEcdsaSigValue {
151    inner: synta_certificate::pkixalgs_types::EcdsaSigValue,
152}
153
154#[pymethods]
155impl PyEcdsaSigValue {
156    /// Parse a DER-encoded ``ECDSA-Sig-Value`` SEQUENCE.
157    #[staticmethod]
158    fn from_der(data: &[u8]) -> PyResult<Self> {
159        let mut dec = Decoder::new(data, Encoding::Der);
160        let inner = dec
161            .decode::<synta_certificate::pkixalgs_types::EcdsaSigValue>()
162            .map_err(SyntaErr)?;
163        Ok(Self { inner })
164    }
165
166    /// Return the DER encoding of this ``ECDSA-Sig-Value`` SEQUENCE.
167    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
168        let mut enc = synta::Encoder::new(Encoding::Der);
169        self.inner.encode(&mut enc).map_err(SyntaErr)?;
170        Ok(PyBytes::new(py, &enc.finish().map_err(SyntaErr)?))
171    }
172
173    /// Signature integer ``r`` (big-endian two's-complement bytes).
174    #[getter]
175    fn r<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
176        PyBytes::new(py, self.inner.r.as_bytes())
177    }
178
179    /// Signature integer ``s`` (big-endian two's-complement bytes).
180    #[getter]
181    fn s<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
182        PyBytes::new(py, self.inner.s.as_bytes())
183    }
184
185    fn __repr__(&self) -> String {
186        format!(
187            "EcdsaSigValue(r=<{} bytes>, s=<{} bytes>)",
188            self.inner.r.as_bytes().len(),
189            self.inner.s.as_bytes().len(),
190        )
191    }
192}
193
194// ── PyECParameters ────────────────────────────────────────────────────────────
195
196/// EC domain parameters (RFC 3279 §2.3.5, X9.62).
197///
198/// A CHOICE with three alternatives:
199///
200/// * ``namedCurve`` — an OID identifying a well-known curve (most common in X.509)
201/// * ``ecParameters`` — explicit ``SpecifiedECDomain`` (rarely used in PKI)
202/// * ``implicitlyCA`` — NULL (inherit parameters from the CA certificate)
203///
204/// Use :attr:`arm` to determine which alternative is present, and
205/// :attr:`named_curve_oid` to obtain the OID for the ``namedCurve`` arm.
206///
207/// ```python,ignore
208/// import synta.pkixalgs as pa
209/// params = pa.ECParameters.from_der(alg_params_der)
210/// if params.arm == "namedCurve":
211///     print(params.named_curve_oid)
212/// ```
213#[pyclass(frozen, name = "ECParameters")]
214pub struct PyECParameters {
215    _data: Py<PyBytes>,
216    raw: &'static [u8],
217    inner: OnceLock<Box<synta_certificate::pkixalgs_types::ECParameters<'static>>>,
218}
219
220impl PyECParameters {
221    fn params(&self) -> PyResult<&synta_certificate::pkixalgs_types::ECParameters<'static>> {
222        if let Some(v) = self.inner.get() {
223            return Ok(v.as_ref());
224        }
225        let mut dec = Decoder::new(self.raw, Encoding::Der);
226        let decoded = dec
227            .decode::<synta_certificate::pkixalgs_types::ECParameters<'_>>()
228            .map_err(SyntaErr)?;
229        // SAFETY: raw is pinned for the lifetime of self (kept alive by _data).
230        let decoded: synta_certificate::pkixalgs_types::ECParameters<'static> =
231            unsafe { std::mem::transmute(decoded) };
232        let _ = self.inner.set(Box::new(decoded));
233        Ok(self.inner.get().unwrap().as_ref())
234    }
235}
236
237#[pymethods]
238impl PyECParameters {
239    /// Parse a DER-encoded ``ECParameters`` CHOICE.
240    #[staticmethod]
241    fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
242        let py_bytes = data.unbind();
243        // Validate before storing
244        {
245            let raw = py_bytes.as_bytes(py);
246            Decoder::new(raw, Encoding::Der)
247                .decode::<synta_certificate::pkixalgs_types::ECParameters<'_>>()
248                .map_err(SyntaErr)?;
249        }
250        let raw: &'static [u8] = unsafe { std::mem::transmute(py_bytes.as_bytes(py)) };
251        Ok(Self {
252            _data: py_bytes,
253            raw,
254            inner: OnceLock::new(),
255        })
256    }
257
258    /// Return the DER encoding of this ``ECParameters`` value.
259    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
260        let mut enc = synta::Encoder::new(Encoding::Der);
261        self.params()?.encode(&mut enc).map_err(SyntaErr)?;
262        Ok(PyBytes::new(py, &enc.finish().map_err(SyntaErr)?))
263    }
264
265    /// Which CHOICE arm is present: ``"namedCurve"``, ``"ecParameters"``, or
266    /// ``"implicitlyCA"``.
267    #[getter]
268    fn arm(&self) -> PyResult<&'static str> {
269        use synta_certificate::pkixalgs_types::ECParameters::*;
270        Ok(match self.params()? {
271            NamedCurve(_) => "namedCurve",
272            EcParameters(_) => "ecParameters",
273            ImplicitlyCA(_) => "implicitlyCA",
274        })
275    }
276
277    /// The named-curve OID, or ``None`` if the arm is not ``namedCurve``.
278    #[getter]
279    fn named_curve_oid(&self, py: Python<'_>) -> PyResult<Option<Py<PyObjectIdentifier>>> {
280        use synta_certificate::pkixalgs_types::ECParameters::*;
281        match self.params()? {
282            NamedCurve(oid) => {
283                let obj = Py::new(py, PyObjectIdentifier::from_oid(oid.clone()))?;
284                Ok(Some(obj))
285            }
286            _ => Ok(None),
287        }
288    }
289
290    fn __repr__(&self) -> PyResult<String> {
291        Ok(format!("ECParameters(arm={})", self.arm()?))
292    }
293}
294
295// ── register_pkixalgs_submodule ───────────────────────────────────────────────
296
297/// Build and register the ``synta.pkixalgs`` submodule.
298pub(super) fn register_pkixalgs_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
299    let py = parent.py();
300    let m = PyModule::new(py, "pkixalgs")?;
301
302    m.add_class::<PyDssParms>()?;
303    m.add_class::<PyDssSigValue>()?;
304    m.add_class::<PyEcdsaSigValue>()?;
305    m.add_class::<PyECParameters>()?;
306
307    // ── DSA / DH OIDs ────────────────────────────────────────────────────────
308    m.add(
309        "ID_DSA",
310        super::oid_const(py, synta_certificate::pkixalgs_types::ID_DSA),
311    )?;
312    m.add(
313        "ID_DSA_WITH_SHA1",
314        super::oid_const(py, synta_certificate::pkixalgs_types::ID_DSA_WITH_SHA1),
315    )?;
316    m.add(
317        "DHPUBLICNUMBER",
318        super::oid_const(py, synta_certificate::pkixalgs_types::DHPUBLICNUMBER),
319    )?;
320
321    // ── EC / ECDSA OIDs ──────────────────────────────────────────────────────
322    m.add(
323        "ID_EC_PUBLIC_KEY",
324        super::oid_const(py, synta_certificate::pkixalgs_types::ID_EC_PUBLIC_KEY),
325    )?;
326    m.add(
327        "ECDSA_WITH_SHA1",
328        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA1),
329    )?;
330    m.add(
331        "ECDSA_WITH_SHA256",
332        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA256),
333    )?;
334    m.add(
335        "ECDSA_WITH_SHA384",
336        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA384),
337    )?;
338    m.add(
339        "ECDSA_WITH_SHA512",
340        super::oid_const(py, synta_certificate::pkixalgs_types::ECDSA_WITH_SHA512),
341    )?;
342
343    // ── Named curve OIDs ─────────────────────────────────────────────────────
344    m.add(
345        "PRIME192V1",
346        super::oid_const(py, synta_certificate::pkixalgs_types::PRIME192V1),
347    )?;
348    m.add(
349        "PRIME256V1",
350        super::oid_const(py, synta_certificate::pkixalgs_types::PRIME256V1),
351    )?;
352    m.add(
353        "SECP224R1",
354        super::oid_const(py, synta_certificate::pkixalgs_types::SECP224R1),
355    )?;
356    m.add(
357        "SECP384R1",
358        super::oid_const(py, synta_certificate::pkixalgs_types::SECP384R1),
359    )?;
360    m.add(
361        "SECP521R1",
362        super::oid_const(py, synta_certificate::pkixalgs_types::SECP521R1),
363    )?;
364
365    crate::install_submodule(
366        parent,
367        &m,
368        "synta.pkixalgs",
369        Some(concat!(
370            "synta.pkixalgs — RFC 3279 algorithm parameter types.\n\n",
371            "Provides DssParms, DssSigValue, EcdsaSigValue, and ECParameters\n",
372            "for decoding DSA/DH domain parameters and DSA/ECDSA signature values,\n",
373            "along with OID constants for DSA, DH, EC, and named-curve algorithms.",
374        )),
375    )
376}