From 075d950ecbab5cb481b51ec96806cc1df3850c86 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Thu, 2 Apr 2026 22:25:08 -0700 Subject: [PATCH] WebCrypto: added crypto.randomUUID(). --- external/njs_webcrypto_module.c | 46 ++++++++++++++++++++++++++++++++ external/qjs_webcrypto_module.c | 36 +++++++++++++++++++++++++ test/webcrypto/random_uuid.t.mjs | 46 ++++++++++++++++++++++++++++++++ ts/njs_webcrypto.d.ts | 1 + 4 files changed, 129 insertions(+) create mode 100644 test/webcrypto/random_uuid.t.mjs diff --git a/external/njs_webcrypto_module.c b/external/njs_webcrypto_module.c index 33d5ff3d..cccfd479 100644 --- a/external/njs_webcrypto_module.c +++ b/external/njs_webcrypto_module.c @@ -141,6 +141,8 @@ static njs_int_t njs_key_ext_usages(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *retval); static njs_int_t njs_ext_get_random_values(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_ext_random_uuid(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_webcrypto_key_t *njs_webcrypto_key_alloc(njs_vm_t *vm, njs_webcrypto_algorithm_t *alg, unsigned usage, njs_bool_t extractable); @@ -647,6 +649,17 @@ static njs_external_t njs_ext_webcrypto[] = { } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("randomUUID"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_random_uuid, + } + }, + { .flags = NJS_EXTERN_OBJECT, .name.string = njs_str("subtle"), @@ -4629,6 +4642,39 @@ njs_ext_get_random_values(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_ext_random_uuid(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + u_char *p; + njs_uint_t i; + u_char bytes[16], buf[36]; + + static const u_char hex[] = "0123456789abcdef"; + + if (RAND_bytes(bytes, 16) != 1) { + njs_webcrypto_error(vm, "RAND_bytes() failed"); + return NJS_ERROR; + } + + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + p = buf; + + for (i = 0; i < 16; i++) { + *p++ = hex[bytes[i] >> 4]; + *p++ = hex[bytes[i] & 0x0f]; + + if (i == 3 || i == 5 || i == 7 || i == 9) { + *p++ = '-'; + } + } + + return njs_vm_value_string_create(vm, retval, buf, sizeof(buf)); +} + + static void njs_webcrypto_cleanup_pkey(void *data) { diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 996f2a84..179af59a 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -139,6 +139,8 @@ static JSValue qjs_webcrypto_key_usages(JSContext *cx, JSValueConst this_val); static JSValue qjs_get_random_values(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_random_uuid(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); static JSValue qjs_webcrypto_key_make(JSContext *cx, qjs_webcrypto_algorithm_t *alg, unsigned usage, int extractable); @@ -456,6 +458,7 @@ static const JSCFunctionListEntry qjs_webcrypto_key_proto[] = { static const JSCFunctionListEntry qjs_webcrypto_export[] = { JS_CFUNC_DEF("getRandomValues", 1, qjs_get_random_values), + JS_CFUNC_DEF("randomUUID", 0, qjs_random_uuid), JS_OBJECT_DEF("subtle", qjs_webcrypto_subtle, njs_nitems(qjs_webcrypto_subtle), @@ -4478,6 +4481,39 @@ qjs_get_random_values(JSContext *cx, JSValueConst this_val, int argc, } +static JSValue +qjs_random_uuid(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + u_char *p; + uint32_t i; + u_char bytes[16], buf[36]; + + static const u_char hex[] = "0123456789abcdef"; + + if (RAND_bytes(bytes, 16) != 1) { + qjs_webcrypto_error(cx, "RAND_bytes() failed"); + return JS_EXCEPTION; + } + + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + p = buf; + + for (i = 0; i < 16; i++) { + *p++ = hex[bytes[i] >> 4]; + *p++ = hex[bytes[i] & 0x0f]; + + if (i == 3 || i == 5 || i == 7 || i == 9) { + *p++ = '-'; + } + } + + return JS_NewStringLen(cx, (const char *) buf, sizeof(buf)); +} + + static JSValue qjs_webcrypto_key_make(JSContext *cx, qjs_webcrypto_algorithm_t *alg, unsigned usage, int extractable) diff --git a/test/webcrypto/random_uuid.t.mjs b/test/webcrypto/random_uuid.t.mjs new file mode 100644 index 00000000..f9a8174d --- /dev/null +++ b/test/webcrypto/random_uuid.t.mjs @@ -0,0 +1,46 @@ +/*--- +includes: [compatWebcrypto.js, runTsuite.js] +flags: [async] +---*/ + +async function test(params) { + let uuid = crypto.randomUUID(); + + if (typeof uuid !== 'string') { + throw Error(`randomUUID returned ${typeof uuid}, expected string`); + } + + if (uuid.length !== 36) { + throw Error(`randomUUID length ${uuid.length}, expected 36`); + } + + let re = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; + if (!re.test(uuid)) { + throw Error(`randomUUID format invalid: "${uuid}"`); + } + + let uuid2 = crypto.randomUUID(); + + if (uuid === uuid2) { + throw Error(`randomUUID not unique: "${uuid}" === "${uuid2}"`); + } + + if (!re.test(uuid2)) { + throw Error(`randomUUID second call format invalid: "${uuid2}"`); + } + + return 'SUCCESS'; +} + +let randomUUID_tsuite = { + name: "crypto.randomUUID()", + skip: () => (!has_webcrypto()), + T: test, + prepare_args: (args) => args, + + tests: [ + { }, +]}; + +run([randomUUID_tsuite]) +.then($DONE, $DONE); diff --git a/ts/njs_webcrypto.d.ts b/ts/njs_webcrypto.d.ts index 97139a22..9aa9c307 100644 --- a/ts/njs_webcrypto.d.ts +++ b/ts/njs_webcrypto.d.ts @@ -328,6 +328,7 @@ interface SubtleCrypto { interface Crypto { readonly subtle: SubtleCrypto; getRandomValues(ta:TypedArray): TypedArray; + randomUUID(): string; } declare const crypto: Crypto; -- 2.47.3