Skip to main content

_synta/certificate/
mod.rs

1//! Python bindings for X.509 and related PKI types.
2//!
3//! Exposes [`PyCertificate`], [`PyCsr`], [`PyCrl`], and [`PyOcspResponse`] as
4//! pyo3 classes, along with OID-lookup helper functions and the
5//! ``synta.oids`` / ``synta.oids.attr`` submodules.
6//!
7//! This module is part of the `synta-python` cdylib.  Call [`register_module`]
8//! from the `#[pymodule]` entry point to expose these types to Python.
9
10use std::str::FromStr;
11
12use pyo3::prelude::*;
13
14use synta::ObjectIdentifier;
15
16use crate::types::{PyObjectIdentifier, PyRelativeOid};
17
18pub mod ac;
19pub mod ace88;
20pub mod acme;
21pub mod cert;
22pub mod cert_builder;
23pub mod cmp;
24pub mod cms;
25pub mod crmf;
26pub mod csr_builder;
27pub mod ess;
28pub mod general_name;
29pub mod kem;
30pub mod key_types;
31pub mod logotype;
32pub mod ms_pki;
33pub mod name_builder;
34pub mod pkcs5;
35pub mod pkcs8;
36pub mod pkcs9;
37pub mod pkix;
38pub mod pkixalgs;
39pub mod tsp;
40
41use ac::register_ac_submodule;
42use ace88::register_ace88_classes;
43use acme::register_acme_submodule;
44use cert::{PyCertificate, PyPolicyInformation, PyPolicyQualifier};
45use cert_builder::PyCertificateBuilder;
46use cmp::register_cmp_submodule;
47use cms::register_cms_submodule;
48use crmf::register_crmf_submodule;
49use csr_builder::PyCsrBuilder;
50use ess::register_ess_classes;
51use kem::register_kem_submodule;
52use key_types::{PyMlDsaPrivateKey, PyMlDsaPublicKey, PyRsaPublicKey, PyRsassaPssParams};
53use logotype::register_logotype_classes;
54use ms_pki::register_ms_pki_submodule;
55use name_builder::PyNameBuilder;
56use pkcs5::register_pkcs5_submodule;
57use pkcs8::register_pkcs8_submodule;
58use pkcs9::register_pkcs9_submodule;
59use pkix::{
60    PyCertID, PyCertificateListBuilder, PyCrl, PyCsr, PyOCSPCertIDSpec, PyOCSPRequestBuilder,
61    PyOCSPResponseBuilder, PyOCSPSingleResponse, PyOcspRequest, PyOcspResponse,
62};
63use pkixalgs::{register_pkixalgs_submodule, PyAlgorithmIdentifier};
64use tsp::register_tsp_classes;
65
66pub use pkix::{
67    create_pkcs12, encode_extended_key_usage, encode_subject_alt_names,
68    load_der_pkcs7_certificates, load_pem_pkcs7_certificates, load_pkcs12,
69    load_pkcs12_certificates, load_pkcs12_keys, name_der_equal, read_pki_blocks,
70};
71
72/// Accept either a Python ``str`` (dotted-decimal OID) or an
73/// ``ObjectIdentifier`` object and return the underlying Rust value.
74pub(crate) fn oid_from_pyany(obj: &Bound<'_, PyAny>) -> PyResult<ObjectIdentifier> {
75    if let Ok(oid_ref) = obj.extract::<PyRef<PyObjectIdentifier>>() {
76        return Ok(oid_ref.inner.clone());
77    }
78    if let Ok(s) = obj.extract::<String>() {
79        return ObjectIdentifier::from_str(&s)
80            .map_err(|_| pyo3::exceptions::PyValueError::new_err(format!("invalid OID: {s}")));
81    }
82    Err(pyo3::exceptions::PyTypeError::new_err(
83        "oid must be a str or ObjectIdentifier",
84    ))
85}
86
87/// Convert a well-known `&[u32]` component slice to a Python `ObjectIdentifier`.
88///
89/// Panics at module-init time if the slice is not a valid OID — every caller
90/// passes a constant generated from the ASN.1 schema, so a panic here is a
91/// bug in the source data.
92pub(crate) fn oid_const(py: Python<'_>, components: &[u32]) -> Py<PyAny> {
93    let inner = ObjectIdentifier::new(components)
94        .expect("oid constant has invalid components — bug in synta-certificate");
95    Py::new(py, PyObjectIdentifier::from_oid(inner))
96        .expect("PyObjectIdentifier allocation failed")
97        .into_any()
98}
99
100/// Call this from a cdylib extension crate's `#[pymodule]` function:
101///
102/// ```rust,ignore
103/// #[pymodule]
104/// fn my_extension(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
105///     synta_certificate::python::register_module(m)
106/// }
107/// ```
108pub fn register_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
109    m.add_class::<PyAlgorithmIdentifier>()?;
110    m.add_class::<PyObjectIdentifier>()?;
111    m.add_class::<PyRelativeOid>()?;
112    m.add_class::<PyPolicyQualifier>()?;
113    m.add_class::<PyPolicyInformation>()?;
114    m.add_class::<PyCertificate>()?;
115    m.add_class::<PyCertificateBuilder>()?;
116    m.add_class::<PyCsrBuilder>()?;
117    m.add_class::<PyNameBuilder>()?;
118    m.add_class::<PyCsr>()?;
119    m.add_class::<PyCrl>()?;
120    m.add_class::<PyCertificateListBuilder>()?;
121    m.add_class::<PyOcspResponse>()?;
122    m.add_class::<PyOcspRequest>()?;
123    m.add_class::<PyCertID>()?;
124    m.add_class::<PyOCSPSingleResponse>()?;
125    m.add_class::<PyOCSPResponseBuilder>()?;
126    m.add_class::<PyOCSPCertIDSpec>()?;
127    m.add_class::<PyOCSPRequestBuilder>()?;
128    m.add_class::<PyMlDsaPrivateKey>()?;
129    m.add_class::<PyMlDsaPublicKey>()?;
130    m.add_class::<PyRsaPublicKey>()?;
131    m.add_class::<PyRsassaPssParams>()?;
132    // ML-DSA algorithm OID constants — also available as synta.oids.ML_DSA_*.
133    // Exposed here so users can write synta.ML_DSA_44 instead of synta.oids.ML_DSA_44
134    // when working with MlDsaPrivateKey / MlDsaPublicKey in the same import.
135    let py = m.py();
136    m.add(
137        "ML_DSA_44",
138        oid_const(py, synta_certificate::oids::ML_DSA_44),
139    )?;
140    m.add(
141        "ML_DSA_65",
142        oid_const(py, synta_certificate::oids::ML_DSA_65),
143    )?;
144    m.add(
145        "ML_DSA_87",
146        oid_const(py, synta_certificate::oids::ML_DSA_87),
147    )?;
148    m.add_function(wrap_pyfunction!(load_der_pkcs7_certificates, m)?)?;
149    m.add_function(wrap_pyfunction!(load_pem_pkcs7_certificates, m)?)?;
150    m.add_function(wrap_pyfunction!(load_pkcs12_certificates, m)?)?;
151    m.add_function(wrap_pyfunction!(load_pkcs12_keys, m)?)?;
152    m.add_function(wrap_pyfunction!(load_pkcs12, m)?)?;
153    m.add_function(wrap_pyfunction!(read_pki_blocks, m)?)?;
154    m.add_function(wrap_pyfunction!(create_pkcs12, m)?)?;
155    register_tsp_classes(m)?;
156    register_ess_classes(m)?;
157    register_logotype_classes(m)?;
158    register_ace88_classes(m)?;
159    register_oids_submodule(m)?;
160    register_cms_submodule(m)?;
161    register_general_name_submodule(m)?;
162    register_pkixalgs_submodule(m)?;
163    register_kem_submodule(m)?;
164    register_ac_submodule(m)?;
165    register_crmf_submodule(m)?;
166    register_cmp_submodule(m)?;
167    register_ms_pki_submodule(m)?;
168    register_pkcs8_submodule(m)?;
169    register_pkcs9_submodule(m)?;
170    register_pkcs5_submodule(m)?;
171    register_acme_submodule(m)?;
172    Ok(())
173}
174
175// ── OID helper functions (exposed in synta.oids) ─────────────────────────────
176//
177// Each function accepts either a PyObjectIdentifier or a dotted-decimal str,
178// delegating to the corresponding crate-level helper in lib.rs.
179
180/// Return the canonical display name for a signature algorithm OID.
181///
182/// Recognises RSA (PKCS #1) variants, ECDSA variants, EdDSA, ML-DSA (FIPS
183/// 204), and SLH-DSA (FIPS 205). Returns ``"Other"`` for unknown OIDs.
184///
185/// :param oid: :class:`~synta.ObjectIdentifier` or dotted-decimal ``str``.
186/// :returns: algorithm name string (one of the ``synta.oids`` name constants).
187#[pyfunction]
188fn identify_signature_algorithm(oid: &Bound<'_, PyAny>) -> PyResult<&'static str> {
189    let inner = oid_from_pyany(oid)?;
190    Ok(synta_certificate::identify_signature_algorithm(&inner))
191}
192
193/// Return the canonical display name for a public-key algorithm OID, if known.
194///
195/// Recognises RSA, EC/ECDSA, DSA, EdDSA, ML-DSA, and ML-KEM.
196/// Returns ``None`` for unrecognised OIDs.
197///
198/// :param oid: :class:`~synta.ObjectIdentifier` or dotted-decimal ``str``.
199/// :returns: algorithm name string or ``None``.
200#[pyfunction]
201fn identify_public_key_algorithm(oid: &Bound<'_, PyAny>) -> PyResult<Option<&'static str>> {
202    let inner = oid_from_pyany(oid)?;
203    Ok(synta_certificate::identify_public_key_algorithm(&inner))
204}
205
206/// Return the short ASN.1 name for a well-known EC named-curve OID.
207///
208/// Returns ``None`` for unrecognised curves.
209///
210/// :param oid: :class:`~synta.ObjectIdentifier` or dotted-decimal ``str``.
211/// :returns: e.g. ``"prime256v1"``, ``"secp384r1"``, or ``None``.
212#[pyfunction]
213fn ec_curve_short_name(oid: &Bound<'_, PyAny>) -> PyResult<Option<&'static str>> {
214    let inner = oid_from_pyany(oid)?;
215    Ok(synta_certificate::ec_curve_short_name(inner.components()))
216}
217
218/// Return the NIST name for a well-known EC named-curve OID.
219///
220/// Returns ``None`` for curves with no NIST name (e.g. ``secp256k1``).
221///
222/// :param oid: :class:`~synta.ObjectIdentifier` or dotted-decimal ``str``.
223/// :returns: e.g. ``"P-256"``, ``"P-384"``, or ``None``.
224#[pyfunction]
225fn ec_curve_nist_name(oid: &Bound<'_, PyAny>) -> PyResult<Option<&'static str>> {
226    let inner = oid_from_pyany(oid)?;
227    Ok(synta_certificate::ec_curve_nist_name(inner.components()))
228}
229
230/// Return the key size in bits for a well-known EC named-curve OID.
231///
232/// Returns the field size (security parameter) as reported by OpenSSL.
233/// Returns ``None`` for unrecognised curves.
234///
235/// :param oid: :class:`~synta.ObjectIdentifier` or dotted-decimal ``str``.
236/// :returns: e.g. ``256``, ``384``, ``521``, or ``None``.
237#[pyfunction]
238fn ec_curve_key_bits(oid: &Bound<'_, PyAny>) -> PyResult<Option<usize>> {
239    let inner = oid_from_pyany(oid)?;
240    Ok(synta_certificate::ec_curve_key_bits(inner.components()))
241}
242
243/// Return the display name for a well-known X.509v3 extension OID.
244///
245/// Covers all RFC 5280 standard extensions and the Certificate Transparency
246/// SCT extension. Returns the dotted-decimal string for unknown OIDs.
247///
248/// :param oid: :class:`~synta.ObjectIdentifier` or dotted-decimal ``str``.
249/// :returns: human-readable name, e.g. ``"X509v3 Subject Alternative Name"``.
250#[pyfunction]
251fn extension_oid_name(oid: &Bound<'_, PyAny>) -> PyResult<String> {
252    let inner = oid_from_pyany(oid)?;
253    Ok(synta_certificate::extension_oid_name(&inner))
254}
255
256// ── OID constants submodule ──────────────────────────────────────────────────
257
258/// Build and register the ``synta.oids`` submodule.
259///
260/// Also registers ``synta.oids.attr`` for DN attribute OIDs and installs both
261/// into ``sys.modules`` so that ``from synta.oids import ...`` works.
262fn register_oids_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
263    let py = parent.py();
264    let m = PyModule::new(py, "oids")?;
265
266    // ── ML-DSA (FIPS 204) ────────────────────────────────────────────────────
267    m.add(
268        "ML_DSA_44",
269        oid_const(py, synta_certificate::oids::ML_DSA_44),
270    )?;
271    m.add(
272        "ML_DSA_65",
273        oid_const(py, synta_certificate::oids::ML_DSA_65),
274    )?;
275    m.add(
276        "ML_DSA_87",
277        oid_const(py, synta_certificate::oids::ML_DSA_87),
278    )?;
279
280    // ── ML-KEM (FIPS 203) ────────────────────────────────────────────────────
281    m.add(
282        "ML_KEM_512",
283        oid_const(py, synta_certificate::oids::ML_KEM_512),
284    )?;
285    m.add(
286        "ML_KEM_768",
287        oid_const(py, synta_certificate::oids::ML_KEM_768),
288    )?;
289    m.add(
290        "ML_KEM_1024",
291        oid_const(py, synta_certificate::oids::ML_KEM_1024),
292    )?;
293
294    // ── SLH-DSA (FIPS 205) ───────────────────────────────────────────────────
295    m.add(
296        "SLH_DSA_SHA2_128S",
297        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_128S),
298    )?;
299    m.add(
300        "SLH_DSA_SHA2_128F",
301        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_128F),
302    )?;
303    m.add(
304        "SLH_DSA_SHA2_192S",
305        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_192S),
306    )?;
307    m.add(
308        "SLH_DSA_SHA2_192F",
309        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_192F),
310    )?;
311    m.add(
312        "SLH_DSA_SHA2_256S",
313        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_256S),
314    )?;
315    m.add(
316        "SLH_DSA_SHA2_256F",
317        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_256F),
318    )?;
319    m.add(
320        "SLH_DSA_SHAKE_128S",
321        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_128S),
322    )?;
323    m.add(
324        "SLH_DSA_SHAKE_128F",
325        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_128F),
326    )?;
327    m.add(
328        "SLH_DSA_SHAKE_192S",
329        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_192S),
330    )?;
331    m.add(
332        "SLH_DSA_SHAKE_192F",
333        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_192F),
334    )?;
335    m.add(
336        "SLH_DSA_SHAKE_256S",
337        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_256S),
338    )?;
339    m.add(
340        "SLH_DSA_SHAKE_256F",
341        oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_256F),
342    )?;
343
344    // ── Composite ML-DSA (draft-ietf-lamps-pq-composite-sigs-19) ────────────
345    m.add(
346        "COMPOSITE_MLDSA_ARC",
347        oid_const(py, synta_certificate::oids::COMPOSITE_MLDSA_ARC),
348    )?;
349    m.add(
350        "MLDSA44_RSA2048_PSS_SHA256",
351        oid_const(py, synta_certificate::oids::MLDSA44_RSA2048_PSS_SHA256),
352    )?;
353    m.add(
354        "MLDSA44_RSA2048_PKCS15_SHA256",
355        oid_const(py, synta_certificate::oids::MLDSA44_RSA2048_PKCS15_SHA256),
356    )?;
357    m.add(
358        "MLDSA44_ED25519_SHA512",
359        oid_const(py, synta_certificate::oids::MLDSA44_ED25519_SHA512),
360    )?;
361    m.add(
362        "MLDSA44_ECDSA_P256_SHA256",
363        oid_const(py, synta_certificate::oids::MLDSA44_ECDSA_P256_SHA256),
364    )?;
365    m.add(
366        "MLDSA65_RSA3072_PSS_SHA512",
367        oid_const(py, synta_certificate::oids::MLDSA65_RSA3072_PSS_SHA512),
368    )?;
369    m.add(
370        "MLDSA65_RSA3072_PKCS15_SHA512",
371        oid_const(py, synta_certificate::oids::MLDSA65_RSA3072_PKCS15_SHA512),
372    )?;
373    m.add(
374        "MLDSA65_RSA4096_PSS_SHA512",
375        oid_const(py, synta_certificate::oids::MLDSA65_RSA4096_PSS_SHA512),
376    )?;
377    m.add(
378        "MLDSA65_RSA4096_PKCS15_SHA512",
379        oid_const(py, synta_certificate::oids::MLDSA65_RSA4096_PKCS15_SHA512),
380    )?;
381    m.add(
382        "MLDSA65_ECDSA_P256_SHA512",
383        oid_const(py, synta_certificate::oids::MLDSA65_ECDSA_P256_SHA512),
384    )?;
385    m.add(
386        "MLDSA65_ECDSA_P384_SHA512",
387        oid_const(py, synta_certificate::oids::MLDSA65_ECDSA_P384_SHA512),
388    )?;
389    m.add(
390        "MLDSA65_ECDSA_BRAINPOOL_P256R1_SHA512",
391        oid_const(
392            py,
393            synta_certificate::oids::MLDSA65_ECDSA_BRAINPOOL_P256R1_SHA512,
394        ),
395    )?;
396    m.add(
397        "MLDSA65_ED25519_SHA512",
398        oid_const(py, synta_certificate::oids::MLDSA65_ED25519_SHA512),
399    )?;
400    m.add(
401        "MLDSA87_ECDSA_P384_SHA512",
402        oid_const(py, synta_certificate::oids::MLDSA87_ECDSA_P384_SHA512),
403    )?;
404    m.add(
405        "MLDSA87_ECDSA_BRAINPOOL_P384R1_SHA512",
406        oid_const(
407            py,
408            synta_certificate::oids::MLDSA87_ECDSA_BRAINPOOL_P384R1_SHA512,
409        ),
410    )?;
411    m.add(
412        "MLDSA87_ED448_SHAKE256",
413        oid_const(py, synta_certificate::oids::MLDSA87_ED448_SHAKE256),
414    )?;
415    m.add(
416        "MLDSA87_RSA3072_PSS_SHA512",
417        oid_const(py, synta_certificate::oids::MLDSA87_RSA3072_PSS_SHA512),
418    )?;
419    m.add(
420        "MLDSA87_RSA4096_PSS_SHA512",
421        oid_const(py, synta_certificate::oids::MLDSA87_RSA4096_PSS_SHA512),
422    )?;
423    m.add(
424        "MLDSA87_ECDSA_P521_SHA512",
425        oid_const(py, synta_certificate::oids::MLDSA87_ECDSA_P521_SHA512),
426    )?;
427
428    // ── Composite ML-KEM (draft-ietf-lamps-pq-composite-kem-18) ─────────────
429    m.add(
430        "COMPOSITE_KEM_ARC",
431        oid_const(py, synta_certificate::oids::COMPOSITE_KEM_ARC),
432    )?;
433    m.add(
434        "MLKEM768_RSA2048_SHA3_256",
435        oid_const(py, synta_certificate::oids::MLKEM768_RSA2048_SHA3_256),
436    )?;
437    m.add(
438        "MLKEM768_RSA3072_SHA3_256",
439        oid_const(py, synta_certificate::oids::MLKEM768_RSA3072_SHA3_256),
440    )?;
441    m.add(
442        "MLKEM768_RSA4096_SHA3_256",
443        oid_const(py, synta_certificate::oids::MLKEM768_RSA4096_SHA3_256),
444    )?;
445    m.add(
446        "MLKEM768_X25519_SHA3_256",
447        oid_const(py, synta_certificate::oids::MLKEM768_X25519_SHA3_256),
448    )?;
449    m.add(
450        "MLKEM768_ECDH_P256_SHA3_256",
451        oid_const(py, synta_certificate::oids::MLKEM768_ECDH_P256_SHA3_256),
452    )?;
453    m.add(
454        "MLKEM768_ECDH_P384_SHA3_256",
455        oid_const(py, synta_certificate::oids::MLKEM768_ECDH_P384_SHA3_256),
456    )?;
457    m.add(
458        "MLKEM768_ECDH_BRAINPOOL_P256R1_SHA3_256",
459        oid_const(
460            py,
461            synta_certificate::oids::MLKEM768_ECDH_BRAINPOOL_P256R1_SHA3_256,
462        ),
463    )?;
464    m.add(
465        "MLKEM1024_RSA3072_SHA3_256",
466        oid_const(py, synta_certificate::oids::MLKEM1024_RSA3072_SHA3_256),
467    )?;
468    m.add(
469        "MLKEM1024_ECDH_P384_SHA3_256",
470        oid_const(py, synta_certificate::oids::MLKEM1024_ECDH_P384_SHA3_256),
471    )?;
472    m.add(
473        "MLKEM1024_ECDH_BRAINPOOL_P384R1_SHA3_256",
474        oid_const(
475            py,
476            synta_certificate::oids::MLKEM1024_ECDH_BRAINPOOL_P384R1_SHA3_256,
477        ),
478    )?;
479    m.add(
480        "MLKEM1024_X448_SHA3_256",
481        oid_const(py, synta_certificate::oids::MLKEM1024_X448_SHA3_256),
482    )?;
483    m.add(
484        "MLKEM1024_ECDH_P521_SHA3_256",
485        oid_const(py, synta_certificate::oids::MLKEM1024_ECDH_P521_SHA3_256),
486    )?;
487
488    // ── EdDSA (RFC 8410) ─────────────────────────────────────────────────────
489    m.add("ED25519", oid_const(py, synta_certificate::oids::ED25519))?;
490    m.add("ED448", oid_const(py, synta_certificate::oids::ED448))?;
491
492    // ── RSA (PKCS #1) ────────────────────────────────────────────────────────
493    m.add(
494        "RSA_ENCRYPTION",
495        oid_const(py, synta_certificate::oids::RSA_ENCRYPTION),
496    )?;
497    m.add(
498        "MD5_WITH_RSA",
499        oid_const(py, synta_certificate::oids::MD5_WITH_RSA),
500    )?;
501    m.add(
502        "SHA1_WITH_RSA",
503        oid_const(py, synta_certificate::oids::SHA1_WITH_RSA),
504    )?;
505    m.add(
506        "SHA256_WITH_RSA",
507        oid_const(py, synta_certificate::oids::SHA256_WITH_RSA),
508    )?;
509    m.add(
510        "SHA384_WITH_RSA",
511        oid_const(py, synta_certificate::oids::SHA384_WITH_RSA),
512    )?;
513    m.add(
514        "SHA512_WITH_RSA",
515        oid_const(py, synta_certificate::oids::SHA512_WITH_RSA),
516    )?;
517    // Prefix OID covering all PKCS #1 RSA variants
518    m.add("RSA", oid_const(py, synta_certificate::oids::RSA))?;
519    // PKCS #1 algorithm OIDs (RFC 8017)
520    m.add(
521        "MD2_WITH_RSA",
522        oid_const(py, synta_certificate::oids::MD2_WITH_RSA),
523    )?;
524    m.add(
525        "SHA512_224_WITH_RSA",
526        oid_const(py, synta_certificate::oids::SHA512_224_WITH_RSA),
527    )?;
528    m.add(
529        "SHA512_256_WITH_RSA",
530        oid_const(py, synta_certificate::oids::SHA512_256_WITH_RSA),
531    )?;
532    m.add(
533        "RSASSA_PSS",
534        oid_const(py, synta_certificate::oids::RSASSA_PSS),
535    )?;
536    m.add(
537        "RSAES_OAEP",
538        oid_const(py, synta_certificate::oids::RSAES_OAEP),
539    )?;
540    m.add("MGF1", oid_const(py, synta_certificate::oids::MGF1))?;
541    m.add(
542        "P_SPECIFIED",
543        oid_const(py, synta_certificate::oids::P_SPECIFIED),
544    )?;
545
546    // ── ECDSA (ANSI X9.62 / RFC 5758) ────────────────────────────────────────
547    m.add(
548        "EC_PUBLIC_KEY",
549        oid_const(py, synta_certificate::oids::EC_PUBLIC_KEY),
550    )?;
551    m.add(
552        "ECDSA_WITH_SHA1",
553        oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA1),
554    )?;
555    m.add(
556        "ECDSA_WITH_SHA256",
557        oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA256),
558    )?;
559    m.add(
560        "ECDSA_WITH_SHA384",
561        oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA384),
562    )?;
563    m.add(
564        "ECDSA_WITH_SHA512",
565        oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA512),
566    )?;
567    // Prefix OID covering all ECDSA signature variants
568    m.add("ECDSA", oid_const(py, synta_certificate::oids::ECDSA_SIG))?;
569
570    // ── EC named curves (SEC 2 / RFC 5480) ───────────────────────────────────
571    m.add(
572        "EC_CURVE_P256",
573        oid_const(py, synta_certificate::oids::EC_CURVE_P256),
574    )?;
575    m.add(
576        "EC_CURVE_P384",
577        oid_const(py, synta_certificate::oids::EC_CURVE_P384),
578    )?;
579    m.add(
580        "EC_CURVE_P521",
581        oid_const(py, synta_certificate::oids::EC_CURVE_P521),
582    )?;
583    m.add(
584        "EC_CURVE_SECP256K1",
585        oid_const(py, synta_certificate::oids::EC_CURVE_SECP256K1),
586    )?;
587
588    // ── SHA-2 hash algorithms (FIPS 180-4) ───────────────────────────────────
589    m.add("SHA224", oid_const(py, synta_certificate::oids::ID_SHA224))?;
590    m.add("SHA256", oid_const(py, synta_certificate::oids::ID_SHA256))?;
591    m.add("SHA384", oid_const(py, synta_certificate::oids::ID_SHA384))?;
592    m.add("SHA512", oid_const(py, synta_certificate::oids::ID_SHA512))?;
593    m.add(
594        "SHA512_224",
595        oid_const(py, synta_certificate::oids::ID_SHA512_224),
596    )?;
597    m.add(
598        "SHA512_256",
599        oid_const(py, synta_certificate::oids::ID_SHA512_256),
600    )?;
601
602    // ── SHA-3 hash algorithms and XOFs (FIPS 202) ────────────────────────────
603    m.add(
604        "SHA3_224",
605        oid_const(py, synta_certificate::oids::ID_SHA3_224),
606    )?;
607    m.add(
608        "SHA3_256",
609        oid_const(py, synta_certificate::oids::ID_SHA3_256),
610    )?;
611    m.add(
612        "SHA3_384",
613        oid_const(py, synta_certificate::oids::ID_SHA3_384),
614    )?;
615    m.add(
616        "SHA3_512",
617        oid_const(py, synta_certificate::oids::ID_SHA3_512),
618    )?;
619    m.add(
620        "SHAKE128",
621        oid_const(py, synta_certificate::oids::ID_SHAKE128),
622    )?;
623    m.add(
624        "SHAKE256",
625        oid_const(py, synta_certificate::oids::ID_SHAKE256),
626    )?;
627
628    // ── X.509v3 extension OIDs (RFC 5280 / 2.5.29.*) ─────────────────────────
629    m.add(
630        "SUBJECT_KEY_IDENTIFIER",
631        oid_const(py, synta_certificate::oids::SUBJECT_KEY_IDENTIFIER),
632    )?;
633    m.add(
634        "KEY_USAGE",
635        oid_const(py, synta_certificate::oids::KEY_USAGE),
636    )?;
637    m.add(
638        "SUBJECT_ALT_NAME",
639        oid_const(py, synta_certificate::oids::SUBJECT_ALT_NAME),
640    )?;
641    m.add(
642        "ISSUER_ALT_NAME",
643        oid_const(py, synta_certificate::oids::ISSUER_ALT_NAME),
644    )?;
645    m.add(
646        "BASIC_CONSTRAINTS",
647        oid_const(py, synta_certificate::oids::BASIC_CONSTRAINTS),
648    )?;
649    m.add(
650        "CRL_NUMBER",
651        oid_const(py, synta_certificate::oids::CRL_NUMBER),
652    )?;
653    m.add(
654        "CRL_DISTRIBUTION_POINTS",
655        oid_const(py, synta_certificate::oids::CRL_DISTRIBUTION_POINTS),
656    )?;
657    m.add(
658        "NAME_CONSTRAINTS",
659        oid_const(py, synta_certificate::oids::NAME_CONSTRAINTS),
660    )?;
661    m.add(
662        "CERTIFICATE_POLICIES",
663        oid_const(py, synta_certificate::oids::CERTIFICATE_POLICIES),
664    )?;
665    m.add(
666        "AUTHORITY_KEY_IDENTIFIER",
667        oid_const(py, synta_certificate::oids::AUTHORITY_KEY_IDENTIFIER),
668    )?;
669    m.add(
670        "EXTENDED_KEY_USAGE",
671        oid_const(py, synta_certificate::oids::EXTENDED_KEY_USAGE),
672    )?;
673    m.add(
674        "AUTHORITY_INFO_ACCESS",
675        oid_const(py, synta_certificate::oids::AUTHORITY_INFO_ACCESS),
676    )?;
677    m.add(
678        "CT_PRECERT_SCTS",
679        oid_const(py, synta_certificate::oids::CT_PRECERT_SCTS),
680    )?;
681    m.add(
682        "PE_ACME_IDENTIFIER",
683        oid_const(py, synta_certificate::oids::PE_ACME_IDENTIFIER),
684    )?;
685
686    // ── Extended Key Usage key-purpose OIDs (RFC 5280 §4.2.1.12) ─────────────
687    m.add(
688        "KP_SERVER_AUTH",
689        oid_const(py, synta_certificate::oids::KP_SERVER_AUTH),
690    )?;
691    m.add(
692        "KP_CLIENT_AUTH",
693        oid_const(py, synta_certificate::oids::KP_CLIENT_AUTH),
694    )?;
695    m.add(
696        "KP_CODE_SIGNING",
697        oid_const(py, synta_certificate::oids::KP_CODE_SIGNING),
698    )?;
699    m.add(
700        "KP_EMAIL_PROTECTION",
701        oid_const(py, synta_certificate::oids::KP_EMAIL_PROTECTION),
702    )?;
703    m.add(
704        "KP_TIME_STAMPING",
705        oid_const(py, synta_certificate::oids::KP_TIME_STAMPING),
706    )?;
707    m.add(
708        "KP_OCSP_SIGNING",
709        oid_const(py, synta_certificate::oids::KP_OCSP_SIGNING),
710    )?;
711    m.add(
712        "ANY_EXTENDED_KEY_USAGE",
713        oid_const(py, synta_certificate::oids::ANY_EXTENDED_KEY_USAGE),
714    )?;
715
716    // ── PKINIT OIDs (RFC 4556 + RFC 6112 + RFC 8636) ─────────────────────────
717    m.add(
718        "PKINIT_SAN",
719        oid_const(py, synta_certificate::oids::ID_PKINIT_SAN),
720    )?;
721    m.add(
722        "PKINIT_KP_CLIENT_AUTH",
723        oid_const(py, synta_certificate::oids::ID_PKINIT_KPCLIENT_AUTH),
724    )?;
725    m.add(
726        "PKINIT_KP_KDC",
727        oid_const(py, synta_certificate::oids::ID_PKINIT_KPKDC),
728    )?;
729    m.add(
730        "PKINIT_AUTH_DATA",
731        oid_const(py, synta_certificate::oids::ID_PKINIT_AUTH_DATA),
732    )?;
733    m.add(
734        "PKINIT_DHKEY_DATA",
735        oid_const(py, synta_certificate::oids::ID_PKINIT_DHKEY_DATA),
736    )?;
737    m.add(
738        "PKINIT_RKEY_DATA",
739        oid_const(py, synta_certificate::oids::ID_PKINIT_RKEY_DATA),
740    )?;
741    m.add(
742        "PKINIT_KDF",
743        oid_const(py, synta_certificate::oids::ID_PKINIT_KDF),
744    )?;
745    m.add(
746        "PKINIT_KDF_SHA1",
747        oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA1),
748    )?;
749    m.add(
750        "PKINIT_KDF_SHA256",
751        oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA256),
752    )?;
753    m.add(
754        "PKINIT_KDF_SHA384",
755        oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA384),
756    )?;
757    m.add(
758        "PKINIT_KDF_SHA512",
759        oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA512),
760    )?;
761
762    // ── Microsoft PKI OIDs (AD CS) ────────────────────────────────────────────
763    m.add(
764        "MS_SAN_UPN",
765        oid_const(py, synta_certificate::oids::ID_MS_SAN_UPN),
766    )?;
767    m.add(
768        "MS_CERTIFICATE_TEMPLATE_NAME",
769        oid_const(py, synta_certificate::oids::ID_MS_CERTIFICATE_TEMPLATE_NAME),
770    )?;
771    m.add(
772        "MS_CERTIFICATE_TEMPLATE",
773        oid_const(py, synta_certificate::oids::ID_MS_CERTIFICATE_TEMPLATE),
774    )?;
775    m.add(
776        "MS_KP_SMARTCARD_LOGON",
777        oid_const(py, synta_certificate::oids::ID_MS_KP_SMARTCARD_LOGON),
778    )?;
779    m.add(
780        "MS_NTDS_REPLICATION",
781        oid_const(py, synta_certificate::oids::ID_MS_NTDS_REPLICATION),
782    )?;
783
784    // ── CMS content-type OIDs (RFC 5652 §14) ─────────────────────────────────
785    m.add("CMS_DATA", oid_const(py, synta_certificate::oids::CMS_DATA))?;
786    m.add(
787        "CMS_SIGNED_DATA",
788        oid_const(py, synta_certificate::oids::CMS_SIGNED_DATA),
789    )?;
790    m.add(
791        "CMS_ENVELOPED_DATA",
792        oid_const(py, synta_certificate::oids::CMS_ENVELOPED_DATA),
793    )?;
794    m.add(
795        "CMS_DIGESTED_DATA",
796        oid_const(py, synta_certificate::oids::CMS_DIGESTED_DATA),
797    )?;
798    m.add(
799        "CMS_ENCRYPTED_DATA",
800        oid_const(py, synta_certificate::oids::CMS_ENCRYPTED_DATA),
801    )?;
802    m.add(
803        "CMS_AUTH_DATA",
804        oid_const(py, synta_certificate::oids::CMS_AUTH_DATA),
805    )?;
806    // ── CMS-KEM OtherRecipientInfo OIDs (RFC 9629 §6.2) ──────────────────────
807    m.add("CMS_ORI", oid_const(py, synta_certificate::oids::CMS_ORI))?;
808    m.add(
809        "CMS_ORI_KEM",
810        oid_const(py, synta_certificate::oids::CMS_ORI_KEM),
811    )?;
812
813    // ── PKCS#9 attribute OIDs (RFC 2985 / RFC 5652 §11 / RFC 2986 / RFC 7292) ─
814    m.add(
815        "PKCS9_EMAIL_ADDRESS",
816        oid_const(py, synta_certificate::oids::PKCS9_EMAIL_ADDRESS),
817    )?;
818    m.add(
819        "PKCS9_CONTENT_TYPE",
820        oid_const(py, synta_certificate::oids::PKCS9_CONTENT_TYPE),
821    )?;
822    m.add(
823        "PKCS9_MESSAGE_DIGEST",
824        oid_const(py, synta_certificate::oids::PKCS9_MESSAGE_DIGEST),
825    )?;
826    m.add(
827        "PKCS9_SIGNING_TIME",
828        oid_const(py, synta_certificate::oids::PKCS9_SIGNING_TIME),
829    )?;
830    m.add(
831        "PKCS9_COUNTERSIGNATURE",
832        oid_const(py, synta_certificate::oids::PKCS9_COUNTERSIGNATURE),
833    )?;
834    m.add(
835        "PKCS9_CHALLENGE_PASSWORD",
836        oid_const(py, synta_certificate::oids::PKCS9_CHALLENGE_PASSWORD),
837    )?;
838    m.add(
839        "PKCS9_EXTENSION_REQUEST",
840        oid_const(py, synta_certificate::oids::PKCS9_EXTENSION_REQUEST),
841    )?;
842    m.add(
843        "PKCS9_FRIENDLY_NAME",
844        oid_const(py, synta_certificate::oids::PKCS9_FRIENDLY_NAME),
845    )?;
846    m.add(
847        "PKCS9_LOCAL_KEY_ID",
848        oid_const(py, synta_certificate::oids::PKCS9_LOCAL_KEY_ID),
849    )?;
850
851    // ── RFC 9925 Unsigned X.509 Certificates ─────────────────────────────────
852    m.add(
853        "ALG_UNSIGNED",
854        oid_const(py, synta_certificate::oids::ALG_UNSIGNED),
855    )?;
856    m.add(
857        "RDNA_UNSIGNED",
858        oid_const(py, synta_certificate::oids::RDNA_UNSIGNED),
859    )?;
860
861    // ── Helper functions ─────────────────────────────────────────────────────
862    m.add_function(wrap_pyfunction!(identify_signature_algorithm, &m)?)?;
863    m.add_function(wrap_pyfunction!(identify_public_key_algorithm, &m)?)?;
864    m.add_function(wrap_pyfunction!(ec_curve_short_name, &m)?)?;
865    m.add_function(wrap_pyfunction!(ec_curve_nist_name, &m)?)?;
866    m.add_function(wrap_pyfunction!(ec_curve_key_bits, &m)?)?;
867    m.add_function(wrap_pyfunction!(extension_oid_name, &m)?)?;
868
869    // ── DN attribute OID submodule ────────────────────────────────────────────
870    let attr = PyModule::new(py, "attr")?;
871    attr.add(
872        "__doc__",
873        "OIDs for X.500 Distinguished Name attribute types (RFC 4519).",
874    )?;
875    attr.add(
876        "COMMON_NAME",
877        oid_const(py, synta_certificate::oids::attr::COMMON_NAME),
878    )?;
879    attr.add(
880        "COUNTRY",
881        oid_const(py, synta_certificate::oids::attr::COUNTRY),
882    )?;
883    attr.add("STATE", oid_const(py, synta_certificate::oids::attr::STATE))?;
884    attr.add(
885        "LOCALITY",
886        oid_const(py, synta_certificate::oids::attr::LOCALITY),
887    )?;
888    attr.add(
889        "ORGANIZATION",
890        oid_const(py, synta_certificate::oids::attr::ORGANIZATION),
891    )?;
892    attr.add(
893        "ORG_UNIT",
894        oid_const(py, synta_certificate::oids::attr::ORG_UNIT),
895    )?;
896    attr.add(
897        "ORG_IDENTIFIER",
898        oid_const(py, synta_certificate::oids::attr::ORG_IDENTIFIER),
899    )?;
900    attr.add(
901        "STREET",
902        oid_const(py, synta_certificate::oids::attr::STREET),
903    )?;
904    attr.add(
905        "SURNAME",
906        oid_const(py, synta_certificate::oids::attr::SURNAME),
907    )?;
908    attr.add(
909        "GIVEN_NAME",
910        oid_const(py, synta_certificate::oids::attr::GIVEN_NAME),
911    )?;
912    attr.add(
913        "INITIALS",
914        oid_const(py, synta_certificate::oids::attr::INITIALS),
915    )?;
916    attr.add("TITLE", oid_const(py, synta_certificate::oids::attr::TITLE))?;
917    attr.add(
918        "SERIAL_NUMBER",
919        oid_const(py, synta_certificate::oids::attr::SERIAL_NUMBER),
920    )?;
921    attr.add(
922        "EMAIL_ADDRESS",
923        oid_const(py, synta_certificate::oids::attr::EMAIL_ADDRESS),
924    )?;
925    attr.add(
926        "USER_ID",
927        oid_const(py, synta_certificate::oids::attr::USER_ID),
928    )?;
929    attr.add(
930        "DOMAIN_COMPONENT",
931        oid_const(py, synta_certificate::oids::attr::DOMAIN_COMPONENT),
932    )?;
933    attr.add(
934        "BUSINESS_CATEGORY",
935        oid_const(py, synta_certificate::oids::attr::BUSINESS_CATEGORY),
936    )?;
937    attr.add(
938        "POSTAL_CODE",
939        oid_const(py, synta_certificate::oids::attr::POSTAL_CODE),
940    )?;
941    attr.add(
942        "GENERATION_QUALIFIER",
943        oid_const(py, synta_certificate::oids::attr::GENERATION_QUALIFIER),
944    )?;
945    attr.add(
946        "DN_QUALIFIER",
947        oid_const(py, synta_certificate::oids::attr::DN_QUALIFIER),
948    )?;
949    attr.add(
950        "PSEUDONYM",
951        oid_const(py, synta_certificate::oids::attr::PSEUDONYM),
952    )?;
953    attr.add(
954        "JURISDICTION_LOCALITY",
955        oid_const(py, synta_certificate::oids::attr::JURISDICTION_LOCALITY),
956    )?;
957    attr.add(
958        "JURISDICTION_STATE",
959        oid_const(py, synta_certificate::oids::attr::JURISDICTION_STATE),
960    )?;
961    attr.add(
962        "JURISDICTION_COUNTRY",
963        oid_const(py, synta_certificate::oids::attr::JURISDICTION_COUNTRY),
964    )?;
965
966    crate::install_submodule(&m, &attr, "synta.oids.attr", None)?;
967    crate::install_submodule(
968        parent,
969        &m,
970        "synta.oids",
971        Some("OID constants for X.509 signature, public-key, and extension algorithms."),
972    )
973}
974
975// ── GeneralName tag constants (synta.general_name) ───────────────────────────
976
977fn register_general_name_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
978    let py = parent.py();
979    let m = PyModule::new(py, "general_name")?;
980
981    // ── GeneralName alternatives ─────────────────────────────────────────────
982    m.add("OTHER_NAME", synta_certificate::general_name::OTHER_NAME)?;
983    m.add("RFC822_NAME", synta_certificate::general_name::RFC822_NAME)?;
984    m.add("DNS_NAME", synta_certificate::general_name::DNS_NAME)?;
985    m.add(
986        "X400_ADDRESS",
987        synta_certificate::general_name::X400_ADDRESS,
988    )?;
989    m.add(
990        "DIRECTORY_NAME",
991        synta_certificate::general_name::DIRECTORY_NAME,
992    )?;
993    m.add(
994        "EDI_PARTY_NAME",
995        synta_certificate::general_name::EDI_PARTY_NAME,
996    )?;
997    m.add("URI", synta_certificate::general_name::URI)?;
998    m.add("IP_ADDRESS", synta_certificate::general_name::IP_ADDRESS)?;
999    m.add(
1000        "REGISTERED_ID",
1001        synta_certificate::general_name::REGISTERED_ID,
1002    )?;
1003
1004    // ── Typed GeneralName classes ────────────────────────────────────────────
1005    m.add_class::<general_name::PyOtherName>()?;
1006    m.add_class::<general_name::PyRfc822Name>()?;
1007    m.add_class::<general_name::PyDnsName>()?;
1008    m.add_class::<general_name::PyX400Address>()?;
1009    m.add_class::<general_name::PyDirectoryName>()?;
1010    m.add_class::<general_name::PyEdiPartyName>()?;
1011    m.add_class::<general_name::PyUri>()?;
1012    m.add_class::<general_name::PyIPAddress>()?;
1013    m.add_class::<general_name::PyRegisteredId>()?;
1014
1015    crate::install_submodule(
1016        parent,
1017        &m,
1018        "synta.general_name",
1019        Some(concat!(
1020            "Context-specific tag numbers for the ``GeneralName`` CHOICE type ",
1021            "(RFC 5280 \u{a7}4.2.1.6).\n\n",
1022            "These integer constants correspond to the first element of tuples returned by\n",
1023            ":func:`~synta.parse_general_names` and ",
1024            ":meth:`~synta.Certificate.subject_alt_names`.\n\n",
1025            "Example usage::\n\n",
1026            "    import synta.general_name as gn\n",
1027            "    for tag, content in cert.subject_alt_names():\n",
1028            "        if tag == gn.DNS_NAME:\n",
1029            "            print('DNS:', content.decode())\n",
1030            "        elif tag == gn.IP_ADDRESS:\n",
1031            "            print('IP:', content.hex())",
1032        )),
1033    )
1034}