DAT C Library

GitHub · Test Code

Репозиторий

Until it is officially merged into vcpkg, you will need to manually install and integrate the project using this repository.
https://github.com/microsoft/vcpkg/pull/52088
version: 4.6.0

Requires: CMake >= 3.15 · OpenSSL · Threads (CURL only when the CMS client is built)

CMakeLists.txt

cmake
find_package(dat CONFIG REQUIRED)

add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE dat)

dat-config.cmake resolves OpenSSL, Threads and CURL through find_dependency, so a static consumer no longer has to declare them itself.

Пример: Сервис управления сертификатами DAT

log

c
static const char* log_level_str(dat_log_level_t level) {
    switch (level) {
        case DAT_LOG_DEBUG:    return "DEBUG";
        case DAT_LOG_INFO:     return "INFO";
        case DAT_LOG_WARN:     return "WARN";
        case DAT_LOG_ERROR:    return "ERROR";
        default:               return "UNKNOWN";
    }
}

static void example_log_fn(dat_log_level_t level, const char* message, void* userdata) {
    (void)userdata;
    printf("[CMS][%s] %s\n", log_level_str(level), message);
}

init

c
const char* url = "http://localhost:8088";
const char* token = "1234";
bool verify_only = false;
// uint64_t interval_seconds = 0; // disable auto sync
uint64_t interval_seconds = 60;
// dat_log_fn_t log_fn = NULL; // disable log
dat_log_fn_t log_fn = example_log_fn;

dat_cms_manager_t* manager = NULL;
dat_error_t err = dat_cms_manager_create(
    url, token, verify_only, interval_seconds,
    log_fn, NULL, &manager);
// Creation now always returns DAT_SUCCESS(0) on success, so the plain `if (err)`
// idiom works. A failed first sync is not a creation failure — the manager is
// still usable once the network recovers.
if (err != DAT_SUCCESS) {
    printf("Failed to create cms manager: %s\n", dat_error_code(err));
    return 1;
}
printf("CMS manager created\n");

// Ask separately whether the first sync went through.
dat_error_t sync_err = dat_cms_manager_last_error(manager);
if (sync_err != DAT_SUCCESS) {
    // A permanent failure (bad token, wrong URL) will never resolve on its own.
    printf("initial sync failed: %s (retry=%s)\n",
           dat_error_code(sync_err),
           dat_error_retry(sync_err) == DAT_RETRY_TRANSIENT ? "transient" : "permanent");
}

// manual sync
// dat_cms_manager_sync(manager);

issue / parse

c
static const char* plain  = "Unicode 유니코드 ユニコード 万国码 يونيكود यूनिकोड Юникод 🦄💻";
static const char* secure = "Ciphertext 암호문 暗号文 密文 Шифротекст Texte chiffré Geheimtext نص مشفر सिफरपाठ 🔐";
char* dat_str = NULL;
err = dat_cms_manager_issue(manager, plain, secure, &dat_str);
if (err != DAT_SUCCESS) {
    printf("Issue failed: %d\n", (int)err);
} else {
    printf("dat: %s\n", dat_str);

    dat_payload_t* payload = NULL;
    err = dat_cms_manager_parse(manager, dat_str, &payload);
    assert(err == DAT_SUCCESS);
    printf("payload plain: %.*s\n",  (int)payload->plain_len,  (char*)payload->plain_bytes);
    printf("payload secure: %.*s\n", (int)payload->secure_len, (char*)payload->secure_bytes);

    assert(payload->plain_len  == strlen(plain));
    assert(memcmp(payload->plain_bytes,  plain,  payload->plain_len)  == 0);
    assert(payload->secure_len == strlen(secure));
    assert(memcmp(payload->secure_bytes, secure, payload->secure_len) == 0);
    dat_payload_free(payload);
}
free(dat_str);

Обработка ошибок

Every failure returns a dat_error_t. The string from dat_error_code() is the contract — the integer is kept only for ABI compatibility.

c
// Expiry, forgery and malformed input each need a different response.
dat_payload_t* payload = NULL;
dat_error_t err = dat_cms_manager_parse(manager, dat_str, &payload);
if (err != DAT_SUCCESS) {
    if (err == DAT_TOKEN_EXPIRED) {
        // Normal end of life — let the client refresh its token.
    } else if (dat_error_is_security_event(err)) {
        // DAT_SIG_MISMATCH or DAT_CRYPTO_TAG_MISMATCH: forged or tampered with.
        fprintf(stderr, "[SECURITY] %s\n", dat_error_code(err));
    } else {
        // Anything else: just reject the request.
        fprintf(stderr, "rejected: %s\n", dat_error_code(err));
    }
}
c
// Never retry a permanent failure — it will not resolve on its own.
if (dat_error_retry(err) == DAT_RETRY_TRANSIENT) {
    // back off, then retry
}
c
// C has no exception chaining, so when issuing fails the reason is a separate
// query. Waiting helps for DAT_CERT_NOT_YET_ISSUABLE and nothing else.
char* out = NULL;
err = dat_cms_manager_issue(manager, plain, secure, &out);
if (err == DAT_MANAGER_NO_ISSUABLE_CERTIFICATE) {
    // issuable_cause() takes the inner dat_manager_t*, not the CMS wrapper.
    dat_error_t cause = dat_manager_issuable_cause(dat_cms_manager_get_manager(manager));
    fprintf(stderr, "cannot issue: %s\n", dat_error_code(cause));
}

Пример: Ручная реализация

c
static const char* PLAIN  = "Unicode 유니코드 ユニコード 万国码 يونيكود यूनिकोड Юникод 🦄💻";
static const char* SECURE = "Ciphertext 암호문 暗号文 密文 Шифротекст Texte chiffré Geheimtext نص مشفر सिफरपाठ 🔐";

dat_manager_t* manager = dat_manager_new();
assert(manager);

// (cid, issuance_start, issuance_duration, dat_ttl, signature_alg, crypto_alg)
dat_certificate_t* cert = NULL;
dat_error_t err = dat_certificate_create(1, now_unix_timestamp() - 10, 3600, 1800, DAT_SIG_ECDSA_P256, DAT_CRYPTO_IV_AES256_GCM, &cert);
assert(err == DAT_SUCCESS);

err = dat_manager_import_certificates(manager, &cert, 1, false, NULL);
assert(err == DAT_SUCCESS);

char* dat = NULL;
err = dat_manager_issue(manager, PLAIN, SECURE, &dat);
assert(err == DAT_SUCCESS);

dat_payload_t* payload = NULL;
err = dat_manager_parse(manager, dat, &payload);
assert(err == DAT_SUCCESS);

char* plain = (char*)payload->plain_bytes;
char* secure = (char*)payload->secure_bytes;

assert(memcmp(plain, PLAIN, payload->plain_len) == 0);
assert(memcmp(secure, SECURE, payload->secure_len) == 0);

printf("PASS DAT %s\n", dat);
/* payload bytes are length-delimited, not NUL-terminated — "%s" would read past
 * the end of the buffer. */
printf("PASS PLAIN %.*s\n", (int)payload->plain_len, plain);
printf("PASS SECURE %.*s\n", (int)payload->secure_len, secure);

free(dat);
dat_payload_free(payload);
dat_manager_free(manager);
/* import_certificates borrows the array; the caller keeps ownership. */
dat_certificate_free(cert);