186 lines
5.3 KiB
C
186 lines
5.3 KiB
C
#include "cell.h"
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "monocypher.h"
|
|
|
|
static inline void to_hex(const uint8_t *in, size_t in_len, char *out)
|
|
{
|
|
static const char hexchars[] = "0123456789abcdef";
|
|
for (size_t i = 0; i < in_len; i++) {
|
|
out[2*i ] = hexchars[(in[i] >> 4) & 0x0F];
|
|
out[2*i + 1] = hexchars[ in[i] & 0x0F];
|
|
}
|
|
out[2 * in_len] = '\0'; // null-terminate
|
|
}
|
|
|
|
static inline int nibble_from_char(char c, uint8_t *nibble)
|
|
{
|
|
if (c >= '0' && c <= '9') { *nibble = (uint8_t)(c - '0'); return 0; }
|
|
if (c >= 'a' && c <= 'f') { *nibble = (uint8_t)(c - 'a' + 10); return 0; }
|
|
if (c >= 'A' && c <= 'F') { *nibble = (uint8_t)(c - 'A' + 10); return 0; }
|
|
return -1; // invalid char
|
|
}
|
|
|
|
static inline int from_hex(const char *hex, uint8_t *out, size_t out_len)
|
|
{
|
|
for (size_t i = 0; i < out_len; i++) {
|
|
uint8_t hi, lo;
|
|
if (nibble_from_char(hex[2*i], &hi) < 0) return -1;
|
|
if (nibble_from_char(hex[2*i + 1], &lo) < 0) return -1;
|
|
out[i] = (uint8_t)((hi << 4) | lo);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Convert a JSValue containing a 64-character hex string into a 32-byte array.
|
|
static inline void js2crypto(JSContext *js, JSValue v, uint8_t *crypto)
|
|
{
|
|
size_t hex_len;
|
|
const char *hex_str = JS_ToCStringLen(js, &hex_len, v);
|
|
if (!hex_str)
|
|
return;
|
|
|
|
if (hex_len != 64) {
|
|
JS_FreeCString(js, hex_str);
|
|
JS_ThrowTypeError(js, "js2crypto: expected 64-hex-char string");
|
|
return;
|
|
}
|
|
|
|
if (from_hex(hex_str, crypto, 32) < 0) {
|
|
JS_FreeCString(js, hex_str);
|
|
JS_ThrowTypeError(js, "js2crypto: invalid hex encoding");
|
|
return;
|
|
}
|
|
|
|
JS_FreeCString(js, hex_str);
|
|
}
|
|
|
|
static inline JSValue crypto2js(JSContext *js, const uint8_t *crypto)
|
|
{
|
|
char hex[65]; // 32*2 + 1 for null terminator
|
|
to_hex(crypto, 32, hex);
|
|
return JS_NewString(js, hex);
|
|
}
|
|
|
|
JSValue js_crypto_keypair(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
|
JSValue ret = JS_NewObject(js);
|
|
|
|
uint8_t public[32];
|
|
uint8_t private[32];
|
|
|
|
JSValue global = JS_GetGlobalObject(js);
|
|
JSValue os = JS_GetPropertyStr(js, global, "os");
|
|
JSValue random_blob = JS_GetPropertyStr(js, os, "random_blob");
|
|
JSValue size_val = JS_NewInt32(js, 32);
|
|
JSValue blob = JS_Call(js, random_blob, os, 1, &size_val);
|
|
size_t len;
|
|
uint8_t *data = js_get_blob_data(js, &len, blob);
|
|
if (!data || len != 32) {
|
|
JS_FreeValue(js, blob);
|
|
JS_FreeValue(js, size_val);
|
|
JS_FreeValue(js, random_blob);
|
|
JS_FreeValue(js, os);
|
|
JS_FreeValue(js, global);
|
|
return JS_ThrowInternalError(js, "failed to get random bytes");
|
|
}
|
|
memcpy(private, data, 32);
|
|
JS_FreeValue(js, blob);
|
|
JS_FreeValue(js, size_val);
|
|
JS_FreeValue(js, random_blob);
|
|
JS_FreeValue(js, os);
|
|
JS_FreeValue(js, global);
|
|
|
|
private[0] &= 248;
|
|
private[31] &= 127;
|
|
private[31] |= 64;
|
|
|
|
crypto_x25519_public_key(public,private);
|
|
|
|
JS_SetPropertyStr(js, ret, "public", crypto2js(js, public));
|
|
JS_SetPropertyStr(js, ret, "private", crypto2js(js,private));
|
|
return ret;
|
|
}
|
|
|
|
JSValue js_crypto_shared(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|
{
|
|
if (argc < 1 || !JS_IsObject(argv[0])) {
|
|
return JS_ThrowTypeError(js, "crypto.shared: expected an object argument");
|
|
}
|
|
|
|
JSValue obj = argv[0];
|
|
|
|
JSValue val_pub = JS_GetPropertyStr(js, obj, "public");
|
|
if (JS_IsException(val_pub)) {
|
|
JS_FreeValue(js, val_pub);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue val_priv = JS_GetPropertyStr(js, obj, "private");
|
|
if (JS_IsException(val_priv)) {
|
|
JS_FreeValue(js, val_pub);
|
|
JS_FreeValue(js, val_priv);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
uint8_t pub[32], priv[32];
|
|
js2crypto(js, val_pub, pub);
|
|
js2crypto(js, val_priv, priv);
|
|
|
|
JS_FreeValue(js, val_pub);
|
|
JS_FreeValue(js, val_priv);
|
|
|
|
uint8_t shared[32];
|
|
crypto_x25519(shared, priv, pub);
|
|
|
|
return crypto2js(js, shared);
|
|
}
|
|
|
|
JSValue js_crypto_hash(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|
{
|
|
if (argc < 1)
|
|
return JS_ThrowTypeError(js, "hash requires at least one argument");
|
|
|
|
// Get input data
|
|
size_t data_len;
|
|
void *data = js_get_blob_data(js, &data_len, argv[0]);
|
|
if (!data)
|
|
return JS_ThrowTypeError(js, "hash: first argument must be an ArrayBuffer");
|
|
|
|
// Get hash length (default 32)
|
|
int32_t hash_len = 32;
|
|
if (argc > 1) {
|
|
if (JS_ToInt32(js, &hash_len, argv[1]))
|
|
return JS_EXCEPTION;
|
|
if (hash_len < 1 || hash_len > 64)
|
|
return JS_ThrowRangeError(js, "hash length must be between 1 and 64");
|
|
}
|
|
|
|
// Allocate output buffer
|
|
uint8_t *hash = js_malloc(js, hash_len);
|
|
if (!hash)
|
|
return JS_EXCEPTION;
|
|
|
|
// Compute BLAKE2b hash
|
|
crypto_blake2b(hash, hash_len, data, data_len);
|
|
|
|
// Return as blob
|
|
JSValue result = js_new_blob_stoned_copy(js, hash, hash_len);
|
|
js_free(js, hash);
|
|
return result;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_crypto_funcs[] = {
|
|
JS_CFUNC_DEF("keypair", 0, js_crypto_keypair),
|
|
JS_CFUNC_DEF("shared", 1, js_crypto_shared),
|
|
JS_CFUNC_DEF("hash", 2, js_crypto_hash),
|
|
};
|
|
|
|
JSValue js_crypto_use(JSContext *js)
|
|
{
|
|
JSValue obj = JS_NewObject(js);
|
|
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
|
|
return obj;
|
|
}
|