Synta Python Bindings
Synta is a high-performance Rust ASN.1 library. This book documents its Python bindings — a native extension module built with PyO3 and maturin.
What the bindings provide
The synta Python package exposes Rust types directly to Python, providing
near-native performance while preserving Python’s ease of use:
-
Core ASN.1 codec —
Decoder,Encoder, all primitive types (Integer,OctetString,ObjectIdentifier,BitString,Boolean,Real,Null, string types, time types,TaggedElement,RawElement). -
X.509 PKI — parse
Certificate,CertificationRequest,CertificateList, andOCSPResponse; extract bundles from PKCS#7 and PKCS#12 archives; build CRLs and OCSP responses; verify certificate chains; encode X.509 extension values. -
CMS cryptography — full RFC 5652 and RFC 9629 type coverage:
ContentInfo,SignedData/SignerInfo,EnvelopedData,EncryptedData,DigestedData,AuthenticatedData, and CMS-KEM types. -
Protocol schemas — Kerberos V5 and PKINIT (
synta.krb5), SPNEGO (synta.spnego), RFC 3279 algorithm parameters (synta.pkixalgs), Attribute Certificates (synta.ac), CRMF (synta.crmf), CMP (synta.cmp), PKCS#8 (synta.pkcs8), Microsoft PKI extensions (synta.ms_pki), and Merkle Tree Certificates (synta.mtc). -
OID constants — 70+ well-known OIDs and helper functions in
synta.oidsandsynta.oids.attr.
Implementation notes
The extension module is implemented in the synta-python workspace crate,
which compiles to a cdylib named _synta and is installed as synta._synta
by maturin. Three Rust crates contribute to the module surface:
| Rust crate | Contributes |
|---|---|
synta | Encoding, Decoder, Encoder, all primitive types |
synta-certificate | ObjectIdentifier, Certificate, CertificationRequest, CertificateList, OCSPResponse, PublicKey, PrivateKey, PKCS#7/12 loaders, synta.oids, and all protocol schema submodules |
synta-krb5 | synta.krb5 submodule: Krb5PrincipalName + PKINIT classes |
synta-x509-verification | synta.x509 submodule: TrustStore, CrlStore, VerificationPolicy, chain verification functions |
How to navigate this book
- Installation and Building — how to build the wheel from source and install it.
- Quick Start — minimal decode and encode examples to get started immediately.
- Module Layout — the full
syntamodule tree. - Core ASN.1 Codec — the
Decoder,Encoder, and all primitive types. - X.509 PKI — certificate parsing, PKCS#7/12, extension builders, chain verification, and PyCA interoperability.
- CMS Cryptography — all CMS content types and their builders.
- Protocol Schemas — Kerberos, SPNEGO, and other protocol-specific schemas.
- OID Reference — tables of well-known OID constants.
- Performance and Development — benchmark results and how to build and test the library.
- Example Programs — 28 runnable example programs covering every binding.
For the Rust API, code-generation tutorial, and ASN.1 schema authoring see docs/tutorial.md.
Installation and Building
Prerequisites
- Rust toolchain (1.70 or later)
- Python 3.8 or later (abi3 stable ABI; compatible with CPython 3.8–3.14+)
- maturin 1.x
Build Instructions
# Install maturin
pip install maturin
# Build the wheel (release mode) — maturin reads pyproject.toml automatically
maturin build --release
# Or build in development mode (with a virtualenv active)
maturin develop
The wheel will be created in target/wheels/.
Maturin is configured in pyproject.toml to build the synta-python crate:
[tool.maturin]
manifest-path = "synta-python/Cargo.toml"
module-name = "synta._synta"
bindings = "pyo3"
python-source = "python"
To enable OpenSSL-backed features (PKCS#12 encryption, CMS EnvelopedData construction, X.509 chain verification):
maturin develop --features openssl
maturin build --release --features openssl
To additionally enable legacy PKCS#12 decryption algorithms (3DES, RC2):
maturin develop --features openssl,deprecated-pkcs12-algorithms
Installation
# Install the built wheel
pip install target/wheels/synta-0.1.0-cp38-abi3-manylinux_2_34_x86_64.whl
Running tests
# Build in development mode first
python -m venv venv
source venv/bin/activate
maturin develop
# Run the Python test suite
python -m pytest tests/python/
Note on PYTHONPATH: When running scripts directly (not via
pytest), usePYTHONPATH=python python3 script.pyso Python can find thesyntapackage inpython/synta/.
Quick Start
After installing the package, import synta and start
decoding or encoding ASN.1 data.
Decode a single element
import synta
# Decode an INTEGER
data = b'\x02\x01\x2A' # DER-encoded INTEGER 42
decoder = synta.Decoder(data, synta.Encoding.DER)
integer = decoder.decode_integer()
print(integer.to_int()) # Output: 42
# Decode an OBJECT IDENTIFIER
oid_data = b'\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01'
decoder = synta.Decoder(oid_data, synta.Encoding.DER)
oid = decoder.decode_oid()
print(str(oid)) # Output: 1.2.840.113549.1.1.1
# Decode an OCTET STRING
octet_data = b'\x04\x05hello'
decoder = synta.Decoder(octet_data, synta.Encoding.DER)
octet_string = decoder.decode_octet_string()
print(octet_string.to_bytes()) # Output: b'hello'
Encode a single element
import synta
# Encode an INTEGER
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_integer(42)
output = encoder.finish()
print(output.hex()) # Output: 02012a
# Encode an OCTET STRING
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_octet_string(b'hello')
output = encoder.finish()
print(output.hex()) # Output: 040568656c6c6f
Encode a SEQUENCE
Encode inner elements into a separate Encoder, then wrap the result in a
SEQUENCE using an outer Encoder.
import synta
# Build SEQUENCE { INTEGER 42, BOOLEAN TRUE }
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_integer(42)
inner.encode_boolean(True)
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_sequence(inner.finish())
result = outer.finish()
# result == b'\x30\x06\x02\x01\x2a\x01\x01\xff'
Decode a SEQUENCE
Use decode_sequence() to enter a SEQUENCE and get a child Decoder
positioned over the content bytes.
import synta
# Encoded SEQUENCE { INTEGER 42, BOOLEAN TRUE }
data = b'\x30\x06\x02\x01\x2a\x01\x01\xff'
decoder = synta.Decoder(data, synta.Encoding.DER)
child = decoder.decode_sequence() # advances past the outer SEQUENCE TLV
assert decoder.is_empty()
while not child.is_empty():
obj = child.decode_any() # INTEGER, then BOOLEAN
Parse an X.509 certificate
import synta
with open('cert.der', 'rb') as f:
cert = synta.Certificate.from_der(f.read())
print(cert.subject) # RFC 4514-style DN string
print(cert.issuer)
print(cert.not_before, "–", cert.not_after)
print(cert.signature_algorithm)
See Certificate for the full property list.
Module Layout
Crate-to-module mapping
The synta Python package is implemented as a PyO3 extension module
(_synta.abi3.so) built from three Rust crates:
| Rust crate | Contributes |
|---|---|
synta | Encoding, Decoder, Encoder, all primitive types |
synta-certificate | ObjectIdentifier, Certificate, CertificationRequest, CertificateList, OCSPResponse, PublicKey, PrivateKey, CertificateListBuilder, OCSPResponseBuilder, synta.oids, PKCS#7/12 loaders, synta.pkixalgs, synta.ac, synta.crmf, synta.cmp, synta.pkcs8, synta.pkcs9, synta.kem; OpensslSignatureVerifier |
synta-krb5 (via synta-python) | synta.krb5 submodule: Krb5PrincipalName + PKINIT classes |
synta-x509-verification (via synta-python) | synta.x509 submodule: TrustStore, CrlStore, VerificationPolicy, verify_server_certificate, verify_client_certificate |
The package surface is defined in python/synta/__init__.py, which re-exports
everything from _synta and makes synta.krb5 and synta.oids available
without an explicit sub-import.
Full module tree
synta # top-level package
├── Encoding # enum: DER | BER | CER
├── Decoder # streaming ASN.1 decoder
├── Encoder # ASN.1 encoder
├── SyntaError # exception class
├── __version__ # str, e.g. "0.1.0"
│
├── Integer # primitive types
├── OctetString
├── ObjectIdentifier
├── BitString
├── Boolean
├── Real
├── Null
├── UtcTime
├── GeneralizedTime
├── Utf8String
├── PrintableString
├── IA5String
├── NumericString
├── TeletexString
├── VisibleString
├── GeneralString
├── UniversalString
├── BmpString
├── TaggedElement
├── RawElement
│
├── Certificate # PKI types
├── CertificationRequest
├── CertificateList
├── OCSPResponse
├── PublicKey # backend-agnostic key types (RSA, EC, EdDSA, ML-DSA, ML-KEM)
├── PrivateKey
├── CertificateListBuilder # RFC 5280 CRL TBS builder
├── OCSPResponseBuilder # RFC 6960 OCSP TBS builder
│
├── pem_to_der() # PEM helpers
├── der_to_pem()
│
├── load_der_pkcs7_certificates() # PKCS#7 / PKCS#12 loaders
├── load_pem_pkcs7_certificates()
├── load_pkcs12_certificates()
├── load_pkcs12_keys()
├── load_pkcs12()
├── read_pki_blocks()
│
├── general_name # synta.general_name submodule
│ ├── OTHER_NAME, RFC822_NAME, DNS_NAME, X400_ADDRESS
│ ├── DIRECTORY_NAME, EDI_PARTY_NAME, URI
│ ├── IP_ADDRESS, REGISTERED_ID (integer tag constants)
│ ├── OtherName, RFC822Name, DNSName, X400Address
│ ├── DirectoryName, EDIPartyName
│ ├── UniformResourceIdentifier, IPAddress, RegisteredID
│ └── AnyGeneralName (union type alias for all typed classes)
│
├── oids # synta.oids submodule
│ ├── RSA_ENCRYPTION, SHA256_WITH_RSA, ...
│ ├── EC_PUBLIC_KEY, EC_CURVE_P256, ...
│ ├── ED25519, ED448, ML_DSA_44, ...
│ ├── SUBJECT_ALT_NAME, BASIC_CONSTRAINTS, ...
│ ├── KP_SERVER_AUTH, KP_CLIENT_AUTH, ...
│ ├── ID_PKINIT_SAN, ID_PKINIT_KPCLIENT_AUTH, ...
│ ├── ID_MS_SAN_UPN, ID_MS_KP_SMARTCARD_LOGON, ...
│ └── attr # synta.oids.attr submodule
│ ├── COMMON_NAME, ORGANIZATION, COUNTRY, ...
│ └── ...
│
├── krb5 # synta.krb5 submodule
│ ├── KRB5_PRINCIPAL_NAME_OID
│ ├── NT_UNKNOWN, NT_PRINCIPAL, NT_SRV_INST, ...
│ ├── Krb5PrincipalName
│ ├── EncryptionKey
│ ├── Checksum
│ ├── KDFAlgorithmId
│ ├── IssuerAndSerialNumber
│ ├── ExternalPrincipalIdentifier
│ ├── PKAuthenticator
│ ├── AuthPack
│ ├── PaPkAsReq
│ ├── DHRepInfo
│ ├── KDCDHKeyInfo
│ ├── ReplyKeyPack
│ └── PaPkAsRep
│
├── pkixalgs # synta.pkixalgs submodule (RFC 3279)
│ ├── DssParms, DssSigValue, EcdsaSigValue, ECParameters
│ └── ID_DSA, ID_EC_PUBLIC_KEY, ECDSA_WITH_SHA256, PRIME256V1, ...
│
├── ac # synta.ac submodule (RFC 5755)
│ ├── AttributeCertificate # parse / PEM / verify_issued_by
│ ├── AttributeCertificateBuilder
│ └── ID_AT_ROLE, ID_AT_CLEARANCE, ID_PE_AC_AUDIT_IDENTITY, ...
│
├── crmf # synta.crmf submodule (RFC 4211)
│ ├── CertReqMessages, CertReqMsg
│ ├── CertReqMsgBuilder, CertReqMessagesBuilder
│ ├── PUB_METHOD_DONT_CARE, PUB_METHOD_X500, PUB_METHOD_WEB, PUB_METHOD_LDAP
│ └── ID_REG_CTRL_REG_TOKEN, ID_REG_CTRL_AUTHENTICATOR, ...
│
├── cmp # synta.cmp submodule (RFC 9810)
│ ├── CMPMessage
│ ├── CMPMessageBuilder
│ └── ID_PASSWORD_BASED_MAC, ID_DHBASED_MAC, ID_KP_CM_KGA, ...
│
├── pkcs8 # synta.pkcs8 submodule (RFC 5958 / PKCS#8)
│ ├── OneAsymmetricKey # parse DER-encoded private-key envelope
│ └── PrivateKeyInfo # alias for OneAsymmetricKey
│
├── pkcs9 # synta.pkcs9 submodule (RFC 2985 / PKCS#9)
│ ├── ID_PKCS_9, ID_EMAIL_ADDRESS, ID_CONTENT_TYPE, ID_MESSAGE_DIGEST
│ ├── ID_SIGNING_TIME, ID_COUNTERSIGNATURE, ID_CHALLENGE_PASSWORD
│ └── ID_EXTENSION_REQUEST, ID_FRIENDLY_NAME, ID_LOCAL_KEY_ID, ...
│
├── kem # synta.kem submodule (RFC 9629 / FIPS 203)
│ ├── KEMRecipientInfo # KEM recipient structure in CMS EnvelopedData
│ ├── CMSORIforKEMOtherInfo # KDF input structure for KEMRecipientInfo
│ ├── ID_ML_KEM_512, ID_ML_KEM_768, ID_ML_KEM_1024
│ └── ID_ORI, ID_ORI_KEM
│
├── spnego # synta.spnego submodule (RFC 4178)
│ ├── NegTokenInit # initiator proposal (mech_types, mech_token, …)
│ ├── NegTokenResp # acceptor response (neg_state, supported_mech, …)
│ ├── NegotiationToken # CHOICE wrapper; from_der handles GSSAPI 0x60 form
│ ├── NEG_STATE_ACCEPT_COMPLETED, NEG_STATE_ACCEPT_INCOMPLETE
│ ├── NEG_STATE_REJECT, NEG_STATE_REQUEST_MIC
│ └── SPNEGO_OID # "1.3.6.1.5.5.2"
│
├── ms_pki # synta.ms_pki submodule (Microsoft AD CS)
│ ├── MSCSTemplateV2 # id-ms-certificate-template (OID 1.3.6.1.4.1.311.21.7)
│ ├── RequestClientInfo # id-ms-request-Client-Info (OID 1.3.6.1.4.1.311.21.20)
│ └── ID_MS_CERTSRV_CA_VERSION, ID_MS_KP_CA_EXCHANGE, ID_MS_KP_EFS_CRYPTO, …
│
├── mtc # synta.mtc submodule (draft-ietf-plants-merkle-tree-certs)
│ ├── ProofNode, Subtree, SubtreeProof, InclusionProof
│ ├── LogID, CosignerID, Checkpoint, SubtreeSignature
│ ├── TbsCertificateLogEntry, MerkleTreeCertEntry
│ ├── LandmarkID, StandaloneCertificate, LandmarkCertificate
│ └── (all classes parsed via from_der; names are raw DER, pass to parse_name_attrs)
│
└── x509 # synta.x509 submodule (RFC 5280 / CABF path validation)
├── TrustStore # trusted root CA store (DER bytes)
├── CrlStore # CRL revocation checking store (DER bytes)
├── VerificationPolicy # server_names, name_match, validation_time, max_chain_depth, profile
├── X509VerificationError # raised on any chain or policy failure
├── verify_server_certificate(leaf, intermediates, store, policy, crls=None) → list[bytes]
└── verify_client_certificate(leaf, intermediates, store, policy, crls=None) → list[bytes]
Encoding Rules: DER, BER, CER
Constants and enumerations
| Name | Type | Description |
|---|---|---|
Encoding.DER | Encoding | Distinguished Encoding Rules |
Encoding.BER | Encoding | Basic Encoding Rules |
Encoding.CER | Encoding | Canonical Encoding Rules |
SyntaError | exception class | Raised on ASN.1 parse or encode failures |
__version__ | str | Package version string (from Cargo.toml) |
Encoding
Pass an Encoding value when constructing a Decoder or Encoder:
import synta
# DER — deterministic, used for X.509 certificates and PKCS structures
decoder = synta.Decoder(data, synta.Encoding.DER)
# BER — accepts indefinite-length forms (PKCS#7, LDAP, SNMP)
decoder = synta.Decoder(data, synta.Encoding.BER)
# CER — canonical streaming subset of BER
decoder = synta.Decoder(data, synta.Encoding.CER)
The Decoder and Encoder classes accept any Encoding value. Most PKI
standards mandate DER. Use BER when reading PKCS#7 files from older tools
that emit indefinite-length encodings.
API types
import synta
# DER encoder example
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_integer(42)
out = encoder.finish() # b'\x02\x01\x2a'
See Decoder and Encoder for full API reference.
Decoder
Constructor
Decoder(data: bytes, encoding: Encoding)
Creates a streaming decoder over data. The internal position starts at 0.
Each decode_* call advances the position past the decoded element.
Primitive decode methods
| Method | Returns | ASN.1 type | Tag |
|---|---|---|---|
decode_integer() | Integer | INTEGER | 0x02 |
decode_octet_string() | OctetString | OCTET STRING | 0x04 |
decode_oid() | ObjectIdentifier | OBJECT IDENTIFIER | 0x06 |
decode_bit_string() | BitString | BIT STRING | 0x03 |
decode_boolean() | Boolean | BOOLEAN | 0x01 |
decode_utc_time() | UtcTime | UTCTime | 0x17 |
decode_generalized_time() | GeneralizedTime | GeneralizedTime | 0x18 |
decode_null() | Null | NULL | 0x05 |
decode_real() | Real | REAL | 0x09 |
decode_utf8_string() | Utf8String | UTF8String | 0x0c |
decode_printable_string() | PrintableString | PrintableString | 0x13 |
decode_ia5_string() | IA5String | IA5String | 0x16 |
decode_numeric_string() | NumericString | NumericString | 0x12 |
decode_teletex_string() | TeletexString | TeletexString / T61String | 0x14 |
decode_visible_string() | VisibleString | VisibleString | 0x1a |
decode_general_string() | GeneralString | GeneralString | 0x1b |
decode_universal_string() | UniversalString | UniversalString | 0x1c |
decode_bmp_string() | BmpString | BMPString | 0x1e |
decode_any() | any Python object | any element | — |
decode_any_str() | str | any string type | — |
decode_any() dispatch table
decode_any() dispatches on the tag at the current position:
| ASN.1 Type | Python value |
|---|---|
| BOOLEAN | Boolean |
| INTEGER | Integer |
| BIT STRING | BitString |
| OCTET STRING | OctetString |
| NULL | Null |
| OBJECT IDENTIFIER | ObjectIdentifier |
| UTF8String | Utf8String |
| PrintableString | PrintableString |
| IA5String | IA5String |
| NumericString | NumericString |
| TeletexString | TeletexString |
| VisibleString | VisibleString |
| GeneralString | GeneralString |
| UniversalString | UniversalString |
| BmpString | BmpString |
| UTCTime | UtcTime |
| GeneralizedTime | GeneralizedTime |
| SEQUENCE / SET | list of the above |
| Tagged | TaggedElement |
| Unknown universal | RawElement |
decode_any_str() encoding table
decode_any_str() reads one TLV and decodes it as a native Python str,
applying the correct encoding for each of the nine ASN.1 string types:
| Tag | Type | Decoding |
|---|---|---|
| 12 | UTF8String | UTF-8 (lossy) |
| 18 | NumericString | UTF-8 |
| 19 | PrintableString | UTF-8 |
| 20 | TeletexString / T61String | Latin-1 (each byte → U+0000–U+00FF) |
| 22 | IA5String | UTF-8 |
| 26 | VisibleString | UTF-8 |
| 27 | GeneralString | UTF-8 |
| 28 | UniversalString | UCS-4 big-endian |
| 30 | BMPString | UCS-2 big-endian |
Raises ValueError for any other tag; raises EOFError if the decoder is
empty. This is the single-call replacement for the duck-typing probe on
decode_any():
# Before — three-way probe:
val = decoder.decode_any()
if hasattr(val, 'as_str'):
s = val.as_str()
elif hasattr(val, 'to_bytes'):
s = val.to_bytes().decode('latin-1')
else:
raise ValueError(f"not a string: {type(val)}")
# After — one call, correct encoding for all nine types:
s = decoder.decode_any_str()
Structured / container decode methods
| Method | Signature | Returns | Description |
|---|---|---|---|
decode_sequence | () | Decoder | Consume a SEQUENCE TLV; return child decoder over its contents. |
decode_set | () | Decoder | Consume a SET TLV; return child decoder over its contents. |
decode_explicit_tag | (tag_num: int) | Decoder | Strip an explicit context-specific tag [tag_num]; return child decoder over the content. |
decode_implicit_tag | (tag_num: int, tag_class: str) | Decoder | Strip an implicit tag; return child decoder over the value bytes only (no tag/length). tag_class is "Context", "Application", "Private", or "Universal". |
decode_raw_tlv | () | bytes | Read the next complete TLV (tag + length + value) as raw bytes and advance past it. |
Introspection helpers
| Method | Returns | Description |
|---|---|---|
peek_tag() | tuple[int, str, bool] | (tag_number, tag_class, is_constructed) — does not advance the position. Raises EOFError if no data remains. |
remaining_bytes() | bytes | All bytes from the current position to the end. Useful after decode_implicit_tag to retrieve bare primitive value bytes. |
is_empty() | bool | True when the current position equals the data length. |
position() | int | Current byte offset. |
remaining() | int | Number of bytes left. |
Full class stub
class Decoder:
def __init__(self, data: bytes, encoding: Encoding) -> None: ...
# Primitive types
def decode_integer(self) -> Integer: ...
def decode_octet_string(self) -> OctetString: ...
def decode_oid(self) -> ObjectIdentifier: ...
def decode_bit_string(self) -> BitString: ...
def decode_boolean(self) -> Boolean: ...
def decode_real(self) -> Real: ...
def decode_null(self) -> Null: ...
def decode_utc_time(self) -> UtcTime: ...
def decode_generalized_time(self) -> GeneralizedTime: ...
# String types
def decode_utf8_string(self) -> Utf8String: ...
def decode_printable_string(self) -> PrintableString: ...
def decode_ia5_string(self) -> IA5String: ...
def decode_numeric_string(self) -> NumericString: ... # tag 18
def decode_teletex_string(self) -> TeletexString: ... # tag 20
def decode_visible_string(self) -> VisibleString: ... # tag 26
def decode_general_string(self) -> GeneralString: ... # tag 27
def decode_universal_string(self) -> UniversalString: ... # tag 28
def decode_bmp_string(self) -> BmpString: ... # tag 30
# Constructed / tagged
def decode_sequence(self) -> Decoder: ...
# Reads a SEQUENCE TLV, advances past it, and returns a new Decoder over
# the content bytes. Raises ValueError if the next element is not a SEQUENCE.
def decode_explicit_tag(self, tag_num: int) -> Decoder: ...
# Reads an explicit context-specific tag [tag_num], advances past it, and
# returns a new Decoder over the tagged content.
# Raises ValueError if the tag number does not match.
def decode_set(self) -> Decoder: ...
# Reads a SET TLV (tag 0x31), advances past it, and returns a new Decoder
# over the content bytes. Raises ValueError if the next element is not a SET.
def decode_implicit_tag(self, tag_num: int, tag_class: str) -> Decoder: ...
# Strips an implicit tag of the given number and class and returns a new
# Decoder over the raw value bytes. tag_class must be "Universal",
# "Context", "Application", or "Private". Raises ValueError on mismatch.
# The caller must know the original type and call the appropriate decode_*
# method on the returned Decoder.
#
# Example:
# raw_decoder = decoder.decode_implicit_tag(0, "Context")
# value = raw_decoder.decode_integer()
def peek_tag(self) -> tuple[int, str, bool]: ...
# Returns (tag_number, tag_class, is_constructed) of the next element without
# consuming any bytes. Raises EOFError if the decoder is empty.
# Use for CHOICE dispatch or optional-field detection:
# tag_num, tag_class, _ = decoder.peek_tag()
# if tag_class == "Context" and tag_num == 0:
# version = decoder.decode_explicit_tag(0)
def decode_raw_tlv(self) -> bytes: ...
# Reads the complete next TLV (tag + length + value bytes) as a bytes object
# and advances past it. Useful when the element type is unknown or when
# decoding should be deferred:
# tlv = decoder.decode_raw_tlv()
# inner = synta.Decoder(tlv, synta.Encoding.DER)
def remaining_bytes(self) -> bytes: ...
# Returns all remaining bytes from the current position without advancing.
# Primarily useful after decode_implicit_tag() for **primitive** implicit
# types whose raw value bytes cannot be decoded with the decode_* methods
# (those expect a full TLV, but implicit stripping leaves only the value):
#
# # Decode dNSName [2] IMPLICIT IA5String
# child = decoder.decode_implicit_tag(2, "Context")
# dns_name = child.remaining_bytes().decode("ascii")
#
# # Decode iPAddress [7] IMPLICIT OCTET STRING
# child = decoder.decode_implicit_tag(7, "Context")
# ip_bytes = child.remaining_bytes() # 4 or 16 raw bytes
# Dynamic decoding
def decode_any(self) -> object: ...
# Returns a typed Python object. Sequence/Set → list.
# Tagged elements → TaggedElement.
# Unknown universal tags → RawElement.
def decode_any_str(self) -> str: ...
# Decode any ASN.1 string type as a Python str (correct encoding per type).
# Raises ValueError for non-string tags; EOFError if empty.
# State
def is_empty(self) -> bool: ...
def position(self) -> int: ...
def remaining(self) -> int: ...
Usage examples
Decoding ASN.1 data
import synta
# Decode an INTEGER
data = b'\x02\x01\x2A' # DER-encoded INTEGER 42
decoder = synta.Decoder(data, synta.Encoding.DER)
integer = decoder.decode_integer()
print(integer.to_int()) # Output: 42
# Decode an OBJECT IDENTIFIER
oid_data = b'\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01'
decoder = synta.Decoder(oid_data, synta.Encoding.DER)
oid = decoder.decode_oid()
print(str(oid)) # Output: 1.2.840.113549.1.1.1
# Decode an OCTET STRING
octet_data = b'\x04\x05hello'
decoder = synta.Decoder(octet_data, synta.Encoding.DER)
octet_string = decoder.decode_octet_string()
print(octet_string.to_bytes()) # Output: b'hello'
# Decode a NULL
null_data = b'\x05\x00'
decoder = synta.Decoder(null_data, synta.Encoding.DER)
null = decoder.decode_null()
# Decode a REAL (IEEE 754 double)
real_data = b'\x09\x01\x40' # PLUS-INFINITY
decoder = synta.Decoder(real_data, synta.Encoding.DER)
r = decoder.decode_real()
import math
assert math.isinf(float(r))
# Decode any element dynamically
data = b'\x02\x01\x2A'
decoder = synta.Decoder(data, synta.Encoding.DER)
obj = decoder.decode_any() # Returns Integer, OctetString, list (Sequence/Set), etc.
Decoding SEQUENCE structures
Use decode_sequence() to enter a SEQUENCE and get a child Decoder
positioned over the content bytes. Iterate with typed decode_* methods
and is_empty().
import synta
# Encoded SEQUENCE { INTEGER 42, BOOLEAN TRUE }
data = b'\x30\x06\x02\x01\x2a\x01\x01\xff'
decoder = synta.Decoder(data, synta.Encoding.DER)
child = decoder.decode_sequence() # advances past the outer SEQUENCE TLV
assert decoder.is_empty()
while not child.is_empty():
obj = child.decode_any() # INTEGER, then BOOLEAN
# Decode an explicit context tag [1] wrapping an INTEGER
tagged_data = b'\xa1\x05\x02\x03\x00\x00\x63' # [1] EXPLICIT INTEGER 99
decoder = synta.Decoder(tagged_data, synta.Encoding.DER)
child = decoder.decode_explicit_tag(1) # raises ValueError if tag != [1]
integer = child.decode_integer()
assert integer.to_int() == 99
Encoder
Constructor
Encoder(encoding: Encoding)
Creates a new in-memory encoder. Elements are appended in call order.
Call finish() to retrieve the accumulated bytes.
Primitive encode methods — raw values
| Method | Signature | ASN.1 type |
|---|---|---|
encode_integer | (value: int) | INTEGER — accepts any Python int (arbitrary magnitude via bigint path) |
encode_octet_string | (data: bytes) | OCTET STRING |
encode_oid | (value: ObjectIdentifier) | OBJECT IDENTIFIER |
encode_bit_string | (value: BitString) | BIT STRING |
encode_boolean | (value: bool) | BOOLEAN |
encode_utc_time | (value: UtcTime) | UTCTime |
encode_generalized_time | (value: GeneralizedTime) | GeneralizedTime |
encode_real | (value: float) | REAL |
encode_null | () | NULL |
encode_utf8_string | (value: str) | UTF8String |
encode_printable_string | (value: str) | PrintableString — raises ValueError for invalid chars |
encode_ia5_string | (value: str) | IA5String — raises ValueError for non-ASCII |
encode_numeric_string | (value: str) | NumericString — raises ValueError for non-digit/non-space |
encode_teletex_string | (data: bytes) | TeletexString / T61String |
encode_visible_string | (value: str) | VisibleString — raises ValueError for invalid chars |
encode_general_string | (data: bytes) | GeneralString |
encode_universal_string | (value: str) | UniversalString (UCS-4 BE) |
encode_bmp_string | (value: str) | BMPString (UCS-2 BE) — raises ValueError for non-BMP code points |
Primitive encode methods — typed objects
Each primitive type has an _object variant that accepts the corresponding
Python wrapper instead of a raw value.
| Method | Accepts |
|---|---|
encode_integer_object | Integer |
encode_octet_string_object | OctetString |
encode_oid_object | ObjectIdentifier (alias for encode_oid) |
encode_bit_string_object | BitString (alias for encode_bit_string) |
encode_boolean_object | Boolean |
encode_utc_time_object | UtcTime (alias for encode_utc_time) |
encode_generalized_time_object | GeneralizedTime (alias for encode_generalized_time) |
encode_real_object | Real |
encode_null_object | Null |
encode_utf8_string_object | Utf8String |
encode_printable_string_object | PrintableString |
encode_ia5_string_object | IA5String |
encode_numeric_string_object | NumericString |
encode_teletex_string_object | TeletexString |
encode_visible_string_object | VisibleString |
encode_general_string_object | GeneralString |
encode_universal_string_object | UniversalString |
encode_bmp_string_object | BmpString |
Container / tagging encode methods
| Method | Signature | Description |
|---|---|---|
encode_sequence | (inner_bytes: bytes) | Wrap inner_bytes with SEQUENCE TLV (tag 0x30). |
encode_set | (inner_bytes: bytes) | Wrap inner_bytes with SET TLV (tag 0x31). |
encode_explicit_tag | (tag_num: int, tag_class: str, inner_bytes: bytes) | Wrap inner_bytes in an explicit constructed tag. tag_class: "Context", "Application", "Private". |
encode_implicit_tag | (tag_num: int, tag_class: str, is_constructed: bool, value_bytes: bytes) | Emit an implicit tag over raw value bytes. Set is_constructed=True for SEQUENCE/SET underlying types. |
Finalisation
| Method | Returns | Description |
|---|---|---|
finish() | bytes | Consume the encoder and return the accumulated DER/BER bytes. The encoder is reset and can be reused (rarely needed). |
Full class stub
class Encoder:
def __init__(self, encoding: Encoding) -> None: ...
# Primitive types
def encode_integer(self, value: int) -> None: ...
# Accepts any Python int, including values beyond i64/i128 range (e.g.
# 20-byte X.509 serial numbers up to 160 bits).
def encode_integer_object(self, value: Integer) -> None: ...
def encode_octet_string(self, data: bytes) -> None: ...
def encode_octet_string_object(self, value: OctetString) -> None: ...
def encode_oid(self, value: ObjectIdentifier) -> None: ...
def encode_bit_string(self, value: BitString) -> None: ...
def encode_boolean(self, value: bool) -> None: ...
def encode_real(self, value: float) -> None: ...
def encode_real_object(self, value: Real) -> None: ...
def encode_null(self) -> None: ...
def encode_null_object(self, value: Null) -> None: ...
def encode_utc_time(self, value: UtcTime) -> None: ...
def encode_generalized_time(self, value: GeneralizedTime) -> None: ...
# String types
def encode_utf8_string(self, value: str) -> None: ...
def encode_utf8_string_object(self, value: Utf8String) -> None: ...
def encode_printable_string(self, value: str) -> None: ... # Raises ValueError for invalid chars
def encode_printable_string_object(self, value: PrintableString) -> None: ...
def encode_ia5_string(self, value: str) -> None: ... # Raises ValueError for non-ASCII
def encode_ia5_string_object(self, value: IA5String) -> None: ...
def encode_numeric_string(self, value: str) -> None: ... # Raises ValueError for non-digit/space
def encode_numeric_string_object(self, value: NumericString) -> None: ...
def encode_teletex_string(self, data: bytes) -> None: ... # Raw bytes, tag 20
def encode_teletex_string_object(self, value: TeletexString) -> None: ...
def encode_visible_string(self, value: str) -> None: ... # Raises ValueError for control chars
def encode_visible_string_object(self, value: VisibleString) -> None: ...
def encode_general_string(self, data: bytes) -> None: ... # Raw bytes, tag 27
def encode_general_string_object(self, value: GeneralString) -> None: ...
def encode_universal_string(self, value: str) -> None: ... # UCS-4 BE, tag 28
def encode_universal_string_object(self, value: UniversalString) -> None: ...
def encode_bmp_string(self, value: str) -> None: ... # UCS-2 BE, tag 30; Raises ValueError if > U+FFFF
def encode_bmp_string_object(self, value: BmpString) -> None: ...
# Constructed / tagged
def encode_sequence(self, inner_bytes: bytes) -> None: ...
# Wraps pre-encoded bytes in a SEQUENCE TLV (tag 0x30).
# Encode inner elements into a separate Encoder, call finish(), then pass
# the result here.
def encode_set(self, inner_bytes: bytes) -> None: ...
# Wraps pre-encoded bytes in a SET TLV (tag 0x31).
def encode_explicit_tag(self, tag_num: int, tag_class: str, inner_bytes: bytes) -> None: ...
# Wraps pre-encoded bytes in an explicit tag TLV (always constructed).
# tag_class must be "Context", "Application", or "Private".
# Raises ValueError for any other value.
def encode_implicit_tag(self, tag_num: int, tag_class: str,
is_constructed: bool, value_bytes: bytes) -> None: ...
# Writes an implicit tag with the given value bytes (not a full TLV).
# For implicit tagging the original type tag is replaced, so pass the raw
# value content (not the original TLV). Set is_constructed=True when the
# underlying type is SEQUENCE, SET, or another constructed form.
# tag_class must be "Context", "Application", or "Private".
#
# Example — [1] IMPLICIT INTEGER with raw value bytes b'\x2a':
# enc.encode_implicit_tag(1, "Context", False, b'\x2a')
def finish(self) -> bytes: ...
Usage examples
Encoding ASN.1 data
import synta
# Encode an INTEGER
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_integer(42)
output = encoder.finish()
print(output.hex()) # Output: 02012a
# Encode an OCTET STRING
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_octet_string(b'hello')
output = encoder.finish()
print(output.hex()) # Output: 040568656c6c6f
# Encode a NULL
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_null()
output = encoder.finish()
print(output.hex()) # Output: 0500
# Encode a REAL
import math
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_real(0.0)
output = encoder.finish()
print(output.hex()) # Output: 0900
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_real(math.inf)
output = encoder.finish()
print(output.hex()) # Output: 090140
# Encode a UTF8String
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_utf8_string("Hello, world!")
output = encoder.finish()
# Encode a GeneralString (Kerberos realm, FreeIPA use-case)
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_general_string(b"EXAMPLE.COM")
output = encoder.finish()
# Encode a BMPString (Microsoft AD-CS template name)
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_bmp_string("User")
output = encoder.finish()
Building SEQUENCE structures
Encode inner elements into a separate Encoder, then wrap the output in a
SEQUENCE or SET using the outer Encoder. Use encode_explicit_tag to
add explicit context tags.
import synta
# Build SEQUENCE { INTEGER 42, BOOLEAN TRUE }
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_integer(42)
inner.encode_boolean(True)
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_sequence(inner.finish())
result = outer.finish()
# result == b'\x30\x06\x02\x01\x2a\x01\x01\xff'
# Build [1] EXPLICIT INTEGER 99 (explicit context tag)
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_integer(99)
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_explicit_tag(1, "Context", inner.finish())
result = outer.finish()
# First byte 0xa1 = context-specific constructed tag [1]
Primitive Types
The following primitive ASN.1 types are available as Python classes in the
synta package. Each section below links to the detailed API reference.
Numeric types
See Numeric Types for full API reference.
String types
| Class | ASN.1 type | Tag | Character set |
|---|---|---|---|
Utf8String | UTF8String | 0x0c | Unicode |
PrintableString | PrintableString | 0x13 | Restricted ASCII |
IA5String | IA5String | 0x16 | ASCII (0x00–0x7f) |
NumericString | NumericString | 0x12 | Digits and space |
TeletexString | TeletexString / T61String | 0x14 | Arbitrary bytes (Latin-1 decode) |
VisibleString | VisibleString | 0x1a | Printable ASCII |
GeneralString | GeneralString | 0x1b | Arbitrary bytes (Latin-1 decode) |
UniversalString | UniversalString | 0x1c | UCS-4 big-endian |
BmpString | BMPString | 0x1e | UCS-2 big-endian (BMP only) |
See String Types for full API reference.
Time types
| Class | ASN.1 type | Tag |
|---|---|---|
UtcTime | UTCTime | 0x17 |
GeneralizedTime | GeneralizedTime | 0x18 |
See Time Types for full API reference.
Bit and octet strings
| Class | ASN.1 type | Tag |
|---|---|---|
OctetString | OCTET STRING | 0x04 |
BitString | BIT STRING | 0x03 |
See Bit and Octet Strings for full API reference.
Tagged and raw elements
| Class | Description |
|---|---|
TaggedElement | Returned by decode_any() for explicitly-tagged or application/private-tagged values. |
RawElement | Returned by decode_any() for unknown universal tags. |
See Tagged and Raw Elements for full API reference.
Numeric Types: Integer, Real, Boolean, Null
Integer
ASN.1 tag 0x02.
Constructors
Integer(value: int) # constructor — i64 range only
Integer.from_bytes(data: bytes) -> Integer # big-endian two's complement
Integer.from_u64(value: int) -> Integer # unsigned 64-bit
Methods
| Method | Returns | Description |
|---|---|---|
to_int() | int | Convert to Python int via i64; raises OverflowError if too large. |
to_i128() | int | Convert via i128; raises OverflowError if too large. |
to_bytes() | bytes | Raw big-endian two’s complement bytes. |
Special methods: __repr__, __str__, __eq__, __hash__.
Full class stub
class Integer:
def __init__(self, value: int) -> None: ...
def to_int(self) -> int: ... # Raises OverflowError if > i64
def to_i128(self) -> int: ... # Raises OverflowError if > i128
def to_bytes(self) -> bytes: ... # Big-endian two's complement
@staticmethod
def from_bytes(data: bytes) -> Integer: ...
@staticmethod
def from_u64(value: int) -> Integer: ...
def __eq__(self, other: object) -> bool: ... # Compares canonical DER encoding
def __hash__(self) -> int: ... # Hash over canonical DER bytes
Boolean
ASN.1 tag 0x01.
Constructors
Boolean(value: bool)
Methods
| Method | Returns |
|---|---|
value() | bool |
__bool__() | bool |
Full class stub
class Boolean:
def __init__(self, value: bool) -> None: ...
def value(self) -> bool: ...
def __bool__(self) -> bool: ...
def __eq__(self, other: object) -> bool: ...
def __hash__(self) -> int: ... # hash(True) == 1, hash(False) == 0
Real
ASN.1 tag 0x09.
Constructors
Real(value: float)
Methods
| Method | Returns | Description |
|---|---|---|
value() | float | |
is_infinite() | bool | True for ±∞ |
is_nan() | bool | True for NaN |
is_finite() | bool | True for finite, non-NaN |
__float__() | float | Enables float(r) |
__eq__, __hash__ | Hash consistent with IEEE 754 (NaN != NaN, -0.0 == 0.0) |
Special values (X.690 encoding)
| Value | DER bytes |
|---|---|
0.0 | 09 00 |
math.inf | 09 01 40 |
-math.inf | 09 01 41 |
math.nan | 09 01 42 |
finite f64 | 09 09 80 <8 bytes> |
Full class stub
class Real:
def __init__(self, value: float) -> None: ...
def value(self) -> float: ...
def is_infinite(self) -> bool: ... # True for +∞ and −∞
def is_nan(self) -> bool: ...
def is_finite(self) -> bool: ...
def __float__(self) -> float: ...
def __eq__(self, other: Real) -> bool: ... # NaN != NaN (IEEE 754)
def __hash__(self) -> int: ... # -0.0 normalised to 0.0 before hashing
Null
ASN.1 tag 0x05.
Null()
__repr__ returns "Null()". All Null() instances compare equal.
Full class stub
class Null:
def __init__(self) -> None: ...
def __eq__(self, other: object) -> bool: ... # Always True for Null instances
def __hash__(self) -> int: ... # Always 0
String Types
All string types expose .as_str() -> str and support __str__, __eq__,
__len__. Types whose character sets are subsets of ASCII raise ValueError
on construction if invalid characters are supplied.
Overview table
| Class | Constructor | Valid charset | Notes |
|---|---|---|---|
Utf8String | (value: str) | Unicode | |
PrintableString | (value: str) | A–Z a–z 0–9 ' ( ) + , - . / : = ? | |
IA5String | (value: str) | ASCII (0x00–0x7f) | |
NumericString | (value: str) | Digits and space | |
TeletexString | (data: bytes) | Arbitrary bytes | Also: TeletexString.from_latin1(str), from_str(str). .to_bytes() returns raw bytes. |
VisibleString | (value: str) | Printable ASCII (0x21–0x7e) | |
GeneralString | (data: bytes) | Arbitrary bytes | Also: GeneralString.from_ascii(str) (ASCII only, raises ValueError for > U+007F), from_str(str). .to_bytes() returns raw bytes. |
UniversalString | (value: str) | Unicode (UCS-4 BE) | Also: UniversalString.from_bytes(data: bytes). |
BmpString | (value: str) | BMP only (U+0000–U+FFFF) | Also: BmpString.from_bytes(data: bytes). Raises ValueError for non-BMP code points. |
Utf8String
ASN.1 tag 0x0c.
class Utf8String:
def __init__(self, value: str) -> None: ...
def as_str(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: Utf8String) -> bool: ...
def __len__(self) -> int: ...
PrintableString
ASN.1 tag 0x13. Valid characters: A-Z a-z 0-9 ' ( ) + , - . / : = ?
class PrintableString:
def __init__(self, value: str) -> None: ... # Raises ValueError for invalid chars
def as_str(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: PrintableString) -> bool: ...
def __len__(self) -> int: ...
IA5String
ASN.1 tag 0x16. ASCII subset (0x00–0x7f).
class IA5String:
def __init__(self, value: str) -> None: ... # Raises ValueError for non-ASCII
def as_str(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: IA5String) -> bool: ...
def __len__(self) -> int: ...
NumericString
ASN.1 tag 0x12. Restricted to the digits 0–9 and the space character.
Valid characters: 0 1 2 3 4 5 6 7 8 9 (digits and space)
class NumericString:
def __init__(self, value: str) -> None: ... # Raises ValueError for invalid chars
def as_str(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: NumericString) -> bool: ...
def __len__(self) -> int: ...
TeletexString
ASN.1 tag 0x14, also known as T61String. Stores arbitrary 8-bit bytes
without enforcing a character encoding. as_str() interprets the bytes as
Latin-1 (ISO 8859-1).
class TeletexString:
def __init__(self, data: bytes) -> None: ...
@staticmethod
def from_str(value: str) -> TeletexString: ... # Encodes as Latin-1 (lossy for non-Latin-1 code points)
def to_bytes(self) -> bytes: ...
def as_str(self) -> str: ... # Latin-1 decode
def __len__(self) -> int: ... # byte length
def __eq__(self, other: TeletexString) -> bool: ...
VisibleString
ASN.1 tag 0x1a. Printable ASCII subset: bytes 0x20–0x7e (space through
tilde). Control characters (below 0x20) and DEL (0x7f) are rejected.
class VisibleString:
def __init__(self, value: str) -> None: ... # Raises ValueError for control chars or non-ASCII
def as_str(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: VisibleString) -> bool: ...
def __len__(self) -> int: ...
GeneralString
ASN.1 tag 0x1b. Stores arbitrary 8-bit bytes without enforcing a character
encoding. as_str() interprets the bytes as Latin-1 (ISO 8859-1). Commonly
used for Kerberos realm names and principal name components (RFC 4120).
class GeneralString:
def __init__(self, data: bytes) -> None: ...
@staticmethod
def from_ascii(value: str) -> GeneralString: ... # Raises ValueError for non-ASCII (> U+007F)
@staticmethod
def from_str(value: str) -> GeneralString: ... # Alias for from_ascii
def to_bytes(self) -> bytes: ...
def as_str(self) -> str: ... # Latin-1 decode
def __len__(self) -> int: ... # byte length
def __eq__(self, other: GeneralString) -> bool: ...
UniversalString
ASN.1 tag 0x1c. Encodes Unicode text as UCS-4 big-endian (4 bytes per code
point, superset of BMP). Accepts any valid Unicode string.
class UniversalString:
def __init__(self, value: str) -> None: ...
@staticmethod
def from_bytes(data: bytes) -> UniversalString: ...
# Decodes UCS-4 BE bytes. Raises ValueError if length is not a multiple of 4
# or if any four-byte sequence is not a valid Unicode scalar value.
def as_str(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: UniversalString) -> bool: ...
def __len__(self) -> int: ...
BmpString
ASN.1 tag 0x1e. Encodes Unicode text as UCS-2 big-endian (2 bytes per code
point, Basic Multilingual Plane only, U+0000–U+FFFF). Code points above
U+FFFF are rejected.
class BmpString:
def __init__(self, value: str) -> None: ...
# Raises ValueError if the string contains code points outside the BMP (> U+FFFF).
@staticmethod
def from_bytes(data: bytes) -> BmpString: ...
# Decodes UCS-2 BE bytes. Raises ValueError if length is odd or if any
# two-byte sequence is a surrogate half.
def as_str(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: BmpString) -> bool: ...
def __len__(self) -> int: ...
Time Types: UtcTime and GeneralizedTime
UtcTime
ASN.1 tag 0x17. Represents a time in the range 1950–2049.
Constructor
UtcTime(year: int, month: int, day: int, hour: int, minute: int, second: int)
Year must be in the range 1950–2049. Raises ValueError on invalid input.
Properties
Properties: year, month, day, hour, minute, second (all int).
__str__ returns the canonical YYMMDDHHMMSSZ form.
Full class stub
class UtcTime:
def __init__(self, year: int, month: int, day: int,
hour: int, minute: int, second: int) -> None: ...
year: int
month: int
day: int
hour: int
minute: int
second: int
def __str__(self) -> str: ... # e.g. "230101120000Z"
Year must be in the range 1950–2049.
GeneralizedTime
ASN.1 tag 0x18. Represents a time with optional millisecond precision.
Constructor
GeneralizedTime(year: int, month: int, day: int, hour: int, minute: int, second: int,
milliseconds: int | None = None)
Properties
Properties: year, month, day, hour, minute, second (int),
milliseconds (int | None). __str__ returns the canonical
YYYYMMDDHHMMSS[.fff]Z form.
Full class stub
class GeneralizedTime:
def __init__(self, year: int, month: int, day: int,
hour: int, minute: int, second: int,
milliseconds: int | None = None) -> None: ...
year: int
month: int
day: int
hour: int
minute: int
second: int
milliseconds: int | None
def __str__(self) -> str: ... # e.g. "20230101120000Z"
Bit and Octet Strings
OctetString
ASN.1 tag 0x04.
Constructor
OctetString(data: bytes)
Methods
| Method | Returns |
|---|---|
to_bytes() | bytes |
__len__() | int |
__eq__(other) | bool |
Full class stub
class OctetString:
def __init__(self, data: bytes) -> None: ...
def to_bytes(self) -> bytes: ...
def __len__(self) -> int: ...
def __eq__(self, other: OctetString) -> bool: ...
BitString
ASN.1 tag 0x03.
Constructor
BitString(data: bytes, unused_bits: int) # unused_bits in 0–7
Methods
| Method | Returns | Description |
|---|---|---|
to_bytes() | bytes | Raw content bytes (excluding unused-bits octet). |
unused_bits() | int | Number of padding bits in the last byte. |
bit_len() | int | Total significant bits (len(data) * 8 - unused_bits). |
__len__() | int | Same as bit_len(). |
__eq__(other) | bool |
Full class stub
class BitString:
def __init__(self, data: bytes, unused_bits: int) -> None: ...
def to_bytes(self) -> bytes: ...
def unused_bits(self) -> int: ...
def bit_len(self) -> int: ...
def __len__(self) -> int: ... # byte length (same as len(to_bytes()))
def __eq__(self, other: BitString) -> bool: ...
Tagged and Raw Elements
TaggedElement
Returned by Decoder.decode_any() for explicitly-tagged or
application/private-tagged values.
| Attribute | Type | Description |
|---|---|---|
tag_number | int | The tag number |
tag_class | str | "Universal", "Context", "Application", or "Private" |
is_constructed | bool | True for constructed (e.g. wrapped SEQUENCE) |
value | any | The inner decoded element |
RawElement
Returned by Decoder.decode_any() for unknown universal tags (enables
forward-compatible parsing).
| Attribute / Method | Type | Description |
|---|---|---|
tag_number | int | |
tag_class | str | |
is_constructed | bool | |
data | bytes | Raw content bytes (no tag or length) |
Full class stub
class RawElement:
tag_number: int # Universal tag number
tag_class: str # "Universal", "Application", "Context", or "Private"
is_constructed: bool # True if the element is constructed
data: bytes # Raw content bytes (tag and length stripped)
data is a #[getter] property — access it as elem.data (not elem.data()).
ObjectIdentifier
ObjectIdentifier is a frozen (immutable, thread-safe) wrapper around an ASN.1
OBJECT IDENTIFIER value.
Constructors
ObjectIdentifier(oid_str: str) # from dotted-decimal, e.g. "2.5.4.3"
ObjectIdentifier.from_components(comps: list[int]) # from arc list
ObjectIdentifier.from_der_value(data: bytes) # from implicit-tag content bytes (tag+length stripped)
Methods and dunders
| Method / Dunder | Returns | Description |
|---|---|---|
components() | tuple[int, ...] | OID arc components |
__str__() | str | Dotted-decimal notation (cached) |
__repr__() | str | ObjectIdentifier('2.5.4.3') |
__eq__(other) | bool | Compares against another ObjectIdentifier or a dotted str |
__hash__() | int | Consistent with hash(str(oid)) so oid in {"2.5.4.3"} works |
Full class stub
class ObjectIdentifier:
def __init__(self, oid_str: str) -> None: ...
@staticmethod
def from_components(components: list[int]) -> ObjectIdentifier: ...
def components(self) -> list[int]: ...
def __str__(self) -> str: ...
def __eq__(self, other: ObjectIdentifier) -> bool: ...
def __hash__(self) -> int: ...
Working with Object Identifiers
import synta
# Create OID from dotted string
oid = synta.ObjectIdentifier("1.2.840.10045.2.1")
print(str(oid)) # Output: 1.2.840.10045.2.1
# Get components
components = oid.components()
print(components) # Output: [1, 2, 840, 10045, 2, 1]
# Create OID from components
oid = synta.ObjectIdentifier.from_components([1, 2, 840, 10045, 4, 3, 2])
print(str(oid)) # Output: 1.2.840.10045.4.3.2
# Equality comparison against a string
import synta.oids as oids
assert oids.EC_PUBLIC_KEY == "1.2.840.10045.2.1"
# Use as a dict key (hashable)
lookup = {oids.SHA256: "SHA-256", oids.SHA384: "SHA-384"}
name = lookup.get(cert.signature_algorithm_oid, "unknown")
See also Well-known OIDs for the full OID constant catalog.
Error Handling
Exception table
| Exception | When raised |
|---|---|
synta.SyntaError | ASN.1 parse or encode failure (wraps the Rust synta::Error) |
synta.x509.X509VerificationError | Certificate chain verification failure (see synta.x509) |
ValueError | Invalid arguments (e.g. invalid OID string, bad charset for PrintableString, wrong password format) |
OverflowError | Integer.to_int() / to_i128() when the value does not fit |
EOFError | Decoder.peek_tag() / decode_* when no data remains |
ImportError | Certificate.to_pyca() / from_pyca() when cryptography is not installed |
Example
import synta
try:
# Invalid OID (first component must be 0, 1, or 2)
oid = synta.ObjectIdentifier("5.2.840")
except ValueError as e:
print(f"Error: {e}")
try:
# Truncated data
decoder = synta.Decoder(b'\x02\x05', synta.Encoding.DER)
integer = decoder.decode_integer()
except EOFError as e:
print(f"Unexpected end of data: {e}")
try:
# Invalid DER
decoder = synta.Decoder(b'\xff\xff', synta.Encoding.DER)
obj = decoder.decode_any()
except synta.SyntaError as e:
print(f"Parse error: {e}")
Error mapping
SyntaErr (the internal Rust error type) is mapped to Python exceptions as
follows:
- EOF / truncated data →
EOFError - Integer overflow →
OverflowError - Any other ASN.1 parse error →
ValueErrororsynta.SyntaError
The SyntaError exception class is a subclass of Exception.
PEM and DER Utilities
Two top-level functions handle conversion between PEM and DER byte representations.
Free functions
| Signature | Returns | Description |
|---|---|---|
pem_to_der(data: bytes) | list[bytes] | Decode one or more PEM blocks to DER. Always returns a list (one entry per block); no block present → ValueError. |
der_to_pem(der: bytes, label: str) | bytes | Wrap DER bytes in a -----BEGIN {label}----- / -----END {label}----- block. |
Usage
import synta
# Decode a PEM file with one or more certificates
pem_data = open("bundle.pem", "rb").read()
der_list = synta.pem_to_der(pem_data) # list[bytes] — one entry per PEM block
for der in der_list:
cert = synta.Certificate.from_der(der)
print(cert.subject)
# Single-block PEM (typical for one certificate)
der_list = synta.pem_to_der(open("cert.pem", "rb").read())
cert = synta.Certificate.from_der(der_list[0])
# Wrap DER bytes back into PEM
der = cert.to_der()
pem = synta.der_to_pem(der, "CERTIFICATE")
open("cert.pem", "wb").write(pem)
# Private key PEM
key_der = open("key.der", "rb").read()
pem = synta.der_to_pem(key_der, "PRIVATE KEY")
# Empty PEM raises ValueError
try:
synta.pem_to_der(b"not pem")
except ValueError as e:
print(f"No PEM block found: {e}")
Notes
pem_to_deralways returns alist, even when only one block is present. A single-block result is accessed asresult[0].pem_to_derraisesValueErrorwhen no PEM block is found. It does not silently return an empty list.- Malformed base64 inside a PEM block is also rejected with
ValueError. der_to_pemproduces the standard 64-character line wrap required by RFC 7468.- The
labelargument toder_to_pemappears verbatim between theBEGIN/ENDmarkers (e.g."CERTIFICATE","PRIVATE KEY","CERTIFICATE REQUEST").
See also PKCS Loaders for format-agnostic reading of PEM bundles, PKCS#7 files, and PKCS#12 archives.
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
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.
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.
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.
Certificate.to_pem(cert_or_list) -> bytes
Encode one or more Certificate objects back to PEM.
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.
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 |
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_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_raw_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 |
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[tuple[int, bytes]] | SAN entries as (tag_number, content) pairs. Returns [] when no SAN extension is present. Use synta.general_name constants to dispatch on tag_number. |
general_names | (oid: str | ObjectIdentifier) | list[tuple[int, bytes]] | Decode any extension whose value is SEQUENCE OF GeneralName. |
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
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
# 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_raw_der: bytes
# Raw DER spans
def to_der(self) -> bytes: ...
tbs_bytes: bytes
extensions_der: bytes | None
# Methods
def get_extension_value_der(self, oid: str) -> bytes | None: ...
def subject_alt_names(self) -> list[tuple[int, bytes]]: ...
def verify_issued_by(self, issuer: Certificate) -> None: ...
def fingerprint(self, algorithm: str) -> bytes: ...
Usage
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
import ipaddress
import synta.general_name as gn
# High-level: subject_alt_names() combines extension lookup and GeneralName parsing
for tag_num, content in cert.subject_alt_names():
if tag_num == gn.DNS_NAME:
print("dns:", content.decode("ascii"))
elif tag_num == gn.IP_ADDRESS:
print("ip:", ipaddress.ip_address(content))
elif tag_num == gn.RFC822_NAME:
print("email:", content.decode("ascii"))
elif tag_num == gn.URI:
print("uri:", content.decode("ascii"))
elif tag_num == gn.DIRECTORY_NAME:
attrs = synta.parse_name_attrs(content) # [(oid_str, value_str), …]
print("dirname:", attrs)
Iterating extensions manually
# 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
# 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 for PKCS#10 certificate signing requests
- GeneralName for
synta.general_nametag constants - PyCA Interoperability for converting to/from
cryptography - X.509 Path Validation for chain verification
CertificationRequest (CSR)
CertificationRequest represents an RFC 2986 PKCS#10 Certificate Signing Request.
Construction
CertificationRequest.from_der(data: bytes) -> CertificationRequest
CertificationRequest.from_pem(data: bytes) -> CertificationRequest | list[CertificationRequest]
CertificationRequest.to_pem(csr_or_list) -> bytes
Properties
| Property | Type | Description |
|---|---|---|
version | int | CSR version (always 0 = v1 per RFC 2986) |
subject | str | RFC 4514 DN string |
subject_raw_der | bytes | Raw DER of subject Name SEQUENCE |
signature_algorithm | str | Algorithm name or dotted OID |
signature_algorithm_oid | ObjectIdentifier | |
signature | bytes | Raw signature bytes |
public_key_algorithm | str | Algorithm name or dotted OID |
public_key_algorithm_oid | ObjectIdentifier | |
public_key | bytes | Raw subject public key bytes |
Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
to_der() | () | bytes | Original DER bytes |
get_extension_value_der | (oid: str | ObjectIdentifier) | bytes | None | Return the extnValue bytes of the named extension from the CSR’s extensionRequest attribute, or None if absent. |
verify_self_signature() | () | None | Verify the PKCS#10 self-signature. Raises ValueError if the signature is invalid or the algorithm is unsupported. |
Full class stub
class CertificationRequest:
@staticmethod
def from_der(data: bytes) -> CertificationRequest: ...
@staticmethod
def from_pem(data: bytes) -> CertificationRequest | list[CertificationRequest]: ...
@staticmethod
def to_pem(csr_or_list) -> bytes: ...
version: int
subject: str
subject_raw_der: bytes
signature_algorithm: str
signature_algorithm_oid: ObjectIdentifier
signature: bytes
public_key_algorithm: str
public_key_algorithm_oid: ObjectIdentifier
public_key: bytes
def to_der(self) -> bytes: ...
def get_extension_value_der(self, oid: str) -> bytes | None: ...
def verify_self_signature(self) -> None: ...
Usage
import synta
# Parse from DER
with open("request.csr", "rb") as f:
csr = synta.CertificationRequest.from_der(f.read())
# Parse from PEM
pem_data = open("request.pem", "rb").read()
csr = synta.CertificationRequest.from_pem(pem_data)
# Access fields
print(csr.subject)
print(csr.public_key_algorithm)
print(csr.public_key_algorithm_oid)
# Verify the self-signature
try:
csr.verify_self_signature()
print("CSR self-signature is valid")
except ValueError as e:
print(f"Invalid CSR: {e}")
# Retrieve the extensionRequest attribute (if present)
san_der = csr.get_extension_value_der("2.5.29.17") # SubjectAltName
if san_der:
# san_der is the extnValue content bytes — pass to a Decoder
dec = synta.Decoder(san_der, synta.Encoding.DER)
san_seq = dec.decode_sequence()
while not san_seq.is_empty():
tag_num, tag_class, _ = san_seq.peek_tag()
child = san_seq.decode_implicit_tag(tag_num, "Context")
print(f"SAN tag {tag_num}: {child.remaining_bytes()}")
See also Certificate for the issued certificate type and CRL for certificate revocation lists.
CertificateList (CRL)
CertificateList represents an RFC 5280 Certificate Revocation List.
Construction
CertificateList.from_der(data: bytes) -> CertificateList
CertificateList.from_pem(data: bytes) -> CertificateList | list[CertificateList]
CertificateList.to_pem(crl_or_list) -> bytes
Properties
| Property | Type | Description |
|---|---|---|
version | int | None | CRL version (1 = v2); None implies v1 (RFC 5280 §5.1.2.1) |
issuer | str | RFC 4514 DN string |
issuer_raw_der | bytes | Raw DER of issuer Name SEQUENCE |
this_update | str | thisUpdate time as string |
next_update | str | None | nextUpdate time as string, or None if absent |
signature_algorithm | str | Algorithm name or dotted OID |
signature_algorithm_oid | ObjectIdentifier | |
signature_value | bytes | Raw signature bytes |
crl_number | int | None | CRL sequence number from cRLNumber extension (OID 2.5.29.20), or None |
revoked_count | int | Number of revoked certificate entries |
Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
to_der() | () | bytes | Original DER bytes |
get_extension_value_der | (oid: str | ObjectIdentifier) | bytes | None | Return the extnValue bytes of the named CRL extension, or None if absent. |
verify_issued_by | (issuer: Certificate) | None | Verify that this CRL was signed by issuer. Checks issuer Name match then signature. Raises ValueError on mismatch or invalid signature. |
Full class stub
class CertificateList:
@staticmethod
def from_der(data: bytes) -> CertificateList: ...
@staticmethod
def from_pem(data: bytes) -> CertificateList | list[CertificateList]: ...
@staticmethod
def to_pem(crl_or_list) -> bytes: ...
version: int | None
issuer: str
issuer_raw_der: bytes
this_update: str
next_update: str | None
signature_algorithm: str
signature_algorithm_oid: ObjectIdentifier
signature_value: bytes
crl_number: int | None
revoked_count: int
def to_der(self) -> bytes: ...
def get_extension_value_der(self, oid: str) -> bytes | None: ...
def verify_issued_by(self, issuer: Certificate) -> None: ...
Usage
import synta
# Parse a DER-encoded CRL
with open("ca.crl", "rb") as f:
crl = synta.CertificateList.from_der(f.read())
# Access fields
print(crl.issuer)
print(crl.this_update)
print(crl.next_update)
print(f"Revoked entries: {crl.revoked_count}")
print(f"CRL number: {crl.crl_number}")
# Verify the CRL signature
ca_cert = synta.Certificate.from_der(open("ca.der", "rb").read())
try:
crl.verify_issued_by(ca_cert)
print("CRL signature valid")
except ValueError as e:
print(f"Invalid CRL: {e}")
# Read the CRL number extension
crl_num_der = crl.get_extension_value_der("2.5.29.20")
See also Certificate and OCSP.
OCSPResponse
OCSPResponse represents an RFC 6960 OCSP Response (outer envelope only).
Construction
OCSPResponse.from_der(data: bytes) -> OCSPResponse
OCSPResponse.from_pem(data: bytes) -> OCSPResponse | list[OCSPResponse]
OCSPResponse.to_pem(resp_or_list) -> bytes
Properties
| Property | Type | Description |
|---|---|---|
status | str | Response status: "successful", "malformedRequest", "internalError", "tryLater", "sigRequired", "unauthorized" |
response_type_oid | ObjectIdentifier | None | OID of the responseBytes contentType, or None for non-successful responses |
response_bytes | bytes | None | Raw content of the responseBytes OCTET STRING, or None |
Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
to_der() | () | bytes | Original DER bytes |
verify_signature | (responder: Certificate) | None | Verify the BasicOCSPResponse signature using the responder’s public key. Raises ValueError if no responseBytes is present, the response type is not id-pkix-ocsp-basic, or the signature is invalid. |
Full class stub
class OCSPResponse:
@staticmethod
def from_der(data: bytes) -> OCSPResponse: ...
@staticmethod
def from_pem(data: bytes) -> OCSPResponse | list[OCSPResponse]: ...
@staticmethod
def to_pem(resp_or_list) -> bytes: ...
status: str
response_type_oid: ObjectIdentifier | None
response_bytes: bytes | None
def to_der(self) -> bytes: ...
def verify_signature(self, responder: Certificate) -> None: ...
Usage
import synta
# Parse a DER-encoded OCSP response
with open("ocsp.der", "rb") as f:
resp = synta.OCSPResponse.from_der(f.read())
# Check the outer status
print(resp.status) # e.g. "successful"
print(resp.response_type_oid) # OID, typically id-pkix-ocsp-basic
# Access the raw inner bytes for further decoding
if resp.status == "successful" and resp.response_bytes:
# resp.response_bytes is the content of the responseBytes OCTET STRING
# Decode it as a BasicOCSPResponse using the Decoder
dec = synta.Decoder(resp.response_bytes, synta.Encoding.DER)
inner = dec.decode_sequence()
# ... (decode tbsResponseData, signatureAlgorithm, signature, etc.)
# Verify the response signature
responder_cert = synta.Certificate.from_der(open("responder.der", "rb").read())
try:
resp.verify_signature(responder_cert)
print("OCSP signature valid")
except ValueError as e:
print(f"OCSP verification failed: {e}")
See also Certificate and CRL.
PublicKey and PrivateKey
PublicKey and PrivateKey are backend-agnostic key types that wrap OpenSSL keys. They
support RSA, EC (NIST curves), EdDSA (Ed25519 / Ed448), ML-DSA (FIPS 204), and ML-KEM
(FIPS 203). Both types are exported from the top-level synta package.
PublicKey
Construction
PublicKey.from_pem(data: bytes) -> PublicKey
Load from PEM-encoded SubjectPublicKeyInfo.
PublicKey.from_der(data: bytes) -> PublicKey
Load from DER-encoded SubjectPublicKeyInfo.
PublicKey.from_rsa_components(n: bytes, e: bytes) -> PublicKey
Construct an RSA public key from big-endian modulus n and public exponent e bytes.
Raises ValueError if the inputs are not a valid RSA key.
PublicKey.from_ec_components(x: bytes, y: bytes, curve: str) -> PublicKey
Construct an EC public key from affine coordinates x, y and NIST curve name.
curve must be "P-256", "P-384", or "P-521". Raises ValueError for unknown curve
names or invalid coordinates.
Serialisation
pub_key.to_pem() -> bytes
pub_key.to_der() -> bytes
Properties
| Property | Type | Description |
|---|---|---|
key_type | str | Lowercase key algorithm: "rsa", "ec", "ed25519", "ed448", "dsa", "ml-dsa-44", "ml-dsa-65", "ml-dsa-87", "ml-kem-512", "ml-kem-768", "ml-kem-1024", or "unknown" |
key_size | int | None | Key size in bits, or None for EdDSA and post-quantum keys |
modulus | bytes | None | RSA modulus n as big-endian bytes, or None for non-RSA keys |
public_exponent | bytes | None | RSA public exponent e as big-endian bytes, or None for non-RSA keys |
curve_name | str | None | NIST curve name for EC keys (e.g. "P-256"), or None |
x | bytes | None | EC affine X coordinate as big-endian bytes, or None for non-EC keys |
y | bytes | None | EC affine Y coordinate as big-endian bytes, or None for non-EC keys |
Methods
pub_key.rsa_oaep_encrypt(plaintext: bytes, hash_algorithm: str = "sha256") -> bytes
pub_key.rsa_pkcs1v15_encrypt(plaintext: bytes) -> bytes
pub_key.kem_encapsulate() -> tuple[bytes, bytes] # ML-KEM: (ciphertext, shared_secret)
pub_key.verify_signature(
signature: bytes,
data: bytes,
algorithm: str | None = None,
context: bytes | None = None,
) -> None
Verify a signature over data. Raises ValueError on failure.
algorithm: hash name (e.g."sha256") for RSA and ECDSA. PassNonefor Ed25519, Ed448, and ML-DSA keys.context: ML-DSA domain-separation string (FIPS 204);None= empty. Ignored for non-ML-DSA keys.
Full class stub
class PublicKey:
@staticmethod
def from_pem(data: bytes) -> PublicKey: ...
@staticmethod
def from_der(data: bytes) -> PublicKey: ...
@staticmethod
def from_rsa_components(n: bytes, e: bytes) -> PublicKey: ...
@staticmethod
def from_ec_components(x: bytes, y: bytes, curve: str) -> PublicKey: ...
def to_pem(self) -> bytes: ...
def to_der(self) -> bytes: ...
key_type: str
key_size: int | None
modulus: bytes | None
public_exponent: bytes | None
curve_name: str | None
x: bytes | None
y: bytes | None
def rsa_oaep_encrypt(self, plaintext: bytes, hash_algorithm: str = "sha256") -> bytes: ...
def rsa_pkcs1v15_encrypt(self, plaintext: bytes) -> bytes: ...
def kem_encapsulate(self) -> tuple[bytes, bytes]: ...
def verify_signature(
self,
signature: bytes,
data: bytes,
algorithm: str | None = None,
context: bytes | None = None,
) -> None: ...
PrivateKey
Construction
PrivateKey.from_pem(data: bytes, password: bytes | None = None) -> PrivateKey
Load from PEM-encoded data (optionally password-protected).
PrivateKey.from_der(data: bytes) -> PrivateKey
Load from unencrypted PKCS#8 DER bytes.
PrivateKey.from_pkcs8_encrypted(data: bytes, password: bytes) -> PrivateKey
Decrypt and load from PKCS#8 EncryptedPrivateKeyInfo DER.
Serialisation
key.to_pem() -> bytes
key.to_der() -> bytes # unencrypted PKCS#8 DER
Properties
| Property | Type | Description |
|---|---|---|
key_type | str | Same values as PublicKey.key_type |
key_size | int | None | Key size in bits, or None |
Methods
key.public_key() -> PublicKey # extract the matching public key
key.sign(data: bytes, algorithm: str | None = None, context: bytes | None = None) -> bytes
key.rsa_oaep_decrypt(ciphertext: bytes, hash_algorithm: str = "sha256") -> bytes
key.rsa_pkcs1v15_decrypt(ciphertext: bytes) -> bytes
key.kem_decapsulate(ciphertext: bytes) -> bytes # ML-KEM: returns shared_secret
Full class stub
class PrivateKey:
@staticmethod
def from_pem(data: bytes, password: bytes | None = None) -> PrivateKey: ...
@staticmethod
def from_der(data: bytes) -> PrivateKey: ...
@staticmethod
def from_pkcs8_encrypted(data: bytes, password: bytes) -> PrivateKey: ...
def to_pem(self) -> bytes: ...
def to_der(self) -> bytes: ...
key_type: str
key_size: int | None
def public_key(self) -> PublicKey: ...
def sign(
self,
data: bytes,
algorithm: str | None = None,
context: bytes | None = None,
) -> bytes: ...
def rsa_oaep_decrypt(self, ciphertext: bytes, hash_algorithm: str = "sha256") -> bytes: ...
def rsa_pkcs1v15_decrypt(self, ciphertext: bytes) -> bytes: ...
def kem_decapsulate(self, ciphertext: bytes) -> bytes: ...
Usage
import synta
# Load a certificate and verify a signature using the embedded public key
cert = synta.Certificate.from_der(open("cert.der", "rb").read())
pub_key = synta.PublicKey.from_der(cert.public_key)
# For ECDSA with SHA-256:
try:
pub_key.verify_signature(signature, message, algorithm="sha256")
print("Valid")
except ValueError:
print("Invalid")
# Load a private key and sign
priv_key = synta.PrivateKey.from_pem(open("key.pem", "rb").read())
sig = priv_key.sign(message, algorithm="sha256")
# ML-KEM key exchange
kem_pub = synta.PublicKey.from_der(kem_pub_der)
ciphertext, shared_secret = kem_pub.kem_encapsulate()
priv_kem = synta.PrivateKey.from_der(kem_priv_der)
recovered = priv_kem.kem_decapsulate(ciphertext)
assert shared_secret == recovered
See also PKCS#8 for parsing PKCS#8 private-key envelopes and CMS-KEM for RFC 9629 quantum-safe KEM recipient structures.
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
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
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
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
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
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
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
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
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 for format-agnostic reading, and PEM/DER for PEM encode/decode helpers.
Format-Agnostic PKI Reader
read_pki_blocks is the single entry point for reading any supported PKI file
format — PEM, PKCS#7/CMS, PKCS#12, or raw DER — without knowing the format in advance.
Function
read_pki_blocks(data: bytes, password: bytes = None) -> list[tuple[str, bytes]]
Auto-detect the encoding of data and return every PKI object as a (label, der_bytes)
tuple, in document order.
Supported formats: PEM (any label), PKCS#7 / CMS SignedData (DER or BER), PKCS#12 PFX (DER or BER), and raw DER.
Label semantics
| Input format | Label per entry |
|---|---|
| PEM — PKCS#7 blocks | "CERTIFICATE" per embedded cert (block expanded) |
| PEM — all other block types | Block type as-is ("CERTIFICATE", "PRIVATE KEY", …) |
| PKCS#7 / CMS SignedData (binary) | "CERTIFICATE" per cert |
PKCS#12 — certBag | "CERTIFICATE" per cert |
PKCS#12 — keyBag / pkcs8ShroudedKeyBag | "PRIVATE KEY" per key |
| Raw DER | "CERTIFICATE" |
Malformed PEM blocks (bad base64, truncated header) are silently skipped.
PKCS#12 encryption
When password is supplied and the library is built with --features openssl, encrypted
bags are decrypted. Without that feature (or when no password is given) encrypted bags are
silently skipped; unencrypted certificates in the same archive are still returned.
This differs from load_pkcs12_certificates, which raises ValueError on encrypted bags
when the openssl feature is absent.
Errors
Raises ValueError on structural failures:
- A PKCS#7 block has malformed DER, or its contentType OID is not id-signedData.
- The PKCS#12 input has a structural ASN.1 error.
passwordwas supplied with theopensslfeature and decryption failed (e.g. wrong password, unsupported cipher).
Raises TypeError if data is not bytes, or password is not bytes or None.
Usage
import synta
# Any format — let synta detect it automatically
data = open("bundle.p7b", "rb").read() # or .pem, .p12, .der, …
blocks = synta.read_pki_blocks(data)
for label, der in blocks:
print(f"{label}: {len(der)} bytes")
# Parse the DER bytes directly
certs = [synta.Certificate.from_der(der)
for label, der in blocks if label == "CERTIFICATE"]
# PKCS#12 with a password (openssl feature required for encrypted bags)
data = open("archive.p12", "rb").read()
blocks = synta.read_pki_blocks(data, b"s3cr3t")
# Split certificates and private keys
certs = [synta.Certificate.from_der(der) for label, der in blocks if label == "CERTIFICATE"]
key_ders = [der for label, der in blocks if label == "PRIVATE KEY"]
See also PKCS Loaders for format-specific functions with precise error semantics, and PEM/DER for simple PEM encode/decode.
X.509 Extension Value Builders
synta.ext provides DER-encoding helpers for the most common X.509 v3 extension values.
Each function returns the raw extnValue bytes (the content inside the OCTET STRING wrapper
in the Extension SEQUENCE). Import with import synta.ext as ext.
subject_key_identifier and authority_key_identifier require the openssl Cargo feature.
basic_constraints, key_usage, and the three builder classes do not.
Key identifier method constants
Pass one of these to subject_key_identifier or authority_key_identifier:
| Constant | Value | Hash | Input | Output length | Specification |
|---|---|---|---|---|---|
KEYID_RFC5280 | 0 | SHA-1 | BIT STRING value of subjectPublicKey | 20 bytes | RFC 5280 §4.2.1.2 |
KEYID_RFC7093M1 | 1 | SHA-256 | BIT STRING value of subjectPublicKey | 20 bytes (truncated) | RFC 7093 §2 m1 |
KEYID_RFC7093M2 | 2 | SHA-384 | BIT STRING value of subjectPublicKey | 20 bytes (truncated) | RFC 7093 §2 m2 |
KEYID_RFC7093M3 | 3 | SHA-512 | BIT STRING value of subjectPublicKey | 20 bytes (truncated) | RFC 7093 §2 m3 |
KEYID_RFC7093M4 | 4 | SHA-256 | full SubjectPublicKeyInfo DER | 32 bytes | RFC 7093 §2 m4 |
Key usage bitmask constants
OR these together and pass the result to key_usage():
| Constant | Mask | Named bit (RFC 5280 §4.2.1.3) |
|---|---|---|
KU_DIGITAL_SIGNATURE | 0x001 | digitalSignature |
KU_NON_REPUDIATION | 0x002 | contentCommitment |
KU_KEY_ENCIPHERMENT | 0x004 | keyEncipherment |
KU_DATA_ENCIPHERMENT | 0x008 | dataEncipherment |
KU_KEY_AGREEMENT | 0x010 | keyAgreement |
KU_KEY_CERT_SIGN | 0x020 | keyCertSign |
KU_CRL_SIGN | 0x040 | cRLSign |
KU_ENCIPHER_ONLY | 0x080 | encipherOnly |
KU_DECIPHER_ONLY | 0x100 | decipherOnly |
Functions
def basic_constraints(ca: bool = False, path_length: int | None = None) -> bytes: ...
Encode a BasicConstraints extension value (OID 2.5.29.19). Returns the DER SEQUENCE bytes.
When ca is False the cA field is omitted (DER DEFAULT-FALSE rule). path_length is
ignored when ca is False.
def key_usage(bits: int) -> bytes: ...
Encode a KeyUsage extension value (OID 2.5.29.15). bits is an integer formed by OR-ing
KU_* constants. Returns the DER BIT STRING bytes.
def subject_key_identifier(spki_der: bytes, method: int = KEYID_RFC5280) -> bytes: ...
Encode a SubjectKeyIdentifier extension value (OID 2.5.29.14). spki_der must be a complete
DER-encoded SubjectPublicKeyInfo. method selects the key-identifier algorithm. Returns the
DER OCTET STRING value. Raises ValueError if spki_der cannot be decoded.
def authority_key_identifier(issuer_spki_der: bytes, method: int = KEYID_RFC5280) -> bytes: ...
Encode an AuthorityKeyIdentifier extension value (OID 2.5.29.35). Sets only the
keyIdentifier [0] field. Returns the DER AuthorityKeyIdentifier SEQUENCE bytes.
Fluent builder classes
Three builder classes accumulate entries and produce the complete DER extnValue on
.build(). No openssl Cargo feature is required.
SubjectAlternativeNameBuilder (alias: SAN)
class SubjectAlternativeNameBuilder:
def __init__(self) -> None: ...
def dns_name(self, name: str) -> SubjectAlternativeNameBuilder: ...
def rfc822_name(self, email: str) -> SubjectAlternativeNameBuilder: ...
def uri(self, uri: str) -> SubjectAlternativeNameBuilder: ...
def ip_address(self, addr: bytes) -> SubjectAlternativeNameBuilder: ...
# Pass 4 bytes for IPv4 or 16 bytes for IPv6.
def directory_name(self, name_der: bytes) -> SubjectAlternativeNameBuilder: ...
# name_der is the DER TLV of a Name SEQUENCE.
def registered_id(self, oid_comps: list[int]) -> SubjectAlternativeNameBuilder: ...
def build(self) -> bytes: ...
# Return the DER GeneralNames SEQUENCE. Raises ValueError on encoding error.
SAN = SubjectAlternativeNameBuilder # short alias
AuthorityInformationAccessBuilder (alias: AIA)
class AuthorityInformationAccessBuilder:
def __init__(self) -> None: ...
def ocsp(self, uri: str) -> AuthorityInformationAccessBuilder: ...
def ca_issuers(self, uri: str) -> AuthorityInformationAccessBuilder: ...
def build(self) -> bytes: ...
AIA = AuthorityInformationAccessBuilder # short alias
ExtendedKeyUsageBuilder (alias: EKU)
class ExtendedKeyUsageBuilder:
def __init__(self) -> None: ...
def server_auth(self) -> ExtendedKeyUsageBuilder: ... # id-kp-serverAuth
def client_auth(self) -> ExtendedKeyUsageBuilder: ... # id-kp-clientAuth
def code_signing(self) -> ExtendedKeyUsageBuilder: ... # id-kp-codeSigning
def email_protection(self) -> ExtendedKeyUsageBuilder: ... # id-kp-emailProtection
def time_stamping(self) -> ExtendedKeyUsageBuilder: ... # id-kp-timeStamping
def ocsp_signing(self) -> ExtendedKeyUsageBuilder: ... # id-kp-OCSPSigning
def add_oid(self, oid: str | ObjectIdentifier) -> ExtendedKeyUsageBuilder: ...
def build(self) -> bytes: ...
EKU = ExtendedKeyUsageBuilder # short alias
Usage
import synta
import synta.ext as ext
# Parse a CA certificate to extract its SubjectPublicKeyInfo DER
ca_der = open("ca.der", "rb").read()
ca_cert = synta.Certificate.from_der(ca_der)
spki_der = ca_cert.subject_public_key_info_raw_der
# ── BasicConstraints (OID 2.5.29.19) ──────────────────────────────────────────
# End-entity certificate: empty SEQUENCE → 30 00
ee_bc = ext.basic_constraints()
# CA with no path-length limit: cA = TRUE → 30 03 01 01 ff
ca_bc = ext.basic_constraints(ca=True)
# CA with pathLen = 0 → 30 06 01 01 ff 02 01 00
ca_pl0 = ext.basic_constraints(ca=True, path_length=0)
# ── KeyUsage (OID 2.5.29.15) ──────────────────────────────────────────────────
# Typical CA key usage: keyCertSign | cRLSign | digitalSignature
ku = ext.key_usage(ext.KU_KEY_CERT_SIGN | ext.KU_CRL_SIGN | ext.KU_DIGITAL_SIGNATURE)
# TLS server key usage: digitalSignature | keyEncipherment
ku_server = ext.key_usage(ext.KU_DIGITAL_SIGNATURE | ext.KU_KEY_ENCIPHERMENT)
# ── SubjectKeyIdentifier (OID 2.5.29.14) ──────────────────────────────────────
# RFC 5280 default: SHA-1 of the BIT STRING value of subjectPublicKey
ski = ext.subject_key_identifier(spki_der)
# RFC 7093 method 1: SHA-256 of BIT STRING value, truncated to 160 bits
ski_m1 = ext.subject_key_identifier(spki_der, method=ext.KEYID_RFC7093M1)
# RFC 7093 method 4: SHA-256 of the full SubjectPublicKeyInfo DER
ski_m4 = ext.subject_key_identifier(spki_der, method=ext.KEYID_RFC7093M4)
# ── AuthorityKeyIdentifier (OID 2.5.29.35) ────────────────────────────────────
aki = ext.authority_key_identifier(spki_der)
aki_m1 = ext.authority_key_identifier(spki_der, method=ext.KEYID_RFC7093M1)
# ── Fluent builders ───────────────────────────────────────────────────────────
san = ext.SAN().dns_name("www.example.com").dns_name("example.com").build()
aia = ext.AIA().ocsp("http://ocsp.example.com").ca_issuers("http://ca.example.com/ca.crt").build()
eku = ext.EKU().server_auth().client_auth().build()
See also CRL and OCSP Builders for building complete CRL and OCSP response structures.
CRL and OCSP Response Builders
CertificateListBuilder and OCSPResponseBuilder are top-level synta exports for
constructing DER-encoded X.509 CRL and OCSP response structures. Both follow the same
pattern: build the unsigned TBS blob, sign it externally, then call assemble to wrap the
signature into the final structure.
CertificateListBuilder
Fluent builder for RFC 5280 §5 TBSCertList.
class CertificateListBuilder:
def __init__(self) -> None: ...
def issuer(self, name_der: bytes) -> CertificateListBuilder: ...
# Set the issuer Name from pre-encoded DER bytes.
def this_update(self, time: str) -> CertificateListBuilder: ...
# Set thisUpdate ("YYYYMMDDHHmmssZ" or "YYMMDDHHmmssZ").
def next_update(self, time: str) -> CertificateListBuilder: ...
# Set optional nextUpdate (same format as this_update).
def revoke(
self,
serial: bytes,
revocation_date: str,
reason: int | None = None,
) -> CertificateListBuilder: ...
# Add a revoked certificate entry.
# serial is the big-endian DER INTEGER value bytes.
# reason is an optional CRL reason code (0–10).
def signature_algorithm(self, alg_der: bytes) -> CertificateListBuilder: ...
# Set the AlgorithmIdentifier DER for TBSCertList.signature.
def build(self) -> bytes: ...
# Build the DER-encoded TBSCertList SEQUENCE.
# Raises ValueError if any required field is absent or encoding fails.
@staticmethod
def assemble(tbs_der: bytes, sig_alg_der: bytes, signature: bytes) -> bytes: ...
# Assemble a complete DER-encoded CertificateList.
# tbs_der: TBSCertList bytes from build().
# sig_alg_der: outer AlgorithmIdentifier SEQUENCE TLV.
# signature: raw signature bytes (BIT STRING value).
# Raises ValueError if DER encoding fails.
Example
import synta
name_der = synta.NameBuilder().common_name("Test CA").build()
alg_der = bytes.fromhex("300d06092a864886f70d01010b0500") # sha256WithRSAEncryption
tbs = (
synta.CertificateListBuilder()
.issuer(name_der)
.signature_algorithm(alg_der)
.this_update("20240101120000Z")
.revoke(bytes([1]), "20231201000000Z", 1) # reason 1 = keyCompromise
.build()
)
# Sign tbs externally, then assemble:
# crl_der = synta.CertificateListBuilder.assemble(tbs, alg_der, sig_bytes)
OCSPResponseBuilder
Fluent builder for RFC 6960 §4.2.1 ResponseData. Set exactly one of responder_name or
responder_key_hash before calling build_tbs.
class OCSPResponseBuilder:
def __init__(self) -> None: ...
def responder_name(self, name_der: bytes) -> OCSPResponseBuilder: ...
# Set responderID byName from a pre-encoded DER Name SEQUENCE TLV.
def responder_key_hash(self, key_hash: bytes) -> OCSPResponseBuilder: ...
# Set responderID byKey from raw key-hash bytes (OCTET STRING value).
def produced_at(self, time: str) -> OCSPResponseBuilder: ...
# Set producedAt time ("YYYYMMDDHHmmssZ").
def add_response(
self,
hash_algorithm_der: bytes,
issuer_name_hash: bytes,
issuer_key_hash: bytes,
serial: bytes,
status: int,
this_update: str,
next_update: str | None = None,
) -> OCSPResponseBuilder: ...
# Add a SingleResponse entry.
# hash_algorithm_der: pre-encoded AlgorithmIdentifier DER TLV.
# issuer_name_hash / issuer_key_hash: raw hash bytes.
# serial: big-endian DER INTEGER value bytes.
# status: 0 = good, 1 = revoked, 2 = unknown.
# this_update / next_update: "YYYYMMDDHHmmssZ" format.
def build_tbs(self) -> bytes: ...
# Build the DER-encoded ResponseData SEQUENCE.
# Raises ValueError if any required field is absent or encoding fails.
@staticmethod
def assemble(tbs_der: bytes, sig_alg_der: bytes, signature: bytes) -> bytes: ...
# Assemble a complete DER-encoded OCSPResponse.
# tbs_der: ResponseData bytes from build_tbs().
# sig_alg_der: outer AlgorithmIdentifier SEQUENCE TLV.
# signature: raw signature bytes (BIT STRING value).
# Raises ValueError if DER encoding fails.
See also X.509 Extension Value Builders and OCSP.
GeneralName Constants
synta.general_name provides integer constants for the GeneralName CHOICE type
(RFC 5280 §4.2.1.6). These are the tag_number values returned by
Certificate.subject_alt_names() and synta.parse_general_names().
import synta.general_name as gn
Constants
| Constant | Value | GeneralName alternative | Content |
|---|---|---|---|
OTHER_NAME | 0 | otherName | Full OtherNameValue TLV (constructed) |
RFC822_NAME | 1 | rfc822Name | Raw IA5String bytes (e-mail address) |
DNS_NAME | 2 | dNSName | Raw IA5String bytes (DNS host name) |
X400_ADDRESS | 3 | x400Address | (rarely used) |
DIRECTORY_NAME | 4 | directoryName | Complete Name SEQUENCE TLV — pass to parse_name_attrs() |
EDI_PARTY_NAME | 5 | ediPartyName | (rarely used) |
URI | 6 | uniformResourceIdentifier | Raw IA5String bytes (URI) |
IP_ADDRESS | 7 | iPAddress | 4 bytes (IPv4) or 16 bytes (IPv6) |
REGISTERED_ID | 8 | registeredID | Raw OID value bytes |
parse_general_names
synta.parse_general_names(san_der: bytes) -> list[tuple[int, bytes]]
Parse a DER SEQUENCE OF GeneralName into (tag_number, content) pairs. Use the
synta.general_name constants to dispatch on tag_number. This function is the
low-level alternative to Certificate.subject_alt_names() — use it when you have raw
extension value bytes from cert.get_extension_value_der(...).
parse_name_attrs
synta.parse_name_attrs(name_der: bytes) -> list[tuple[str, str]]
Walk a DER Name SEQUENCE and return (dotted_oid, value_str) pairs in traversal order.
Pass DIRECTORY_NAME content bytes (a complete Name SEQUENCE TLV) to this function.
Typical dispatch pattern
import ipaddress
import synta
import synta.general_name as gn
for tag_num, content in cert.subject_alt_names():
if tag_num == gn.DNS_NAME:
print("DNS:", content.decode("ascii"))
elif tag_num == gn.IP_ADDRESS:
print("IP:", ipaddress.ip_address(content))
elif tag_num == gn.RFC822_NAME:
print("email:", content.decode("ascii"))
elif tag_num == gn.URI:
print("URI:", content.decode("ascii"))
elif tag_num == gn.DIRECTORY_NAME:
attrs = synta.parse_name_attrs(content) # [(oid_str, value_str), …]
print("DirName:", attrs)
The same constants apply when calling synta.parse_general_names(san_der) directly on
raw extnValue bytes:
san_der = cert.get_extension_value_der(synta.oids.SUBJECT_ALT_NAME)
if san_der:
for tag_num, content in synta.parse_general_names(san_der):
if tag_num == gn.DNS_NAME:
print("DNS:", content.decode("ascii"))
See also Certificate for subject_alt_names() and the
Well-known OIDs page for synta.oids.SUBJECT_ALT_NAME and related constants.
PyCA Interoperability
synta.Certificate and cryptography.x509.Certificate can be converted back and forth.
The two libraries are complementary and can coexist in the same process; the conversion
is designed for environments where both are already in use and you need to hand certificate
objects between them without re-parsing.
synta provides its own full cryptographic operation support — signature verification,
key generation and signing, asymmetric encryption, and KEM operations — through its
PublicKey and PrivateKey API. PyCA interop is not needed to do
cryptography; it is for transparent data handover when both libraries are present.
Requires the cryptography package (pip install cryptography).
Both methods raise ImportError with an actionable message if the package is absent.
When to use each library
| Task | Recommended |
|---|---|
| Parsing certificates at scale | synta (4–10× faster, lazy decode) |
| Accessing certificate fields | synta (cached, zero-copy getters) |
| Signature verification | synta Certificate.verify_issued_by() or PublicKey.verify_signature() |
| Key generation and signing | synta PrivateKey.generate_*() / PrivateKey.sign() |
| Asymmetric encryption / decryption | synta PublicKey.rsa_oaep_encrypt() / PrivateKey.rsa_oaep_decrypt() |
| KEM encapsulate / decapsulate | synta PublicKey.kem_encapsulate() / PrivateKey.kem_decapsulate() |
| Integrating with an existing PyCA code path | cert.to_pyca() / Certificate.from_pyca() |
Receiving a cryptography.x509.Certificate from a third-party library | Certificate.from_pyca() |
Conversion methods
cert.to_pyca() -> cryptography.x509.Certificate
Return a cryptography.x509.Certificate backed by the same DER bytes.
No re-encoding occurs; the original DER buffer is passed directly to
load_der_x509_certificate. Use this to hand a synta-parsed certificate to a
function or library that expects a PyCA object.
Certificate.from_pyca(pyca_cert: object) -> Certificate
Convert a cryptography.x509.Certificate to a synta.Certificate. Checks
obj._synta_der_bytes first (the fast path when the object was originally created by
synta’s to_pyca()); falls back to public_bytes(Encoding.DER) otherwise.
Use this when you receive a PyCA certificate from a third-party library and want
synta’s fast field access or bulk-processing capabilities.
Usage
Handing a synta certificate to a PyCA-based API
import synta
synta_cert = synta.Certificate.from_der(open("cert.der", "rb").read())
# Fast field access via synta (cached after first call):
print(synta_cert.subject)
print(synta_cert.not_before, "–", synta_cert.not_after)
# Hand off to a third-party function that requires a PyCA certificate:
pyca_cert = synta_cert.to_pyca()
some_pyca_library.register_certificate(pyca_cert)
Verifying a certificate chain using synta’s own verifier
import synta
import synta.x509 as x509
# synta has full chain verification — no PyCA needed.
cert_der = open("cert.der", "rb").read()
ca_der = open("ca.der", "rb").read()
store = x509.TrustStore.from_pem(open("ca.pem", "rb").read())
policy = x509.VerificationPolicy(server_name="example.com")
x509.verify_server_certificate(
synta.Certificate.from_der(cert_der), store, policy
)
print("chain valid")
Receiving a PyCA certificate from a third-party library
import synta
from cryptography.x509 import load_pem_x509_certificate
# Some external library hands you a PyCA certificate.
pyca_cert = load_pem_x509_certificate(open("cert.pem", "rb").read())
# Convert to synta for fast bulk field access.
synta_cert = synta.Certificate.from_pyca(pyca_cert)
print(synta_cert.serial_number.hex())
print(synta_cert.subject_alt_names())
Signing a CSR with synta’s own key API
import synta
from synta import CsrBuilder
# Generate key and sign a CSR entirely within synta — no PyCA required.
key = synta.PrivateKey.generate_ec("P-256")
csr_der = (
CsrBuilder()
.subject_common_name("example.com")
.add_san_dns("example.com")
.build(key)
)
csr = synta.CertificationRequest.from_der(csr_der)
csr.verify_self_signature() # verify the CSR's proof-of-possession
Performance notes
to_pyca()is zero-copy: the already-held DER bytes are passed by reference tocryptography’sload_der_x509_certificate. No re-encoding or copying occurs.from_pyca()has a fast path: when the PyCA object was originally created by synta’sto_pyca(), the_synta_der_bytesattribute is present and read back directly, avoiding thepublic_bytes(Encoding.DER)call entirely.- synta is typically 4–10× faster than
cryptographyfor parsing and field access.
See also PublicKey and PrivateKey for synta’s native crypto API, X.509 Path Validation for chain verification, and Performance for benchmark results.
X.509 Path Validation
synta.x509 verifies RFC 5280 / CABF TLS server and client certificate chains.
import synta.x509 as x509
Classes
TrustStore
A set of trusted root CA certificates used as trust anchors for chain verification.
x509.TrustStore(ders: list[bytes])
Construct a TrustStore from a list of DER-encoded CA certificates.
CrlStore
A set of Certificate Revocation Lists for revocation checking.
x509.CrlStore(ders: list[bytes])
Construct a CrlStore from a list of DER-encoded CRLs.
VerificationPolicy
Policy parameters for chain verification.
x509.VerificationPolicy(
server_names: list[str] | None = None,
name_match: str = "any",
validation_time: float | None = None,
max_chain_depth: int | None = None,
profile: str | None = None,
)
| Parameter | Type | Description |
|---|---|---|
server_names | list[str] | None | DNS names the certificate must cover |
name_match | str | "any" (default) or "all" — whether the cert must match any or all supplied names |
validation_time | float | None | Unix timestamp for validity window check; None uses current time |
max_chain_depth | int | None | Maximum allowed chain length |
profile | str | None | Validation profile identifier |
X509VerificationError
Raised on any chain or policy failure. Subclass of Exception.
try:
x509.verify_server_certificate(...)
except x509.X509VerificationError as exc:
print(f"verification failed: {exc}")
Functions
x509.verify_server_certificate(
leaf: bytes | Certificate,
intermediates: list[bytes | Certificate],
store: TrustStore,
policy: VerificationPolicy,
crls: CrlStore | None = None,
) -> list[bytes]
Verify a TLS server certificate chain. Returns the verified chain as a list of DER-encoded certificates in order from trust anchor to leaf.
x509.verify_client_certificate(
leaf: bytes | Certificate,
intermediates: list[bytes | Certificate],
store: TrustStore,
policy: VerificationPolicy,
crls: CrlStore | None = None,
) -> list[bytes]
Verify a TLS client certificate chain. Same return type as verify_server_certificate.
Usage
import synta
import synta.x509 as x509
# Load trust anchors from a PEM bundle
with open("roots.pem", "rb") as f:
ders = synta.pem_to_der(f.read())
store = x509.TrustStore(ders)
# Verify a server certificate (single name)
with open("leaf.der", "rb") as f:
leaf_der = f.read()
with open("intermediate.der", "rb") as f:
intermediate_der = f.read()
policy = x509.VerificationPolicy(server_names=["example.com"])
chain = x509.verify_server_certificate(leaf_der, [intermediate_der], store, policy)
for i, cert_der in enumerate(chain):
cert = synta.Certificate.from_der(cert_der)
role = "trust anchor" if i == 0 else ("leaf" if i == len(chain) - 1 else "CA")
print(f"chain[{i}] ({role}): {cert.subject}")
# Multi-name any-match (cert must cover at least one name)
policy = x509.VerificationPolicy(
server_names=["example.com", "www.example.com"],
name_match="any",
)
# Multi-name all-match (cert must cover every name)
policy = x509.VerificationPolicy(
server_names=["example.com", "api.example.com"],
name_match="all",
)
# Error handling
try:
chain = x509.verify_server_certificate(leaf_der, [], store, policy)
except x509.X509VerificationError as exc:
print(f"verification failed: {exc}")
See also Certificate for verify_issued_by (single-issuer signature
check without a full chain build) and Error Handling for the exception
hierarchy.
CMS Cryptography
The synta.cms submodule provides Python bindings for the Cryptographic Message Syntax
(RFC 5652) and CMS-KEM (RFC 9629). It exposes parsers and builders for all major CMS
content types.
Content types
| Class | RFC | Description |
|---|---|---|
ContentInfo | RFC 5652 §3 | Outer CMS envelope; entry point for all content types |
SignedData | RFC 5652 §5 | Encapsulates signed content plus signer information |
SignerInfo | RFC 5652 §5.3 | Per-signer structure within SignedData |
EnvelopedData | RFC 5652 §6 | Encrypted content with key transport per recipient |
EnvelopedDataBuilder | RFC 5652 §6 | Fluent builder for EnvelopedData |
EncryptedData | RFC 5652 §8 | Symmetric encryption (shared key, no recipient info) |
DigestedData | RFC 5652 §7 | Hash-protected content |
AuthenticatedData | RFC 5652 §9 | MAC-authenticated content |
IssuerAndSerialNumber | RFC 5652 §10.2.4 | Certificate identifier |
KEMRecipientInfo | RFC 9629 §5 | Quantum-safe KEM recipient structure |
CMSORIforKEMOtherInfo | RFC 9629 §5.3 | KDF input structure for KEM-based key derivation |
Import
from synta.cms import (
ContentInfo,
SignedData, SignerInfo,
EnvelopedData, EnvelopedDataBuilder,
EncryptedData,
DigestedData,
AuthenticatedData,
IssuerAndSerialNumber,
KEMRecipientInfo, CMSORIforKEMOtherInfo,
# OID constants:
ID_DATA, ID_SIGNED_DATA, ID_ENVELOPED_DATA,
ID_DIGESTED_DATA, ID_ENCRYPTED_DATA, ID_CT_AUTH_DATA,
ID_AES128_CBC, ID_AES192_CBC, ID_AES256_CBC,
ID_RSAES_OAEP, ID_RSA_ENCRYPTION,
ID_ORI, ID_ORI_KEM,
)
OID constants
Content-type OIDs (RFC 5652 §14)
| Constant | OID | Name |
|---|---|---|
ID_DATA | 1.2.840.113549.1.7.1 | id-data |
ID_SIGNED_DATA | 1.2.840.113549.1.7.2 | id-signedData |
ID_ENVELOPED_DATA | 1.2.840.113549.1.7.3 | id-envelopedData |
ID_DIGESTED_DATA | 1.2.840.113549.1.7.5 | id-digestedData |
ID_ENCRYPTED_DATA | 1.2.840.113549.1.7.6 | id-encryptedData |
ID_CT_AUTH_DATA | 1.2.840.113549.1.9.16.1.2 | id-ct-authData |
Content-encryption algorithm OIDs (RFC 3565)
| Constant | OID | Key length |
|---|---|---|
ID_AES128_CBC | 2.16.840.1.101.3.4.1.2 | 16 bytes |
ID_AES192_CBC | 2.16.840.1.101.3.4.1.22 | 24 bytes |
ID_AES256_CBC | 2.16.840.1.101.3.4.1.42 | 32 bytes |
Key-transport algorithm OIDs (RFC 8017)
| Constant | OID | Notes |
|---|---|---|
ID_RSAES_OAEP | 1.2.840.113549.1.1.7 | RSA-OAEP with SHA-256 (recommended) |
ID_RSA_ENCRYPTION | 1.2.840.113549.1.1.1 | RSA PKCS#1 v1.5 (legacy) |
CMS-KEM OtherRecipientInfo OIDs (RFC 9629 §6.2)
| Constant | OID | Description |
|---|---|---|
ID_ORI | 1.2.840.113549.1.9.16.13 | Root arc for OtherRecipientInfo alternatives |
ID_ORI_KEM | 1.2.840.113549.1.9.16.13.3 | Identifies a KEMRecipientInfo |
Sections
- ContentInfo — outer CMS envelope
- SignedData — signed content types
- EnvelopedData — encrypted-to-recipient content
- EncryptedData — symmetric encrypted content
- DigestedData and AuthenticatedData — hash and MAC content types
- CMS-KEM — RFC 9629 quantum-safe KEM recipient types
- OID Constants — all CMS OID constants
ContentInfo
ContentInfo is the outer CMS envelope — every CMS structure is wrapped in a ContentInfo
SEQUENCE. It is the standard entry point for parsing any CMS payload.
Class
class ContentInfo:
@staticmethod
def from_der(data: bytes) -> ContentInfo: ...
# Parse a DER- or BER-encoded ContentInfo SEQUENCE.
def to_der(self) -> bytes: ...
content_type_oid: ObjectIdentifier
# e.g. ID_SIGNED_DATA, ID_ENVELOPED_DATA, ID_ENCRYPTED_DATA, …
content: bytes
# Raw bytes of the content field (including the [0] EXPLICIT tag for most types).
Usage
from synta.cms import ContentInfo, ID_SIGNED_DATA, ID_ENVELOPED_DATA, ID_ENCRYPTED_DATA
data = open("message.p7m", "rb").read()
ci = ContentInfo.from_der(data)
print(ci.content_type_oid) # e.g. ObjectIdentifier("1.2.840.113549.1.7.2")
if ci.content_type_oid == ID_SIGNED_DATA:
from synta.cms import SignedData
sd = SignedData.from_der(ci.content)
print(f"Signers: {len(sd.signer_infos)}")
elif ci.content_type_oid == ID_ENVELOPED_DATA:
from synta.cms import EnvelopedData
ed = EnvelopedData.from_der(ci.content)
# decrypt with ed.decrypt(private_key)
elif ci.content_type_oid == ID_ENCRYPTED_DATA:
from synta.cms import EncryptedData
enc = EncryptedData.from_der(ci.content)
# decrypt with enc.decrypt(key)
See also the CMS Overview for a summary of all content types.
SignedData and SignerInfo
SignedData
Encapsulates signed content plus signer information (RFC 5652 §5).
class SignedData:
@staticmethod
def from_der(data: bytes) -> SignedData: ...
def to_der(self) -> bytes: ...
version: int
encap_content_type: ObjectIdentifier
encap_content: bytes | None # OCTET STRING value bytes
certificates: bytes | None # raw [0] IMPLICIT value bytes
crls: bytes | None # raw [1] IMPLICIT value bytes
signer_infos: list[SignerInfo]
Usage
from synta.cms import SignedData
import synta
# Parse a PKCS#7 SignedData blob (the [0] EXPLICIT wrapping from ContentInfo is
# handled internally when using synta.load_der_pkcs7_certificates; strip it
# manually if you have a raw ContentInfo).
data = open("message.p7s", "rb").read()
sd = SignedData.from_der(data)
print(f"version: {sd.version}")
print(f"content type: {sd.encap_content_type}")
print(f"signers: {len(sd.signer_infos)}")
# Access embedded certificates
if sd.certificates:
# certificates is the raw [0] IMPLICIT bytes; wrap with SEQUENCE tag for Decoder
certs_der = bytes([0x30]) + sd.certificates[1:] # swap IMPLICIT [0] tag with SEQUENCE
# ... decode with synta.Decoder
# Iterate signer infos
for si in sd.signer_infos:
print(f" digest alg: {si.digest_algorithm_oid}")
print(f" signature alg: {si.signature_algorithm_oid}")
SignerInfo
Per-signer structure within SignedData (RFC 5652 §5.3).
class SignerInfo:
@staticmethod
def from_der(data: bytes) -> SignerInfo: ...
def to_der(self) -> bytes: ...
version: int
sid: bytes
# Raw TLV bytes of the SignerIdentifier CHOICE:
# - SEQUENCE tag (0x30) → issuerAndSerialNumber
# - tag 0x80 → subjectKeyIdentifier
digest_algorithm_oid: ObjectIdentifier
digest_algorithm_params: bytes | None
signature_algorithm_oid: ObjectIdentifier
signature_algorithm_params: bytes | None
signature: bytes
signed_attrs: bytes | None
# Raw [0] IMPLICIT bytes. Replace the tag byte with 0x31 before hashing.
unsigned_attrs: bytes | None
Usage
from synta.cms import SignedData
import synta
sd = SignedData.from_der(data)
for si in sd.signer_infos:
print(f"digest: {si.digest_algorithm_oid}")
print(f"signature: {si.signature_algorithm_oid}")
print(f"sig bytes: {si.signature.hex()}")
if si.signed_attrs:
# signed_attrs carries an IMPLICIT [0] tag; replace the first byte with 0x31
# to get a valid SET TLV for hashing.
attrs_set = b'\x31' + si.signed_attrs[1:]
dec = synta.Decoder(attrs_set, synta.Encoding.DER)
attrs_seq = dec.decode_set()
while not attrs_seq.is_empty():
attr_tlv = attrs_seq.decode_raw_tlv()
# each attr_tlv is a complete Attribute SEQUENCE
See also CMS Overview and PKCS Loaders for
load_der_pkcs7_certificates which extracts certificates from a SignedData automatically.
EnvelopedData
EnvelopedData implements RFC 5652 §6 — encrypted content with per-recipient key transport.
The content-encryption key (CEK) is encrypted under each recipient’s public key and carried
in RecipientInfo structures. The content itself is encrypted using a symmetric cipher.
Decryption and encryption require the openssl Cargo feature.
EnvelopedData
class EnvelopedData:
@staticmethod
def from_der(data: bytes) -> EnvelopedData: ...
def to_der(self) -> bytes: ...
def decrypt(self, private_key: PrivateKey) -> bytes: ...
# Decrypt using the recipient's private key.
# Raises ValueError if no matching RecipientInfo is found or decryption fails.
# Requires the openssl Cargo feature.
version: int
originator_info: bytes | None
recipient_infos: bytes # raw RecipientInfos SET bytes
content_type: ObjectIdentifier
content_encryption_algorithm_oid: ObjectIdentifier
content_encryption_algorithm_params: bytes | None
encrypted_content: bytes | None
unprotected_attrs: bytes | None
EnvelopedDataBuilder
Fluent builder for EnvelopedData (RFC 5652 §6). Requires the openssl Cargo feature.
class EnvelopedDataBuilder:
def __init__(
self,
plaintext: bytes,
recipients: list[tuple[Certificate | bytes, ObjectIdentifier]],
*,
content_enc_alg: ObjectIdentifier | None = None,
) -> None: ...
# recipients: list of (cert, key_wrap_oid) tuples.
# cert may be a Certificate object or DER bytes.
# key_wrap_oid: ID_RSAES_OAEP (recommended) or ID_RSA_ENCRYPTION (legacy).
# content_enc_alg defaults to id-aes256-CBC.
def add_originator_cert(self, cert: Certificate | bytes) -> EnvelopedDataBuilder: ...
def add_originator_crl(self, crl: CertificateList | bytes) -> EnvelopedDataBuilder: ...
def set_unprotected_attrs(self, attrs: bytes) -> EnvelopedDataBuilder: ...
def build(self) -> EnvelopedData: ...
Usage
import synta
from synta.cms import EnvelopedData, EnvelopedDataBuilder, ID_RSAES_OAEP, ID_AES256_CBC
# Encrypt for one recipient
recipient_cert = synta.Certificate.from_der(open("recipient.der", "rb").read())
plaintext = b"Confidential message."
ed = EnvelopedDataBuilder(
plaintext,
[(recipient_cert, ID_RSAES_OAEP)],
content_enc_alg=ID_AES256_CBC,
).build()
ciphertext_der = ed.to_der()
# Decrypt
priv_key = synta.PrivateKey.from_pem(open("recipient.pem", "rb").read())
ed2 = EnvelopedData.from_der(ciphertext_der)
recovered = ed2.decrypt(priv_key)
assert recovered == plaintext
See also EncryptedData for shared-key symmetric encryption and CMS-KEM for quantum-safe KEM recipient structures.
EncryptedData
EncryptedData implements RFC 5652 §8 — symmetric content encryption using a shared key.
Unlike EnvelopedData, there is no per-recipient key transport; the caller provides the
symmetric key directly.
Encryption and decryption require the openssl Cargo feature (maturin develop --features openssl).
Class
class EncryptedData:
@staticmethod
def from_der(data: bytes) -> EncryptedData: ...
# Parse a DER- or BER-encoded EncryptedData SEQUENCE.
# Raises ValueError if the outer SEQUENCE envelope is malformed.
@staticmethod
def create(
plaintext: bytes,
key: bytes,
algorithm_oid: ObjectIdentifier | str,
content_type_oid: ObjectIdentifier | str | None = None,
) -> EncryptedData: ...
# Encrypt plaintext under key using the cipher identified by algorithm_oid.
# A fresh random IV is generated for every call.
# content_type_oid defaults to ID_DATA (1.2.840.113549.1.7.1).
# Raises NotImplementedError if built without the openssl Cargo feature.
def decrypt(self, key: bytes) -> bytes: ...
# Recover plaintext. Raises ValueError on key-length mismatch.
# Raises NotImplementedError if built without the openssl Cargo feature.
def to_der(self) -> bytes: ...
version: int # always 0
content_type: ObjectIdentifier # e.g. ID_DATA
content_encryption_algorithm_oid: ObjectIdentifier
content_encryption_algorithm_params: bytes | None
# For CBC ciphers: DER OCTET STRING (04 <len> <iv>) carrying the 16-byte IV.
encrypted_content: bytes | None
unprotected_attrs: bytes | None
Usage
from synta.cms import EncryptedData, ID_AES128_CBC, ID_AES256_CBC
KEY_128 = bytes.fromhex("00112233445566778899aabbccddeeff")
KEY_256 = bytes(range(32))
# ── Encrypt ───────────────────────────────────────────────────────────────────
plaintext = b"Confidential payload."
ed = EncryptedData.create(plaintext, KEY_128, ID_AES128_CBC)
# Inspect fields
print(ed.version) # 0
print(ed.content_type) # 1.2.840.113549.1.7.1 (id-data)
print(ed.content_encryption_algorithm_oid) # 2.16.840.1.101.3.4.1.2
iv = ed.content_encryption_algorithm_params[2:] # strip 04 <len> header
print(iv.hex()) # 16-byte random IV
# ── Round-trip ────────────────────────────────────────────────────────────────
assert EncryptedData.from_der(ed.to_der()).decrypt(KEY_128) == plaintext
# ── Re-encrypt with replaced content ─────────────────────────────────────────
decrypted = ed.decrypt(KEY_128)
modified = decrypted.replace(b"PENDING", b"APPROVED")
ed2 = EncryptedData.create(modified, KEY_256, ID_AES256_CBC)
assert ed2.decrypt(KEY_256) == modified
Each create call generates a fresh random IV; two calls on the same plaintext produce
different ciphertext. See examples/example_cms_encrypted_data.py for a full example
including openssl enc interop in both directions.
See also EnvelopedData for per-recipient key transport and CMS Overview for all content types.
DigestedData and AuthenticatedData
DigestedData
DigestedData implements RFC 5652 §7 — hash-protected content.
class DigestedData:
@staticmethod
def from_der(data: bytes) -> DigestedData: ...
def to_der(self) -> bytes: ...
version: int
digest_algorithm_oid: ObjectIdentifier
digest_algorithm_params: bytes | None
encap_content_type: ObjectIdentifier
encap_content: bytes | None
digest: bytes
Usage
from synta.cms import DigestedData
dd = DigestedData.from_der(data)
print(f"digest algorithm: {dd.digest_algorithm_oid}")
print(f"digest: {dd.digest.hex()}")
if dd.encap_content:
content = dd.encap_content # raw content bytes
AuthenticatedData
AuthenticatedData implements RFC 5652 §9 — MAC-authenticated content.
class AuthenticatedData:
@staticmethod
def from_der(data: bytes) -> AuthenticatedData: ...
def to_der(self) -> bytes: ...
version: int
originator_info: bytes | None
recipient_infos: bytes # raw RecipientInfos SET bytes
mac_algorithm_oid: ObjectIdentifier
mac_algorithm_params: bytes | None
digest_algorithm_oid: ObjectIdentifier | None
digest_algorithm_params: bytes | None
encap_content_type: ObjectIdentifier
encap_content: bytes | None
mac: bytes
auth_attrs: bytes | None
# Raw [2] IMPLICIT bytes. Replace the leading tag byte with 0x31 before MAC
# verification (same substitution as SignerInfo.signed_attrs).
unauth_attrs: bytes | None
Usage
from synta.cms import AuthenticatedData
ad = AuthenticatedData.from_der(data)
print(f"mac algorithm: {ad.mac_algorithm_oid}")
print(f"mac: {ad.mac.hex()}")
if ad.auth_attrs:
# Replace IMPLICIT [2] tag with SET tag 0x31 for MAC computation
import synta
attrs_set = b'\x31' + ad.auth_attrs[1:]
dec = synta.Decoder(attrs_set, synta.Encoding.DER)
# decode attributes ...
See also CMS Overview for the complete list of content types.
CMS-KEM Types
synta.kem provides KEMRecipientInfo and CMSORIforKEMOtherInfo for RFC 9629
quantum-safe KEM recipient structures, along with ML-KEM (FIPS 203) OID constants.
KEMRecipientInfo is carried as an OtherRecipientInfo alternative identified by
id-ori-kem (1.2.840.113549.1.9.16.13.3).
import synta.kem as kem
These types are also re-exported from synta.cms.
KEMRecipientInfo
RFC 9629 §5 recipient structure for KEM-based key transport.
class KEMRecipientInfo:
@staticmethod
def from_der(data: bytes) -> KEMRecipientInfo: ...
def to_der(self) -> bytes: ...
version: int # always 0
recipient_id: bytes # raw RecipientIdentifier CHOICE TLV
kem_algorithm_oid: ObjectIdentifier # KEM algorithm (e.g. ML-KEM-768)
kem_algorithm_params: bytes | None
kem_ciphertext: bytes # KEM ciphertext (kemct field)
kdf_algorithm_oid: ObjectIdentifier # key-derivation algorithm
kdf_algorithm_params: bytes | None
kek_length: int # KEK length in bytes (1–65535)
ukm: bytes | None # user keying material
key_encryption_algorithm_oid: ObjectIdentifier # key-wrap algorithm
key_encryption_algorithm_params: bytes | None
encrypted_key: bytes # encrypted content-encryption key
CMSORIforKEMOtherInfo
RFC 9629 §5.3 — otherInfo input to the KDF when deriving a KEK from a KEM shared secret.
class CMSORIforKEMOtherInfo:
@staticmethod
def from_der(data: bytes) -> CMSORIforKEMOtherInfo: ...
def to_der(self) -> bytes: ...
key_encryption_algorithm_oid: ObjectIdentifier
key_encryption_algorithm_params: bytes | None
kek_length: int
ukm: bytes | None
OID constants
ML-KEM OIDs (FIPS 203)
| Constant | OID | Description |
|---|---|---|
ID_ML_KEM_512 | 2.16.840.1.101.3.4.4.1 | ML-KEM-512 key encapsulation |
ID_ML_KEM_768 | 2.16.840.1.101.3.4.4.2 | ML-KEM-768 key encapsulation |
ID_ML_KEM_1024 | 2.16.840.1.101.3.4.4.3 | ML-KEM-1024 key encapsulation |
CMS OtherRecipientInfo OIDs (RFC 9629 §6.2)
| Constant | OID | Description |
|---|---|---|
ID_ORI | 1.2.840.113549.1.9.16.13 | Root arc for OtherRecipientInfo alternatives |
ID_ORI_KEM | 1.2.840.113549.1.9.16.13.3 | Identifies a KEMRecipientInfo |
Usage
import synta
import synta.kem as kem
# Parse a KEMRecipientInfo from an OtherRecipientInfo body
kri = kem.KEMRecipientInfo.from_der(ori_body_der)
print(f"KEM algorithm: {kri.kem_algorithm_oid}")
print(f"KDF algorithm: {kri.kdf_algorithm_oid}")
print(f"KEK length: {kri.kek_length}")
print(f"ciphertext: {kri.kem_ciphertext.hex()}")
# Check if this is an ML-KEM-768 recipient
if kri.kem_algorithm_oid == kem.ID_ML_KEM_768:
# Decapsulate with a PrivateKey
priv_key = synta.PrivateKey.from_der(kem_priv_der)
shared_secret = priv_key.kem_decapsulate(kri.kem_ciphertext)
# Then apply KDF (kri.kdf_algorithm_oid) over shared_secret + kri.ukm to derive KEK
See also Keys for PublicKey.kem_encapsulate() and
PrivateKey.kem_decapsulate(), and CMS Overview for the full module import.
CMS OID Constants
All OID constants in synta.cms are ObjectIdentifier instances (frozen, hashable).
Content-type OIDs (RFC 5652 §14)
| Constant | OID | Name |
|---|---|---|
ID_DATA | 1.2.840.113549.1.7.1 | id-data |
ID_SIGNED_DATA | 1.2.840.113549.1.7.2 | id-signedData |
ID_ENVELOPED_DATA | 1.2.840.113549.1.7.3 | id-envelopedData |
ID_DIGESTED_DATA | 1.2.840.113549.1.7.5 | id-digestedData |
ID_ENCRYPTED_DATA | 1.2.840.113549.1.7.6 | id-encryptedData |
ID_CT_AUTH_DATA | 1.2.840.113549.1.9.16.1.2 | id-ct-authData |
Content-encryption algorithm OIDs (RFC 3565)
Passed as algorithm_oid to EncryptedData.create; also returned by the
content_encryption_algorithm_oid property.
| Constant | OID | Key length |
|---|---|---|
ID_AES128_CBC | 2.16.840.1.101.3.4.1.2 | 16 bytes |
ID_AES192_CBC | 2.16.840.1.101.3.4.1.22 | 24 bytes |
ID_AES256_CBC | 2.16.840.1.101.3.4.1.42 | 32 bytes |
Key-transport algorithm OIDs (RFC 8017)
Passed as key_wrap_oid to EnvelopedDataBuilder.
| Constant | OID | Notes |
|---|---|---|
ID_RSAES_OAEP | 1.2.840.113549.1.1.7 | RSA-OAEP with SHA-256 (recommended) |
ID_RSA_ENCRYPTION | 1.2.840.113549.1.1.1 | RSA PKCS#1 v1.5 (legacy) |
CMS-KEM OtherRecipientInfo OIDs (RFC 9629 §6.2)
| Constant | OID | Description |
|---|---|---|
ID_ORI | 1.2.840.113549.1.9.16.13 | Root arc for OtherRecipientInfo alternatives |
ID_ORI_KEM | 1.2.840.113549.1.9.16.13.3 | Identifies a KEMRecipientInfo |
Import
from synta.cms import (
ID_DATA, ID_SIGNED_DATA, ID_ENVELOPED_DATA,
ID_DIGESTED_DATA, ID_ENCRYPTED_DATA, ID_CT_AUTH_DATA,
ID_AES128_CBC, ID_AES192_CBC, ID_AES256_CBC,
ID_RSAES_OAEP, ID_RSA_ENCRYPTION,
ID_ORI, ID_ORI_KEM,
)
ML-KEM OID constants (ID_ML_KEM_512, ID_ML_KEM_768, ID_ML_KEM_1024) are in
synta.kem; see CMS-KEM.
See also Well-known OIDs for the full synta.oids catalog which
includes the CMS content-type OIDs as CMS_DATA, CMS_SIGNED_DATA, etc.
Kerberos V5 Types
synta.krb5 provides Kerberos V5 principal name types, constants, and PKINIT protocol
structures (RFC 4556, RFC 6112, RFC 8636).
import synta.krb5
Principal-name type constants
Integer constants from RFC 4120 §6.2:
| Constant | Value | Description |
|---|---|---|
NT_UNKNOWN | 0 | Unknown |
NT_PRINCIPAL | 1 | User/host principal |
NT_SRV_INST | 2 | Service + instance (e.g. krbtgt) |
NT_SRV_HST | 3 | Service + hostname |
NT_SRV_XHST | 4 | Service + host (remaining components) |
NT_UID | 5 | Unique ID |
NT_X500_PRINCIPAL | 6 | Encoded X.500 DN |
NT_SMTP_NAME | 7 | SMTP email address |
NT_ENTERPRISE | 10 | Enterprise (UPN-style), RFC 6806 |
NT_WELLKNOWN | 11 | Well-known (anonymous), RFC 8062 |
NT_SRV_HST_DOMAIN | 12 | Host-based service, Windows MS-SFU |
Encryption type constants
IANA Kerberos Encryption Type Numbers:
| Constant | Value | RFC / source | Notes |
|---|---|---|---|
ETYPE_DES_CBC_CRC | 1 | RFC 6649 | Deprecated |
ETYPE_DES_CBC_MD4 | 2 | RFC 6649 | Deprecated |
ETYPE_DES_CBC_MD5 | 3 | RFC 6649 | Deprecated |
ETYPE_DES3_CBC_MD5 | 5 | — | Deprecated |
ETYPE_DES3_CBC_SHA1 | 7 | — | Deprecated |
ETYPE_DES_HMAC_SHA1 | 8 | — | Deprecated |
ETYPE_DES3_CBC_SHA1_KD | 16 | RFC 3961 §6.3 | Deprecated |
ETYPE_AES128_CTS_HMAC_SHA1_96 | 17 | RFC 3962 | |
ETYPE_AES256_CTS_HMAC_SHA1_96 | 18 | RFC 3962 | |
ETYPE_AES128_CTS_HMAC_SHA256_128 | 19 | RFC 8009 | Recommended |
ETYPE_AES256_CTS_HMAC_SHA384_192 | 20 | RFC 8009 | Recommended |
ETYPE_RC4_HMAC | 23 | MS-KILE | Deprecated |
ETYPE_RC4_HMAC_EXP | 24 | MS-KILE | Deprecated |
ETYPE_CAMELLIA128_CTS_CMAC | 25 | RFC 6803 | |
ETYPE_CAMELLIA256_CTS_CMAC | 26 | RFC 6803 |
OID constant
KRB5_PRINCIPAL_NAME_OID # ObjectIdentifier("1.3.6.1.5.2.2") — id-pkinit-san
This is the OtherName type-id for KRB5PrincipalName entries in certificate SAN extensions.
See also:
- Krb5PrincipalName — the principal name encoder/decoder class
- PKINIT types — RFC 4556 protocol structures
- PKINIT OIDs —
ID_PKINIT_SANand related OID constants
Krb5PrincipalName
Krb5PrincipalName encodes and decodes the KRB5PrincipalName structure (RFC 4556 §3.2.2)
used as an OtherName Subject Alternative Name value in PKINIT certificates.
Class
class Krb5PrincipalName:
def __init__(self, realm: str, name_type: int, components: list[str]) -> None: ...
# realm must be ASCII. name_type is one of the NT_* constants.
# All string arguments must be ASCII; raises ValueError otherwise.
@staticmethod
def from_der(data: bytes) -> Krb5PrincipalName: ...
def to_der(self) -> bytes: ...
realm: str # Kerberos realm (e.g. "EXAMPLE.COM")
name_type: int # One of the NT_* constants
components: list[str] # Name-string components
def __repr__(self) -> str: ...
def __eq__(self, other) -> bool: ...
Usage
import synta.krb5 as krb5
# Construct a host principal: host/server.example.com@EXAMPLE.COM
principal = krb5.Krb5PrincipalName(
realm="EXAMPLE.COM",
name_type=krb5.NT_SRV_HST,
components=["host", "server.example.com"],
)
der_bytes = principal.to_der()
# Parse from DER
parsed = krb5.Krb5PrincipalName.from_der(der_bytes)
print(parsed.realm) # "EXAMPLE.COM"
print(parsed.name_type) # 3 (NT_SRV_HST)
print(parsed.components) # ["host", "server.example.com"]
# Read from a PKINIT certificate SAN (otherName [0] tag)
import synta
import synta.general_name as gn
for tag_num, content in cert.subject_alt_names():
if tag_num == gn.OTHER_NAME:
# content is the full OtherNameValue TLV; decode type-id and value
dec = synta.Decoder(content, synta.Encoding.DER)
seq = dec.decode_sequence()
type_id = seq.decode_oid() # should be krb5.KRB5_PRINCIPAL_NAME_OID
explicit = seq.decode_explicit_tag(0)
kpn = krb5.Krb5PrincipalName.from_der(explicit.remaining_bytes())
print(f"realm={kpn.realm}, name_type={kpn.name_type}, components={kpn.components}")
Encoding a principal name for certificate generation
import synta
import synta.krb5 as krb5
# Build an otherName SAN entry for a PKINIT client certificate
kpn = krb5.Krb5PrincipalName(
realm="EXAMPLE.COM",
name_type=krb5.NT_PRINCIPAL,
components=["alice"],
)
kpn_der = kpn.to_der()
# Wrap in OtherName: SEQUENCE { type-id OID, value [0] EXPLICIT <kpn_der> }
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_oid(krb5.KRB5_PRINCIPAL_NAME_OID)
inner.encode_explicit_tag(0, "Context", kpn_der)
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_sequence(inner.finish())
other_name_der = outer.finish()
# Use with SubjectAlternativeNameBuilder
import synta.ext as ext
san = ext.SAN().directory_name(other_name_der).build()
See also Kerberos V5 Types for principal-name type and encryption type constants, and PKINIT Types for the full PKINIT protocol structure set.
PKINIT Protocol Types
All PKINIT classes in synta.krb5 are frozen (immutable after construction). Each provides
a from_der(data: bytes) static method for parsing. Fields that are OPTIONAL in the ASN.1
schema are exposed as ... | None.
import synta.krb5 as krb5
EncryptionKey
RFC 3961 §2 encryption key structure.
class EncryptionKey:
@staticmethod
def from_der(data: bytes) -> EncryptionKey: ...
keytype: int # Kerberos etype number
keyvalue: bytes # raw key material
Checksum
RFC 3961 §4 checksum structure.
class Checksum:
@staticmethod
def from_der(data: bytes) -> Checksum: ...
cksumtype: int # checksum type
checksum: bytes # raw checksum bytes
KDFAlgorithmId
RFC 8636 §3.1 KDF algorithm identifier.
class KDFAlgorithmId:
@staticmethod
def from_der(data: bytes) -> KDFAlgorithmId: ...
kdf_id: ObjectIdentifier # KDF algorithm OID
IssuerAndSerialNumber
RFC 4556 §3.2.2 — identifies a certificate by issuer name and serial number.
class IssuerAndSerialNumber:
@staticmethod
def from_der(data: bytes) -> IssuerAndSerialNumber: ...
issuer: bytes # DER-encoded Name SEQUENCE
serial_number: int # certificate serial number
ExternalPrincipalIdentifier
RFC 4556 §3.2.2 — identifies a client certificate by one of three optional methods.
class ExternalPrincipalIdentifier:
@staticmethod
def from_der(data: bytes) -> ExternalPrincipalIdentifier: ...
subject_name: bytes | None # DER of subject Name
issuer_and_serial_number: IssuerAndSerialNumber | None
subject_key_identifier: bytes | None # raw SKI bytes
PKAuthenticator
RFC 4556 §3.2.1 — client proof of liveness in AS-REQ.
class PKAuthenticator:
@staticmethod
def from_der(data: bytes) -> PKAuthenticator: ...
cusec: int # microseconds component (0–999999)
ctime: str # client time as "YYYYMMDDHHMMSSz"
nonce: int
pa_checksum: bytes | None # SHA-1 checksum of AS-REQ
freshness_token: bytes | None # RFC 8070 freshness token
AuthPack
RFC 4556 §3.2.1 — content signed by the client.
class AuthPack:
@staticmethod
def from_der(data: bytes) -> AuthPack: ...
pk_authenticator: PKAuthenticator
client_public_value: bytes | None # DER SubjectPublicKeyInfo
supported_cmstypes: bytes | None # DER AlgorithmIdentifiers
client_dhnonce: bytes | None
supported_kdfs: list[KDFAlgorithmId] | None # RFC 8636 KDF list
PaPkAsReq
RFC 4556 §3.2.2 — PKINIT pre-authentication request.
class PaPkAsReq:
@staticmethod
def from_der(data: bytes) -> PaPkAsReq: ...
signed_auth_pack: bytes # CMS SignedData wrapping AuthPack
trusted_certifiers: list[ExternalPrincipalIdentifier] | None
kdc_pk_id: bytes | None # raw SKI bytes for KDC certificate
DHRepInfo
RFC 4556 §3.2.4 — KDC Diffie-Hellman reply data.
class DHRepInfo:
@staticmethod
def from_der(data: bytes) -> DHRepInfo: ...
dh_signed_data: bytes # CMS SignedData wrapping KDCDHKeyInfo
server_dhnonce: bytes | None
KDCDHKeyInfo
RFC 4556 §3.2.4 — KDC DH public key and nonce.
class KDCDHKeyInfo:
@staticmethod
def from_der(data: bytes) -> KDCDHKeyInfo: ...
subject_public_key: bytes # BIT STRING payload bytes (KDC DH public key)
nonce: int
dh_key_expiration: str | None # "YYYYMMDDHHMMSSz"
ReplyKeyPack
RFC 4556 §3.2.3 — session key and checksum from KDC (Diffie-Hellman-less path).
class ReplyKeyPack:
@staticmethod
def from_der(data: bytes) -> ReplyKeyPack: ...
reply_key: EncryptionKey # the session key
as_checksum: Checksum # checksum over the AS-REQ
PaPkAsRep
RFC 4556 §3.2.4 — PKINIT pre-authentication reply (CHOICE type).
class PaPkAsRep:
@staticmethod
def from_der(data: bytes) -> PaPkAsRep: ...
variant: str # "DhInfo" or "EncKeyPack"
dh_info: DHRepInfo | None
enc_key_pack: bytes | None # CMS EnvelopedData bytes
See also Kerberos V5 Types for constants and Krb5PrincipalName for principal name encoding.
RFC 3279 Algorithm Parameters
synta.pkixalgs provides types for decoding DSA/DH domain parameters and DSA/ECDSA
signature values, as defined in RFC 3279.
import synta.pkixalgs as pa
DssParms
DSA domain parameters (RFC 3279 §2.3.2). Decoded from the parameters field of an
AlgorithmIdentifier whose OID is id-dsa.
class DssParms:
@staticmethod
def from_der(data: bytes) -> DssParms: ...
def to_der(self) -> bytes: ...
p: bytes # prime modulus (big-endian two's-complement)
q: bytes # prime divisor
g: bytes # generator
DssSigValue
DSA signature value (RFC 3279 §2.2.2). Contains the (r, s) integer pair.
class DssSigValue:
@staticmethod
def from_der(data: bytes) -> DssSigValue: ...
def to_der(self) -> bytes: ...
r: bytes # signature integer r (big-endian two's-complement)
s: bytes # signature integer s
EcdsaSigValue
ECDSA signature value (RFC 3279 §2.2.3, X9.62). Contains the (r, s) integer pair.
class EcdsaSigValue:
@staticmethod
def from_der(data: bytes) -> EcdsaSigValue: ...
def to_der(self) -> bytes: ...
r: bytes
s: bytes
ECParameters
EC domain parameters CHOICE (RFC 3279 §2.3.5, X9.62). Represents the three-arm CHOICE:
namedCurve (OID), ecParameters (explicit domain params), or implicitlyCA (NULL).
class ECParameters:
@staticmethod
def from_der(data: bytes) -> ECParameters: ...
def to_der(self) -> bytes: ...
arm: str # "namedCurve", "ecParameters", or "implicitlyCA"
named_curve_oid: ObjectIdentifier | None # None if arm is not "namedCurve"
OID constants
| Constant | OID | Description |
|---|---|---|
ID_DSA | 1.2.840.10040.4.1 | DSA public key |
ID_DSA_WITH_SHA1 | 1.2.840.10040.4.3 | DSA with SHA-1 signature |
DHPUBLICNUMBER | 1.2.840.10046.2.1 | Diffie-Hellman public key |
ID_EC_PUBLIC_KEY | 1.2.840.10045.2.1 | EC public key |
ECDSA_WITH_SHA1 | 1.2.840.10045.4.1 | ECDSA with SHA-1 |
ECDSA_WITH_SHA256 | 1.2.840.10045.4.3.2 | ECDSA with SHA-256 |
ECDSA_WITH_SHA384 | 1.2.840.10045.4.3.3 | ECDSA with SHA-384 |
ECDSA_WITH_SHA512 | 1.2.840.10045.4.3.4 | ECDSA with SHA-512 |
PRIME192V1 | 1.2.840.10045.3.1.1 | NIST P-192 / secp192r1 |
PRIME256V1 | 1.2.840.10045.3.1.7 | NIST P-256 / secp256r1 |
SECP224R1 | 1.3.132.0.33 | NIST P-224 |
SECP384R1 | 1.3.132.0.34 | NIST P-384 |
SECP521R1 | 1.3.132.0.35 | NIST P-521 |
Usage
import synta
import synta.pkixalgs as pa
# Decode ECDSA signature value from cert.signature_value
sig = pa.EcdsaSigValue.from_der(cert.signature_value)
print(f"r: {sig.r.hex()}")
print(f"s: {sig.s.hex()}")
# Decode EC curve OID from public key algorithm parameters
if cert.public_key_algorithm_params:
ec_params = pa.ECParameters.from_der(cert.public_key_algorithm_params)
if ec_params.arm == "namedCurve":
print(f"curve OID: {ec_params.named_curve_oid}")
Attribute Certificates
synta.ac provides RFC 5755 Attribute Certificate v2 types. Attribute Certificates (ACs)
bind a set of attributes (roles, clearances, service-authentication information) to a holder
identified by reference to their Public Key Certificate (PKC), without requiring re-issuance
of the PKC.
import synta.ac as ac
AttributeCertificate
class AttributeCertificate:
@staticmethod
def from_der(data: bytes) -> AttributeCertificate: ...
# Raises ValueError if the bytes cannot be decoded.
@staticmethod
def from_pem(data: bytes) -> AttributeCertificate: ...
# Parse the first ATTRIBUTE CERTIFICATE PEM block.
# Raises ValueError if no valid PEM block is found or the DER is invalid.
def to_der(self) -> bytes: ...
def to_pem(self) -> str: ...
# PEM encoding with the "ATTRIBUTE CERTIFICATE" label (RFC 5755).
version: int # always 1 for v2 (RFC 5755)
serial_number: bytes # certificate serial number as big-endian bytes
not_before: str # "YYYYMMDDHHmmssZ"
not_after: str # "YYYYMMDDHHmmssZ"
signature_algorithm_oid: ObjectIdentifier
signature: bytes # raw signature bytes (zero-byte padding stripped)
holder_der: bytes # DER bytes of the Holder SEQUENCE
issuer_der: bytes # DER bytes of the AttCertIssuer CHOICE
attributes_der: bytes # DER bytes of SEQUENCE OF Attribute
def verify_issued_by(self, issuer: Certificate) -> None: ...
# Verify the cryptographic signature against issuer's public key.
# No AC issuer-to-subject name comparison is performed.
# Raises ValueError if the signature is invalid or encoding fails.
AttributeCertificateBuilder
Fluent builder for RFC 5755 AttributeCertificateInfo TBS encoding.
class AttributeCertificateBuilder:
def __init__(self) -> None: ...
def serial_number(self, n: int) -> AttributeCertificateBuilder: ...
def not_before(self, s: str) -> AttributeCertificateBuilder: ... # "YYYYMMDDHHmmssZ"
def not_after(self, s: str) -> AttributeCertificateBuilder: ... # "YYYYMMDDHHmmssZ"
def issuer_rfc822(self, email: str) -> AttributeCertificateBuilder: ...
def issuer_dns(self, name: str) -> AttributeCertificateBuilder: ...
def holder_entity_name_rfc822(self, email: str) -> AttributeCertificateBuilder: ...
def build(self) -> bytes: ...
# Encode the AttributeCertificateInfo SEQUENCE to DER bytes.
# Raises ValueError if any required field is missing or encoding fails.
OID constants
| Constant | Description |
|---|---|
ID_PE_AC_AUDIT_IDENTITY | Audit identity extension (RFC 5755 §4.4.1) |
ID_PE_AA_CONTROLS | AA controls extension (RFC 5755 §4.4.2) |
ID_PE_AC_PROXYING | AC proxying extension (RFC 5755 §4.4.3) |
ID_CE_TARGET_INFORMATION | Target information extension (RFC 5755 §4.3.2) |
ID_ACA_AUTHENTICATION_INFO | Authentication information attribute |
ID_ACA_ACCESS_IDENTITY | Access identity attribute |
ID_ACA_CHARGING_IDENTITY | Charging identity attribute |
ID_ACA_GROUP | Group attribute |
ID_ACA_ENC_ATTRS | Encrypted attributes |
ID_AT_ROLE | Role attribute type |
ID_AT_CLEARANCE | Clearance attribute type |
Usage
import synta
import synta.ac as ac
# Parse an attribute certificate
with open("holder.ac", "rb") as f:
attr_cert = ac.AttributeCertificate.from_der(f.read())
print(f"version: {attr_cert.version}")
print(f"not_before: {attr_cert.not_before}")
print(f"not_after: {attr_cert.not_after}")
print(f"sig alg: {attr_cert.signature_algorithm_oid}")
# Verify the AC signature
issuer_cert = synta.Certificate.from_der(open("issuer.der", "rb").read())
try:
attr_cert.verify_issued_by(issuer_cert)
print("AC signature valid")
except ValueError as e:
print(f"Invalid: {e}")
# Decode attributes (decode holder_der/attributes_der with synta.Decoder)
import synta
dec = synta.Decoder(attr_cert.attributes_der, synta.Encoding.DER)
attrs = dec.decode_sequence()
while not attrs.is_empty():
attr_tlv = attrs.decode_raw_tlv()
# attr_tlv is one complete Attribute SEQUENCE
CRMF Messages
synta.crmf provides RFC 4211 Certificate Request Message Format types used in CMP
certificate management.
import synta.crmf as crmf
CertReqMessages
A batch of Certificate Request Messages (SEQUENCE OF CertReqMsg, RFC 4211 §3).
class CertReqMessages:
@staticmethod
def from_der(data: bytes) -> CertReqMessages: ...
def to_der(self) -> bytes: ...
requests: list[CertReqMsg]
def __len__(self) -> int: ...
def __iter__(self): ...
def __getitem__(self, index: int) -> CertReqMsg: ...
CertReqMsg
A single certificate request message (RFC 4211 §4).
class CertReqMsg:
cert_req_id: int # certReqId integer
cert_template_der: bytes # DER bytes of the CertTemplate SEQUENCE
popo_type: str | None # Active ProofOfPossession arm name, or None
# One of: "raVerified", "signature", "keyEncipherment", "keyAgreement"
popo_der: bytes | None # DER bytes of the ProofOfPossession CHOICE
subject_der: bytes | None # DER bytes of subject Name from CertTemplate
issuer_der: bytes | None # DER bytes of issuer Name from CertTemplate
CertReqMsgBuilder
Fluent builder for a single CertReqMsg.
class CertReqMsgBuilder:
def __init__(self) -> None: ...
def cert_req_id(self, id: int) -> CertReqMsgBuilder: ...
def subject_name(self, name_der: bytes) -> CertReqMsgBuilder: ...
def issuer_name(self, name_der: bytes) -> CertReqMsgBuilder: ...
def public_key_der(self, spki_der: bytes) -> CertReqMsgBuilder: ...
def popo_ra_verified(self) -> CertReqMsgBuilder: ...
def publication_action(self, action: int) -> CertReqMsgBuilder: ...
def add_pub_info(self, pub_method: int) -> CertReqMsgBuilder: ...
def pub_location_uri(self, pub_method: int, uri: str) -> CertReqMsgBuilder: ...
def pub_location_rfc822(self, pub_method: int, email: str) -> CertReqMsgBuilder: ...
def pub_location_dns(self, pub_method: int, host: str) -> CertReqMsgBuilder: ...
def pub_location_directory_name(self, pub_method: int, name_der: bytes) -> CertReqMsgBuilder: ...
def build(self) -> CertReqMsg: ...
# Raises ValueError if stored Name/SPKI bytes are invalid.
CertReqMessagesBuilder
Fluent builder for a CRMF batch.
class CertReqMessagesBuilder:
def __init__(self) -> None: ...
def add_request(self, req: CertReqMsg) -> CertReqMessagesBuilder: ...
def build(self) -> CertReqMessages: ...
Publication method constants
| Constant | Value | Meaning |
|---|---|---|
PUB_METHOD_DONT_CARE | 0 | No publication preference |
PUB_METHOD_X500 | 1 | Publish in X.500 directory |
PUB_METHOD_WEB | 2 | Publish via HTTP/HTTPS URI |
PUB_METHOD_LDAP | 3 | Publish in LDAP directory |
OID constants
| Constant | Description |
|---|---|
ID_REG_CTRL_REG_TOKEN | Registration token control (RFC 4211 §6.1) |
ID_REG_CTRL_AUTHENTICATOR | Authenticator control (RFC 4211 §6.2) |
ID_REG_CTRL_PKI_PUBLICATION_INFO | PKI publication info control |
ID_REG_CTRL_PKI_ARCHIVE_OPTIONS | PKI archive options control |
ID_REG_CTRL_OLD_CERT_ID | Old certificate ID control |
ID_REG_CTRL_PROTOCOL_ENCR_KEY | Protocol encryption key control |
ID_REG_INFO_UTF8_PAIRS | UTF-8 pairs registration info |
ID_REG_INFO_CERT_REQ | Certificate request registration info |
Usage
import synta.crmf as crmf
# Build a CRMF request
req = (
crmf.CertReqMsgBuilder()
.cert_req_id(1)
.subject_name(subject_name_der)
.public_key_der(spki_der)
.popo_ra_verified()
.build()
)
batch = crmf.CertReqMessagesBuilder().add_request(req).build()
batch_der = batch.to_der()
# Parse incoming CRMF
msgs = crmf.CertReqMessages.from_der(data)
for msg in msgs:
print(f"id={msg.cert_req_id}, popo={msg.popo_type}")
if msg.subject_der:
import synta
attrs = synta.parse_name_attrs(msg.subject_der)
print(f" subject: {attrs}")
See also CMP Messages for the CMP envelope that carries CRMF requests.
CMP Messages
synta.cmp provides RFC 9810 Certificate Management Protocol v3 types.
import synta.cmp as cmp
CMPMessage
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
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().
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
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 for the request structures carried in CMP bodies.
PKCS#8 Private Key Structures
synta.pkcs8 provides OneAsymmetricKey (also exported as PrivateKeyInfo) for parsing
DER-encoded private key envelopes produced by OpenSSL and other PKI tools (RFC 5958 / PKCS#8).
import synta.pkcs8 as pkcs8
OneAsymmetricKey
class OneAsymmetricKey:
@staticmethod
def from_der(data: bytes) -> OneAsymmetricKey: ...
def to_der(self) -> bytes: ...
version: int # 0 = v1 (PrivateKeyInfo), 1 = v2 (RFC 5958)
private_key_algorithm: ObjectIdentifier # the private-key algorithm OID
private_key: bytes # raw key material (OCTET STRING value)
attributes_der: bytes | None # raw DER of [0] IMPLICIT attributes bag
public_key_der: bytes | None # raw DER of [1] IMPLICIT public key BIT STRING
alg_parameters_der: bytes | None # algorithm parameters DER, or None
PrivateKeyInfo = OneAsymmetricKey # RFC 5958 / PKCS#8 alias
Usage
import synta.pkcs8 as pkcs8
# Parse a PKCS#8 key from DER bytes
with open("key.der", "rb") as f:
key = pkcs8.OneAsymmetricKey.from_der(f.read())
print(f"version: {key.version}")
print(f"algorithm: {key.private_key_algorithm}")
print(f"key bytes: {key.private_key.hex()}")
# Algorithm parameters (e.g. curve OID for EC keys)
if key.alg_parameters_der:
import synta.pkixalgs as pa
ec_params = pa.ECParameters.from_der(key.alg_parameters_der)
if ec_params.arm == "namedCurve":
print(f"curve: {ec_params.named_curve_oid}")
# Check for optional public key component (v2 / RFC 5958)
if key.public_key_der:
print(f"public key DER: {len(key.public_key_der)} bytes")
# Use the PrivateKeyInfo alias
key2 = pkcs8.PrivateKeyInfo.from_der(key_der)
For cryptographic operations (signing, decryption, key generation), use
synta.PrivateKey which wraps an OpenSSL key. OneAsymmetricKey is
a pure ASN.1 parser that does not invoke any cryptographic backend.
See also PKCS#9 OIDs for id-friendlyName and id-localKeyId bag attributes
used in PKCS#12 archives, and PKCS Loaders for extracting raw
PKCS#8 DER bytes from PKCS#12 archives via load_pkcs12_keys.
SPNEGO Negotiation Tokens
synta.spnego provides classes for the GSSAPI SPNEGO negotiation mechanism (RFC 4178).
It handles both the full GSSAPI-wrapped form (leading 0x60 APPLICATION tag) and the plain
CHOICE form (0xa0/0xa1).
Wire format note:
GssapiSpnego.asn1usesDEFINITIONS IMPLICIT TAGS. All context-tagged fields use IMPLICIT encoding (the universal tag is replaced, not wrapped).
import synta.spnego as spnego
NegTokenInit
Sent by the GSSAPI initiator to propose mechanisms and supply an optional initial token (RFC 4178 §4.2.1).
class NegTokenInit:
@staticmethod
def from_der(data: bytes) -> NegTokenInit: ...
# Parse a DER-encoded NegTokenInit SEQUENCE.
mech_types: list[str] # proposed mechanism OIDs in dot-notation
req_flags: bytes | None # context flags bit-string bytes, or None
mech_token: bytes | None # initial token for preferred mech, or None
mech_list_mic: bytes | None # MIC over mechanism list, or None
NegTokenResp
Sent by the GSSAPI acceptor to indicate negotiation state and supply a reply token
(RFC 4178 §4.2.2). Compare neg_state against the NEG_STATE_* constants.
class NegTokenResp:
@staticmethod
def from_der(data: bytes) -> NegTokenResp: ...
# Parse a DER-encoded NegTokenResp SEQUENCE.
neg_state: int | None # NEG_STATE_* constant, or None
supported_mech: str | None # selected mechanism OID (dot-notation), or None
response_token: bytes | None
mech_list_mic: bytes | None
NegotiationToken
The top-level CHOICE wrapper. from_der handles both the full GSSAPI-wrapped form and the
raw CHOICE form.
class NegotiationToken:
@staticmethod
def from_der(data: bytes) -> NegotiationToken: ...
# Accepts 0x60 (GSSAPI-wrapped), 0xa0 (NegTokenInit), or 0xa1 (NegTokenResp).
variant: str # "NegTokenInit" or "NegTokenResp"
neg_token_init: NegTokenInit | None
neg_token_resp: NegTokenResp | None
Constants
spnego.NEG_STATE_ACCEPT_COMPLETED # 0 — negotiation succeeded
spnego.NEG_STATE_ACCEPT_INCOMPLETE # 1 — more tokens needed
spnego.NEG_STATE_REJECT # 2 — negotiation rejected
spnego.NEG_STATE_REQUEST_MIC # 3 — MIC is requested
spnego.SPNEGO_OID # "1.3.6.1.5.5.2" (string, not ObjectIdentifier)
Usage
import synta.spnego as spnego
# Parse a GSSAPI SPNEGO token from the network
tok = spnego.NegotiationToken.from_der(gssapi_bytes)
if tok.variant == "NegTokenInit":
init = tok.neg_token_init
print("Proposed mechanisms:", init.mech_types)
if init.mech_token:
print("Initial token:", len(init.mech_token), "bytes")
else:
resp = tok.neg_token_resp
if resp.neg_state == spnego.NEG_STATE_ACCEPT_COMPLETED:
print("Negotiation complete; mech:", resp.supported_mech)
elif resp.neg_state == spnego.NEG_STATE_REQUEST_MIC:
print("MIC requested")
Microsoft PKI (AD CS) Extension Types
synta.ms_pki exposes two certificate extension types used by Microsoft Active Directory
Certificate Services (AD CS), along with OID constants for Microsoft-specific extensions.
import synta.ms_pki as ms_pki
MSCSTemplateV2
Parsed from the extension value DER bytes of OID 1.3.6.1.4.1.311.21.7
(id-ms-certificate-template).
class MSCSTemplateV2:
@staticmethod
def from_der(data: bytes) -> MSCSTemplateV2: ...
def to_der(self) -> bytes: ...
template_id: ObjectIdentifier # template OID
template_major_version: int # major version number
template_minor_version: int | None # minor version, or None if absent
import synta
import synta.ms_pki as ms_pki
ext_der = cert.get_extension_value_der("1.3.6.1.4.1.311.21.7")
if ext_der:
tmpl = ms_pki.MSCSTemplateV2.from_der(ext_der)
print(tmpl.template_id, tmpl.template_major_version)
RequestClientInfo
Parsed from the extension value DER bytes of OID 1.3.6.1.4.1.311.21.20
(id-ms-request-Client-Info). Carried in certificate requests to identify the enrolling
Windows client.
class RequestClientInfo:
@staticmethod
def from_der(data: bytes) -> RequestClientInfo: ...
client_id: int | None # numeric enrollment type, or None
machine_name: str | None # NetBIOS or DNS machine name, or None
user_name: str | None # Windows user name (DOMAIN\user), or None
process_name: str | None # enrolling process (e.g. "certreq"), or None
ext_der = cert.get_extension_value_der("1.3.6.1.4.1.311.21.20")
if ext_der:
info = ms_pki.RequestClientInfo.from_der(ext_der)
print(info.machine_name, info.user_name)
OID constants
All are ObjectIdentifier instances.
| Constant | OID | Description |
|---|---|---|
ID_MS_CERTSRV_CA_VERSION | 1.3.6.1.4.1.311.21.1 | CA version |
ID_MS_CERTSRV_PREVIOUS_CERT_HASH | 1.3.6.1.4.1.311.21.2 | previous cert hash |
ID_MS_CRL_VIRTUAL_BASE | 1.3.6.1.4.1.311.21.3 | CRL virtual base |
ID_MS_CRL_NEXT_PUBLISH | 1.3.6.1.4.1.311.21.4 | CRL next publish time |
ID_MS_KP_CA_EXCHANGE | 1.3.6.1.4.1.311.21.5 | CA key exchange |
ID_MS_KP_KEY_RECOVERY_AGENT | 1.3.6.1.4.1.311.21.6 | key recovery agent |
ID_MS_ENTERPRISE_OID_ROOT | 1.3.6.1.4.1.311.21.8 | enterprise OID root |
ID_MS_APPLICATION_CERT_POLICIES | 1.3.6.1.4.1.311.21.10 | application cert policies |
ID_MS_REQUEST_CLIENT_INFO | 1.3.6.1.4.1.311.21.20 | enrollment client info |
ID_MS_ENCRYPTED_KEY_HASH | 1.3.6.1.4.1.311.21.21 | encrypted key hash |
ID_MS_CERTSRV_CROSSCA_VERSION | 1.3.6.1.4.1.311.21.22 | cross-CA version |
ID_MS_KP_CTL_USAGE_SIGNING | 1.3.6.1.4.1.311.10.3.1 | CTL usage signing |
ID_MS_KP_TIME_STAMP_SIGNING | 1.3.6.1.4.1.311.10.3.2 | time stamp signing |
ID_MS_KP_EFS_CRYPTO | 1.3.6.1.4.1.311.10.3.4 | EFS file encryption |
ID_MS_KP_EFS_RECOVERY | 1.3.6.1.4.1.311.10.3.4.1 | EFS recovery |
ID_MS_KP_KEY_RECOVERY | 1.3.6.1.4.1.311.10.3.11 | key recovery |
ID_MS_KP_DOCUMENT_SIGNING | 1.3.6.1.4.1.311.10.3.12 | document signing |
ID_MS_KP_LIFETIME_SIGNING | 1.3.6.1.4.1.311.10.3.13 | lifetime signing |
ID_MS_AUTO_ENROLL_CTL_USAGE | 1.3.6.1.4.1.311.20.1 | auto-enrollment CTL |
See also Well-known OIDs for the synta.oids Microsoft PKI constants
(ID_MS_SAN_UPN, ID_MS_CERTIFICATE_TEMPLATE_NAME, ID_MS_CERTIFICATE_TEMPLATE,
ID_MS_KP_SMARTCARD_LOGON, ID_MS_NTDS_REPLICATION).
Merkle Tree Certificates
synta.mtc implements the ASN.1 types from draft-ietf-plants-merkle-tree-certs.
All classes are immutable (frozen) and constructed via from_der static methods.
import synta.mtc as mtc
Name fields such as issuer_der and subject_der are raw DER-encoded Name SEQUENCES;
pass them to synta.parse_name_attrs() to decode the DN attributes.
ProofNode
A single node in a Merkle inclusion proof path.
class ProofNode:
@staticmethod
def from_der(data: bytes) -> ProofNode: ...
is_left: bool # True if this is the left sibling in the path
hash: bytes # raw hash bytes at this proof node
Subtree
A hash subtree covering leaf range [start, end).
class Subtree:
@staticmethod
def from_der(data: bytes) -> Subtree: ...
start: int # start leaf index (inclusive)
end: int # end leaf index (exclusive)
value: bytes # aggregated hash bytes for [start, end)
SubtreeProof
Left and right subtree lists for log compaction.
class SubtreeProof:
@staticmethod
def from_der(data: bytes) -> SubtreeProof: ...
left_subtrees: list[Subtree] | None
right_subtrees: list[Subtree] | None
InclusionProof
Merkle inclusion proof for a log entry.
class InclusionProof:
@staticmethod
def from_der(data: bytes) -> InclusionProof: ...
log_entry_index: int # leaf position of the certified entry
tree_size: int # total leaves at proof time
inclusion_path: list[ProofNode] # ordered sibling hashes
LogID
Identifies a transparency log by its hash algorithm and public key.
class LogID:
@staticmethod
def from_der(data: bytes) -> LogID: ...
hash_alg_oid: str # hash algorithm OID (dot-notation)
public_key_der: bytes # DER-encoded SubjectPublicKeyInfo
CosignerID
Identifies a cosigner (witness) in a countersigned checkpoint.
class CosignerID:
@staticmethod
def from_der(data: bytes) -> CosignerID: ...
hash_alg_oid: str
public_key_der: bytes
Checkpoint
A signed tree head from a transparency log.
class Checkpoint:
@staticmethod
def from_der(data: bytes) -> Checkpoint: ...
log_id: LogID
tree_size: int
timestamp: int # milliseconds since Unix epoch
root_hash: bytes
signature: bytes
SubtreeSignature
A cosigner signature over a checkpoint and subtree proof.
class SubtreeSignature:
@staticmethod
def from_der(data: bytes) -> SubtreeSignature: ...
cosigner_id: CosignerID
tree_size: int
timestamp: int
root_hash: bytes
signature: bytes
TbsCertificateLogEntry
The to-be-signed body of a Merkle Tree Certificate log entry.
class TbsCertificateLogEntry:
@staticmethod
def from_der(data: bytes) -> TbsCertificateLogEntry: ...
issuer_der: bytes # raw Name DER
validity_not_before: str # GeneralizedTime string
validity_not_after: str # GeneralizedTime string
subject_der: bytes # raw Name DER
subject_public_key_algorithm_oid: str # algorithm OID
subject_public_key_hash: bytes # hash of the SPKI
issuer_unique_id: bytes | None
subject_unique_id: bytes | None
extensions_der: bytes | None # raw extensions DER
MerkleTreeCertEntry
Top-level CHOICE wrapper for a Merkle Tree Certificate entry.
class MerkleTreeCertEntry:
@staticmethod
def from_der(data: bytes) -> MerkleTreeCertEntry: ...
variant: str # "StandaloneCertificate" or "LandmarkCertificate"
standalone: StandaloneCertificate | None
landmark: LandmarkCertificate | None
LandmarkID
Identifies a landmark certificate in the MTC ecosystem.
class LandmarkID:
@staticmethod
def from_der(data: bytes) -> LandmarkID: ...
hash_alg_oid: str
public_key_der: bytes
tree_size: int
StandaloneCertificate
class StandaloneCertificate:
@staticmethod
def from_der(data: bytes) -> StandaloneCertificate: ...
proof: InclusionProof
checkpoint: Checkpoint
subtree_proof: SubtreeProof | None
cosignatures: list[SubtreeSignature]
tbs_cert: TbsCertificateLogEntry
cert_der: bytes | None # optional original X.509 DER
extensions_der: bytes | None
LandmarkCertificate
class LandmarkCertificate:
@staticmethod
def from_der(data: bytes) -> LandmarkCertificate: ...
landmark_id: LandmarkID
proof: InclusionProof
checkpoint: Checkpoint
tbs_cert: TbsCertificateLogEntry
cert_der: bytes | None
extensions_der: bytes | None
Usage
import synta
import synta.mtc as mtc
# Parse a Merkle Tree Certificate entry
entry = mtc.MerkleTreeCertEntry.from_der(data)
if entry.variant == "StandaloneCertificate":
sc = entry.standalone
print(f"tree size: {sc.checkpoint.tree_size}")
print(f"leaf index: {sc.proof.log_entry_index}")
issuer = synta.parse_name_attrs(sc.tbs_cert.issuer_der)
subject = synta.parse_name_attrs(sc.tbs_cert.subject_der)
print(f"issuer: {issuer}")
print(f"subject: {subject}")
Well-known OIDs
synta.oids provides 70+ well-known OID constants as ObjectIdentifier instances
(frozen, hashable). Import with import synta.oids as oids.
Algorithm OIDs
| Constant | OID | Standard |
|---|---|---|
RSA_ENCRYPTION | 1.2.840.113549.1.1.1 | PKCS #1 |
MD5_WITH_RSA | 1.2.840.113549.1.1.4 | PKCS #1 |
SHA1_WITH_RSA | 1.2.840.113549.1.1.5 | PKCS #1 |
SHA256_WITH_RSA | 1.2.840.113549.1.1.11 | RFC 4055 |
SHA384_WITH_RSA | 1.2.840.113549.1.1.12 | RFC 4055 |
SHA512_WITH_RSA | 1.2.840.113549.1.1.13 | RFC 4055 |
EC_PUBLIC_KEY | 1.2.840.10045.2.1 | RFC 5480 |
ECDSA_WITH_SHA1 | 1.2.840.10045.4.1 | ANSI X9.62 |
ECDSA_WITH_SHA256 | 1.2.840.10045.4.3.2 | RFC 5758 |
ECDSA_WITH_SHA384 | 1.2.840.10045.4.3.3 | RFC 5758 |
ECDSA_WITH_SHA512 | 1.2.840.10045.4.3.4 | RFC 5758 |
ED25519 | 1.3.101.112 | RFC 8410 |
ED448 | 1.3.101.113 | RFC 8410 |
ML_DSA_44 | 2.16.840.1.101.3.4.3.17 | FIPS 204 |
ML_DSA_65 | 2.16.840.1.101.3.4.3.18 | FIPS 204 |
ML_DSA_87 | 2.16.840.1.101.3.4.3.19 | FIPS 204 |
ML_KEM_512 | 2.16.840.1.101.3.4.4.1 | FIPS 203 |
ML_KEM_768 | 2.16.840.1.101.3.4.4.2 | FIPS 203 |
ML_KEM_1024 | 2.16.840.1.101.3.4.4.3 | FIPS 203 |
EC_CURVE_P256 | 1.2.840.10045.3.1.7 | NIST P-256 |
EC_CURVE_P384 | 1.3.132.0.34 | NIST P-384 |
EC_CURVE_P521 | 1.3.132.0.35 | NIST P-521 |
EC_CURVE_SECP256K1 | 1.3.132.0.10 | Bitcoin curve |
Hash algorithm OIDs
| Constant | OID | Standard |
|---|---|---|
SHA224 | 2.16.840.1.101.3.4.2.4 | FIPS 180-4 |
SHA256 | 2.16.840.1.101.3.4.2.1 | FIPS 180-4 |
SHA384 | 2.16.840.1.101.3.4.2.2 | FIPS 180-4 |
SHA512 | 2.16.840.1.101.3.4.2.3 | FIPS 180-4 |
SHA512_224 | 2.16.840.1.101.3.4.2.5 | FIPS 180-4 |
SHA512_256 | 2.16.840.1.101.3.4.2.6 | FIPS 180-4 |
SHA3_224 | 2.16.840.1.101.3.4.2.7 | FIPS 202 |
SHA3_256 | 2.16.840.1.101.3.4.2.8 | FIPS 202 |
SHA3_384 | 2.16.840.1.101.3.4.2.9 | FIPS 202 |
SHA3_512 | 2.16.840.1.101.3.4.2.10 | FIPS 202 |
SHAKE128 | 2.16.840.1.101.3.4.2.11 | FIPS 202 |
SHAKE256 | 2.16.840.1.101.3.4.2.12 | FIPS 202 |
SLH-DSA OIDs (FIPS 205)
SLH_DSA_SHA2_128F, SLH_DSA_SHA2_128S, SLH_DSA_SHA2_192F, SLH_DSA_SHA2_192S,
SLH_DSA_SHA2_256F, SLH_DSA_SHA2_256S, SLH_DSA_SHAKE_128F, SLH_DSA_SHAKE_128S,
SLH_DSA_SHAKE_192F, SLH_DSA_SHAKE_192S, SLH_DSA_SHAKE_256F, SLH_DSA_SHAKE_256S.
Prefix OIDs
These are prefix arcs for use with oid.components() rather than exact match.
| Constant | OID prefix | Covers |
|---|---|---|
RSA | 1.2.840.113549.1.1 | All PKCS#1 signature algorithms |
ECDSA_SIG | 1.2.840.10045.4 | All ECDSA signature algorithms |
ECDSA_KEY | 1.2.840.10045.2 | EC public-key types |
DSA | 1.2.840.10040.4 | DSA and DSA-with-hash algorithms |
# Example: match any RSA algorithm
import synta.oids as oids
rsa_prefix = oids.RSA.components()
if cert.signature_algorithm_oid.components()[:len(rsa_prefix)] == rsa_prefix:
print("RSA family")
X.509v3 extension OIDs
| Constant | OID | RFC reference |
|---|---|---|
SUBJECT_ALT_NAME | 2.5.29.17 | RFC 5280 |
ISSUER_ALT_NAME | 2.5.29.18 | RFC 5280 |
BASIC_CONSTRAINTS | 2.5.29.19 | RFC 5280 |
KEY_USAGE | 2.5.29.15 | RFC 5280 |
EXTENDED_KEY_USAGE | 2.5.29.37 | RFC 5280 |
SUBJECT_KEY_IDENTIFIER | 2.5.29.14 | RFC 5280 |
AUTHORITY_KEY_IDENTIFIER | 2.5.29.35 | RFC 5280 |
CERTIFICATE_POLICIES | 2.5.29.32 | RFC 5280 |
CRL_DISTRIBUTION_POINTS | 2.5.29.31 | RFC 5280 |
AUTHORITY_INFO_ACCESS | 1.3.6.1.5.5.7.1.1 | RFC 5280 |
CT_PRECERT_SCTS | 1.3.6.1.4.1.11129.2.4.2 | RFC 6962 |
Extended Key Usage (EKU) OIDs
| Constant | OID | Use |
|---|---|---|
KP_SERVER_AUTH | 1.3.6.1.5.5.7.3.1 | TLS server authentication |
KP_CLIENT_AUTH | 1.3.6.1.5.5.7.3.2 | TLS client authentication |
KP_CODE_SIGNING | 1.3.6.1.5.5.7.3.3 | Code signing |
KP_EMAIL_PROTECTION | 1.3.6.1.5.5.7.3.4 | S/MIME |
KP_TIME_STAMPING | 1.3.6.1.5.5.7.3.8 | RFC 3161 TSA |
KP_OCSP_SIGNING | 1.3.6.1.5.5.7.3.9 | OCSP responder |
ANY_EXTENDED_KEY_USAGE | 2.5.29.37.0 | Match any EKU |
PKINIT OIDs (RFC 4556 / RFC 8636)
| Constant | OID | Description |
|---|---|---|
ID_PKINIT_SAN | 1.3.6.1.5.2.2 | KRB5PrincipalName OtherName type-id |
ID_PKINIT_KPCLIENT_AUTH | 1.3.6.1.5.2.3.4 | PKINIT client auth EKU |
ID_PKINIT_KPKDC | 1.3.6.1.5.2.3.5 | PKINIT KDC EKU |
ID_PKINIT_AUTH_DATA | 1.3.6.1.5.2.3.1 | PA-PK-AS-REQ content type |
ID_PKINIT_DHKEY_DATA | 1.3.6.1.5.2.3.2 | DH key data content type |
ID_PKINIT_RKEY_DATA | 1.3.6.1.5.2.3.3 | Reply key pack content type |
ID_PKINIT_KDF | 1.3.6.1.5.2.3.6 | KDF algorithm arc (RFC 8636) |
ID_PKINIT_KDF_AH_SHA1 | 1.3.6.1.5.2.3.6.1 | PKINIT KDF with SHA-1 |
ID_PKINIT_KDF_AH_SHA256 | 1.3.6.1.5.2.3.6.2 | PKINIT KDF with SHA-256 |
ID_PKINIT_KDF_AH_SHA384 | 1.3.6.1.5.2.3.6.4 | PKINIT KDF with SHA-384 |
ID_PKINIT_KDF_AH_SHA512 | 1.3.6.1.5.2.3.6.3 | PKINIT KDF with SHA-512 |
Microsoft PKI OIDs
| Constant | OID | Windows name |
|---|---|---|
ID_MS_SAN_UPN | 1.3.6.1.4.1.311.20.2.3 | szOID_NT_PRINCIPAL_NAME — UPN in OtherName |
ID_MS_CERTIFICATE_TEMPLATE_NAME | 1.3.6.1.4.1.311.20.2 | szOID_CERTIFICATE_TEMPLATE_NAME (v1) |
ID_MS_CERTIFICATE_TEMPLATE | 1.3.6.1.4.1.311.21.7 | szOID_CERTIFICATE_TEMPLATE (v2) |
ID_MS_KP_SMARTCARD_LOGON | 1.3.6.1.4.1.311.20.2.2 | szOID_MS_KP_SMARTCARD_LOGON EKU |
ID_MS_NTDS_REPLICATION | 1.3.6.1.4.1.311.25.1 | szOID_NTDS_REPLICATION EKU |
CMS content-type OIDs (RFC 5652)
| Constant | OID | Name |
|---|---|---|
CMS_DATA | 1.2.840.113549.1.7.1 | id-data |
CMS_SIGNED_DATA | 1.2.840.113549.1.7.2 | id-signedData |
CMS_ENVELOPED_DATA | 1.2.840.113549.1.7.3 | id-envelopedData |
CMS_DIGESTED_DATA | 1.2.840.113549.1.7.5 | id-digestedData |
CMS_ENCRYPTED_DATA | 1.2.840.113549.1.7.6 | id-encryptedData |
CMS_AUTH_DATA | 1.2.840.113549.1.9.16.1.2 | id-ct-authData |
CMS_ORI | 1.2.840.113549.1.9.16.13 | OtherRecipientInfo arc (RFC 9629) |
CMS_ORI_KEM | 1.2.840.113549.1.9.16.13.3 | KEMRecipientInfo (RFC 9629) |
PKCS#9 attribute OIDs
| Constant | OID | Description |
|---|---|---|
PKCS9_EMAIL_ADDRESS | 1.2.840.113549.1.9.1 | emailAddress |
PKCS9_CONTENT_TYPE | 1.2.840.113549.1.9.3 | id-contentType |
PKCS9_MESSAGE_DIGEST | 1.2.840.113549.1.9.4 | id-messageDigest |
PKCS9_SIGNING_TIME | 1.2.840.113549.1.9.5 | id-signingTime |
PKCS9_COUNTERSIGNATURE | 1.2.840.113549.1.9.6 | id-countersignature |
PKCS9_CHALLENGE_PASSWORD | 1.2.840.113549.1.9.7 | id-challengePassword |
PKCS9_EXTENSION_REQUEST | 1.2.840.113549.1.9.14 | id-extensionRequest |
PKCS9_FRIENDLY_NAME | 1.2.840.113549.1.9.20 | id-friendlyName |
PKCS9_LOCAL_KEY_ID | 1.2.840.113549.1.9.21 | id-localKeyId |
OID helper functions
def identify_signature_algorithm(oid: ObjectIdentifier | str) -> str: ...
# Returns a display name such as "sha256WithRSAEncryption", "ecdsa-with-SHA256",
# "Ed25519", "ML-DSA-65", etc. Returns "Other" for unknown OIDs.
def identify_public_key_algorithm(oid: ObjectIdentifier | str) -> str | None: ...
# Returns "RSA", "EC", "Ed25519", "ML-DSA-65", etc., or None for unknown OIDs.
def ec_curve_short_name(oid: ObjectIdentifier | str) -> str | None: ...
# Returns the ASN.1 short name, e.g. "prime256v1", "secp384r1".
def ec_curve_nist_name(oid: ObjectIdentifier | str) -> str | None: ...
# Returns the NIST name, e.g. "P-256", "P-384". None for curves with no NIST name.
def ec_curve_key_bits(oid: ObjectIdentifier | str) -> int | None: ...
# Returns the field size in bits, e.g. 256, 384, 521.
def extension_oid_name(oid: ObjectIdentifier | str) -> str: ...
# Returns a display name, e.g. "X509v3 Subject Alternative Name".
# Returns the dotted-decimal string for unknown OIDs.
Usage
import synta.oids as oids
# Equality comparison against a string
assert oids.EC_PUBLIC_KEY == "1.2.840.10045.2.1"
# Use as a dict key (hashable)
lookup = {oids.SHA256: "SHA-256", oids.SHA384: "SHA-384"}
name = lookup.get(cert.signature_algorithm_oid, "unknown")
# OID helper functions
print(oids.identify_signature_algorithm(cert.signature_algorithm_oid))
print(oids.identify_public_key_algorithm(cert.public_key_algorithm_oid))
See also DN Attribute OIDs and PKCS#9 OIDs.
DN Attribute OIDs
synta.oids.attr provides OID constants for Distinguished Name (DN) attribute types.
import synta.oids.attr
Constants
All constants are ObjectIdentifier instances.
| Constant | OID | RFC 4514 label |
|---|---|---|
COMMON_NAME | 2.5.4.3 | CN |
ORGANIZATION | 2.5.4.10 | O |
ORG_UNIT | 2.5.4.11 | OU |
COUNTRY | 2.5.4.6 | C |
STATE | 2.5.4.8 | ST |
LOCALITY | 2.5.4.7 | L |
STREET | 2.5.4.9 | street |
SERIAL_NUMBER | 2.5.4.5 | serialNumber |
EMAIL_ADDRESS | 1.2.840.113549.1.9.1 | emailAddress |
GIVEN_NAME | 2.5.4.42 | givenName |
SURNAME | 2.5.4.4 | SN |
TITLE | 2.5.4.12 | title |
INITIALS | 2.5.4.43 | initials |
ORG_IDENTIFIER | 2.5.4.97 | organizationIdentifier |
USER_ID | 0.9.2342.19200300.100.1.1 | uid |
DOMAIN_COMPONENT | 0.9.2342.19200300.100.1.25 | dc |
Usage
import synta
import synta.oids.attr as attr
# Parse a certificate subject and look up specific attributes
cert = synta.Certificate.from_der(open("cert.der", "rb").read())
name_attrs = synta.parse_name_attrs(cert.subject_raw_der)
# name_attrs is a list of (dotted_oid_str, value_str) tuples
for oid_str, value in name_attrs:
if oid_str == str(attr.COMMON_NAME):
print(f"CN={value}")
elif oid_str == str(attr.ORGANIZATION):
print(f"O={value}")
elif oid_str == str(attr.COUNTRY):
print(f"C={value}")
# Use as hashable key
lookup = {attr.COMMON_NAME: "common name", attr.ORGANIZATION: "org"}
See also Well-known OIDs for the complete OID constant catalog.
PKCS#9 OID Constants
PKCS#9 attribute OIDs are available from two locations:
synta.pkcs9— a dedicated module with the full PKCS#9 arc (13 constants)synta.oids— the top-level OID catalog (9 of the most common constants)
From synta.oids
import synta.oids as oids
oids.PKCS9_EMAIL_ADDRESS # 1.2.840.113549.1.9.1 — emailAddress
oids.PKCS9_CONTENT_TYPE # 1.2.840.113549.1.9.3 — id-contentType
oids.PKCS9_MESSAGE_DIGEST # 1.2.840.113549.1.9.4 — id-messageDigest
oids.PKCS9_SIGNING_TIME # 1.2.840.113549.1.9.5 — id-signingTime
oids.PKCS9_COUNTERSIGNATURE # 1.2.840.113549.1.9.6 — id-countersignature
oids.PKCS9_CHALLENGE_PASSWORD # 1.2.840.113549.1.9.7 — id-challengePassword
oids.PKCS9_EXTENSION_REQUEST # 1.2.840.113549.1.9.14 — id-extensionRequest
oids.PKCS9_FRIENDLY_NAME # 1.2.840.113549.1.9.20 — id-friendlyName
oids.PKCS9_LOCAL_KEY_ID # 1.2.840.113549.1.9.21 — id-localKeyId
From synta.pkcs9
synta.pkcs9 adds four more constants not in synta.oids:
import synta.pkcs9 as pkcs9
pkcs9.ID_PKCS_9 # 1.2.840.113549.1.9 — id-pkcs-9 arc
pkcs9.ID_EMAIL_ADDRESS # 1.2.840.113549.1.9.1 — emailAddress
pkcs9.ID_UNSTRUCTURED_NAME # 1.2.840.113549.1.9.2 — unstructuredName
pkcs9.ID_CONTENT_TYPE # 1.2.840.113549.1.9.3 — id-contentType
pkcs9.ID_MESSAGE_DIGEST # 1.2.840.113549.1.9.4 — id-messageDigest
pkcs9.ID_SIGNING_TIME # 1.2.840.113549.1.9.5 — id-signingTime
pkcs9.ID_COUNTERSIGNATURE # 1.2.840.113549.1.9.6 — id-countersignature
pkcs9.ID_CHALLENGE_PASSWORD # 1.2.840.113549.1.9.7 — id-challengePassword
pkcs9.ID_UNSTRUCTURED_ADDRESS # 1.2.840.113549.1.9.8 — unstructuredAddress
pkcs9.ID_EXTENSION_REQUEST # 1.2.840.113549.1.9.14 — id-extensionRequest
pkcs9.ID_SMIME # 1.2.840.113549.1.9.16 — id-smime arc
pkcs9.ID_FRIENDLY_NAME # 1.2.840.113549.1.9.20 — id-friendlyName
pkcs9.ID_LOCAL_KEY_ID # 1.2.840.113549.1.9.21 — id-localKeyId
Usage context
| OID | Typical context |
|---|---|
ID_CONTENT_TYPE | CMS SignedData signed attribute (RFC 5652 §11) |
ID_MESSAGE_DIGEST | CMS SignedData signed attribute (RFC 5652 §11) |
ID_SIGNING_TIME | CMS SignedData signed attribute (RFC 5652 §11) |
ID_COUNTERSIGNATURE | CMS SignedData unsigned attribute |
ID_EXTENSION_REQUEST | PKCS#10 CSR attribute (RFC 2986 §5.4.2) |
ID_CHALLENGE_PASSWORD | PKCS#10 CSR attribute (RFC 2986 §5.4.1) |
ID_FRIENDLY_NAME | PKCS#12 bag attribute (RFC 7292 §4.2) |
ID_LOCAL_KEY_ID | PKCS#12 bag attribute (RFC 7292 §4.2) |
See also PKCS#9 Module for the protocol-level documentation and Well-known OIDs for the full OID catalog.
Performance
Measured with python/bench_certificate.py. Run after maturin develop --release or
maturin build --release --interpreter python3 and installing the wheel.
The benchmark uses Criterion’s Linear sampling mode (100 samples, linearly
increasing iteration counts; see python/criterion_compat.py), so timings are
directly comparable to the Rust Criterion benchmarks in synta-bench.
Note: Always verify the installed
.sois a release build aftermaturin develop --release— the file size should be ≈1.4 MB (release) not ≈20 MB (debug). If in doubt, copytarget/release/lib_synta.somanually topython/synta/_synta.abi3.so.
Parse-only (call only, no field access)
Two Python parse-only modes are benchmarked, corresponding to the two Rust variants:
| Certificate set | synta lazy (from_der) | synta_full eager (full_from_der) | cryptography.x509 | Rust rust_shallow | Rust rust_typed |
|---|---|---|---|---|---|
| Traditional (~900 B, 5 certs) | 0.16 µs | 0.72–0.76 µs | 1.62–1.68 µs | 0.019–0.020 µs | 0.50–0.51 µs |
| ML-DSA-44 (3,992 B) | 0.17 µs | 0.74 µs | 1.55 µs | 0.020 µs | 0.54 µs |
| ML-DSA-65 (5,521 B) | 0.17 µs | 0.73 µs | 1.55 µs | 0.019 µs | 0.49 µs |
| ML-DSA-87 (7,479 B) | 0.17 µs | 0.73 µs | 1.54 µs | 0.020 µs | 0.49 µs |
Comparison note: synta (from_der) and Rust rust_shallow perform the same
4-operation envelope scan. The ~0.14 µs gap between them (0.16 µs vs 0.019 µs) is
pure PyO3 overhead: GIL acquisition, Py<PyBytes> setup, and constructing a
PyCertificate struct with 17 OnceLock fields. synta_full (full_from_der) and
Rust rust_typed both do a complete RFC 5280 decode; the ~0.23 µs gap is the same
PyO3 overhead amortised over the full parse.
For parse-only workloads the honest comparison with cryptography.x509 (also lazy) is
synta vs cryptography.x509 — ~10× faster for traditional certs, ~9× faster
for ML-DSA certs. For full-decode workloads the apples-to-apples comparison is
synta_full vs cryptography.x509 — ~2.3× faster.
from_der holds a Py<PyBytes> reference to the caller’s bytes object — no copy of
the DER data. Parse time is flat across ML-DSA cert sizes: the large signature BIT
STRING is not decoded at all during the shallow scan.
Parse + access all fields (cold cache — fresh cert each iteration)
| Certificate set | synta (PyO3, 19 fields) | cryptography.x509 (9 fields) |
|---|---|---|
| Traditional (~900 B, 5 certs) | 3.30–3.42 µs | 15.90–16.29 µs |
| ML-DSA-44 (3,992 B) | 3.71 µs | 13.41 µs |
| ML-DSA-65 (5,521 B) | 3.63 µs | 13.14 µs |
| ML-DSA-87 (7,479 B) | 3.71 µs | 13.38 µs |
synta is ~4.8× faster than cryptography.x509 for traditional certs (parse+fields,
19 vs 9 fields). ML-DSA certs measure ~3.6× faster — the LAMPS WG test certs are
simpler (fewer extensions) than the NIST PKITS certs, so cryptography.x509 processes
fewer extension fields, reducing its parse+fields time. Requires cryptography ≥ 44.0
(FIPS 204) for ML-DSA public_key() decoding; older versions raise UnsupportedAlgorithm.
Field access only (warm cache — same cert, repeated access)
| Certificate set | synta (PyO3, 19 fields) | cryptography.x509 stock | cryptography.x509 perf-opt |
|---|---|---|---|
| Traditional (~900 B, 5 certs) | 0.41–0.43 µs | ~14.8 µs | 0.91–0.95 µs |
| ML-DSA-44 (3,992 B) | 0.41 µs | ~11.2 µs | 2.21–2.28 µs |
| ML-DSA-65 (5,521 B) | 0.41 µs | ~11.2 µs | 2.21–2.28 µs |
| ML-DSA-87 (7,479 B) | 0.42 µs | ~11.1 µs | 2.21–2.28 µs |
synta warm-cache access (~0.42 µs) is 19 clone_ref calls — essentially free per field.
Stock cryptography.x509 memoises only extensions (via PyOnceLock). The other
8 fields (subject, issuer, serial_number, not_valid_before_utc,
not_valid_after_utc, signature, signature_hash_algorithm, public_key) are
re-derived from the zero-copy in-memory ASN.1 structure on every Python access, each
allocating a new Python object. Warm and cold times are therefore close (~11–15 µs).
cryptography PR #14441 adds
OnceLock caching for issuer, subject, public_key, and signature_algorithm on
Certificate, and caching on CertificationRequest, CertificateList, and
OCSPResponse. This brings traditional warm access from ~14.8 µs down to
0.91–0.95 µs — a ~16× improvement. synta remains ~2.2× faster for traditional
warm access and ~5× faster for ML-DSA (larger key objects have higher PyBytes copy
cost even on the cached path). Parse-only and cold parse+fields times are unaffected by
the caching changes.
PKCS#7 and PKCS#12 extraction
Measured with python/bench_pkcs.py. Benchmark IDs match the Rust Criterion IDs in
synta-bench/benches/pkcs_formats.rs exactly (pkcs7/{synta,cryptography}/value,
pkcs12/{synta,cryptography}/value).
| Benchmark ID | Input | synta | cryptography | Speedup |
|---|---|---|---|---|
pkcs7/…/amazon_roots | 1,848 B DER/BER, 2 certs | 0.82 µs (Rust) / 1.27 µs (Py) | 41.7 µs | ~33× |
pkcs7/…/pem_isrg | 1,992 B PEM, 1 cert | 3.58 µs (Rust) / 4.14 µs (Py) | 32.1 µs | ~8× |
pkcs12/…/unencrypted_3certs | 3,539 B, 3 certs | 1.10 µs (Rust) / 1.80 µs (Py) | 134 µs | ~75× |
pkcs12/…/unencrypted_1cert_with_key | 756 B, 1 cert + key | 639 ns (Rust) / 0.96 µs (Py) | — | — |
The unencrypted_1cert_with_key vector (cert-none-key-none.p12) uses a non-standard
format that cryptography cannot parse; it is benchmarked as synta-only to exercise
the key-bag-skipping code path.
The ~0.5–0.7 µs gap between Rust and Python times is the usual PyO3 overhead: GIL
acquisition, PyBytes allocation for each returned certificate, and PyList construction.
The pem_isrg Python time (4.14 µs) is higher than pure PKCS#7 parse (1.27 µs) because
PEM decoding and base64 allocation are included in the timed loop.
cryptography comparison note: The amazon_roots DER benchmark triggers a
UserWarning from cryptography — the file uses BER indefinite-length encoding
(0x30 0x80…), which cryptography handles via an internal fallback with a deprecation
warning. synta accepts BER transparently with no warning.
Architecture notes
Parse-only: synta.Certificate.from_der() holds a Py<PyBytes> strong
reference to the caller’s bytes object (no copy). It performs only a 4-operation shallow
envelope scan (outer SEQUENCE tag+length, TBSCertificate SEQUENCE tag+length) — equivalent
to synta_certificate::validate_envelope() in Rust — and records the TBS byte range.
The full recursive Certificate::decode() is deferred to the first getter call.
synta.Certificate.full_from_der() performs the same shallow scan and then immediately
triggers the full decode, so all 19 field caches are warm before any Python code accesses them.
Parse+fields: All 19 getters cache their Python object in an
OnceLock<Py<T>>. String-returning getters (issuer, subject, signature_algorithm,
signature_algorithm_oid, public_key_algorithm, public_key_algorithm_oid, not_before,
not_after) store Py<PyString>. Bytes-returning getters (signature_value, public_key,
tbs_bytes, issuer_raw_der, subject_raw_der) store Py<PyBytes>. Optional getters
(signature_algorithm_params, public_key_algorithm_params, extensions_der) store
Option<Py<PyBytes>>. to_der skips the lock entirely — direct clone_ref of the
stored Py<PyBytes> that was passed to from_der.
#[pyclass(frozen)]: PyCertificate is declared frozen, which removes the
8-byte PyO3 borrow-tracking field (borrow_flag: Cell<isize>) from every instance
and eliminates per-getter Cell::get/set calls. This gives a ~26% speedup on the
warm-path field-access benchmark.
ML-DSA parse+fields: The first-call PyBytes copies of signature_value
(2,420–4,627 bytes) and public_key (1,312–1,952 bytes) add ~0.3 µs over traditional
certs (3.63–3.71 µs vs 3.30–3.42 µs). Subsequent accesses use clone_ref at
~0.41 µs total regardless of cert size.
For the full binding-layer comparison (Rust rust_shallow / rust_typed / rust_element /
c_ffi / Python synta / synta_full / cryptography_x509, parse-only and parse+fields)
see docs/performance.md.
See also Development for how to run benchmarks.
Development
Running Tests
# Build in development mode
python -m venv venv
source venv/bin/activate
maturin develop
# Run Python tests
python -m pytest tests/python/
# Or via the CI helper script
./contrib/ci/local-ci.sh python-test
Running Benchmarks
# Build release extension first (verify .so is ~1.4 MB, not ~20 MB debug)
maturin develop --release
# Certificate parsing and field-access benchmark (synta vs cryptography)
python python/bench_certificate.py
# Include per-field getter breakdown
python python/bench_certificate.py --per-field
# PKCS#7 / PKCS#12 certificate extraction benchmark (synta vs cryptography)
python python/bench_pkcs.py
# Save Criterion-compatible JSON alongside Rust benchmarks
# (default output: target/criterion/)
python python/bench_certificate.py --save-criterion
python python/bench_pkcs.py --save-criterion
# Custom output directory
python python/bench_certificate.py --save-criterion path/to/criterion
# Compare with a previous run using criterion-compare
# (first run writes new/, second run rotates new/→base/ then writes new/)
python python/bench_certificate.py --save-criterion
criterion-compare target/criterion
The benchmark certificate files are cloned by the Rust Criterion benchmarks on
their first run. If tests/vectors/ is missing, run:
cargo bench -p synta-bench --bench bindings --features bench-bindings --no-run
The parse-only group prints three Python entries per certificate in the
binding_comparison and binding_post_quantum groups:
binding_comparison/synta/cert_00_… # from_der: shallow scan only (~0.15 µs)
binding_comparison/synta_full/cert_00_… # full_from_der: complete RFC 5280 decode
binding_comparison/cryptography_x509/… # envelope validation
The Rust benchmark (cargo bench --bench bindings) adds matching entries:
binding_comparison/rust_shallow/… # validate_envelope: same 4-op scan as from_der
binding_comparison/rust_typed/… # full Certificate::decode — apples-to-apples with synta_full
criterion_compat.py
python/criterion_compat.py is a standalone, import-ready module that provides
the Criterion-compatible sampling and JSON-writing primitives. Import it
directly from other benchmark or test scripts:
from criterion_compat import measure, save_criterion_files
from pathlib import Path
avg_us, iters, times_ns = measure(fn, warmup_s=3.0, measure_s=5.0)
print(f"avg: {avg_us:.2f} µs ({sum(int(n) for n in iters)} iterations)")
save_criterion_files(
Path("target/criterion"),
group_id="my_group",
function_id="my_fn",
value_str="param_name",
iters=iters,
times_ns=times_ns,
)
See also Performance for benchmark result tables and Project Structure for the source tree layout.
Project Structure
synta-python/ # Python extension crate (cdylib → _synta)
├── Cargo.toml # Declares crate-type = ["cdylib"]
└── src/
├── lib.rs # #[pymodule] entry point, Encoding enum, pem_to_der/der_to_pem
├── types.rs # ASN.1 primitive type wrappers (Integer, OID, BitString, …)
├── decoder.rs # PyDecoder wrapper
├── encoder.rs # PyEncoder wrapper
├── error.rs # SyntaErr newtype + exception mapping
├── certificate/
│ ├── mod.rs # re-exports; PKI types live in synta-certificate
│ ├── cert.rs # PyCertificate
│ ├── cert_builder.rs # CertificateBuilder
│ ├── csr_builder.rs # CsrBuilder
│ ├── name_builder.rs # NameBuilder
│ ├── pkix.rs # CertificateList, OCSPResponse
│ └── cms/ # synta.cms submodule (RFC 5652 / RFC 9629)
│ ├── mod.rs # register_cms_submodule, encode_element_opt helper
│ ├── container.rs # ContentInfo, IssuerAndSerialNumber
│ ├── signed.rs # SignedData, SignerInfo
│ ├── enveloped.rs # EnvelopedData, EncryptedData
│ ├── digest.rs # DigestedData, AuthenticatedData
│ ├── kem.rs # KEMRecipientInfo, CMSORIforKEMOtherInfo
│ └── builder.rs # EnvelopedDataBuilder
├── ext_builders.rs # synta.ext submodule: basic_constraints, key_usage, SKI/AKI, SAN/AIA/EKU builders
├── crypto.rs # synta symmetric-crypto primitives (HMAC, PBKDF2, AES, Fernet, OTP)
├── crypto_keys.rs # PublicKey, PrivateKey (RSA, EC, EdDSA)
└── krb5.rs # synta.krb5 submodule: Krb5PrincipalName, PKINIT classes
synta-certificate/src/python.rs # ObjectIdentifier, Certificate, CertificationRequest,
# CertificateList, OCSPResponse, PKCS loaders,
# synta.oids / synta.oids.attr submodules
synta-krb5/src/python.rs # EncryptionKey, Checksum, KDFAlgorithmId,
# IssuerAndSerialNumber, ExternalPrincipalIdentifier,
# PKAuthenticator, AuthPack, PaPkAsReq,
# DHRepInfo, KDCDHKeyInfo, ReplyKeyPack, PaPkAsRep
python/ # Python package source (installed by maturin)
├── bench_certificate.py # Certificate parsing benchmark (synta vs cryptography)
├── bench_x509.py # Port of cryptography's test_x509.py benchmarks
├── bench_pkcs.py # PKCS#7 / PKCS#12 extraction benchmark (synta vs cryptography)
├── criterion_compat.py # Criterion-compatible sampling + JSON output (importable)
└── synta/
├── __init__.py # Package exports (Certificate, PKCS loaders, pem_to_der, …)
└── py.typed # PEP 561 marker
pyproject.toml # Maturin configuration (manifest-path → synta-python)
Key source file roles
| Source file | Contents |
|---|---|
src/lib.rs | Module registration, Encoding enum, pem_to_der, der_to_pem, top-level exports |
src/decoder.rs | Decoder class wrapping synta::Decoder; includes decode_any_str() |
src/encoder.rs | Encoder class wrapping synta::Encoder |
src/types.rs | Python wrappers for all ASN.1 primitive types |
src/error.rs | SyntaError Python exception |
src/certificate.rs | PKI types: ObjectIdentifier, Certificate, CertificationRequest, CertificateList, OCSPResponse, PKCS loaders, synta.oids / synta.oids.attr submodules |
src/pkinit.rs | PKINIT classes: EncryptionKey, Checksum, KDFAlgorithmId, PKAuthenticator, AuthPack, PaPkAsReq/Rep, etc. |
src/krb5.rs | synta.krb5 submodule registration: Krb5PrincipalName, PKINIT classes, NT_* constants |
src/x509_verification.rs | synta.x509 submodule: TrustStore, VerificationPolicy, X509VerificationError, verify functions |
src/certificate/pkixalgs.rs | synta.pkixalgs submodule: DssParms, DssSigValue, EcdsaSigValue, ECParameters, OID constants |
src/certificate/ac.rs | synta.ac submodule: AttributeCertificate, RFC 5755 OID constants |
src/certificate/crmf.rs | synta.crmf submodule: CertReqMessages, CertReqMsg, registration-control OID constants |
src/certificate/cmp.rs | synta.cmp submodule: CMPMessage, MAC algorithm and key-purpose OID constants |
All PyO3 bindings live directly in synta-python. The synta-certificate and synta-krb5
library crates have no PyO3 dependency; their types are wrapped in src/certificate.rs
and src/pkinit.rs respectively.
See also Cargo Features and Development.
Cargo Features
The synta-python crate exposes the following Cargo features:
| Feature | Default | Description |
|---|---|---|
extension-module | yes (maturin) | Required by PyO3 for native extension module builds; set automatically by maturin |
abi3-py38 | yes (maturin) | Targets the stable Python 3.8 ABI; compatible with CPython 3.8–3.14+ |
openssl | yes | OpenSSL-backed crypto: PKCS#12 decryption (load_pkcs12_certificates with password, create_pkcs12 with password), CMS EnvelopedData.create / EnvelopedDataBuilder.build, EncryptedData.create / decrypt, PrivateKey.to_pkcs8_encrypted / from_pkcs8_encrypted, synta.x509 chain verification |
deprecated-pkcs12-algorithms | no | Enables legacy PKCS#12 decryption algorithms (3DES, RC2) — requires openssl |
Crate dependencies
| Crate | Role |
|---|---|
synta | Core ASN.1 encoder/decoder |
synta-certificate | X.509 PKI types + OID constants + OpenSSL signature verifier |
synta-krb5 | Kerberos V5 / PKINIT types |
synta-x509-verification | RFC 5280 / CABF certificate chain validation (synta.x509) |
pyo3 0.26 | Python/Rust interop (abi3-py38 feature) |
openssl 0.10 | OpenSSL bindings for signature verification (synta.x509, synta.cms) |
Building with optional features
# Default build (includes openssl feature)
maturin develop
# Build without openssl (disables PKCS#12 password support, x509 verification, CMS crypto)
maturin develop --no-default-features --features extension-module,abi3-py38
# Build with legacy PKCS#12 algorithm support
maturin develop --features deprecated-pkcs12-algorithms
See Installation for standard build instructions and Development for test and benchmark workflows.
Example Programs
Twenty-eight runnable programs in examples/ exercise every binding documented
in this guide. Each program is self-contained and runs with:
uv run python3 examples/example_<name>.py
from the repository root (after maturin develop).
1. example_pem_helpers.py — PEM/DER conversion
Bindings: pem_to_der, der_to_pem, Certificate.from_pem, Certificate.to_pem,
CertificationRequest.from_pem, CertificationRequest.to_pem,
CertificateList.from_pem, CertificateList.to_pem.
- Round-trip a single PEM certificate block through
pem_to_der/der_to_pem(pem_to_deralways returnslist[bytes]). - Show
pem_to_derwith a multi-block PEM chain. - Use
Certificate.from_pemon a single block and on a two-certificate chain. - Use
Certificate.to_pemon a single cert and a list of certs.
2. example_certificate_fields.py — All Certificate properties
Bindings: Certificate.from_der, Certificate.full_from_der, all Certificate
properties (serial_number, version, issuer, issuer_raw_der, subject,
subject_raw_der, not_before, not_after, signature_algorithm,
signature_algorithm_oid, signature_algorithm_params, signature_value,
public_key_algorithm, public_key_algorithm_oid, public_key_algorithm_params,
public_key, tbs_bytes), and Certificate.to_der() method.
- Parse the same certificate with
from_der(lazy) andfull_from_der(eager). - Print every property with its Python type.
- Verify
to_der()round-trip (re-parse the returned bytes). - Show
signature_algorithm_paramsisNonefor Ed25519 and non-Nonefor RSA.
3. example_certificate_extensions.py — Extension access and SAN parsing
Bindings: Certificate.subject_alt_names, Certificate.extensions_der,
Certificate.get_extension_value_der, synta.general_name tag constants,
synta.parse_general_names, synta.parse_name_attrs,
synta.oids.SUBJECT_ALT_NAME, synta.oids.BASIC_CONSTRAINTS,
synta.oids.SUBJECT_KEY_IDENTIFIER, Decoder.decode_sequence, Decoder.peek_tag,
Decoder.decode_implicit_tag, Decoder.decode_explicit_tag,
Decoder.remaining_bytes, Decoder.decode_raw_tlv, Decoder.is_empty.
- Call
cert.subject_alt_names()for high-level SAN access; dispatch onsynta.general_nameconstants (gn.DNS_NAME,gn.IP_ADDRESS, etc.); useipaddress.ip_address(content)for IP rendering. - Verify that a certificate without a SAN extension returns an empty list.
- Access
extensions_derand iterate the raw extension TLVs. - Look up individual extensions by
get_extension_value_der. - Parse SAN entries manually with
peek_tag/decode_implicit_tagas a lower-level alternative tosubject_alt_names(). - Demonstrate
peek_tagfor CHOICE dispatch;remaining_bytesfor primitive implicit values. - Look up BasicConstraints and decode the cA BOOLEAN.
4. example_certificate_pyca.py — PyCA interoperability
Bindings: Certificate.to_pyca, Certificate.from_pyca.
- Parse a DER certificate with synta, then call
.to_pyca()for cryptographic operations. - Load a PyCA certificate, convert to synta with
from_pyca(), and compare fields. - Show
ImportErrormessage whencryptographyis absent (caught and printed).
5. example_csr.py — PKCS#10 CSR parsing
Bindings: CertificationRequest.from_der, CertificationRequest.from_pem,
CertificationRequest.to_pem, and all CertificationRequest properties.
- Parse a CSR from DER and print every field.
- Round-trip through PEM with
from_pem/to_pem. - Verify
subject_raw_derby re-decoding it as a raw Name SEQUENCE.
6. example_crl.py — CRL parsing
Bindings: CertificateList.from_der, CertificateList.from_pem,
CertificateList.to_pem, and all CertificateList properties.
- Parse a CRL and print issuer, dates, algorithm, revoked count.
- Show
next_updateisNonefor CRLs that omit the field. - Round-trip through
to_pem/from_pem.
7. example_ocsp.py — OCSP response parsing
Bindings: OCSPResponse.from_der, OCSPResponse.from_pem, OCSPResponse.to_pem,
and all OCSPResponse properties.
- Parse a successful OCSP response; print
status,response_type_oid, length ofresponse_bytes. - Parse a non-successful response (e.g.
tryLater); confirmresponse_bytesisNone. - Round-trip through
to_pem/from_pem.
8. example_pkcs7.py — PKCS#7 certificate bundles
Bindings: load_der_pkcs7_certificates, load_pem_pkcs7_certificates.
- Load a
.p7bfile and print the subject of each extracted certificate. - Demonstrate PEM-encoded PKCS#7 with
load_pem_pkcs7_certificates. - Show
ValueErrorfor non-PKCS#7 input.
9. example_pkcs12.py — PKCS#12 archive parsing and creation
Bindings: load_pkcs12_certificates, load_pkcs12_keys, load_pkcs12,
create_pkcs12.
- Parse a password-less PKCS#12 file; print subjects of extracted certificates.
- Extract private keys using
load_pkcs12_keys; show raw PKCS#8 DER length. - Extract both with
load_pkcs12; show the(certs, keys)tuple. - Parse an AES-256-CBC encrypted PKCS#12 with a password.
- Show
ValueErrorfor wrong password (encrypted case, withopensslfeature). - Build a PFX from extracted certificates using
create_pkcs12without a password. - Build a password-protected PFX with
create_pkcs12(requiresopensslfeature). - Build a cert+key PFX bundle.
- Roundtrip: build an archive then parse it back and verify the extracted certificates.
10. example_pki_blocks.py — Format-agnostic PKI reader
Bindings: read_pki_blocks.
- Pass PEM, DER, PKCS#7, and PKCS#12 bytes to
read_pki_blocksand print the(label, len(der))tuples returned. - Show PKCS#12 password handling.
11. example_objectidentifier.py — ObjectIdentifier constructors and operations
Bindings: ObjectIdentifier(str), ObjectIdentifier.from_components,
ObjectIdentifier.from_der_value, ObjectIdentifier.components,
ObjectIdentifier.__eq__, ObjectIdentifier.__hash__,
Decoder.decode_oid, Encoder.encode_oid, Encoder.encode_oid_object.
- Construct OIDs via all three constructors; verify they compare equal.
- Show
__eq__working against a dotted string. - Use OIDs as dict keys (demonstrate hashability).
- Round-trip through
encode_oid/decode_oid. - Show
from_der_valuewith the raw content bytes from an implicit-tag context.
12. example_oids_catalog.py — synta.oids constant groups
Bindings: every constant in synta.oids and synta.oids.attr,
plus the helper functions identify_signature_algorithm,
identify_public_key_algorithm.
- Print algorithm OIDs (RSA, EC, EdDSA, ML-DSA, ML-KEM).
- Print hash OIDs (SHA-2, SHA-3).
- Print SLH-DSA OIDs.
- Print prefix OIDs and demonstrate
components()-based prefix matching. - Print X.509v3 extension OIDs.
- Print EKU OIDs.
- Print PKINIT OIDs.
- Print MS PKI OIDs.
- Print all nine PKCS#9 attribute OIDs (
PKCS9_EMAIL_ADDRESS,PKCS9_CONTENT_TYPE,PKCS9_MESSAGE_DIGEST,PKCS9_SIGNING_TIME,PKCS9_COUNTERSIGNATURE,PKCS9_CHALLENGE_PASSWORD,PKCS9_EXTENSION_REQUEST,PKCS9_FRIENDLY_NAME,PKCS9_LOCAL_KEY_ID). - Print every
synta.oids.attrDN attribute OID with its RFC 4514 label.
13. example_time_types.py — UtcTime and GeneralizedTime
Bindings: UtcTime(…), UtcTime properties (year, month, day, hour,
minute, second), GeneralizedTime(…), GeneralizedTime properties,
Encoder.encode_utc_time, Encoder.encode_utc_time_object,
Encoder.encode_generalized_time, Encoder.encode_generalized_time_object,
Decoder.decode_utc_time, Decoder.decode_generalized_time.
- Construct both time types and inspect every property.
- Round-trip each through encode / decode; verify string representations.
- Show millisecond sub-second precision on
GeneralizedTime. - Show
ValueErrorfor out-of-range UTCTime year (< 1950 or > 2049). - Demonstrate
encode_utc_time_objectandencode_generalized_time_objectvariants.
14. example_integer_advanced.py — Integer edge cases and bigint
Bindings: Integer(int), Integer.from_bytes, Integer.from_u64,
Integer.to_int, Integer.to_i128, Integer.to_bytes,
Encoder.encode_integer (bigint path), Encoder.encode_integer_object.
- Construct integers via all three constructors.
- Show
to_int()succeeds for small values; raisesOverflowErrorfor 20-byte serials. - Use
to_i128()for up to 16-byte values (i128 max = 2¹²⁷−1); showOverflowErrorbeyond that. - Use
to_bytes()for arbitrary-precision big-endian representation. - Encode a 20-byte certificate serial number via
encode_integer(large_python_int). - Verify
encode_integer_objectround-trip.
15. example_string_types_advanced.py — Alternative string constructors
Bindings: TeletexString.from_latin1, TeletexString.from_str,
GeneralString.from_ascii, GeneralString.from_str,
UniversalString.from_bytes, BmpString.from_bytes,
plus to_bytes() / as_str() on each.
- Construct
TeletexStringfrom bytes and Latin-1 strings;GeneralStringfrom bytes and ASCII strings. - Construct
UniversalStringandBmpStringfrom raw UCS-4/UCS-2 byte buffers. - Show
ValueErrorwhen non-BMP code points are passed toBmpString. - Round-trip each through the encoder / decoder.
16. example_decoder_advanced.py — Advanced Decoder operations
Bindings: Decoder.decode_set, Decoder.peek_tag, Decoder.decode_raw_tlv,
Decoder.remaining_bytes, Decoder.decode_implicit_tag, Decoder.position,
Decoder.remaining, Decoder.is_empty, Decoder.decode_any_str.
- Build a hand-crafted SET and decode it with
decode_set. - Use
peek_tagin a loop to drive CHOICE dispatch. - Capture unknown elements with
decode_raw_tlv. - Use
decode_implicit_tag+remaining_bytesto decode a primitive implicit type (e.g.[2] IMPLICIT IA5Stringfor a dNSName GeneralName). - Show
position()andremaining()advancing through multi-element input. - Use
decode_any_str()to decode a mixed-string SEQUENCE without tag inspection.
17. example_encoder_advanced.py — Advanced Encoder operations
Bindings: Encoder.encode_implicit_tag, Encoder.encode_set,
Encoder.encode_explicit_tag, Encoder.encode_sequence,
and all _object variants not demonstrated elsewhere.
- Build a SET containing two PrintableStrings using
encode_set. - Encode an implicit context tag
[2] IMPLICIT IA5String(dNSName). - Encode a nested structure: SEQUENCE { [0] EXPLICIT SEQUENCE { INTEGER } }.
- Demonstrate every
_objectencode variant with a typed object sourced from a decode. - Show
ValueErrorfor unknown tag class strings.
18. example_krb5_principal.py — Kerberos principal names
Bindings: synta.krb5.Krb5PrincipalName, synta.krb5.KRB5_PRINCIPAL_NAME_OID,
and every NT_* constant.
- Construct
Krb5PrincipalNameinstances for each name type (NT_PRINCIPAL,NT_SRV_INST,NT_SRV_HST,NT_ENTERPRISE,NT_WELLKNOWN). - Encode each with
to_der()and decode back withfrom_der(). - Verify realm, name_type, and components survive the round-trip.
- Show
__eq__and__repr__. - Print
KRB5_PRINCIPAL_NAME_OIDand demonstrate it matchessynta.oids.ID_PKINIT_SAN. - Show
ValueErrorfor non-ASCII realm or component.
19. example_krb5_pkinit.py — PKINIT protocol classes
Bindings: EncryptionKey, Checksum, KDFAlgorithmId, IssuerAndSerialNumber,
ExternalPrincipalIdentifier, PKAuthenticator, AuthPack, PaPkAsReq,
DHRepInfo, KDCDHKeyInfo, ReplyKeyPack, PaPkAsRep.
For each class: parse hand-crafted DER bytes with from_der, access every property,
and print a summary of key values.
20. example_error_handling.py — Exception catalogue
Bindings: synta.SyntaError, ValueError, OverflowError, EOFError.
- Inspect
SyntaErroras a module exception class (MRO,issubclasscheck). - Demonstrate
EOFErrorfrom empty input, tag-only input, and truncated value inDecoder. - Demonstrate
ValueErrorfrom tag mismatch (decoding BOOLEAN as INTEGER). - Demonstrate
ValueErrorfrom DER constraint violations (non-canonical BOOLEAN, non-minimal INTEGER). - Demonstrate
OverflowErrorfromInteger.to_int()andto_i128()on values exceeding the target type. - Demonstrate
ValueErrorfrom constructor validation:BmpStringwith a non-BMP character,GeneralizedTimewith an invalid month,ObjectIdentifierwith a malformed string,Krb5PrincipalNamewith a non-ASCII realm,GeneralString.from_asciiwith a non-ASCII character.
21. example_cms_encrypted_data.py — CMS EncryptedData round-trip
Bindings: synta.cms.EncryptedData, synta.cms.ID_AES128_CBC,
synta.cms.ID_AES192_CBC, synta.cms.ID_AES256_CBC.
Requires the openssl Cargo feature (maturin develop --features openssl).
- Create an
EncryptedDatawithEncryptedData.create(AES-128-CBC) and inspect all properties:version,content_type,content_encryption_algorithm_oid,content_encryption_algorithm_params(IV extraction),encrypted_content. - Decrypt → replace text in plaintext → re-encrypt with a fresh random IV;
verify the round-trip with
to_der()/from_der(). - Encrypt with each of AES-128/192/256-CBC using
ID_AES128_CBC,ID_AES192_CBC,ID_AES256_CBC; confirm random IV differs across calls. - Verify synta-produced ciphertext with
openssl enc -d(synta → openssl interop). - Inspect the
EncryptedDataDER structure withopenssl asn1parse. - Encrypt raw bytes with
openssl enc -e, wrap in anEncryptedDataDER by hand, parse withfrom_der, and decrypt with synta (openssl → synta interop).
22. example_x509_verify.py — X.509 certificate chain verification
Bindings: synta.x509.TrustStore, synta.x509.VerificationPolicy,
synta.x509.verify_server_certificate, synta.x509.verify_client_certificate,
synta.x509.X509VerificationError.
Requires the openssl Cargo feature (maturin develop --features openssl).
- Build a self-signed root CA DER in memory with
openssl req -x509and load it into aTrustStore; printstore.lenandrepr(store). - Verify a leaf certificate signed by that root with
verify_server_certificate; print the chain length and each certificate’s subject withsynta.Certificate.from_der. - Demonstrate
VerificationPolicyvariants: single name, multi-nameany, multi-nameall, fixedvalidation_time,max_chain_depth,profile="rfc5280". - Demonstrate client certificate verification with
verify_client_certificate. - Show that
X509VerificationErroris raised when the trust store does not contain the issuer, the server name does not match the SAN, and the validation time is outside the validity window.
23. example_general_name.py — Typed GeneralName API
Bindings: synta.general_name.DNSName, synta.general_name.IPAddress,
synta.general_name.RFC822Name, synta.general_name.OtherName,
synta.general_name.UniformResourceIdentifier, synta.parse_general_names.
- Call
cert.subject_alt_names()on a certificate with four SANs (twodNSName, oneiPAddress, onerfc822Name); dispatch byisinstance. - Use
cert.general_names(oid)to retrieve SANs by extension OID. - Use
synta.parse_general_names(san_der)for low-level tag/bytes access. - Demonstrate integer tag constants (
gn.DNS_NAME,gn.IP_ADDRESS, etc.).
24. example_pkixalgs.py — RFC 3279 algorithm parameter types
Bindings: synta.pkixalgs.DssParms, synta.pkixalgs.ECParameters,
synta.pkixalgs.EcdsaSigValue, synta.pkixalgs.DssSigValue and OID constants.
- Parse
DssParms(DSA domain parameters P/Q/G) from hand-crafted DER; verify round-trip. - Parse
ECParametersinnamedCurveform; accessarmandnamed_curve_oid. - Parse
EcdsaSigValue(r and s components of an ECDSA signature). - Use OID constants:
ID_DSA,ID_EC_PUBLIC_KEY,ECDSA_WITH_SHA256,PRIME256V1.
25. example_ac.py — RFC 5755 Attribute Certificates
Bindings: synta.ac.AttributeCertificate and related OID constants.
- Build a minimal RFC 5755 v2 Attribute Certificate from scratch in DER.
- Access all eight
AttributeCertificateproperties:version,holder,issuer,signature_algorithm,serial_number,not_before,not_after,attributes. - Demonstrate OID constants:
ID_AT_ROLE,ID_PE_AC_AUDIT_IDENTITY,ID_CE_SUBJECT_DIRECTORY_ATTRIBUTES.
26. example_ms_pki.py — Microsoft PKI (AD CS) extension types
Bindings: synta.ms_pki.MSCSTemplateV2, synta.ms_pki.RequestClientInfo
and OID constants.
- Parse
MSCSTemplateV2with major version only and with both versions; verifytemplate_id,template_major_version,template_minor_version, andto_der()round-trip. - Parse
RequestClientInfowith all four fields present, with partial fields, and from an empty SEQUENCE. - Print all nineteen OID constants with their dot-notation values.
27. example_spnego.py — SPNEGO negotiation tokens (RFC 4178)
Bindings: synta.spnego.NegTokenInit, synta.spnego.NegTokenResp,
synta.spnego.NegotiationToken, NEG_STATE_* constants, SPNEGO_OID.
DER encoding note: GssapiSpnego.asn1 uses
DEFINITIONS IMPLICIT TAGS. The example builds all test vectors using IMPLICIT encoding helpers.
- Parse
NegTokenInitwithmech_types(two OIDs) andmech_token; verify each property and confirmreq_flags/mech_list_micare absent. - Parse an empty
NegTokenInitSEQUENCE; verifymech_types == []. - Parse
NegotiationTokenin thenegTokenInitCHOICE arm (0xa0) and thenegTokenResparm (0xa1). - Parse a GSSAPI-wrapped token (
0x60APPLICATION tag) and verify thatfrom_derstrips the OID prefix automatically. - Parse
NegTokenRespwithneg_state3 (NEG_STATE_REQUEST_MIC) and aresponse_token; verifysupported_mechandmech_list_mic. - Verify all four
NEG_STATE_*integer constants andSPNEGO_OID.
28. example_mtc.py — Merkle Tree Certificates
Bindings: synta.mtc.ProofNode, synta.mtc.Subtree, synta.mtc.SubtreeProof,
synta.mtc.InclusionProof, synta.mtc.LogID, synta.mtc.CosignerID,
synta.mtc.Checkpoint, synta.mtc.SubtreeSignature,
synta.mtc.MerkleTreeCertEntry, synta.mtc.LandmarkID.
- Parse
ProofNode(left and right variants); verifyis_leftandhash. - Parse
Subtree; verifystart,end, andvalue. - Parse
SubtreeProofwith both subtree lists; verify element count. - Parse
InclusionProof; verifylog_entry_index,tree_size, and path length. - Parse
LogIDandCosignerID; verifyhash_alg_oidandpublic_key_der. - Parse
Checkpoint; verify all five properties. - Parse
SubtreeSignature; verifycosigner_id,tree_size,root_hash. - Parse
MerkleTreeCertEntryas aLandmarkCertificateCHOICE arm; verifylandmark_idwithtree_size. - Verify
repr()output forLandmarkID.