synta 0.1.6

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


Five top-level functions extract X.509 certificates and/or PKCS#8 private keys from
container formats (PKCS#7, PKCS#12). All functions accept DER, BER, or CER input;
the encoding is detected automatically.

## Functions

### load_der_pkcs7_certificates

```python
load_der_pkcs7_certificates(data: bytes) -> list[Certificate]
```
Parse a DER or BER PKCS#7 SignedData blob and return its embedded certificates.
BER indefinite-length encodings (common in PKCS#7 files from older tools) are handled
transparently. Non-certificate entries in the CertificateSet are silently skipped.

Raises `ValueError` if the outer ContentInfo contentType is not id-signedData, or on any
ASN.1 structural error.

### load_pem_pkcs7_certificates

```python
load_pem_pkcs7_certificates(data: bytes) -> list[Certificate]
```
Decode PEM block(s) in `data` (any label accepted: `PKCS7`, `CMS`, `CERTIFICATE`, etc.),
then extract certificates from each PKCS#7 payload. All certificates across all blocks are
returned as a single flat list.

Raises `ValueError` if no PEM block is found, or if any block fails to parse as PKCS#7
SignedData.

### load_pkcs12_certificates

```python
load_pkcs12_certificates(data: bytes, password: bytes = None) -> list[Certificate]
```
Extract certificates from a PKCS#12 / PFX archive. Non-certificate bag types
(`keyBag`, `pkcs8ShroudedKeyBag`, `crlBag`, `secretBag`) are silently skipped.

`password` is the archive password as raw bytes (UTF-8, no NUL terminator). Pass `b""`
or omit for no-password archives.

Encrypted bags are supported when built with the `openssl` Cargo feature
(PBES2/PBKDF2/AES-256-CBC) and optionally `deprecated-pkcs12-algorithms` (legacy 3DES).
Without the `openssl` feature a `ValueError` is raised when any encrypted bag is
encountered.

### load_pkcs12_keys

```python
load_pkcs12_keys(data: bytes, password: bytes = None) -> list[bytes]
```
Extract PKCS#8 private keys from a PKCS#12 archive. Returns a list of raw DER-encoded
`OneAsymmetricKey` blobs, one per `keyBag` (unencrypted) and `pkcs8ShroudedKeyBag`
(decrypted with the `openssl` feature) entry. `certBag`, `crlBag`, and `secretBag`
entries are silently skipped.

### load_pkcs12

```python
load_pkcs12(data: bytes, password: bytes = None) -> tuple[list[Certificate], list[bytes]]
```
Extract both certificates and private keys from a PKCS#12 archive in a single call.
Returns `(certs, keys)` where `certs` is a `list[Certificate]` and `keys` is a `list[bytes]`
of DER-encoded PKCS#8 structures.

### create_pkcs12

```python
create_pkcs12(
    certificates: list[Certificate | bytes],
    private_key: PrivateKey | bytes | None = None,
    password: bytes | None = None,
) -> bytes
```
Build a DER-encoded PKCS#12 PFX archive. Each element of `certificates` may be a
`Certificate` object or raw DER `bytes`; both types may appear in the same list.
`private_key` may be a `PrivateKey` object or DER-encoded PKCS#8 `bytes`.

When `password` is `None` or omitted, the archive is written without encryption.
When `password` is provided and the library is built with the `openssl` Cargo feature,
bags are encrypted with PBES2/PBKDF2-SHA256/AES-256-CBC using 600,000 iterations.
Without the `openssl` feature, supplying a password raises `ValueError`.

Raises `TypeError` for unrecognised element types in `certificates` or for an
unrecognised `private_key` type.

## Usage

```python
import synta

# ── PKCS#7 SignedData (DER or BER) ───────────────────────────────────────────
# amazon-roots.p7b uses BER indefinite-length encoding (0x30 0x80…); both are
# handled transparently — no caller-visible difference.
data = open("bundle.p7b", "rb").read()
certs = synta.load_der_pkcs7_certificates(data)
for cert in certs:
    print(cert.subject)

# ── PKCS#7 SignedData (PEM-wrapped) ──────────────────────────────────────────
pem = open("bundle.pem", "rb").read()
certs = synta.load_pem_pkcs7_certificates(pem)

# ── PKCS#12 — certificates only ──────────────────────────────────────────────
data = open("archive.p12", "rb").read()
certs = synta.load_pkcs12_certificates(data)             # unencrypted
certs = synta.load_pkcs12_certificates(data, b"s3cr3t")  # password-protected

print(f"found {len(certs)} certificate(s)")
for cert in certs:
    print(cert.subject, cert.not_before, "–", cert.not_after)

# ── PKCS#12 — private keys only ──────────────────────────────────────────────
# Returns a list[bytes] of DER-encoded OneAsymmetricKey (PKCS#8) structures.
keys = synta.load_pkcs12_keys(data, b"s3cr3t")
for key_der in keys:
    from cryptography.hazmat.primitives.serialization import load_der_private_key
    key = load_der_private_key(key_der, None)

# ── PKCS#12 — both certificates and keys in one call ─────────────────────────
certs, keys = synta.load_pkcs12(data, b"s3cr3t")
```

### Creating PKCS#12 Archives

```python
import synta

# ── Certificate objects directly ─────────────────────────────────────────────
certs = synta.load_pkcs12_certificates(open("src.p12", "rb").read())
pfx = synta.create_pkcs12(certs)                        # no password
open("bundle.p12", "wb").write(pfx)

# ── With a password (openssl feature required) ────────────────────────────────
try:
    pfx = synta.create_pkcs12(certs, password=b"s3cr3t")
except ValueError:
    # Raised when password is provided without the openssl Cargo feature
    pass

# ── With a cert and a PrivateKey object ──────────────────────────────────────
certs, keys = synta.load_pkcs12(open("src.p12", "rb").read())
key = synta.PrivateKey.from_der(keys[0])
pfx = synta.create_pkcs12(certs, private_key=key, password=b"s3cr3t")

# ── Raw DER bytes also accepted ───────────────────────────────────────────────
pfx = synta.create_pkcs12(
    [cert.to_der() for cert in certs],
    private_key=keys[0],           # bytes from load_pkcs12_keys
)

# ── Roundtrip verification ────────────────────────────────────────────────────
parsed_back = synta.load_pkcs12_certificates(pfx, None)
assert len(parsed_back) == len(certs)
```

**Encryption support:** when `password` is provided and the library is built with
`--features openssl`, bags are encrypted with PBES2/PBKDF2-SHA256/AES-256-CBC using
600,000 iterations. Without the `openssl` feature, supplying a non-`None` password raises
`ValueError`.

See also [PKI Blocks](pki-blocks.md) for format-agnostic reading, and [PEM/DER](pem-der.md)
for PEM encode/decode helpers.