// crypto_backend.c // // Exposes to Wren (module "crypto", class "Crypto"): // static uuid4() -> String // static uuid5(data) -> String // static encrypt(text, name) -> ByteBuffer // static decrypt(blob, name) -> String | null // // Build (Linux/macOS): // cc -shared -fPIC crypto_backend.c -o libcrypto_backend.so -lcrypto // // Build (Windows / MSVC): // cl /LD crypto_backend.c /I"path\to\wren\include" /link libcrypto.lib // ───────────────────────────────────────────────────────────────────────────── #include "wren.h" #include #include #include #include #include #include #include #include /* --------------------------------------------------------------------------- * Compatibility for older Wren headers (no Bytes type). * ---------------------------------------------------------------------------*/ #ifndef WREN_TYPE_BYTES #define WREN_TYPE_BYTES WREN_TYPE_STRING #endif /* --------------------------------------------------------------------------- * Simple in-memory keystore (salted name → 256-bit key). * ---------------------------------------------------------------------------*/ typedef struct KeyNode { char* name; unsigned char key[32]; struct KeyNode* next; } KeyNode; static KeyNode* g_keys = NULL; static const char* SALT = "8f53eb74-4c3b-4a3b-b694-409a53ce258d"; static void get_key(const char* name, unsigned char out[32]) { /* Anonymous/ephemeral key if name empty. */ if (!name || !*name) { RAND_bytes(out, 32); return; } char salted[256]; snprintf(salted, sizeof(salted), "%s_%s", name, SALT); for (KeyNode* n = g_keys; n; n = n->next) if (strcmp(n->name, salted) == 0) { memcpy(out, n->key, 32); return; } KeyNode* node = (KeyNode*)malloc(sizeof(KeyNode)); node->name = strdup(salted); RAND_bytes(node->key, 32); memcpy(out, node->key, 32); node->next = g_keys; g_keys = node; } /* --------------------------------------------------------------------------- * Helpers * ---------------------------------------------------------------------------*/ static bool validate_string(WrenVM* vm, int slot, const char* name) { if (wrenGetSlotType(vm, slot) == WREN_TYPE_STRING) return true; char msg[64]; snprintf(msg, sizeof(msg), "Argument '%s' must be a String.", name); wrenSetSlotString(vm, 0, msg); wrenAbortFiber(vm, 0); return false; } /* Format 16 raw bytes as canonical UUID (8-4-4-4-12) into out[37]. */ static void format_uuid(const uint8_t b[16], char out[37]) { snprintf(out, 37, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); } /* --------------------------------------------------------------------------- * UUID-4 (random) * ---------------------------------------------------------------------------*/ static void crypto_uuid4(WrenVM* vm) { uint8_t bytes[16]; RAND_bytes(bytes, 16); bytes[6] = (bytes[6] & 0x0F) | 0x40; // version 4 bytes[8] = (bytes[8] & 0x3F) | 0x80; // variant char uuid[37]; format_uuid(bytes, uuid); wrenSetSlotString(vm, 0, uuid); } /* --------------------------------------------------------------------------- * UUID-5 (SHA-1, namespace DNS) * ---------------------------------------------------------------------------*/ static const uint8_t NS_DNS[16] = { 0x8f,0x53,0xeb,0x74,0x4c,0x3b,0x1a,0x33, 0xb7,0x94,0x40,0x9a,0x53,0xce,0x25,0x8d }; static void crypto_uuid5(WrenVM* vm) { if (!validate_string(vm, 1, "data")) return; int len; const char* data = wrenGetSlotBytes(vm, 1, &len); unsigned char hash[SHA_DIGEST_LENGTH]; SHA_CTX ctx; SHA1_Init(&ctx); SHA1_Update(&ctx, NS_DNS, 16); SHA1_Update(&ctx, data, len); SHA1_Final(hash, &ctx); hash[6] = (hash[6] & 0x0F) | 0x50; // version 5 hash[8] = (hash[8] & 0x3F) | 0x80; // variant char uuid[37]; format_uuid(hash, uuid); wrenSetSlotString(vm, 0, uuid); } /* --------------------------------------------------------------------------- * AES-256-GCM * ---------------------------------------------------------------------------*/ #define NONCE 12 #define TAG 16 static void crypto_encrypt(WrenVM* vm) { if (!validate_string(vm, 1, "plaintext")) return; if (!validate_string(vm, 2, "keyName")) return; int pLen; const char* pt = wrenGetSlotBytes(vm, 1, &pLen); const char* keyName = wrenGetSlotString(vm, 2); unsigned char key[32]; get_key(keyName, key); unsigned char nonce[NONCE]; RAND_bytes(nonce, NONCE); EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) { wrenSetSlotString(vm,0,"OpenSSL init error"); wrenAbortFiber(vm,0); return; } if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)!=1 || EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, NONCE, NULL)!=1 || EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce)!=1) { EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Encrypt init failed"); wrenAbortFiber(vm,0); return; } /* Allocate: nonce + ciphertext + tag */ unsigned char* buf = (unsigned char*)malloc(NONCE + pLen + TAG); if (!buf) { EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Alloc fail"); wrenAbortFiber(vm,0); return; } memcpy(buf, nonce, NONCE); int outLen; if (EVP_EncryptUpdate(ctx, buf+NONCE, &outLen, (unsigned char*)pt, pLen)!=1) { free(buf); EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Encrypt update failed"); wrenAbortFiber(vm,0); return; } int tmp; if (EVP_EncryptFinal_ex(ctx, buf+NONCE+outLen, &tmp)!=1) { free(buf); EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Encrypt final failed"); wrenAbortFiber(vm,0); return; } outLen += tmp; if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, TAG, buf+NONCE+outLen)!=1) { free(buf); EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Get tag failed"); wrenAbortFiber(vm,0); return; } EVP_CIPHER_CTX_free(ctx); size_t total = NONCE + outLen + TAG; wrenSetSlotBytes(vm, 0, (const char*)buf, total); free(buf); } static void crypto_decrypt(WrenVM* vm) { WrenType t = wrenGetSlotType(vm, 1); if (t != WREN_TYPE_BYTES && t != WREN_TYPE_STRING) { wrenSetSlotString(vm, 0, "First argument must be binary data (String)."); wrenAbortFiber(vm, 0); return; } if (!validate_string(vm, 2, "keyName")) return; int cLen; const unsigned char* blob = (const unsigned char*)wrenGetSlotBytes(vm, 1, &cLen); if (cLen < NONCE + TAG) { wrenSetSlotNull(vm, 0); return; } const char* keyName = wrenGetSlotString(vm, 2); unsigned char key[32]; get_key(keyName, key); const unsigned char* nonce = blob; const unsigned char* cipher = blob + NONCE; int cipherLen = cLen - NONCE - TAG; const unsigned char* tag = blob + NONCE + cipherLen; EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) { wrenSetSlotNull(vm,0); return; } if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)!=1 || EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, NONCE, NULL)!=1 || EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce)!=1) { EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; } unsigned char* plain = (unsigned char*)malloc(cipherLen); if (!plain) { EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; } int outLen; if (EVP_DecryptUpdate(ctx, plain, &outLen, cipher, cipherLen)!=1) { free(plain); EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; } EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, TAG, (void*)tag); int tmp; if (EVP_DecryptFinal_ex(ctx, plain+outLen, &tmp)!=1) { free(plain); EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; } outLen += tmp; EVP_CIPHER_CTX_free(ctx); wrenSetSlotBytes(vm, 0, (const char*)plain, outLen); free(plain); } /* --------------------------------------------------------------------------- * Foreign binder for Wren * ---------------------------------------------------------------------------*/ WrenForeignMethodFn bindCryptoForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* sig) { if (strcmp(module, "crypto") != 0) return NULL; if (strcmp(className, "Crypto") == 0 && isStatic) { if (strcmp(sig, "uuid4()") == 0) return crypto_uuid4; if (strcmp(sig, "uuid5(_)") == 0) return crypto_uuid5; if (strcmp(sig, "encrypt(_,_)") == 0) return crypto_encrypt; if (strcmp(sig, "decrypt(_,_)") == 0) return crypto_decrypt; } return NULL; }