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