]> git.kaiwu.me - njs.git/commitdiff
WebCrypto: added JWK unwrap() support.
authorDmitry Volyntsev <xeioex@nginx.com>
Sat, 4 Apr 2026 00:47:57 +0000 (17:47 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 6 Apr 2026 22:24:37 +0000 (15:24 -0700)
Added JWK format support to unwrapKey: decrypted data is parsed as
JSON and imported through the shared import path.

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

index a421dfac5f194e6a415f835def37986206be8e35..bd464626dcf4d68fdd3e9f6d27dd3ef8a0dbade3 100644 (file)
@@ -134,6 +134,10 @@ static njs_int_t njs_webcrypto_generate_25519_keypair(njs_vm_t *vm,
 #endif
 static njs_int_t njs_ext_import_key(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_webcrypto_import_key_internal(njs_vm_t *vm,
+    njs_webcrypto_key_format_t fmt, njs_value_t *key_data,
+    njs_webcrypto_algorithm_t *alg, njs_value_t *options,
+    njs_bool_t extractable, unsigned usage, njs_value_t *retval);
 static njs_int_t njs_ext_sign(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t verify, njs_value_t *retval);
 static njs_int_t njs_webcrypto_export_key_raw(njs_vm_t *vm,
@@ -3840,47 +3844,35 @@ done:
 
 
 static njs_int_t
-njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused, njs_value_t *retval)
+njs_webcrypto_import_key_internal(njs_vm_t *vm, njs_webcrypto_key_format_t fmt,
+    njs_value_t *key_data, njs_webcrypto_algorithm_t *alg, njs_value_t *options,
+    njs_bool_t extractable, unsigned usage, njs_value_t *retval)
 {
-    int                         nid;
-    BIO                         *bio;
+    int                   nid;
+    BIO                   *bio;
 #if (OPENSSL_VERSION_NUMBER < 0x30000000L)
-    RSA                         *rsa;
-    EC_KEY                      *ec;
+    RSA                   *rsa;
+    EC_KEY                *ec;
 #else
-    char                        gname[80];
+    char                  gname[80];
 #endif
-    unsigned                    mask, usage;
-    EVP_PKEY                    *pkey;
-    njs_int_t                   ret;
-    njs_str_t                   key_data, kty;
-    njs_value_t                 *options, *jwk, *val;
-    const u_char                *start;
+    unsigned              mask;
+    EVP_PKEY              *pkey;
+    njs_int_t             ret;
+    njs_str_t             raw_data, kty;
+    njs_value_t           *jwk, *val;
+    const u_char          *start;
 #if (OPENSSL_VERSION_NUMBER < 0x30000000L)
-    const EC_GROUP              *group;
+    const EC_GROUP        *group;
 #endif
-    njs_webcrypto_key_t         *key;
-    PKCS8_PRIV_KEY_INFO         *pkcs8;
-    njs_opaque_value_t          value;
-    njs_webcrypto_hash_t        hash;
-    njs_webcrypto_algorithm_t   *alg;
-    njs_webcrypto_key_format_t  fmt;
+    njs_opaque_value_t    value;
+    njs_webcrypto_key_t   *key;
+    PKCS8_PRIV_KEY_INFO   *pkcs8;
+    njs_webcrypto_hash_t  hash;
 
     pkey = NULL;
-    key_data.start = NULL;
-    key_data.length = 0;
-
-    fmt = njs_key_format(vm, njs_arg(args, nargs, 1));
-    if (njs_slow_path(fmt == NJS_KEY_FORMAT_UNKNOWN)) {
-        goto fail;
-    }
-
-    options = njs_arg(args, nargs, 3);
-    alg = njs_key_algorithm(vm, options);
-    if (njs_slow_path(alg == NULL)) {
-        goto fail;
-    }
+    raw_data.start = NULL;
+    raw_data.length = 0;
 
     if (njs_slow_path(!(fmt & alg->fmt))) {
         njs_vm_type_error(vm, "unsupported key fmt \"%V\" for \"%V\" key",
@@ -3889,11 +3881,6 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         goto fail;
     }
 
-    ret = njs_key_usage(vm, njs_arg(args, nargs, 5), &usage);
-    if (njs_slow_path(ret != NJS_OK)) {
-        goto fail;
-    }
-
     if (njs_slow_path(usage & ~alg->usage)) {
         njs_vm_type_error(vm, "unsupported key usage for \"%V\" key",
                           njs_algorithm_string(alg));
@@ -3901,14 +3888,13 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     }
 
     if (fmt != NJS_KEY_FORMAT_JWK) {
-        ret = njs_vm_value_to_bytes(vm, &key_data, njs_arg(args, nargs, 2));
+        ret = njs_vm_value_to_bytes(vm, &raw_data, key_data);
         if (njs_slow_path(ret != NJS_OK)) {
             goto fail;
         }
     }
 
-    key = njs_webcrypto_key_alloc(vm, alg, usage,
-                                  njs_value_bool(njs_arg(args, nargs, 4)));
+    key = njs_webcrypto_key_alloc(vm, alg, usage, extractable);
     if (njs_slow_path(key == NULL)) {
         goto fail;
     }
@@ -3926,7 +3912,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     switch (fmt) {
     case NJS_KEY_FORMAT_PKCS8:
-        bio = njs_bio_new_mem_buf(key_data.start, key_data.length);
+        bio = njs_bio_new_mem_buf(raw_data.start, raw_data.length);
         if (njs_slow_path(bio == NULL)) {
             njs_webcrypto_error(vm, "BIO_new_mem_buf() failed");
             goto fail;
@@ -3955,8 +3941,8 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         break;
 
     case NJS_KEY_FORMAT_SPKI:
-        start = key_data.start;
-        pkey = d2i_PUBKEY(NULL, &start, key_data.length);
+        start = raw_data.start;
+        pkey = d2i_PUBKEY(NULL, &start, raw_data.length);
         if (njs_slow_path(pkey == NULL)) {
             njs_webcrypto_error(vm, "d2i_PUBKEY() failed");
             goto fail;
@@ -3965,7 +3951,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         break;
 
     case NJS_KEY_FORMAT_JWK:
-        jwk = njs_arg(args, nargs, 2);
+        jwk = key_data;
         if (!njs_value_is_object(jwk)) {
             njs_vm_type_error(vm, "invalid JWK key data: object value "
                               "expected");
@@ -4121,7 +4107,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         }
 
         if (fmt == NJS_KEY_FORMAT_RAW) {
-            pkey = njs_import_raw_ec(vm, &key_data, key);
+            pkey = njs_import_raw_ec(vm, &raw_data, key);
             if (njs_slow_path(pkey == NULL)) {
                 goto fail;
             }
@@ -4186,10 +4172,9 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     case NJS_ALGORITHM_ED25519:
         if (fmt == NJS_KEY_FORMAT_RAW) {
             pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
-                                               key_data.start, key_data.length);
+                                               raw_data.start, raw_data.length);
             if (njs_slow_path(pkey == NULL)) {
-                njs_webcrypto_error(vm,
-                    "EVP_PKEY_new_raw_public_key() failed");
+                njs_webcrypto_error(vm, "EVP_PKEY_new_raw_public_key() failed");
                 goto fail;
             }
         }
@@ -4214,7 +4199,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     case NJS_ALGORITHM_X25519:
         if (fmt == NJS_KEY_FORMAT_RAW) {
             pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL,
-                                               key_data.start, key_data.length);
+                                               raw_data.start, raw_data.length);
             if (njs_slow_path(pkey == NULL)) {
                 njs_webcrypto_error(vm,
                     "EVP_PKEY_new_raw_public_key() failed");
@@ -4251,7 +4236,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
                 goto fail;
             }
 
-            key->u.s.raw = key_data;
+            key->u.s.raw = raw_data;
 
         } else {
             /* NJS_KEY_FORMAT_JWK. */
@@ -4274,7 +4259,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     case NJS_ALGORITHM_AES_CBC:
     case NJS_ALGORITHM_AES_KW:
         if (fmt == NJS_KEY_FORMAT_RAW) {
-            switch (key_data.length) {
+            switch (raw_data.length) {
             case 16:
             case 24:
             case 32:
@@ -4285,7 +4270,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
                 goto fail;
             }
 
-            key->u.s.raw = key_data;
+            key->u.s.raw = raw_data;
         }
 
         break;
@@ -4293,17 +4278,17 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     case NJS_ALGORITHM_PBKDF2:
     case NJS_ALGORITHM_HKDF:
     default:
-        key->u.s.raw = key_data;
+        key->u.s.raw = raw_data;
         break;
     }
 
-    ret = njs_vm_external_create(vm, njs_value_arg(&value),
+    ret = njs_vm_external_create(vm, retval,
                                  njs_webcrypto_crypto_key_proto_id, key, 0);
     if (njs_slow_path(ret != NJS_OK)) {
         goto fail;
     }
 
-    return njs_webcrypto_result(vm, &value, NJS_OK, retval);
+    return NJS_OK;
 
 fail:
 
@@ -4311,6 +4296,53 @@ fail:
         EVP_PKEY_free(pkey);
     }
 
+    return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    unsigned                    usage;
+    njs_int_t                   ret;
+    njs_value_t                 *options;
+    njs_bool_t                  extractable;
+    njs_opaque_value_t          value;
+    njs_webcrypto_algorithm_t   *alg;
+    njs_webcrypto_key_format_t  fmt;
+
+    fmt = njs_key_format(vm, njs_arg(args, nargs, 1));
+    if (njs_slow_path(fmt == NJS_KEY_FORMAT_UNKNOWN)) {
+        goto fail;
+    }
+
+    options = njs_arg(args, nargs, 3);
+    alg = njs_key_algorithm(vm, options);
+    if (njs_slow_path(alg == NULL)) {
+        goto fail;
+    }
+
+    ret = njs_key_usage(vm, njs_arg(args, nargs, 5), &usage);
+    if (njs_slow_path(ret != NJS_OK)) {
+        goto fail;
+    }
+
+    extractable = njs_value_bool(njs_arg(args, nargs, 4));
+
+    ret = njs_webcrypto_import_key_internal(vm, fmt,
+                                            njs_arg(args, nargs, 2),
+                                            alg, options, extractable,
+                                            usage,
+                                            njs_value_arg(&value));
+    if (njs_slow_path(ret != NJS_OK)) {
+        goto fail;
+    }
+
+    return njs_webcrypto_result(vm, &value, NJS_OK, retval);
+
+fail:
+
     return njs_webcrypto_result(vm, NULL, NJS_ERROR, retval);
 }
 
@@ -5121,11 +5153,11 @@ njs_ext_unwrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 {
     unsigned                   usage;
     njs_int_t                  ret;
-    njs_str_t                  data, key_data;
+    njs_str_t                  data;
     njs_value_t                *options;
     njs_bool_t                 extractable;
     njs_opaque_value_t         decrypted, value;
-    njs_webcrypto_key_t        *wrapping_key, *ikey;
+    njs_webcrypto_key_t        *wrapping_key;
     njs_webcrypto_algorithm_t  *alg, *key_alg;
     njs_webcrypto_key_format_t fmt;
 
@@ -5182,61 +5214,39 @@ njs_ext_unwrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         goto fail;
     }
 
-    ret = njs_vm_value_to_bytes(vm, &key_data, njs_value_arg(&decrypted));
-    if (njs_slow_path(ret != NJS_OK)) {
-        goto fail;
-    }
-
-    if (njs_slow_path(usage & ~key_alg->usage)) {
-        njs_vm_type_error(vm, "unsupported key usage for \"%V\" key",
-                          njs_algorithm_string(key_alg));
-        goto fail;
-    }
-
-    if (njs_slow_path(!(fmt & key_alg->fmt))) {
-        njs_vm_type_error(vm, "unsupported key fmt for \"%V\" key",
-                          njs_algorithm_string(key_alg));
-        goto fail;
-    }
-
-    ikey = njs_webcrypto_key_alloc(vm, key_alg, usage, extractable);
-    if (njs_slow_path(ikey == NULL)) {
-        goto fail;
-    }
+    if (fmt == NJS_KEY_FORMAT_JWK) {
+        njs_str_t           json_data;
+        njs_opaque_value_t  json_str, jwk;
 
-    if (fmt == NJS_KEY_FORMAT_RAW) {
-        switch (key_alg->type) {
-        case NJS_ALGORITHM_AES_GCM:
-        case NJS_ALGORITHM_AES_CTR:
-        case NJS_ALGORITHM_AES_CBC:
-        case NJS_ALGORITHM_AES_KW:
-            switch (key_data.length) {
-            case 16:
-            case 24:
-            case 32:
-                break;
-            default:
-                njs_vm_type_error(vm, "AES Invalid key length");
-                goto fail;
-            }
+        ret = njs_vm_value_to_bytes(vm, &json_data,
+                                    njs_value_arg(&decrypted));
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
 
-            ikey->u.s.raw = key_data;
-            break;
+        ret = njs_vm_value_string_create(vm, njs_value_arg(&json_str),
+                                         json_data.start, json_data.length);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
 
-        case NJS_ALGORITHM_HMAC:
-        default:
-            ikey->u.s.raw = key_data;
-            break;
+        ret = njs_vm_json_parse(vm, njs_value_arg(&json_str), 1,
+                                njs_value_arg(&jwk));
+        if (njs_slow_path(ret != NJS_OK)) {
+            njs_vm_type_error(vm,
+                              "wrapped key is not valid JWK JSON");
+            goto fail;
         }
 
-    } else {
-        njs_vm_type_error(vm, "unwrapKey: unsupported format \"%V\"",
-                          njs_format_string(fmt));
-        goto fail;
+        njs_value_assign(&decrypted, &jwk);
     }
 
-    ret = njs_vm_external_create(vm, njs_value_arg(&value),
-                                 njs_webcrypto_crypto_key_proto_id, ikey, 0);
+    ret = njs_webcrypto_import_key_internal(vm, fmt,
+                                            njs_value_arg(&decrypted),
+                                            key_alg,
+                                            njs_arg(args, nargs, 5),
+                                            extractable, usage,
+                                            njs_value_arg(&value));
     if (njs_slow_path(ret != NJS_OK)) {
         goto fail;
     }
index b4f97bcc1542126b26473b933d205701ac927533..965918beabfd580043dd05abe288e9ea959441cc 100644 (file)
@@ -141,6 +141,10 @@ static int qjs_webcrypto_generate_25519_keypair(JSContext *cx, int pkey_id,
 #endif
 static JSValue qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val,
     int argc, JSValueConst *argv);
+static JSValue qjs_webcrypto_import_key_internal(JSContext *cx,
+    qjs_webcrypto_key_format_t fmt, JSValueConst key_data,
+    qjs_webcrypto_algorithm_t *alg, JSValueConst options,
+    int extractable, unsigned usage);
 static JSValue qjs_webcrypto_sign(JSContext *cx, JSValueConst this_val,
     int argc, JSValueConst *argv, int verify);
 static JSValue qjs_webcrypto_wrap_key(JSContext *cx, JSValueConst this_val,
@@ -3834,45 +3838,38 @@ done:
 
 
 static JSValue
-qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
-    JSValueConst *argv)
+qjs_webcrypto_import_key_internal(JSContext *cx, qjs_webcrypto_key_format_t fmt,
+    JSValueConst key_data, qjs_webcrypto_algorithm_t *alg, JSValueConst options,
+    int extractable, unsigned usage)
 {
-    int                         nid;
-    BIO                         *bio;
+    int                      nid;
+    BIO                      *bio;
 #if (OPENSSL_VERSION_NUMBER < 0x30000000L)
-    RSA                         *rsa;
-    EC_KEY                      *ec;
+    RSA                      *rsa;
+    EC_KEY                   *ec;
 #else
-    char                        gname[80];
+    char                     gname[80];
 #endif
-    JSValue                     options, key, jwk, val, ret;
-    unsigned                    mask, usage;
-    EVP_PKEY                    *pkey;
-    njs_str_t                   key_data;
-    const u_char                *start;
+    JSValue                  key, jwk, val, ret;
+    unsigned                 mask;
+    EVP_PKEY                 *pkey;
+    njs_str_t                kd;
+    const u_char             *start;
 #if (OPENSSL_VERSION_NUMBER < 0x30000000L)
-    const EC_GROUP              *group;
+    const EC_GROUP           *group;
 #endif
-    qjs_webcrypto_key_t         *wkey;
-    PKCS8_PRIV_KEY_INFO         *pkcs8;
-    qjs_webcrypto_hash_t        hash;
-    qjs_webcrypto_jwk_kty_t     kty;
-    qjs_webcrypto_algorithm_t   *alg;
-    qjs_webcrypto_key_format_t  fmt;
+    qjs_webcrypto_key_t      *wkey;
+    PKCS8_PRIV_KEY_INFO      *pkcs8;
+    qjs_webcrypto_hash_t     hash;
+    qjs_webcrypto_jwk_kty_t  kty;
 
     pkey = NULL;
-    key_data.start = NULL;
-    key_data.length = 0;
-
-    fmt = qjs_key_format(cx, argv[0]);
-    if (fmt == QJS_KEY_FORMAT_UNKNOWN) {
-        return JS_EXCEPTION;
-    }
+    kd.start = NULL;
+    kd.length = 0;
 
-    options = argv[2];
-
-    alg = qjs_key_algorithm(cx, options);
-    if (alg == NULL) {
+    if (usage & ~alg->usage) {
+        JS_ThrowTypeError(cx, "unsupported key usage for \"%s\" key",
+                          qjs_algorithm_string(alg));
         return JS_EXCEPTION;
     }
 
@@ -3882,45 +3879,23 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
         return JS_EXCEPTION;
     }
 
-    ret = qjs_key_usage(cx, argv[4], &usage);
-    if (JS_IsException(ret)) {
-        return JS_EXCEPTION;
-    }
-
-    if (usage & ~alg->usage) {
-        JS_ThrowTypeError(cx, "unsupported key usage for \"%s\" key",
-                          qjs_algorithm_string(alg));
-        return JS_EXCEPTION;
-    }
-
     if (fmt != QJS_KEY_FORMAT_JWK) {
-        ret = qjs_typed_array_data(cx, argv[1], &key_data);
+        ret = qjs_typed_array_data(cx, key_data, &kd);
         if (JS_IsException(ret)) {
             return JS_EXCEPTION;
         }
     }
 
-    key = qjs_webcrypto_key_make(cx, alg, usage, JS_ToBool(cx, argv[3]));
+    key = qjs_webcrypto_key_make(cx, alg, usage, extractable);
     if (JS_IsException(key)) {
         return JS_EXCEPTION;
     }
 
     wkey = JS_GetOpaque(key, QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
 
-    /*
-     * set by qjs_webcrypto_key_make():
-     *
-     *  key->u.a.pkey = NULL;
-     *  key->u.s.raw.length = 0;
-     *  key->u.s.raw.start = NULL;
-     *  key->u.a.curve = 0;
-     *  key->u.a.privat = 0;
-     *  key->hash = QJS_HASH_UNSET;
-     */
-
     switch (fmt) {
     case QJS_KEY_FORMAT_PKCS8:
-        bio = BIO_new_mem_buf(key_data.start, key_data.length);
+        bio = BIO_new_mem_buf(kd.start, kd.length);
         if (bio == NULL) {
             qjs_webcrypto_error(cx, "BIO_new_mem_buf() failed");
             goto fail;
@@ -3949,8 +3924,8 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
         break;
 
     case QJS_KEY_FORMAT_SPKI:
-        start = key_data.start;
-        pkey = d2i_PUBKEY(NULL, &start, key_data.length);
+        start = kd.start;
+        pkey = d2i_PUBKEY(NULL, &start, kd.length);
         if (pkey == NULL) {
             qjs_webcrypto_error(cx, "d2i_PUBKEY() failed");
             goto fail;
@@ -3959,7 +3934,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
         break;
 
     case QJS_KEY_FORMAT_JWK:
-        jwk = argv[1];
+        jwk = key_data;
         if (!JS_IsObject(jwk)) {
             JS_ThrowTypeError(cx, "invalid JWK key data: object value "
                               "expected");
@@ -4125,7 +4100,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
         }
 
         if (fmt == QJS_KEY_FORMAT_RAW) {
-            pkey = qjs_import_raw_ec(cx, &key_data, wkey);
+            pkey = qjs_import_raw_ec(cx, &kd, wkey);
             if (pkey == NULL) {
                 goto fail;
             }
@@ -4185,7 +4160,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
     case QJS_ALGORITHM_ED25519:
         if (fmt == QJS_KEY_FORMAT_RAW) {
             pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
-                                               key_data.start, key_data.length);
+                                               kd.start, kd.length);
             if (pkey == NULL) {
                 qjs_webcrypto_error(cx,
                     "EVP_PKEY_new_raw_public_key() failed");
@@ -4212,7 +4187,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
     case QJS_ALGORITHM_X25519:
         if (fmt == QJS_KEY_FORMAT_RAW) {
             pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL,
-                                               key_data.start, key_data.length);
+                                               kd.start, kd.length);
             if (pkey == NULL) {
                 qjs_webcrypto_error(cx,
                     "EVP_PKEY_new_raw_public_key() failed");
@@ -4248,14 +4223,14 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
                 goto fail;
             }
 
-            wkey->u.s.raw.start = js_malloc(cx, key_data.length);
+            wkey->u.s.raw.start = js_malloc(cx, kd.length);
             if (wkey->u.s.raw.start == NULL) {
                 JS_ThrowOutOfMemory(cx);
                 goto fail;
             }
 
-            wkey->u.s.raw.length = key_data.length;
-            memcpy(wkey->u.s.raw.start, key_data.start, key_data.length);
+            wkey->u.s.raw.length = kd.length;
+            memcpy(wkey->u.s.raw.start, kd.start, kd.length);
 
         } else {
             /* QJS_KEY_FORMAT_JWK. */
@@ -4278,7 +4253,7 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
     case QJS_ALGORITHM_AES_CBC:
     case QJS_ALGORITHM_AES_KW:
         if (fmt == QJS_KEY_FORMAT_RAW) {
-            switch (key_data.length) {
+            switch (kd.length) {
             case 16:
             case 24:
             case 32:
@@ -4289,14 +4264,14 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
                 goto fail;
             }
 
-            wkey->u.s.raw.start = js_malloc(cx, key_data.length);
+            wkey->u.s.raw.start = js_malloc(cx, kd.length);
             if (wkey->u.s.raw.start == NULL) {
                 JS_ThrowOutOfMemory(cx);
                 goto fail;
             }
 
-            wkey->u.s.raw.length = key_data.length;
-            memcpy(wkey->u.s.raw.start, key_data.start, key_data.length);
+            wkey->u.s.raw.length = kd.length;
+            memcpy(wkey->u.s.raw.start, kd.start, kd.length);
         }
 
         break;
@@ -4304,18 +4279,18 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
     case QJS_ALGORITHM_PBKDF2:
     case QJS_ALGORITHM_HKDF:
     default:
-        wkey->u.s.raw.start = js_malloc(cx, key_data.length);
+        wkey->u.s.raw.start = js_malloc(cx, kd.length);
         if (wkey->u.s.raw.start == NULL) {
             JS_ThrowOutOfMemory(cx);
             goto fail;
         }
 
-        wkey->u.s.raw.length = key_data.length;
-        memcpy(wkey->u.s.raw.start, key_data.start, key_data.length);
+        wkey->u.s.raw.length = kd.length;
+        memcpy(wkey->u.s.raw.start, kd.start, kd.length);
         break;
     }
 
-    return qjs_promise_result(cx, key);
+    return key;
 
 fail:
 
@@ -4325,7 +4300,41 @@ fail:
 
     JS_FreeValue(cx, key);
 
-    return qjs_promise_result(cx, JS_EXCEPTION);
+    return JS_EXCEPTION;
+}
+
+
+static JSValue
+qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
+    JSValueConst *argv)
+{
+    JSValue                     ret, key;
+    unsigned                    usage;
+    qjs_webcrypto_algorithm_t   *alg;
+    qjs_webcrypto_key_format_t  fmt;
+
+    fmt = qjs_key_format(cx, argv[0]);
+    if (fmt == QJS_KEY_FORMAT_UNKNOWN) {
+        return JS_EXCEPTION;
+    }
+
+    alg = qjs_key_algorithm(cx, argv[2]);
+    if (alg == NULL) {
+        return JS_EXCEPTION;
+    }
+
+    ret = qjs_key_usage(cx, argv[4], &usage);
+    if (JS_IsException(ret)) {
+        return JS_EXCEPTION;
+    }
+
+    key = qjs_webcrypto_import_key_internal(cx, fmt, argv[1], alg, argv[2],
+                                            JS_ToBool(cx, argv[3]), usage);
+    if (JS_IsException(key)) {
+        return qjs_promise_result(cx, JS_EXCEPTION);
+    }
+
+    return qjs_promise_result(cx, key);
 }
 
 
@@ -4973,10 +4982,13 @@ static JSValue
 qjs_webcrypto_unwrap_key(JSContext *cx, JSValueConst this_val, int argc,
     JSValueConst *argv)
 {
-    unsigned                    mask, usage;
+    unsigned                    usage;
     JSValue                     options, ret, decrypted, key_value;
     njs_str_t                   data, key_data;
-    qjs_webcrypto_key_t         *wrapping_key, *ikey;
+    const char                  *str;
+    size_t                      len;
+    JSValue                     json_str;
+    qjs_webcrypto_key_t         *wrapping_key;
     qjs_webcrypto_algorithm_t   *alg, *key_alg;
     qjs_webcrypto_key_format_t  fmt;
 
@@ -5010,8 +5022,7 @@ qjs_webcrypto_unwrap_key(JSContext *cx, JSValueConst this_val, int argc,
         goto fail;
     }
 
-    mask = QJS_KEY_USAGE_UNWRAP_KEY;
-    if (!(wrapping_key->usage & mask)) {
+    if (!(wrapping_key->usage & QJS_KEY_USAGE_UNWRAP_KEY)) {
         JS_ThrowTypeError(cx, "unwrapping key does not support unwrapKey");
         goto fail;
     }
@@ -5029,89 +5040,48 @@ qjs_webcrypto_unwrap_key(JSContext *cx, JSValueConst this_val, int argc,
         goto fail;
     }
 
-    ret = qjs_typed_array_data(cx, decrypted, &key_data);
-    if (JS_IsException(ret)) {
-        JS_FreeValue(cx, decrypted);
-        goto fail;
-    }
-
-    if (usage & ~key_alg->usage) {
-        JS_FreeValue(cx, decrypted);
-        JS_ThrowTypeError(cx, "unsupported key usage for \"%s\" key",
-                          qjs_algorithm_string(key_alg));
-        goto fail;
-    }
-
-    if (!(fmt & key_alg->fmt)) {
-        JS_FreeValue(cx, decrypted);
-        JS_ThrowTypeError(cx, "unsupported key fmt for \"%s\" key",
-                          qjs_algorithm_string(key_alg));
-        goto fail;
-    }
+    if (fmt == QJS_KEY_FORMAT_JWK) {
+        ret = qjs_typed_array_data(cx, decrypted, &key_data);
+        if (JS_IsException(ret)) {
+            JS_FreeValue(cx, decrypted);
+            goto fail;
+        }
 
-    key_value = qjs_webcrypto_key_make(cx, key_alg, usage,
-                                       JS_ToBool(cx, argv[5]));
-    if (JS_IsException(key_value)) {
+        json_str = JS_NewStringLen(cx, (const char *) key_data.start,
+                                   key_data.length);
         JS_FreeValue(cx, decrypted);
-        goto fail;
-    }
-
-    ikey = JS_GetOpaque2(cx, key_value, QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
 
-    if (fmt == QJS_KEY_FORMAT_RAW) {
-        switch (key_alg->type) {
-        case QJS_ALGORITHM_AES_GCM:
-        case QJS_ALGORITHM_AES_CTR:
-        case QJS_ALGORITHM_AES_CBC:
-        case QJS_ALGORITHM_AES_KW:
-            switch (key_data.length) {
-            case 16:
-            case 24:
-            case 32:
-                break;
-            default:
-                JS_FreeValue(cx, decrypted);
-                JS_FreeValue(cx, key_value);
-                JS_ThrowTypeError(cx, "AES Invalid key length");
-                goto fail;
-            }
+        if (JS_IsException(json_str)) {
+            goto fail;
+        }
 
-            ikey->u.s.raw.start = js_malloc(cx, key_data.length);
-            if (ikey->u.s.raw.start == NULL) {
-                JS_FreeValue(cx, decrypted);
-                JS_FreeValue(cx, key_value);
-                JS_ThrowOutOfMemory(cx);
-                goto fail;
-            }
+        str = JS_ToCStringLen(cx, &len, json_str);
+        JS_FreeValue(cx, json_str);
 
-            ikey->u.s.raw.length = key_data.length;
-            memcpy(ikey->u.s.raw.start, key_data.start, key_data.length);
-            break;
+        if (str == NULL) {
+            goto fail;
+        }
 
-        case QJS_ALGORITHM_HMAC:
-        default:
-            ikey->u.s.raw.start = js_malloc(cx, key_data.length);
-            if (ikey->u.s.raw.start == NULL) {
-                JS_FreeValue(cx, decrypted);
-                JS_FreeValue(cx, key_value);
-                JS_ThrowOutOfMemory(cx);
-                goto fail;
-            }
+        decrypted = JS_ParseJSON(cx, str, len, "<unwrapKey>");
+        JS_FreeCString(cx, str);
 
-            ikey->u.s.raw.length = key_data.length;
-            memcpy(ikey->u.s.raw.start, key_data.start, key_data.length);
-            break;
+        if (JS_IsException(decrypted)) {
+            JS_ThrowTypeError(cx, "wrapped key is not valid JWK "
+                              "JSON");
+            goto fail;
         }
-
-    } else {
-        JS_FreeValue(cx, decrypted);
-        JS_FreeValue(cx, key_value);
-        JS_ThrowTypeError(cx, "unwrapKey: unsupported format");
-        goto fail;
     }
 
+    key_value = qjs_webcrypto_import_key_internal(cx, fmt, decrypted,
+                                                  key_alg, argv[4],
+                                                  JS_ToBool(cx, argv[5]),
+                                                  usage);
     JS_FreeValue(cx, decrypted);
 
+    if (JS_IsException(key_value)) {
+        goto fail;
+    }
+
     return qjs_promise_result(cx, key_value);
 
 fail:
index 9a5b6b0fdcfc37bed18079680a9ad90e3ff34713..b5fce41d5c0ab27507c9ce311ed61a29e5718e10 100644 (file)
@@ -171,6 +171,25 @@ let wrap_tsuite = {
                         counter: crypto.getRandomValues(new Uint8Array(16)),
                         length: 64},
           check_raw: true },
+
+        /* AES-GCM wrapping an AES key in JWK format */
+        { wrap_alg: {name: "AES-GCM", length: 256},
+          key_alg: {name: "AES-GCM", length: 128},
+          key_usage: ["encrypt", "decrypt"],
+          format: "jwk",
+          wrap_params: {name: "AES-GCM",
+                        iv: crypto.getRandomValues(new Uint8Array(12))},
+          verify_encrypt: {name: "AES-GCM",
+                           iv: crypto.getRandomValues(new Uint8Array(12))} },
+
+        /* AES-GCM wrapping an HMAC key in JWK format */
+        { wrap_alg: {name: "AES-GCM", length: 256},
+          key_alg: {name: "HMAC", hash: "SHA-256"},
+          key_usage: ["sign", "verify"],
+          format: "jwk",
+          wrap_params: {name: "AES-GCM",
+                        iv: crypto.getRandomValues(new Uint8Array(12))},
+          verify_sign: {name: "HMAC"} },
 ]};
 
 let wrap_error_tsuite = {