synta 0.2.2

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# Extended Security Services (RFC 2634)

`synta` provides three fluent builder classes for RFC 2634 Extended Security
Services (ESS) structures.  ESS defines optional CMS signed-attribute extensions
for S/MIME: signed receipts, security labels, and signing certificate binding.

All three builders are exported directly from the top-level `synta` module.

| Builder | CMS attribute | RFC 2634 § |
|---------|--------------|------------|
| `SigningCertificateBuilder` | `id-aa-signingCertificate` | §5.4 |
| `ReceiptRequestBuilder` | `id-aa-receiptRequest` | §2.7 |
| `ESSSecurityLabelBuilder` | `id-aa-securityLabel` | §3.2 |

## SigningCertificateBuilder

Fluent builder for a `SigningCertificate` DER SEQUENCE (RFC 2634 §5.4).

`SigningCertificate` is a CMS signed attribute that identifies the signer's
certificate by its SHA-1 hash, preventing an attacker from substituting a
different certificate with the same key.  At least one certificate ID must be
added before calling `build()`.

```python
class SigningCertificateBuilder:
    def __init__(self) -> None: ...

    def add_cert_id(
        self,
        cert_hash: bytes,
        issuer_serial_der: bytes | None,
    ) -> SigningCertificateBuilder: ...
    # Add a certificate entry identified by its SHA-1 hash.
    # cert_hash: 20-byte SHA-1 digest of the complete DER-encoded certificate.
    # issuer_serial_der: optional pre-encoded IssuerSerial SEQUENCE DER TLV,
    #   or None to omit the issuer/serial field.
    # Raises ValueError on decode error (deferred to build()).

    def add_policy(self, policy_oid: list[int]) -> SigningCertificateBuilder: ...
    # Add a certificate policy OID to the optional policies field.
    # policy_oid: OID arc components as a list of ints.
    # Raises ValueError if the OID is invalid (deferred to build()).

    def build(self) -> bytes: ...
    # Build the DER-encoded SigningCertificate SEQUENCE.
    # Raises ValueError if no certificate IDs were added or DER encoding fails.
```

### Example

```python,ignore
import hashlib
import synta

# Read the signer's DER certificate
cert_der = open("signer.der", "rb").read()
cert_sha1 = hashlib.sha1(cert_der).digest()   # 20-byte SHA-1 hash

# Minimal: hash only, no issuer/serial
sc_der = (
    synta.SigningCertificateBuilder()
    .add_cert_id(cert_sha1, None)
    .build()
)

# With issuer/serial (provides stronger binding)
# issuer_serial_der is a pre-encoded IssuerSerial SEQUENCE DER TLV
sc_with_is_der = (
    synta.SigningCertificateBuilder()
    .add_cert_id(cert_sha1, issuer_serial_der)
    .build()
)

# sc_der can then be added as a signed attribute to a CMS SignedData
```

---

## ReceiptRequestBuilder

Fluent builder for a `ReceiptRequest` DER SEQUENCE (RFC 2634 §2.7).

A `ReceiptRequest` signed attribute requests that recipients return a signed
receipt.  Required fields: `signed_content_identifier` and exactly one
`receipts_from_*` call.

```python
class ReceiptRequestBuilder:
    def __init__(self) -> None: ...

    def signed_content_identifier(self, id: bytes) -> ReceiptRequestBuilder: ...
    # Set the signedContentIdentifier (an OCTET STRING value).

    def receipts_from_all(self) -> ReceiptRequestBuilder: ...
    # Request receipts from all recipients (allReceipts [0]).

    def receipts_from_first_tier(self) -> ReceiptRequestBuilder: ...
    # Request receipts from first-tier recipients only
    # (firstTierRecipients [0]).

    def add_receipt_to_email(self, email: str) -> ReceiptRequestBuilder: ...
    # Add an email address to the receiptsTo list.
    # Wraps a single RFC 822 name in a GeneralNames SEQUENCE.
    # Raises ValueError on invalid email (deferred to build()).

    def add_receipt_to_raw(self, general_names_der: bytes) -> ReceiptRequestBuilder: ...
    # Add a pre-encoded GeneralNames DER TLV to the receiptsTo list.
    # Use this for non-email GeneralName types.

    def build(self) -> bytes: ...
    # Build the DER-encoded ReceiptRequest SEQUENCE.
    # Raises ValueError if signed_content_identifier or receipts_from_*
    # were not set, or if DER encoding fails.
```

