11 releases

new 0.2.2 May 9, 2026
0.2.1 May 2, 2026
0.1.12 Apr 30, 2026

#2316 in Cryptography

MIT/Apache

7MB
50K SLoC

Rust 29K SLoC // 0.1% comments ASN.1 9K SLoC // 0.4% comments Python 9K SLoC // 0.4% comments Shell 2K SLoC // 0.2% comments RPM Specfile 211 SLoC // 0.0% comments

synta-ffi (csynta)

Table of Contents generated with DocToc

C Foreign Function Interface for the synta ASN.1 library.

The compiled library is named csynta (libcsynta.so on Linux, libcsynta.dylib on macOS, csynta.dll on Windows). The C header include/synta.h is generated automatically by cbindgen during the build.

Overview

synta-ffi exposes the full synta ASN.1 encoder/decoder and a comprehensive X.509 PKI parser to C and C++ programs through a stable, thread-safe C ABI. It provides over 100 exported C functions covering all ASN.1 primitive types, string types, time types, constructed types, Object Identifiers, PEM encoding/decoding, parsing for X.509 certificates, CRLs (RFC 5280), PKCS#10 CSRs (RFC 2986), OCSP responses (RFC 6960), PKCS#7/12 certificate bundles, and a full CMS API (RFC 5652): ContentInfo parser, SignedData, SignerInfo, EnvelopedData, DigestedData inspection, and EncryptedData encrypt/decrypt (AES-CBC, with the openssl feature).

Building

Prerequisites:

  • cbindgen — for C header generation: cargo install cbindgen
  • cargo-c — optional, for pkg-config support: cargo install cargo-c

Build the shared library and regenerate include/synta.h:

cargo build --release -p synta-ffi

This places the library in target/release/ and regenerates include/synta.h via cbindgen (invoked automatically by build.rs).

For pkg-config integration, use cargo-c:

cargo cbuild --release -p synta-ffi

This produces a pkg-config-compatible installation with the library at target/<host-triple>/release/.

Linking

Using pkg-config

gcc myapp.c $(pkg-config --cflags --libs csynta) -o myapp

Using CMake

find_package(PkgConfig REQUIRED)
pkg_check_modules(CSYNTA REQUIRED csynta)
target_link_libraries(myapp ${CSYNTA_LIBRARIES})
target_include_directories(myapp PRIVATE ${CSYNTA_INCLUDE_DIRS})

Manual

gcc myapp.c -I/path/to/include -L/path/to/lib -lcsynta -lpthread -ldl -lm -o myapp

API Overview

All functions return SyntaErrorCode. On error, the thread-local last-error is set and can be retrieved with synta_get_last_error_message().

Core Types

C Type Description
SyntaDecoder * Opaque decoder handle
SyntaEncoder * Opaque encoder handle
SyntaByteArray Borrowed or owned byte slice (16 bytes: data, len: uint32_t, owned: uint32_t)
SyntaDecoderConfig DoS-protection limits (max depth, elements, length)
SyntaEncoding SyntaEncoding_Der, SyntaEncoding_Ber, SyntaEncoding_Cer
SyntaErrorCode Return code; SyntaErrorCode_Success == 0
SyntaInteger * Opaque arbitrary-precision integer
SyntaOctetString * Opaque byte string
SyntaObjectIdentifier * Opaque OID
SyntaBitString * Opaque bit string
SyntaCertificate * Opaque parsed X.509 certificate
SyntaCrl * Opaque parsed X.509 CRL (RFC 5280 §5)
SyntaCsr * Opaque parsed PKCS#10 CSR (RFC 2986)
SyntaOcsp * Opaque parsed OCSP response (RFC 6960)
SyntaDerList * Opaque list of DER byte vectors (PEM decode result)

SyntaByteArray Memory Contract

SyntaByteArray carries a 1-bit ownership flag (owned). When owned != 0 the caller must call synta_byte_array_free() to release the allocation. When owned == 0 the bytes are borrowed from the parent object (e.g. a decoded certificate) and must not be freed independently.

SyntaByteArray ba = {0};
if (synta_certificate_subject(cert, &ba) == SyntaErrorCode_Success) {
    // ba.owned == 0 → borrowed; valid as long as cert is alive
    process(ba.data, ba.len);
    // do NOT call synta_byte_array_free(&ba) here
}

SyntaByteArray owned_ba = {0};
if (synta_encode_something(..., &owned_ba) == SyntaErrorCode_Success) {
    // owned_ba.owned != 0 → must free
    use(owned_ba.data, owned_ba.len);
    synta_byte_array_free(&owned_ba);
}

