// 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 <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ---------------------------------------------------------------------------
* 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;
}