synta 0.2.4

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# Certificate


`Certificate` represents an RFC 5280 X.509 v3 certificate. Parsing is lazy: `from_der`
performs only a 4-operation shallow scan (outer SEQUENCE TLV + TBSCertificate SEQUENCE TLV);
the full RFC 5280 decode is deferred to the first field access, after which every property
is cached in an `OnceLock` and returned in O(1) on subsequent calls.

## Construction

```python
Certificate.from_der(data: bytes) -> Certificate
```
Parse a DER-encoded X.509 certificate. Performs a shallow 4-operation structural scan on
construction; the full decode is deferred to the first field access.

```python
Certificate.full_from_der(data: bytes) -> Certificate
```
Parse and perform a complete RFC 5280 decode immediately. All field getters are on the warm
path from the first call. Use when benchmarking full parse cost or when latency on the first
field access must be avoided.

```python
Certificate.from_pem(data: bytes) -> Certificate | list[Certificate]
```
Parse a PEM-encoded certificate. Returns a single `Certificate` for a single PEM block, or
a `list[Certificate]` when multiple blocks are present.

```python
Certificate.to_pem(cert_or_list) -> bytes
```
Encode one or more `Certificate` objects back to PEM.

```python
Certificate.from_pyca(pyca_cert: object) -> Certificate
```
Convert a `cryptography.x509.Certificate` to a `synta.Certificate` by serialising to DER
and calling `from_der`. Raises `ImportError` if the `cryptography` package is not installed.

```python
cert.to_pyca() -> cryptography.x509.Certificate
```
Return a `cryptography.x509.Certificate` backed by the same DER bytes (no re-encoding).
Use for cryptographic operations not directly supported by synta. Raises `ImportError` if
the `cryptography` package is not installed.

## Properties

All properties are cached after first access. `to_der` skips the lock entirely (direct
reference to the stored bytes, no copy).

### Identity

| Property | Type | Description |
|---|---|---|
| `serial_number` | `int` | Arbitrary-precision Python int (RFC 5280: up to 20 bytes) |
| `version` | `int \| None` | Version field: `None` = v1, `1` = v2, `2` = v3 |
| `issuer` | `str` | RFC 4514 DN string |
| `issuer_raw_der` | `bytes` | Raw DER of issuer Name SEQUENCE |
| `subject` | `str` | RFC 4514 DN string |
| `subject_raw_der` | `bytes` | Raw DER of subject Name SEQUENCE |

### Validity

| Property | Type | Description |
|---|---|---|
| `not_before` | `str` | `"YYMMDDHHMMSSZ"` (UTCTime) or `"YYYYMMDDHHMMSSZ"` (GeneralizedTime) |
| `not_after` | `str` | Same format as `not_before` |
| `not_before_utc` | `datetime.datetime` | notBefore as a UTC-aware `datetime.datetime` |
| `not_after_utc` | `datetime.datetime` | notAfter as a UTC-aware `datetime.datetime` |

### Signature

| Property | Type | Description |
|---|---|---|
| `signature_algorithm` | `str` | Human-readable algorithm name (e.g. `"RSA"`, `"ECDSA"`, `"ML-DSA-65"`) or dotted OID for unrecognised algorithms |
| `signature_algorithm_oid` | `ObjectIdentifier` | Always machine-readable dotted OID |
| `signature_algorithm_params` | `bytes \| None` | DER-encoded parameters, or `None` when absent (e.g. Ed25519) |
| `signature_hash_algorithm_name` | `str \| None` | Lowercase hash name (e.g. `"sha256"`, `"sha1"`) implied by the signature algorithm OID, or `None` for Ed25519, ML-DSA, etc. |
| `signature_value` | `bytes` | Raw signature bytes (BIT STRING value, unused-bit prefix stripped) |

### Subject Public Key