Decoder Functions

// Create a decoder (optionally with DoS-protection config)
SyntaDecoder *synta_decoder_new(const uint8_t *data, uintptr_t len, SyntaEncoding enc);
SyntaDecoder *synta_decoder_new_with_config(const uint8_t *data, uintptr_t len,
                                             SyntaEncoding enc, SyntaDecoderConfig config);

// Decode primitives
SyntaErrorCode synta_decode_integer(SyntaDecoder *, SyntaInteger **out);
SyntaErrorCode synta_decode_octet_string(SyntaDecoder *, SyntaOctetString **out);
SyntaErrorCode synta_decode_oid(SyntaDecoder *, SyntaObjectIdentifier **out);
SyntaErrorCode synta_decode_bit_string(SyntaDecoder *, SyntaBitString **out);
SyntaErrorCode synta_decode_boolean(SyntaDecoder *, bool *out);
SyntaErrorCode synta_decode_utf8_string(SyntaDecoder *, SyntaByteArray *out);
// … and all other string/time types

// Traverse constructed types
SyntaErrorCode synta_decoder_enter_sequence(SyntaDecoder *, SyntaDecoder **inner);
SyntaErrorCode synta_decoder_enter_set(SyntaDecoder *, SyntaDecoder **inner);
SyntaErrorCode synta_decoder_enter_explicit_tag(SyntaDecoder *, uint32_t tag, SyntaDecoder **inner);
SyntaErrorCode synta_decoder_enter_implicit_tag(SyntaDecoder *, uint32_t tag,
                                                 SyntaTag type_tag, SyntaDecoder **inner);
SyntaErrorCode synta_decoder_peek_tag(SyntaDecoder *, SyntaTag *tag, SyntaTagClass *class_);
bool synta_decoder_is_empty(SyntaDecoder *);
void synta_decoder_free(SyntaDecoder *);

Encoder Functions

SyntaEncoder *synta_encoder_new(SyntaEncoding enc);

SyntaErrorCode synta_encode_integer_i64(SyntaEncoder *, int64_t value);
SyntaErrorCode synta_encode_boolean(SyntaEncoder *, bool value);
SyntaErrorCode synta_encode_octet_string(SyntaEncoder *, const uint8_t *data, uintptr_t len);
SyntaErrorCode synta_encode_oid(SyntaEncoder *, const SyntaObjectIdentifier *);
// … and all other primitive types

// Constructed types
SyntaErrorCode synta_encoder_start_sequence(SyntaEncoder *, SyntaEncoder **inner);
SyntaErrorCode synta_encoder_start_set(SyntaEncoder *, SyntaEncoder **inner);
SyntaErrorCode synta_encoder_end_constructed(SyntaEncoder *);

// Finish encoding and retrieve bytes
SyntaErrorCode synta_encoder_finish(SyntaEncoder *, SyntaByteArray *out);
void synta_encoder_free(SyntaEncoder *);

X.509 Certificate API

// Parse a DER-encoded X.509 certificate; returns NULL on error.
// Call synta_get_last_error_message() to retrieve the error string.
SyntaCertificate *synta_certificate_parse_der(const uint8_t *data, uintptr_t len);

// Version: v1=0, v2=1, v3=2
SyntaErrorCode synta_certificate_get_version(const SyntaCertificate *cert, int64_t *out);

// Serial number, issuer DN, subject DN — all return borrowed SyntaByteArray (owned == 0).
// The returned data points into the original DER buffer; do not free independently.
SyntaErrorCode synta_certificate_get_serial_number(const SyntaCertificate *cert,
                                                    SyntaByteArray *out);
SyntaErrorCode synta_certificate_get_issuer_der(const SyntaCertificate *cert,
                                                 SyntaByteArray *out);
SyntaErrorCode synta_certificate_get_subject_der(const SyntaCertificate *cert,
                                                  SyntaByteArray *out);

// Signature algorithm OID as a dotted-decimal string.
// Pass (cert, NULL, 0) to query the required buffer length (including NUL terminator).
// The OID is computed and cached on first call; repeated calls are allocation-free.
uintptr_t synta_certificate_get_signature_algorithm(const SyntaCertificate *cert,
                                                     char *buffer, uintptr_t buffer_len);

// Raw DER bytes of the signature AlgorithmIdentifier (inner TBS copy and outer copy).
// Returns borrowed SyntaByteArray (owned == 0); valid while cert is alive.
SyntaErrorCode synta_certificate_get_signature_algorithm_der(const SyntaCertificate *cert,
                                                              SyntaByteArray *out);
