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