| Property | Type | Description |
|---|---|---|
| `public_key_algorithm` | `str` | Human-readable name or dotted OID |
| `public_key_algorithm_oid` | `ObjectIdentifier` | Dotted OID (e.g. `"1.2.840.10045.2.1"` for id-ecPublicKey) |
| `public_key_algorithm_params` | `bytes \| None` | DER parameters (curve OID for EC; `None` for RSA/EdDSA) |
| `public_key` | `bytes` | Raw SubjectPublicKeyInfo BIT STRING content (unused-bit byte stripped) |
| `subject_public_key_info_der` | `bytes` | Full SubjectPublicKeyInfo SEQUENCE DER (for SKI/AKI computation) |

### Raw DER Spans

| Property / Method | Type | Description |
|---|---|---|
| `to_der()` | `bytes` | Complete original certificate DER (zero-copy) |
| `tbs_bytes` | `bytes` | TBSCertificate DER — the bytes that were signed |
| `tbs_certificate_der` | `bytes` | Alias for `tbs_bytes` |
| `issuer_raw_der` | `bytes` | Issuer Name SEQUENCE DER |
| `subject_raw_der` | `bytes` | Subject Name SEQUENCE DER |
| `extensions_der` | `bytes \| None` | SEQUENCE OF Extension DER, or `None` for v1/v2 |

## Methods

| Method | Signature | Returns | Description |
|---|---|---|---|
| `to_der()` | `()` | `bytes` | Original DER bytes (zero-copy) |
| `get_extension_value_der` | `(oid: str \| ObjectIdentifier)` | `bytes \| None` | Return the extnValue content bytes for the named extension OID, or `None` if absent. Scans in O(n) over the already-parsed Extension list. Raises `ValueError` for invalid OID strings. |
| `subject_alt_names()` | `()` | `list[AnyGeneralName]` | SAN entries as typed `synta.general_name` objects (`DNSName`, `IPAddress`, `RFC822Name`, etc.). Returns `[]` when no SAN extension is present. |
| `general_names` | `(oid: str \| ObjectIdentifier)` | `list[AnyGeneralName]` | Decode any extension whose value is `SEQUENCE OF GeneralName`. Returns typed `synta.general_name` objects. |
| `certificate_policies()` | `()` | `list[PolicyInformation]` | Decode the CertificatePolicies extension (OID 2.5.29.32). Returns `[]` when absent. |
| `verify_issued_by` | `(issuer: Certificate)` | `None` | Verify this certificate was signed by `issuer`. Raises `ValueError` on failure. |
| `fingerprint` | `(algorithm: str)` | `bytes` | Certificate fingerprint using the named hash (e.g. `"sha256"`). Raises `ValueError` for unknown algorithms. |

## Full class stub

```python
class Certificate:
    @staticmethod
    def from_der(data: bytes) -> Certificate: ...
    @staticmethod
    def full_from_der(data: bytes) -> Certificate: ...
    @staticmethod
    def from_pyca(pyca_cert: object) -> Certificate: ...
    def to_pyca(self) -> object: ...
    def __repr__(self) -> str: ...

    # Identity
    serial_number: int
    version: int | None
    issuer: str
    issuer_raw_der: bytes
    subject: str
    subject_raw_der: bytes

    # Validity
    not_before: str
    not_after: str
    not_before_utc: datetime.datetime
    not_after_utc: datetime.datetime

    # Signature
    signature_algorithm: str
    signature_algorithm_oid: ObjectIdentifier
    signature_algorithm_params: bytes | None
    signature_value: bytes

    # Subject public key
    public_key_algorithm: str
    public_key_algorithm_oid: ObjectIdentifier
    public_key_algorithm_params: bytes | None
    public_key: bytes
    subject_public_key_info_der: bytes

    # Raw DER spans
    def to_der(self) -> bytes: ...
    tbs_bytes: bytes
    tbs_certificate_der: bytes          # alias for tbs_bytes
    extensions_der: bytes | None

    # Methods
    def get_extension_value_der(self, oid: str) -> bytes | None: ...
    def subject_alt_names(self) -> list: ...  # list of typed synta.general_name objects
    def general_names(self, oid: str | ObjectIdentifier) -> list: ...
    def certificate_policies(self) -> list[PolicyInformation]: ...
    def verify_issued_by(self, issuer: Certificate) -> None: ...
    def fingerprint(self, algorithm: str) -> bytes: ...
```