SyntaErrorCode synta_certificate_get_outer_signature_algorithm_der(const SyntaCertificate *cert,
                                                                    SyntaByteArray *out);

void synta_certificate_free(SyntaCertificate *cert);

X.509 CRL API

// Parse from DER or PEM (first block only); returns NULL on error.
SyntaCrl *synta_crl_parse_der(const uint8_t *data, uintptr_t len);
SyntaCrl *synta_crl_parse_pem(const uint8_t *data, uintptr_t len);
void synta_crl_free(SyntaCrl *crl);

// All getters return borrowed SyntaByteArray (owned == 0); valid while crl is alive.
SyntaErrorCode synta_crl_get_issuer_der(const SyntaCrl *crl, SyntaByteArray *out);
SyntaErrorCode synta_crl_get_signature_algorithm_der(const SyntaCrl *crl, SyntaByteArray *out);
SyntaErrorCode synta_crl_get_signature_value_der(const SyntaCrl *crl, SyntaByteArray *out);
SyntaErrorCode synta_crl_get_this_update_der(const SyntaCrl *crl, SyntaByteArray *out);
// next_update: out->len == 0 when absent
SyntaErrorCode synta_crl_get_next_update_der(const SyntaCrl *crl, SyntaByteArray *out);
SyntaErrorCode synta_crl_get_revoked_count(const SyntaCrl *crl, uintptr_t *out);

PKCS#10 CSR API

// Parse from DER or PEM (first block only); returns NULL on error.
SyntaCsr *synta_csr_parse_der(const uint8_t *data, uintptr_t len);
SyntaCsr *synta_csr_parse_pem(const uint8_t *data, uintptr_t len);
void synta_csr_free(SyntaCsr *csr);

// All getters return borrowed SyntaByteArray (owned == 0); valid while csr is alive.
SyntaErrorCode synta_csr_get_version(const SyntaCsr *csr, int64_t *out);
SyntaErrorCode synta_csr_get_subject_der(const SyntaCsr *csr, SyntaByteArray *out);
SyntaErrorCode synta_csr_get_signature_algorithm_der(const SyntaCsr *csr, SyntaByteArray *out);
SyntaErrorCode synta_csr_get_signature_der(const SyntaCsr *csr, SyntaByteArray *out);
SyntaErrorCode synta_csr_get_public_key_der(const SyntaCsr *csr, SyntaByteArray *out);

OCSP Response API

// Parse from DER; returns NULL on error.
SyntaOcsp *synta_ocsp_parse_der(const uint8_t *data, uintptr_t len);
void synta_ocsp_free(SyntaOcsp *ocsp);

// Numeric status code (0=successful, 1=malformedRequest, …)
SyntaErrorCode synta_ocsp_get_status_code(const SyntaOcsp *ocsp, int32_t *out);
// Status as a NUL-terminated string (e.g. "successful"); size-query pattern.
uintptr_t synta_ocsp_get_status(const SyntaOcsp *ocsp, char *buffer, uintptr_t buffer_len);
// Response type OID string (absent when status != successful); size-query pattern.
uintptr_t synta_ocsp_get_response_type_oid(const SyntaOcsp *ocsp, char *buffer, uintptr_t buffer_len);
// Raw response bytes (OCTET STRING content, absent when status != successful)
SyntaErrorCode synta_ocsp_get_response_bytes_der(const SyntaOcsp *ocsp, SyntaByteArray *out);

PEM and DER List API

// Decode all PEM blocks from input; returns an opaque list, or NULL on error.
SyntaDerList *synta_pem_to_der(const uint8_t *data, uintptr_t len);
void synta_der_list_free(SyntaDerList *list);
uintptr_t synta_der_list_count(const SyntaDerList *list);
// Get the i-th DER entry as a borrowed SyntaByteArray (owned == 0).
SyntaErrorCode synta_der_list_get_der(const SyntaDerList *list, uintptr_t index,
                                       SyntaByteArray *out);

// Encode DER as a single PEM block; label is a NUL-terminated C string.
// Returns an owned SyntaByteArray (owned != 0); caller must call synta_byte_array_free().
SyntaByteArray synta_der_to_pem(const uint8_t *data, uintptr_t len, const char *label);

PKCS#7 and PKCS#12 API

// Extract DER certificates from a PKCS#7 SignedData blob (DER or BER input).
// Returns an owned SyntaDerList; caller must call synta_der_list_free().
SyntaDerList *synta_pkcs7_certs_from_der(const uint8_t *data, uintptr_t len);
// Same, but first decodes a PEM block.
SyntaDerList *synta_pkcs7_certs_from_pem(const uint8_t *data, uintptr_t len);

