synta 0.1.11

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# Name and PKI Utility Functions


Several top-level `synta` functions provide convenient access to DN formatting,
extension lookup, GeneralName encoding, algorithm identification, and key
introspection without constructing a full decoder pipeline.

---

## DN Formatting

### `format_dn`

```python
format_dn(name_der: bytes) -> str
```

Format a DER-encoded X.500 Name as an RFC 4514 distinguished name string.

`name_der` must be the complete TLV bytes of the Name SEQUENCE (tag + length +
value), as returned by `Certificate.issuer_raw_der` or `Certificate.subject_raw_der`.

Returns a string like `"CN=example.com, O=Example Inc, C=US"`.
Returns an empty string if `name_der` cannot be parsed.

```python,ignore
dn = synta.format_dn(cert.subject_raw_der)
print(dn)  # CN=example.com, O=Example Inc, C=US
```

### `format_dn_slash`

```python
format_dn_slash(name_der: bytes) -> str
```

Format a DER-encoded X.500 Name in OpenSSL slash-separated form.

`name_der` must be the complete TLV bytes of the Name SEQUENCE (tag + length +
value), as returned by `Certificate.issuer_raw_der` or `Certificate.subject_raw_der`.

Returns a string like `"/C=US/O=Example Inc/CN=example.com"`.
Returns an empty string if `name_der` cannot be parsed.

```python,ignore
dn = synta.format_dn_slash(cert.subject_raw_der)
print(dn)  # /C=US/O=Example Inc/CN=example.com
```

These two functions are the structured counterparts to `Certificate.issuer` and
`Certificate.subject` (which return RFC 4514 strings directly from the parsed
certificate object). Use them when you have raw Name DER bytes from other sources
such as `parse_general_names()` `directoryName` entries.

---

## Extension Lookup

### `find_extension_value`

```python
find_extension_value(
    ext_seq_der: bytes,
    oid: str | ObjectIdentifier,
) -> bytes | None
```

Find the value bytes of an X.509v3 extension by OID.

`ext_seq_der` must be the complete DER bytes of the `Extensions` SEQUENCE (i.e. the
bytes captured by the `extensions` field of a parsed `Certificate` after the
`[3] EXPLICIT` wrapper is stripped). For the common case of looking up an extension
value in a certificate directly, prefer `Certificate.get_extension_value_der()`.

`oid` is either a dotted-decimal OID string (e.g. `"2.5.29.17"`) or an
`ObjectIdentifier` instance.

Returns the extension value bytes (the content of the `extnValue` OCTET STRING,
without the OCTET STRING TLV wrapper), or `None` if no matching extension is present.
Raises `ValueError` if `oid` is not a valid OID string.

```python,ignore
import synta

# Preferred for certificate extensions โ€” use get_extension_value_der directly:
san_der = cert.get_extension_value_der("2.5.29.17")

# find_extension_value is useful when you have a raw Extensions SEQUENCE DER blob
# (e.g. from a CSR or a custom structure):
csr_ext_der = ...  # raw Extensions SEQUENCE DER
san_der = synta.find_extension_value(csr_ext_der, "2.5.29.17")
if san_der is not None:
    for tag, content in synta.parse_general_names(san_der):
        print(tag, content)
```

---

## GeneralName Encoding

### `encode_general_names`

```python
encode_general_names(entries: list[tuple[int, bytes]]) -> bytes | None
```

Encode a list of `(tag_number, value_bytes)` pairs as a DER `SEQUENCE OF GeneralName`.

`entries` must be a list of `(tag_number: int, value: bytes)` tuples in the same
format returned by `parse_general_names()`. Tag numbers follow RFC 5280 (see
`synta.general_name` for named constants).

Returns the DER-encoded `SEQUENCE OF GeneralName` bytes, or `None` if any entry
cannot be encoded. Raises `ValueError` if the input is structurally invalid (e.g.
not a list of 2-tuples).

This is the inverse of `parse_general_names()`.

```python,ignore
import synta
import synta.general_name as gn

san_der = synta.encode_general_names([
    (gn.DNS_NAME, b"example.com"),
    (gn.DNS_NAME, b"www.example.com"),
    (gn.IP_ADDRESS, b"\xc0\xa8\x00\x01"),  # 192.168.0.1
    (gn.RFC822_NAME, b"admin@example.com"),
])
# san_der is a DER SEQUENCE OF GeneralName suitable for use as an extension value.
```

---

## Algorithm Identification

### `signing_algorithm_der`

```python
signing_algorithm_der(
    key_oid: str | ObjectIdentifier,
    hash_algo: str,
) -> bytes | None
```

Build the DER encoding of an `AlgorithmIdentifier` for signing.

`key_oid` is the public key algorithm OID โ€” either a dotted-decimal string (e.g.
`"1.2.840.113549.1.1.1"` for RSA) or an `ObjectIdentifier` instance.

`hash_algo` is the hash algorithm name, e.g. `"sha256"`, `"sha384"`, or `"sha512"`.

Returns the DER bytes of the `AlgorithmIdentifier` SEQUENCE, or `None` if the key
OID is not recognised or the hash algorithm is not valid for the key type. Raises
`ValueError` if `key_oid` is not a valid OID string.