## Usage

```python
import synta

# Parse a DER-encoded certificate
with open('cert.der', 'rb') as f:
    cert = synta.Certificate.from_der(f.read())

# Identity fields
print(cert.subject)            # RFC 4514-style DN string
print(cert.issuer)             # RFC 4514-style DN string
print(cert.serial_number)      # Python int (arbitrary precision)
print(cert.version)            # None (v1), 1 (v2), or 2 (v3)

# Validity
print(cert.not_before)         # String, e.g. "230101000000Z"
print(cert.not_after)          # String, e.g. "320101000000Z"

# Signature algorithm
print(cert.signature_algorithm)      # Human-readable name or dotted OID
print(cert.signature_algorithm_oid)  # Dotted OID string (always machine-readable)
print(cert.signature_algorithm_params)  # DER bytes or None (e.g. None for Ed25519)
print(cert.signature_value)          # Raw signature bytes

# Subject public key
print(cert.public_key_algorithm)       # Human-readable name or dotted OID
print(cert.public_key_algorithm_oid)   # Dotted OID string
print(cert.public_key_algorithm_params)  # DER bytes or None (EC: curve OID)
print(cert.public_key)                 # Raw SubjectPublicKeyInfo bit-string bytes

# Raw DER spans
print(cert.to_der())        # Complete original certificate DER bytes
print(cert.tbs_bytes)       # TBSCertificate DER (the bytes that were signed)
print(cert.issuer_raw_der)  # Issuer Name SEQUENCE DER
print(cert.subject_raw_der) # Subject Name SEQUENCE DER
print(cert.extensions_der)  # Extensions SEQUENCE OF DER, or None for v1/v2

# Look up a single extension by OID — returns the extnValue OCTET STRING
# content (the DER-encoded extension-specific structure), or None if absent.
san_der = cert.get_extension_value_der("2.5.29.17")   # SubjectAltName
bc_der  = cert.get_extension_value_der("2.5.29.19")   # BasicConstraints
eku_der = cert.get_extension_value_der("2.5.29.37")   # ExtendedKeyUsage
```

### SAN access

```python
import ipaddress
import synta.general_name as gn

# High-level: subject_alt_names() returns typed synta.general_name objects
for name in cert.subject_alt_names():
    if isinstance(name, gn.DNSName):
        print("dns:",   name.value)
    elif isinstance(name, gn.IPAddress):
        print("ip:",    ipaddress.ip_address(name.address))
    elif isinstance(name, gn.RFC822Name):
        print("email:", name.value)
    elif isinstance(name, gn.UniformResourceIdentifier):
        print("uri:",   name.value)
    elif isinstance(name, gn.DirectoryName):
        attrs = synta.parse_name_attrs(name.name_der)   # [(oid_str, value_str), …]
        print("dirname:", attrs)
```

### Iterating extensions manually

```python
# Iterate all extensions when you need every extension
ext_der = cert.extensions_der
if ext_der:
    dec = synta.Decoder(ext_der, synta.Encoding.DER)
    exts = dec.decode_sequence()
    while not exts.is_empty():
        ext_tlv = exts.decode_raw_tlv()  # bytes covering one Extension TLV
```

### Signature verification

```python
# Verify a certificate was signed by a CA certificate
ca_cert = synta.Certificate.from_der(open("ca.der", "rb").read())
end_cert = synta.Certificate.from_der(open("end.der", "rb").read())
try:
    end_cert.verify_issued_by(ca_cert)
    print("Signature valid")
except ValueError as e:
    print(f"Invalid: {e}")
```

See also:
- [CSR]csr.md for PKCS#10 certificate signing requests
- [GeneralName]general-name.md for `synta.general_name` tag constants
- [PyCA Interoperability]pyca-interop.md for converting to/from `cryptography`
- [X.509 Path Validation]x509-verification.md for chain verification