synta 0.2.0

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


`synta.cmp` provides RFC 9810 Certificate Management Protocol v3 types.

```python
import synta.cmp as cmp
```

## CMPMessage

```python
class CMPMessage:
    @staticmethod
    def from_der(data: bytes) -> CMPMessage: ...
    def to_der(self) -> bytes: ...

    pvno: int              # CMP protocol version (2 = cmp2000, 3 = cmp2021)
    body_type: str         # Active PKIBody arm in lowercase: "ir", "ip", "cr", "cp",
                           # "rr", "rp", "pkiconf", "error", "genm", "genp", etc.
    body_der: bytes | None # Raw DER bytes of the PKIBody content, or None for "pkiconf"
    sender_der: bytes      # DER bytes of the sender GeneralName field
    recipient_der: bytes   # DER bytes of the recipient GeneralName field
    transaction_id: bytes | None
    sender_nonce: bytes | None
    recip_nonce: bytes | None
    protection_alg_oid: ObjectIdentifier | None  # OID of protectionAlg, or None
    message_time: str | None                     # GeneralizedTime string, or None
```

### Body type dispatch

```python
if msg.body_type == "ir" or msg.body_type == "cr":
    # body_der is a CertReqMessages SEQUENCE OF
    import synta.crmf as crmf
    cert_reqs = crmf.CertReqMessages.from_der(msg.body_der)
    for req in cert_reqs:
        print(f"req id={req.cert_req_id}")
```

## CMPMessageBuilder

Fluent builder for a CMP PKIMessage (RFC 9810). Set exactly one sender and one recipient
GeneralName before calling `build()`.

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

    def pvno(self, pvno: int) -> CMPMessageBuilder: ...
    # 2 = cmp2000, 3 = cmp2021

    # Sender (exactly one must be set)
    def sender_rfc822(self, email: str) -> CMPMessageBuilder: ...
    def sender_dns(self, host: str) -> CMPMessageBuilder: ...
    def sender_uri(self, uri: str) -> CMPMessageBuilder: ...
    def sender_directory_name(self, name_der: bytes) -> CMPMessageBuilder: ...

    # Recipient (exactly one must be set)
    def recipient_rfc822(self, email: str) -> CMPMessageBuilder: ...
    def recipient_dns(self, host: str) -> CMPMessageBuilder: ...
    def recipient_uri(self, uri: str) -> CMPMessageBuilder: ...
    def recipient_directory_name(self, name_der: bytes) -> CMPMessageBuilder: ...

    def transaction_id(self, data: bytes) -> CMPMessageBuilder: ...
    def sender_nonce(self, data: bytes) -> CMPMessageBuilder: ...
    def recip_nonce(self, data: bytes) -> CMPMessageBuilder: ...

    # Body (set exactly one)
    def body_pkiconf(self) -> CMPMessageBuilder: ...        # [19] pkiConf (NULL)
    def body_ir(self, cert_req_messages_der: bytes) -> CMPMessageBuilder: ...   # [0]
    def body_cr(self, cert_req_messages_der: bytes) -> CMPMessageBuilder: ...   # [2]
    def body_kur(self, cert_req_messages_der: bytes) -> CMPMessageBuilder: ...  # [7]
    def body_p10cr(self, csr_der: bytes) -> CMPMessageBuilder: ...              # [4]
    def body_genm(self, gen_msg_der: bytes) -> CMPMessageBuilder: ...           # [21]

    def build(self) -> CMPMessage: ...
    # Raises ValueError if sender or recipient are not set, or if any GeneralName
    # data is invalid.
```

## OID constants

| Constant | Description |
|---|---|
| `ID_PASSWORD_BASED_MAC` | Password-based MAC algorithm |
| `ID_DHBASED_MAC` | DH-based MAC algorithm |
| `ID_KEM_BASED_MAC` | KEM-based MAC algorithm |
| `ID_KP_CM_KGA` | CMP key-generation authority key purpose |
| `ID_REG_CTRL_ALT_CERT_TEMPLATE` | Alternate certificate template control |
| `ID_REG_CTRL_ALG_ID` | Algorithm ID control |
| `ID_REG_CTRL_RSA_KEY_LEN` | RSA key length control |

## Usage

```python
import synta.cmp as cmp
import synta.crmf as crmf

# Parse an incoming CMP message
msg = cmp.CMPMessage.from_der(data)
print(f"pvno: {msg.pvno}, body: {msg.body_type}")

# Build a CMP ir (Initialization Request) with a CRMF batch
crmf_der = batch.to_der()  # from CertReqMessagesBuilder.build()

ir_msg = (
    cmp.CMPMessageBuilder()
    .pvno(2)
    .sender_rfc822("alice@example.com")
    .recipient_directory_name(ca_name_der)
    .transaction_id(os.urandom(16))
    .sender_nonce(os.urandom(16))
    .body_ir(crmf_der)
    .build()
)
ir_der = ir_msg.to_der()
```

See also [CRMF Messages](crmf.md) for the request structures carried in CMP bodies.