// Extract DER certificates from a PKCS#12 PFX archive.
// password may be NULL for unencrypted archives (or encrypted with empty password).
// Returns an owned SyntaDerList; caller must call synta_der_list_free().
SyntaDerList *synta_pkcs12_certs_from_der(const uint8_t *data, uintptr_t len,
                                            const uint8_t *password, uintptr_t password_len);

CMS ContentInfo API

The recommended entry point for CMS parsing. Parses the outer ContentInfo SEQUENCE (RFC 5652 §3) and exposes the inner content via borrowed pointers. Callers no longer need to strip the ContentInfo wrapper manually.

// Parse a full ContentInfo DER blob.
// Returns an opaque handle; free with synta_cms_content_info_free().
SyntaCmsContentInfo *synta_cms_content_info_parse_der(const uint8_t *data, uintptr_t len);

// Free a ContentInfo handle (also releases all borrowed inner pointers).
void synta_cms_content_info_free(SyntaCmsContentInfo *ci);

// contentType OID string — size-query pattern (pass buffer=NULL to get length).
uintptr_t synta_cms_content_info_get_content_type(const SyntaCmsContentInfo *ci,
                                                   char *buffer, uintptr_t buffer_len);

// Borrow inner SignedData (non-NULL only when contentType == id-signedData).
const SyntaCmsSignedData *synta_cms_content_info_get0_signed_data(
    const SyntaCmsContentInfo *ci);

// Borrow inner EnvelopedData (non-NULL only when contentType == id-envelopedData).
const SyntaCmsEnvelopedData *synta_cms_content_info_get0_enveloped_data(
    const SyntaCmsContentInfo *ci);

// Borrow inner EncryptedData (non-NULL only when contentType == id-encryptedData).
const SyntaCmsEncryptedData *synta_cms_content_info_get0_encrypted_data(
    const SyntaCmsContentInfo *ci);

// Borrow inner DigestedData (non-NULL only when contentType == id-digestedData).
const SyntaCmsDigestedData *synta_cms_content_info_get0_digested_data(
    const SyntaCmsContentInfo *ci);

Ownership: Pointers returned by get0_* functions are borrowed from the SyntaCmsContentInfo and are valid until synta_cms_content_info_free is called. Do not pass them to any _free function.

CMS EncryptedData API

// Parse a DER-encoded EncryptedData SEQUENCE.
// Returns SyntaErrorCode_Success on success; call synta_get_last_error_message()
// on failure.  Fields are accessed via the returned handle.
SyntaErrorCode synta_cms_encrypted_data_parse(const uint8_t *data, uintptr_t len,
                                              SyntaCmsEncryptedData **out);

// Decrypt the EncryptedData content under `key`.
// Returns the plaintext as an owned SyntaByteArray.
// Requires the library to be built with the `openssl` Cargo feature.
SyntaErrorCode synta_cms_encrypted_data_decrypt(SyntaCmsEncryptedData *ed,
                                                const uint8_t *key, uintptr_t key_len,
                                                SyntaByteArray *out);

// Create a DER-encoded EncryptedData SEQUENCE by encrypting `plaintext` under `key`.
// enc_alg_oid_der: DER-encoded OID TLV for the content-encryption algorithm.
// content_type_oid_der: DER-encoded OID TLV for the content type (NULL = id-data).
// A fresh random IV is generated for each call.
// Requires the `openssl` Cargo feature.
SyntaErrorCode synta_cms_encrypted_data_create(const uint8_t *plaintext, uintptr_t plaintext_len,
                                               const uint8_t *key, uintptr_t key_len,
                                               const uint8_t *content_type_oid_der,
                                               uintptr_t content_type_oid_der_len,
                                               const uint8_t *enc_alg_oid_der,
                                               uintptr_t enc_alg_oid_der_len,
                                               SyntaByteArray *der_out);

// Free a parsed EncryptedData handle.
void synta_cms_encrypted_data_free(SyntaCmsEncryptedData *ed);

Error Handling

// Thread-local last-error string (valid until next FFI call on this thread)
const char *synta_get_last_error_message(void);

// Clear the thread-local error
void synta_clear_last_error(void);

Quick Start Examples

Decode an INTEGER

#include <synta.h>
#include <stdio.h>