```python,ignore
import synta

# AlgorithmIdentifier DER for sha256WithRSAEncryption
alg_der = synta.signing_algorithm_der("1.2.840.113549.1.1.1", "sha256")

# Or from an ObjectIdentifier instance
alg_der = synta.signing_algorithm_der(
    synta.ObjectIdentifier.from_str("1.2.840.10045.2.1"),  # EC public key OID
    "sha384",
)
```

---

## Key Usage

### `key_usage_bit`

```python
key_usage_bit(ku_value_bytes: bytes, bit_n: int) -> bool
```

Test whether a bit position is set in a KeyUsage BIT STRING value.

`ku_value_bytes` must be the raw value bytes of the KeyUsage BIT STRING โ€” the bytes
inside the OCTET STRING wrapper of the extension value, after decoding the BIT STRING
tag and length. The first byte is the unused-bits count, followed by the named-bit
flags.

`bit_n` is the named-bit index as defined in RFC 5280 ยง4.2.1.3. Named-bit constants
are available in `synta.cert` (e.g. `synta.cert.KEY_USAGE_DIGITAL_SIGNATURE == 0`).

Returns `True` if bit `bit_n` is set, `False` otherwise.

```python,ignore
import synta

ku_der = cert.get_extension_value_der("2.5.29.15")
if ku_der is not None:
    # Bit 5 = keyCertSign, bit 6 = cRLSign
    is_ca_cert_sign = synta.key_usage_bit(ku_der, 5)
    is_crl_sign     = synta.key_usage_bit(ku_der, 6)
    is_dig_sig      = synta.key_usage_bit(ku_der, 0)
    print(f"keyCertSign={is_ca_cert_sign}, cRLSign={is_crl_sign}, digitalSignature={is_dig_sig}")
```

---

## Public Key Inspection

### `decode_public_key_info`

```python
decode_public_key_info(spki_der: bytes) -> dict
```

Decode a DER-encoded `SubjectPublicKeyInfo` into a dictionary.

`spki_der` must be the complete DER bytes of the `SubjectPublicKeyInfo` SEQUENCE TLV,
as returned by `Certificate.subject_public_key_info_der` or `PublicKey.to_der()`.

Returns a `dict` with at minimum these keys:

| Key | Type | Description |
|---|---|---|
| `"algorithm_oid"` | `str` | Dotted OID of the public-key algorithm |
| `"key_bytes"` | `bytes` | Raw key bytes from the BIT STRING |

For RSA keys the dict additionally contains:

| Key | Type | Description |
|---|---|---|
| `"modulus"` | `bytes` | Raw modulus bytes (may include 0x00 sign byte) |
| `"exponent"` | `int` | Public exponent (typically 65537) |
| `"bit_count"` | `int` | Key size in bits |

For EC keys the dict additionally contains:

| Key | Type | Description |
|---|---|---|
| `"bit_count"` | `int` | Key size in bits |
| `"curve_oid"` | `str` | Dotted OID of the named curve |
| `"curve_short_name"` | `str \| None` | Short name, e.g. `"prime256v1"` |
| `"curve_nist_name"` | `str \| None` | NIST name, e.g. `"P-256"` |

Raises `ValueError` if `spki_der` cannot be parsed.

```python,ignore
import synta

spki_der = cert.subject_public_key_info_der
info = synta.decode_public_key_info(spki_der)
print(info["algorithm_oid"])        # e.g. "1.2.840.10045.2.1" for EC
print(info.get("curve_nist_name"))  # "P-256"
print(info.get("bit_count"))        # 256

# RSA key
rsa_info = synta.decode_public_key_info(rsa_cert.subject_public_key_info_der)
print(rsa_info["algorithm_oid"])    # "1.2.840.113549.1.1.1"
print(rsa_info["bit_count"])        # 2048
print(rsa_info["exponent"])         # 65537
```

---

## Summary table

| Function | Input | Output |
|---|---|---|
| `format_dn(name_der)` | Name DER bytes | RFC 4514 string (`"CN=..., O=..."`) |
| `format_dn_slash(name_der)` | Name DER bytes | Slash DN (`"/C=US/..."`) |
| `find_extension_value(ext_seq_der, oid)` | Extensions SEQUENCE DER + OID | Extension value bytes or `None` |
| `encode_general_names(entries)` | `list[tuple[int, bytes]]` | GeneralNames SEQUENCE DER or `None` |
| `signing_algorithm_der(key_oid, hash_algo)` | Key OID + hash name | AlgorithmIdentifier DER or `None` |
| `key_usage_bit(ku_value_bytes, bit_n)` | KeyUsage BIT STRING value bytes + bit index | `bool` |
| `decode_public_key_info(spki_der)` | SPKI DER bytes | `dict` with algorithm and key details |

See also [GeneralName and SAN](general-name.md) for `parse_general_names()` and
`parse_name_attrs()`, [Certificate](certificate.md) for `get_extension_value_der()`,
and [PEM/DER Utilities](pem-der.md) for `pem_to_der()` and `der_to_pem()`.