]> git.kaiwu.me - njs.git/commitdiff
WebCrypto: validate JWK key type against algorithm in importKey().
authorDmitry Volyntsev <xeioex@nginx.com>
Mon, 2 Mar 2026 17:35:48 +0000 (09:35 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Tue, 3 Mar 2026 17:03:28 +0000 (09:03 -0800)
Previously, importKey() did not verify that the JWK "kty" field
matched the requested algorithm.  For example, importing a JWK with
kty "oct" (symmetric) while specifying an asymmetric algorithm like
ECDH caused a SEGV in EVP_PKEY_free() during cleanup.  This happened
because the symmetric key data written into the union's "raw" member
overlapped with the "pkey" pointer, corrupting it.

The fix validates kty before calling any JWK import function:
    - "RSA" is only accepted for RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5
    - "EC" is only accepted for ECDSA, ECDH
    - "oct" is only accepted for HMAC, AES-GCM, AES-CTR, AES-CBC

Found by Akshay Jain (akshaythe@gmail.com).

external/njs_webcrypto_module.c
external/qjs_webcrypto_module.c
test/webcrypto/import.t.mjs

index b9a7435346568c9a5fe11533c643a947293d9464..33d5ff3d68d4816c8301a646e6374f879a7f33e9 100644 (file)
@@ -3634,12 +3634,31 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         }
 
         if (njs_strstr_eq(&kty, &njs_str_value("RSA"))) {
+            if (alg->type != NJS_ALGORITHM_RSASSA_PKCS1_v1_5
+                && alg->type != NJS_ALGORITHM_RSA_PSS
+                && alg->type != NJS_ALGORITHM_RSA_OAEP)
+            {
+                njs_vm_type_error(vm, "JWK kty \"RSA\" doesn't match "
+                                  "algorithm \"%V\"",
+                                  njs_algorithm_string(alg));
+                goto fail;
+            }
+
             pkey = njs_import_jwk_rsa(vm, jwk, key);
             if (njs_slow_path(pkey == NULL)) {
                 goto fail;
             }
 
         } else if (njs_strstr_eq(&kty, &njs_str_value("EC"))) {
+            if (alg->type != NJS_ALGORITHM_ECDSA
+                && alg->type != NJS_ALGORITHM_ECDH)
+            {
+                njs_vm_type_error(vm, "JWK kty \"EC\" doesn't match "
+                                  "algorithm \"%V\"",
+                                  njs_algorithm_string(alg));
+                goto fail;
+            }
+
             ret = njs_algorithm_curve(vm, options, &key->u.a.curve);
             if (njs_slow_path(ret == NJS_ERROR)) {
                 goto fail;
@@ -3651,6 +3670,13 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
             }
 
         } else if (njs_strstr_eq(&kty, &njs_str_value("oct"))) {
+            if (!alg->raw) {
+                njs_vm_type_error(vm, "JWK kty \"oct\" doesn't match "
+                                  "algorithm \"%V\"",
+                                  njs_algorithm_string(alg));
+                goto fail;
+            }
+
             ret = njs_import_jwk_oct(vm, jwk, key);
             if (njs_slow_path(ret != NJS_OK)) {
                 goto fail;
@@ -4609,7 +4635,10 @@ njs_webcrypto_cleanup_pkey(void *data)
     njs_webcrypto_key_t  *key = data;
 
     if (!key->alg->raw) {
-        EVP_PKEY_free(key->u.a.pkey);
+        if (key->u.a.pkey != NULL) {
+            EVP_PKEY_free(key->u.a.pkey);
+        }
+
     }
 }
 
index b9c645d981f0874a1b02f45159ced618ee3828c4..996f2a846d3fc7921576ecd303e67fccdaf52acc 100644 (file)
@@ -3487,6 +3487,16 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
 
         switch (kty) {
         case QJS_KEY_JWK_KTY_RSA:
+            if (alg->type != QJS_ALGORITHM_RSASSA_PKCS1_v1_5
+                && alg->type != QJS_ALGORITHM_RSA_PSS
+                && alg->type != QJS_ALGORITHM_RSA_OAEP)
+            {
+                JS_ThrowTypeError(cx, "JWK kty \"RSA\" doesn't "
+                                  "match algorithm \"%s\"",
+                                  qjs_algorithm_string(alg));
+                goto fail;
+            }
+
             pkey = qjs_import_jwk_rsa(cx, jwk, wkey);
             if (pkey == NULL) {
                 goto fail;
@@ -3495,6 +3505,15 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
             break;
 
         case QJS_KEY_JWK_KTY_EC:
+            if (alg->type != QJS_ALGORITHM_ECDSA
+                && alg->type != QJS_ALGORITHM_ECDH)
+            {
+                JS_ThrowTypeError(cx, "JWK kty \"EC\" doesn't "
+                                  "match algorithm \"%s\"",
+                                  qjs_algorithm_string(alg));
+                goto fail;
+            }
+
             ret = qjs_algorithm_curve(cx, options, &wkey->u.a.curve);
             if (JS_IsException(ret)) {
                 goto fail;
@@ -3509,6 +3528,13 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
 
         case QJS_KEY_JWK_KTY_OCT:
         default:
+            if (!alg->raw) {
+                JS_ThrowTypeError(cx, "JWK kty \"oct\" doesn't "
+                                  "match algorithm \"%s\"",
+                                  qjs_algorithm_string(alg));
+                goto fail;
+            }
+
             ret = qjs_import_jwk_oct(cx, jwk, wkey);
             if (JS_IsException(ret)) {
                 goto fail;
index 9ec0fb6076a298e078aab5b94e5826f5b965a428..8ea544d6a09fc3b1a151ac0ab803af544486c7d2 100644 (file)
@@ -178,6 +178,18 @@ let aes_tsuite = {
                alg: { name: "AES-CBC" },
                usage: [ "encrypt", "decrypt" ] },
         exception: "TypeError: Invalid JWK oct alg" },
+      { key: { fmt: "jwk",
+               key: { kty: 'RSA' },
+               alg: { name: "AES-CBC" },
+               extractable: true,
+               usage: [ "encrypt" ] },
+        exception: "TypeError: JWK kty \"RSA\" doesn't match algorithm \"AES-CBC\"" },
+      { key: { fmt: "jwk",
+               key: { kty: 'EC' },
+               alg: { name: "AES-GCM" },
+               extractable: true,
+               usage: [ "encrypt" ] },
+        exception: "TypeError: JWK kty \"EC\" doesn't match algorithm \"AES-GCM\"" },
 ]};
 
 let ec_tsuite = {
@@ -341,6 +353,24 @@ let ec_tsuite = {
                extractable: true,
                usage: [ "verify" ] },
         exception: "TypeError: EC_POINT_oct2point()" },
+      { key: { fmt: "jwk",
+               key: { kty: 'oct', k: 'AQIDBAUG', alg: 'HS256' },
+               alg: { name: "ECDSA", namedCurve: "P-256" },
+               extractable: true,
+               usage: [ "sign" ] },
+        exception: "TypeError: JWK kty \"oct\" doesn't match algorithm \"ECDSA\"" },
+      { key: { fmt: "jwk",
+               key: { kty: 'oct', k: 'AQIDBAUG', alg: 'HS256' },
+               alg: { name: "ECDH", namedCurve: "P-256" },
+               extractable: true,
+               usage: [ "deriveBits" ] },
+        exception: "TypeError: JWK kty \"oct\" doesn't match algorithm \"ECDH\"" },
+      { key: { fmt: "jwk",
+               key: { kty: 'RSA' },
+               alg: { name: "ECDSA", namedCurve: "P-256" },
+               extractable: true,
+               usage: [ "sign" ] },
+        exception: "TypeError: JWK kty \"RSA\" doesn't match algorithm \"ECDSA\"" },
 ]};
 
 let hmac_tsuite = {
@@ -455,6 +485,18 @@ let hmac_tsuite = {
                extractable: true,
                usage: [ "verify" ]},
         exception: "TypeError: Key operations and usage mismatch" },
