Some checks failed
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
228 lines
6.2 KiB
C
228 lines
6.2 KiB
C
#include "qjs_crypto.h"
|
||
#include "quickjs.h"
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include "monocypher.h"
|
||
|
||
// randombytes.c - Minimal cross-platform CSPRNG shim (single file)
|
||
/*
|
||
Usage:
|
||
#include "randombytes.c"
|
||
|
||
int main() {
|
||
uint8_t buffer[32];
|
||
if (randombytes(buffer, sizeof(buffer)) != 0) {
|
||
// handle error
|
||
}
|
||
// buffer now has 32 cryptographically secure random bytes
|
||
}
|
||
*/
|
||
|
||
#include <stdint.h>
|
||
#include <stddef.h>
|
||
|
||
#if defined(_WIN32)
|
||
// ------- Windows: use BCryptGenRandom -------
|
||
#include <windows.h>
|
||
#include <bcrypt.h>
|
||
|
||
int randombytes(void *buf, size_t n) {
|
||
NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||
return (status == 0) ? 0 : -1;
|
||
}
|
||
|
||
#elif defined(__linux__)
|
||
// ------- Linux: try getrandom, fall back to /dev/urandom -------
|
||
#include <unistd.h>
|
||
#include <sys/syscall.h>
|
||
#include <fcntl.h>
|
||
#include <errno.h>
|
||
|
||
// If we have a new enough libc and kernel, getrandom is available.
|
||
// Otherwise, we’ll do a /dev/urandom fallback.
|
||
#include <sys/stat.h>
|
||
|
||
static int randombytes_fallback(void *buf, size_t n) {
|
||
int fd = open("/dev/urandom", O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
ssize_t r = read(fd, buf, n);
|
||
close(fd);
|
||
return (r == (ssize_t)n) ? 0 : -1;
|
||
}
|
||
|
||
int randombytes(void *buf, size_t n) {
|
||
#ifdef SYS_getrandom
|
||
// Try getrandom(2) if available
|
||
ssize_t ret = syscall(SYS_getrandom, buf, n, 0);
|
||
if (ret < 0) {
|
||
// If getrandom is not supported or fails, fall back
|
||
if (errno == ENOSYS) {
|
||
return randombytes_fallback(buf, n);
|
||
}
|
||
return -1;
|
||
}
|
||
return (ret == (ssize_t)n) ? 0 : -1;
|
||
#else
|
||
// getrandom not available, just fallback
|
||
return randombytes_fallback(buf, n);
|
||
#endif
|
||
}
|
||
|
||
#else
|
||
// ------- Other Unix: read from /dev/urandom -------
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
|
||
int randombytes(void *buf, size_t n) {
|
||
int fd = open("/dev/urandom", O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
ssize_t r = read(fd, buf, n);
|
||
close(fd);
|
||
return (r == (ssize_t)n) ? 0 : -1;
|
||
}
|
||
#endif
|
||
|
||
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];
|
||
|
||
randombytes(private,32);
|
||
|
||
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_random(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
// 1) Pull 64 bits of cryptographically secure randomness
|
||
uint64_t r;
|
||
if (randombytes(&r, sizeof(r)) != 0) {
|
||
// If something fails (extremely rare), throw an error
|
||
return JS_ThrowInternalError(js, "crypto.random: unable to get random bytes");
|
||
}
|
||
|
||
// 2) Convert r to a double in the range [0,1).
|
||
// We divide by (UINT64_MAX + 1.0) to ensure we never produce exactly 1.0.
|
||
double val = (double)r / ((double)UINT64_MAX + 1.0);
|
||
|
||
// 3) Return that as a JavaScript number
|
||
return JS_NewFloat64(js, val);
|
||
}
|
||
|
||
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("random", 0, js_crypto_random),
|
||
};
|
||
|
||
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;
|
||
}
|