11 releases
| new 0.2.2 | May 9, 2026 |
|---|---|
| 0.2.1 | May 2, 2026 |
| 0.1.12 | Apr 30, 2026 |
#2316 in Cryptography
7MB
50K
SLoC
synta-ffi (csynta)
Table of Contents generated with DocToc
- Overview
- Building
- Linking
- API Overview
- Quick Start Examples
- Architecture
- Documentation
- Testing
- Cargo Features
- License
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
- C API Reference — Complete documentation for all 69 functions
- Memory Management Guide — Ownership rules and safety contracts
- Migration from OpenSSL — Guide for OpenSSL users
- Migration from libtasn1 — Guide for libtasn1 users
- C Code Generation — Generating C structs from ASN.1 schemas
- C Examples — Standalone example programs
- C Integration Tests — 25 integration test cases (roundtrip, errors, memory)
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:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.