+      { key: { fmt: "jwk",
+               key: { kty: 'RSA' },
+               alg: { name: "HMAC", hash: "SHA-256" },
+               extractable: true,
+               usage: [ "sign" ] },
+        exception: "TypeError: JWK kty \"RSA\" doesn't match algorithm \"HMAC\"" },
+      { key: { fmt: "jwk",
+               key: { kty: 'EC' },
+               alg: { name: "HMAC", hash: "SHA-256" },
+               extractable: true,
+               usage: [ "sign" ] },
+        exception: "TypeError: JWK kty \"EC\" doesn't match algorithm \"HMAC\"" },
 ]};
 
 let rsa_tsuite = {
@@ -631,6 +673,18 @@ let rsa_tsuite = {
                extractable: true,
                usage: [ "encrypt" ] },
         exception: "TypeError: d2i_PUBKEY() failed" },
+      { key: { fmt: "jwk",
+               key: { kty: 'oct', k: 'AQIDBAUG', alg: 'HS256' },
+               alg: { name: "RSA-OAEP", hash: "SHA-256" },
+               extractable: true,
+               usage: [ "encrypt" ] },
+        exception: "TypeError: JWK kty \"oct\" doesn't match algorithm \"RSA-OAEP\"" },
+      { key: { fmt: "jwk",
+               key: { kty: 'EC' },
+               alg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
+               extractable: true,
+               usage: [ "sign" ] },
+        exception: "TypeError: JWK kty \"EC\" doesn't match algorithm \"RSASSA-PKCS1-v1_5\"" },
 ]};
 
 run([