int main(void) {
    const uint8_t data[] = {0x02, 0x01, 0x2A}; // INTEGER 42
    SyntaDecoder *dec = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);

    SyntaInteger *integer = NULL;
    if (synta_decode_integer(dec, &integer) == SyntaErrorCode_Success) {
        int64_t value;
        synta_integer_to_i64(integer, &value);
        printf("Value: %lld\n", (long long)value);
        synta_integer_free(integer);
    } else {
        fprintf(stderr, "Error: %s\n", synta_get_last_error_message());
    }

    synta_decoder_free(dec);
    return 0;
}

Encode a SEQUENCE

#include <synta.h>

int main(void) {
    SyntaEncoder *enc = synta_encoder_new(SyntaEncoding_Der);

    SyntaEncoder *seq = NULL;
    synta_encoder_start_sequence(enc, &seq);
    synta_encode_integer_i64(seq, 42);
    synta_encode_boolean(seq, true);
    synta_encoder_end_constructed(seq);

    SyntaByteArray output = {0};
    synta_encoder_finish(enc, &output);

    /* use output.data[0..output.len-1] */

    synta_byte_array_free(&output);
    return 0;
}

Parse an X.509 Certificate

#include <synta.h>
#include <stdio.h>

void print_cert_info(const uint8_t *der, uintptr_t der_len) {
    SyntaCertificate *cert = synta_certificate_parse_der(der, der_len);
    if (!cert) {
        fprintf(stderr, "Parse failed: %s\n", synta_get_last_error_message());
        return;
    }

    /* Subject DN: zero-copy borrowed slice into the original DER buffer */
    SyntaByteArray subject = {0};
    synta_certificate_get_subject_der(cert, &subject);
    /* subject.owned == 0: bytes are borrowed from cert; do not free separately */
    printf("Subject DER: %u bytes\n", subject.len);

    /* Signature algorithm OID as a dotted-decimal string (size-query / fill pattern) */
    uintptr_t needed = synta_certificate_get_signature_algorithm(cert, NULL, 0);
    char sig_alg[64];
    if (needed <= sizeof(sig_alg)) {
        synta_certificate_get_signature_algorithm(cert, sig_alg, sizeof(sig_alg));
        printf("Sig alg: %s\n", sig_alg);
    }

    synta_certificate_free(cert);
}

Configuring DoS Protection

#include <synta.h>

SyntaDecoder *synta_make_decoder_with_limits(const uint8_t *data, uintptr_t len) {
    SyntaDecoderConfig cfg = {
        .max_depth             = 32,
        .max_sequence_elements = 10000,
        .max_length            = 16 * 1024 * 1024,  /* 16 MB */
    };
    /* Pass config by pointer; SyntaDecoder takes ownership of the limits */
    return synta_decoder_new_with_config(data, len, SyntaEncoding_Der, &cfg);
}

Architecture

Memory Safety

All heap allocations are performed inside the Rust runtime. The C API never exposes raw Rust pointers that could be freed with free(3). Each opaque handle type has a dedicated _free function, and SyntaByteArray uses the owned flag to distinguish borrowed spans from owned allocations that must be released via synta_byte_array_free().

Thread Safety

Error information is stored in thread-local storage. Each thread has its own independent error state; the decoder and encoder handles themselves are not thread-safe and must not be shared across threads.

Zero-Copy Certificate Parsing

The certificate field accessors for issuer, subject, and extensions return borrowed SyntaByteArray values pointing directly into the original DER buffer. No re-encoding or copying occurs. The signature_algorithm OID string is computed once and cached.

OwnedDecoder Pattern

The SyntaDecoder handle wraps an internal OwnedDecoder struct that co-owns both the DecoderConfig and the Decoder<'static> obtained via a transmute of the borrowed lifetime. This pattern enables safe heap allocation of the decoder across FFI boundaries without requiring the caller to manage the input buffer's lifetime separately.

Documentation

Testing

C integration tests are compiled and executed via the Rust test harness:

cargo test -p synta-ffi

To run under Valgrind:

./contrib/ci/local-ci.sh --valgrind c-test

All tests are verified Valgrind-clean (zero memory leaks).

Cargo Features

Feature Default Description
capi yes Exposes the full C API surface; required for all synta_* functions
openssl yes Links OpenSSL for AES-CBC content encryption/decryption: enables synta_cms_encrypted_data_create, synta_cms_encrypted_data_decrypt, and synta_pkcs12_certs_from_der decryption of PBES2/PBKDF2-encrypted bags
nss no NSS-backed signature verification; takes priority over openssl. Use --no-default-features --features nss to select NSS as the sole backend

License

Licensed under either of:

at your option.

Dependencies