From: Dmitry Volyntsev Date: Sat, 4 Apr 2026 00:47:57 +0000 (-0700) Subject: WebCrypto: added JWK unwrap() support. X-Git-Tag: 0.9.7~2 X-Git-Url: http://www.kaiwu.me/postgresql/commit/static/gitweb.js?a=commitdiff_plain;h=79d5b1866738088c359681b62499d27b70f210ea;p=njs.git WebCrypto: added JWK unwrap() support. Added JWK format support to unwrapKey: decrypted data is parsed as JSON and imported through the shared import path. --- diff --git a/external/njs_webcrypto_module.c b/external/njs_webcrypto_module.c index a421dfac..bd464626 100644 --- a/external/njs_webcrypto_module.c +++ b/external/njs_webcrypto_module.c @@ -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; } diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index b4f97bcc..965918be 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -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, ""); + 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: diff --git a/test/webcrypto/wrap.t.mjs b/test/webcrypto/wrap.t.mjs index 9a5b6b0f..b5fce41d 100644 --- a/test/webcrypto/wrap.t.mjs +++ b/test/webcrypto/wrap.t.mjs @@ -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 = {