### Example

```python,ignore
import os
import synta

# Content identifier — typically a hash or random value
content_id = os.urandom(16)

rr_der = (
    synta.ReceiptRequestBuilder()
    .signed_content_identifier(content_id)
    .receipts_from_all()
    .add_receipt_to_email("receipts@example.com")
    .build()
)

# First-tier only, multiple receipt-to addresses
rr_ft_der = (
    synta.ReceiptRequestBuilder()
    .signed_content_identifier(content_id)
    .receipts_from_first_tier()
    .add_receipt_to_email("alice@example.com")
    .add_receipt_to_email("bob@example.com")
    .build()
)
```

---

## ESSSecurityLabelBuilder

Fluent builder for an `ESSSecurityLabel` DER SET (RFC 2634 §3.2).

An `ESSSecurityLabel` signed attribute carries an information security label on
a signed message.  The `security_policy` OID is required; all other fields are
optional.

```python
class ESSSecurityLabelBuilder:
    def __init__(self) -> None: ...

    def security_policy(self, policy_oid: list[int]) -> ESSSecurityLabelBuilder: ...
    # Set the securityPolicyIdentifier OID.  Required.
    # policy_oid: OID arc components as a list of ints.
    # Raises ValueError if the OID is invalid (deferred to build()).

    def classification(self, value: int) -> ESSSecurityLabelBuilder: ...
    # Set the optional securityClassification value (0–32767).
    # Standard values: 0=UNMARKED, 1=UNCLASSIFIED, 2=RESTRICTED,
    # 3=CONFIDENTIAL, 4=SECRET, 5=TOP_SECRET.

    def privacy_mark_utf8(self, mark: str) -> ESSSecurityLabelBuilder: ...
    # Set the optional privacyMark as a UTF8String.

    def privacy_mark_printable(self, mark: str) -> ESSSecurityLabelBuilder: ...
    # Set the optional privacyMark as a PrintableString.
    # Raises ValueError if mark contains non-PrintableString characters
    # (deferred to build()).

    def build(self) -> bytes: ...
    # Build the DER-encoded ESSSecurityLabel SET.
    # Raises ValueError if security_policy was not set or DER encoding fails.
```

### Security classification constants

RFC 2634 §3.2 defines these standard classification values:

| Value | Name |
|-------|------|
| 0 | UNMARKED |
| 1 | UNCLASSIFIED |
| 2 | RESTRICTED |
| 3 | CONFIDENTIAL |
| 4 | SECRET |
| 5 | TOP_SECRET |

Values 6–32767 are reserved for national or local use.

### Example

```python,ignore
import synta

# Custom or well-known policy OID — here using a PKIX test OID
policy_oid = [1, 3, 6, 1, 5, 5, 7, 13, 1]  # id-TEST-certPolicyOne

# Minimal: policy only
label_der = (
    synta.ESSSecurityLabelBuilder()
    .security_policy(policy_oid)
    .build()
)

# With classification and privacy mark
label_classified = (
    synta.ESSSecurityLabelBuilder()
    .security_policy(policy_oid)
    .classification(4)                      # SECRET
    .privacy_mark_utf8("SECRET")
    .build()
)
```

See also [Time-Stamp Protocol](tsp.md) for the RFC 3161 TSP builder, and
[CMS Signed Data](../cms/signed-data.md) for the CMS context in which ESS
attributes are used.