synta-python 0.1.3

Python extension module for the synta ASN.1 library
Documentation
# synta-python

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Overview]#overview
- [Module Structure]#module-structure
  - [Python module hierarchy]#python-module-hierarchy
- [Building]#building
- [Testing]#testing
- [Dependencies]#dependencies
- [Performance Design]#performance-design
- [Cargo Features]#cargo-features
- [License]#license

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Python extension module crate for the [synta](https://codeberg.org/abbra/synta) ASN.1 library.

## Overview

This crate builds the `_synta` native extension module using [PyO3](https://pyo3.rs/) and
[maturin](https://www.maturin.rs/). It exposes the synta ASN.1 encoder/decoder and the
`synta-certificate` X.509 parser to Python 3.8+ through a low-overhead Rust-native API.

The resulting module is imported as `synta._synta` and re-exported through the `synta`
Python package. End-users should refer to [python/README.md](../python/README.md) and
[docs/PYTHON_BINDINGS.md](../docs/PYTHON_BINDINGS.md) for usage documentation.

## Module Structure

| Source file | Contents |
|-------------|----------|
| `src/lib.rs` | Module registration, `Encoding` enum, `pem_to_der`, `der_to_pem`, top-level exports |
| `src/decoder.rs` | `Decoder` class wrapping `synta::Decoder`; includes `decode_any_str()` |
| `src/encoder.rs` | `Encoder` class wrapping `synta::Encoder` |
| `src/types.rs` | Python wrappers for all ASN.1 primitive types |
| `src/error.rs` | `SyntaError` Python exception |
| `src/certificate.rs` | PKI types: `ObjectIdentifier`, `Certificate`, `CertificationRequest`, `CertificateList`, `OCSPResponse`, PKCS loaders, `synta.oids` / `synta.oids.attr` submodules |
| `src/pkinit.rs` | PKINIT classes: `EncryptionKey`, `Checksum`, `KDFAlgorithmId`, `PKAuthenticator`, `AuthPack`, `PaPkAsReq/Rep`, etc. |
| `src/krb5.rs` | `synta.krb5` submodule registration: `Krb5PrincipalName`, PKINIT classes, `NT_*` constants |
| `src/x509_verification.rs` | `synta.x509` submodule: `TrustStore`, `VerificationPolicy`, `X509VerificationError`, `verify_server_certificate`, `verify_client_certificate` |
| `src/certificate/pkixalgs.rs` | `synta.pkixalgs` submodule: `DssParms`, `DssSigValue`, `EcdsaSigValue`, `ECParameters`, DSA/DH/EC/ECDSA OID constants (RFC 3279) |
| `src/certificate/ac.rs` | `synta.ac` submodule: `AttributeCertificate`, RFC 5755 extension and attribute-type OID constants |
| `src/certificate/crmf.rs` | `synta.crmf` submodule: `CertReqMessages`, `CertReqMsg`, registration-control OID constants (RFC 4211) |
| `src/certificate/cmp.rs` | `synta.cmp` submodule: `CMPMessage`, MAC algorithm and key-purpose OID constants (RFC 9810) |

All PyO3 bindings live directly in `synta-python`. The `synta-certificate` and `synta-krb5`
library crates have no PyO3 dependency; their types are wrapped here in `src/certificate.rs`
and `src/pkinit.rs` respectively.

### Python module hierarchy

```
synta                          top-level module (_synta extension)
├── Encoding                   enum: DER / BER / CER
├── SyntaError                 exception
├── Decoder / Encoder          streaming ASN.1 codec
├── Integer, OctetString, BitString, Boolean, Real, Null
├── UtcTime, GeneralizedTime
├── Utf8String, PrintableString, IA5String, NumericString,
│   TeletexString, VisibleString, GeneralString,
│   UniversalString, BmpString
├── TaggedElement, RawElement
├── ObjectIdentifier           OID type (from synta-certificate)
├── Certificate, CertificationRequest,
│   CertificateList, OCSPResponse
├── load_der_pkcs7_certificates, load_pem_pkcs7_certificates,
│   load_pkcs12_certificates, load_pkcs12_keys, load_pkcs12,
│   read_pki_blocks, create_pkcs12
├── PrivateKey                 private key (PKCS#8); from_der, to_der, to_pkcs8_encrypted, from_pkcs8_encrypted
├── CertificateBuilder         build and sign DER certificates
├── CsrBuilder                 build and sign PKCS#10 CSR DER
├── parse_general_names        parse SEQUENCE OF GeneralName → list[tuple[int, bytes]]
├── parse_name_attrs           parse Name DER → list[tuple[str, str]]
├── pem_to_der, der_to_pem
├── synta.general_name         GeneralName tag constants
│   ├── OTHER_NAME (0), RFC822_NAME (1), DNS_NAME (2), X400_ADDRESS (3)
│   ├── DIRECTORY_NAME (4), EDI_PARTY_NAME (5), URI (6)
│   ├── IP_ADDRESS (7), REGISTERED_ID (8)
├── synta.krb5                 Kerberos V5 / PKINIT submodule
│   ├── Krb5PrincipalName
│   ├── EncryptionKey, Checksum, KDFAlgorithmId
│   ├── IssuerAndSerialNumber, ExternalPrincipalIdentifier
│   ├── PKAuthenticator, AuthPack, PaPkAsReq
│   ├── DHRepInfo, KDCDHKeyInfo, ReplyKeyPack, PaPkAsRep
│   ├── KRB5_PRINCIPAL_NAME_OID
│   └── NT_UNKNOWN … NT_SRV_HST_DOMAIN
├── synta.pkixalgs             RFC 3279 algorithm parameter types
│   ├── DssParms, DssSigValue, EcdsaSigValue
│   ├── ECParameters (namedCurve / ecParameters / implicitlyCA)
│   └── DSA, DH, EC, ECDSA, named-curve OID constants
├── synta.ac                   RFC 5755 Attribute Certificate v2
│   ├── AttributeCertificate
│   └── RFC 5755 extension and attribute-type OID constants
├── synta.crmf                 RFC 4211 Certificate Request Message Format
│   ├── CertReqMessages, CertReqMsg
│   └── Registration-control OID constants
├── synta.cmp                  RFC 9810 Certificate Management Protocol v3
│   ├── CMPMessage
│   └── MAC algorithm and key-purpose OID constants
├── synta.cms                  CMS (RFC 5652) and CMS-KEM (RFC 9629) types
│   ├── ContentInfo            parse DER ContentInfo wrapper
│   ├── SignedData, SignerInfo  signed-data inspection
│   ├── EnvelopedData          encrypted-for-recipients data; .create(plaintext, recipients)
│   ├── EnvelopedDataBuilder   chainable builder: .add_originator_cert(), .add_originator_crl(),
│   │                          .set_unprotected_attrs(), .build() → EnvelopedData
│   ├── EncryptedData          symmetric encrypted data; .create(plaintext, key, alg_oid) / .decrypt(key)
│   ├── DigestedData, AuthenticatedData
│   ├── IssuerAndSerialNumber, KEMRecipientInfo, CMSORIforKEMOtherInfo
│   └── ID_DATA, ID_SIGNED_DATA, ID_ENVELOPED_DATA, ID_ENCRYPTED_DATA,
│       ID_DIGESTED_DATA, ID_CT_AUTH_DATA, ID_ORI, ID_ORI_KEM,
│       ID_AES128_CBC, ID_AES192_CBC, ID_AES256_CBC
├── synta.x509                 RFC 5280 / CABF X.509 certificate chain verification
│   ├── TrustStore             trusted root CA store
│   ├── VerificationPolicy     validation parameters (names, profile, time, depth, OCSP)
│   ├── OcspStore              OCSP response cache for revocation checking
│   ├── X509VerificationError  raised on chain or policy failure
│   ├── verify_server_certificate(leaf, intermediates, store, policy) → list[bytes]
│   └── verify_client_certificate(leaf, intermediates, store, policy) → list[bytes]
└── synta.oids                 well-known OID constants
    ├── ML_DSA_44/65/87, ML_KEM_512/768/1024
    ├── SLH_DSA_SHA2_*/SHAKE_* (12 variants)
    ├── ED25519, ED448
    ├── RSA_ENCRYPTION, SHA*_WITH_RSA, RSA
    ├── EC_PUBLIC_KEY, ECDSA_WITH_SHA*, ECDSA, EC_CURVE_*
    ├── SHA224/256/384/512, SHA512_224/256
    ├── SHA3_224/256/384/512, SHAKE128/256
    ├── SUBJECT_KEY_IDENTIFIER, KEY_USAGE, SUBJECT_ALT_NAME,
    │   ISSUER_ALT_NAME, BASIC_CONSTRAINTS,
    │   CRL_DISTRIBUTION_POINTS, CERTIFICATE_POLICIES,
    │   AUTHORITY_KEY_IDENTIFIER, EXTENDED_KEY_USAGE,
    │   AUTHORITY_INFO_ACCESS, CT_PRECERT_SCTS
    ├── KP_SERVER_AUTH, KP_CLIENT_AUTH, KP_CODE_SIGNING,
    │   KP_EMAIL_PROTECTION, KP_TIME_STAMPING,
    │   KP_OCSP_SIGNING, ANY_EXTENDED_KEY_USAGE
    ├── PKINIT_SAN, PKINIT_KP_CLIENT_AUTH, PKINIT_KP_KDC,
    │   PKINIT_AUTH_DATA, PKINIT_DHKEY_DATA, PKINIT_RKEY_DATA,
    │   PKINIT_KDF, PKINIT_KDF_SHA1/256/384/512
    ├── MS_SAN_UPN, MS_CERTIFICATE_TEMPLATE_NAME,
    │   MS_CERTIFICATE_TEMPLATE, MS_KP_SMARTCARD_LOGON,
    │   MS_NTDS_REPLICATION
    ├── PKCS9_EMAIL_ADDRESS, PKCS9_CONTENT_TYPE, PKCS9_MESSAGE_DIGEST,
    │   PKCS9_SIGNING_TIME, PKCS9_COUNTERSIGNATURE,
    │   PKCS9_CHALLENGE_PASSWORD, PKCS9_EXTENSION_REQUEST,
    │   PKCS9_FRIENDLY_NAME, PKCS9_LOCAL_KEY_ID
    ├── identify_signature_algorithm(oid) -> str
    ├── identify_public_key_algorithm(oid) -> str | None
    ├── ec_curve_short_name(oid) -> str | None
    ├── ec_curve_nist_name(oid) -> str | None
    ├── ec_curve_key_bits(oid) -> int | None
    ├── extension_oid_name(oid) -> str
    └── synta.oids.attr        DN attribute OIDs
        └── COMMON_NAME, COUNTRY, STATE, LOCALITY,
            ORGANIZATION, ORG_UNIT, ORG_IDENTIFIER,
            STREET, SURNAME, GIVEN_NAME, INITIALS,
            TITLE, SERIAL_NUMBER, EMAIL_ADDRESS,
            USER_ID, DOMAIN_COMPONENT
```

## Building

The extension module is built with maturin. The workspace `pyproject.toml` points to this
crate automatically.

```bash
# Install maturin (once)
pip install maturin

# Development install into active virtualenv
maturin develop

# Build release wheel
maturin build --release

# Install built wheel
pip install target/wheels/synta-*.whl
```

Requirements:
- Rust toolchain (1.70 or later)
- Python 3.8 or later (abi3 stable ABI; compatible with CPython 3.8–3.14+)
- maturin 1.x

## Testing

```bash
# Run the Python test suite from the workspace root
python -m pytest tests/python/

# Or via the CI helper script
./contrib/ci/local-ci.sh python-test
```

## Dependencies

| Crate | Role |
|-------|------|
| `synta` | Core ASN.1 encoder/decoder |
| `synta-certificate` | X.509 PKI types + OID constants + OpenSSL signature verifier |
| `synta-krb5` | Kerberos V5 / PKINIT types |
| `synta-x509-verification` | RFC 5280 / CABF certificate chain validation (`synta.x509`) |
| `pyo3` 0.26 | Python/Rust interop (abi3-py38 feature) |
| `openssl` 0.10 | OpenSSL bindings for signature verification (`synta.x509`, `synta.cms`) |

## Performance Design

- **Input bytes are borrowed**: `PyDecoder` holds a `Py<PyBytes>` reference and passes a
  `&[u8]` slice to the Rust decoder — no copy of the input buffer.
- **Python string caching**: `Certificate` stores `OnceLock<Py<PyString>>` for each string
  field. The first access constructs the Python object; subsequent calls use
  `clone_ref()` (~0.3 µs for all fields together).
- **Zero-copy certificate fields**: `issuer`, `subject`, and `extensions` are stored as
  raw DER byte spans inside the Rust `Certificate`; the Python binding wraps them in
  `PyBytes` on first access.
- **Lazy decode**: field values are decoded on demand rather than at parse time.

See [docs/performance.md](../docs/performance.md) and [python/README.md](../python/README.md)
for benchmark numbers.

## Cargo Features

| Feature | Default | Description |
|---------|---------|-------------|
| `extension-module` | yes (maturin) | Required by PyO3 for native extension module builds; set automatically by maturin |
| `abi3-py38` | yes (maturin) | Targets the stable Python 3.8 ABI; compatible with CPython 3.8–3.14+ |
| `openssl` | yes | OpenSSL-backed crypto: PKCS#12 decryption (`load_pkcs12_certificates` with password, `create_pkcs12` with password), CMS `EnvelopedData.create` / `EnvelopedDataBuilder.build`, `EncryptedData.create` / `decrypt`, `PrivateKey.to_pkcs8_encrypted` / `from_pkcs8_encrypted`, `synta.x509` chain verification |
| `deprecated-pkcs12-algorithms` | no | Enables legacy PKCS#12 decryption algorithms (3DES, RC2) — requires `openssl` |

## License

Licensed under either of:

- Apache License, Version 2.0 ([LICENSE-APACHE]../LICENSE-APACHE)
- MIT license ([LICENSE-MIT]../LICENSE-MIT)

at your option.