]> git.kaiwu.me - njs.git/commitdiff
WebCrypto: added crypto.randomUUID().
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 3 Apr 2026 05:25:08 +0000 (22:25 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 6 Apr 2026 22:24:37 +0000 (15:24 -0700)
external/njs_webcrypto_module.c
external/qjs_webcrypto_module.c
test/webcrypto/random_uuid.t.mjs [new file with mode: 0644]
ts/njs_webcrypto.d.ts

index 33d5ff3d68d4816c8301a646e6374f879a7f33e9..cccfd47948fa7843d630d153f4cb8e51eadc0940 100644 (file)
@@ -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)
 {
index 996f2a846d3fc7921576ecd303e67fccdaf52acc..179af59a1e28f8238837e85b294755cd3898f005 100644 (file)
@@ -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 (file)
index 0000000..f9a8174
--- /dev/null
@@ -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);
index 97139a22564300bb9f2f121f5ce8abb5dc8d66cc..9aa9c30768525e22ced9c13ff91b4676b67c7c87 100644 (file)
@@ -328,6 +328,7 @@ interface SubtleCrypto {
 interface Crypto {
     readonly subtle: SubtleCrypto;
     getRandomValues(ta:TypedArray): TypedArray;
+    randomUUID(): string;
 }
 
 declare const crypto: Crypto;