details: https://hg.nginx.org/njs/rev/2e3bbe8743af branches: changeset: 2021:2e3bbe8743af user: Dmitry Volyntsev <xei...@nginx.com> date: Wed Jan 04 18:07:30 2023 -0800 description: WebCrypto: extended support for symmetric keys.
The following functionality for HMAC and AES-* keys were added: importKey() supporting 'jwk' format, exportKey() supporting 'jwk' and 'raw' formats, generateKey(). diffstat: external/njs_webcrypto_module.c | 365 +++++++++++++++++++++++++++++++++++++-- test/ts/test.ts | 8 + test/webcrypto/export.t.js | 180 +++++++++++++++++++ test/webcrypto/sign.t.js | 19 ++ ts/njs_webcrypto.d.ts | 12 +- 5 files changed, 555 insertions(+), 29 deletions(-) diffs (806 lines): diff -r 0681bf662222 -r 2e3bbe8743af external/njs_webcrypto_module.c --- a/external/njs_webcrypto_module.c Wed Jan 04 17:49:22 2023 -0800 +++ b/external/njs_webcrypto_module.c Wed Jan 04 18:07:30 2023 -0800 @@ -167,7 +167,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_WRAP_KEY | NJS_KEY_USAGE_UNWRAP_KEY | NJS_KEY_USAGE_GENERATE_KEY, - NJS_KEY_FORMAT_RAW) + NJS_KEY_FORMAT_RAW | + NJS_KEY_FORMAT_JWK) }, { @@ -178,7 +179,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_WRAP_KEY | NJS_KEY_USAGE_UNWRAP_KEY | NJS_KEY_USAGE_GENERATE_KEY, - NJS_KEY_FORMAT_RAW) + NJS_KEY_FORMAT_RAW | + NJS_KEY_FORMAT_JWK) }, { @@ -189,7 +191,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_WRAP_KEY | NJS_KEY_USAGE_UNWRAP_KEY | NJS_KEY_USAGE_GENERATE_KEY, - NJS_KEY_FORMAT_RAW) + NJS_KEY_FORMAT_RAW | + NJS_KEY_FORMAT_JWK) }, { @@ -258,7 +261,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_GENERATE_KEY | NJS_KEY_USAGE_SIGN | NJS_KEY_USAGE_VERIFY, - NJS_KEY_FORMAT_RAW) + NJS_KEY_FORMAT_RAW | + NJS_KEY_FORMAT_JWK) }, { @@ -325,7 +329,7 @@ static njs_webcrypto_entry_t njs_webcryp static njs_str_t - njs_webcrypto_alg_name[NJS_ALGORITHM_RSA_OAEP + 1][NJS_HASH_SHA512 + 1] = { + njs_webcrypto_alg_name[NJS_ALGORITHM_HMAC + 1][NJS_HASH_SHA512 + 1] = { { njs_null_str, njs_str("RS1"), @@ -349,6 +353,37 @@ static njs_str_t njs_str("RSA-OAEP-384"), njs_str("RSA-OAEP-512"), }, + + { + njs_null_str, + njs_str("HS1"), + njs_str("HS256"), + njs_str("HS384"), + njs_str("HS512"), + }, +}; + +static njs_str_t njs_webcrypto_alg_aes_name[3][3 + 1] = { + { + njs_str("A128GCM"), + njs_str("A192GCM"), + njs_str("A256GCM"), + njs_null_str, + }, + + { + njs_str("A128CTR"), + njs_str("A192CTR"), + njs_str("A256CTR"), + njs_null_str, + }, + + { + njs_str("A128CBC"), + njs_str("A192CBC"), + njs_str("A256CBC"), + njs_null_str, + }, }; @@ -560,6 +595,7 @@ static const njs_value_t string_d = njs static const njs_value_t string_dp = njs_string("dp"); static const njs_value_t string_dq = njs_string("dq"); static const njs_value_t string_e = njs_string("e"); +static const njs_value_t string_k = njs_string("k"); static const njs_value_t string_n = njs_string("n"); static const njs_value_t string_p = njs_string("p"); static const njs_value_t string_q = njs_string("q"); @@ -570,6 +606,7 @@ static const njs_value_t string_ext = n static const njs_value_t string_crv = njs_string("crv"); static const njs_value_t string_kty = njs_string("kty"); static const njs_value_t key_ops = njs_string("key_ops"); +static const njs_value_t string_length = njs_string("length"); static njs_int_t njs_webcrypto_crypto_key_proto_id; @@ -1036,7 +1073,6 @@ njs_cipher_aes_ctr(njs_vm_t *vm, njs_str u_char iv2[16]; static const njs_value_t string_counter = njs_string("counter"); - static const njs_value_t string_length = njs_string("length"); switch (key->raw.length) { case 16: @@ -1356,7 +1392,6 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t static const njs_value_t string_info = njs_string("info"); static const njs_value_t string_salt = njs_string("salt"); - static const njs_value_t string_length = njs_string("length"); static const njs_value_t string_iterations = njs_string("iterations"); aobject = njs_arg(args, nargs, 1); @@ -2032,6 +2067,71 @@ njs_export_jwk_asymmetric(njs_vm_t *vm, static njs_int_t +njs_export_jwk_oct(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval) +{ + njs_int_t ret; + njs_str_t *nm; + njs_value_t k, alg, ops, extractable; + njs_webcrypto_alg_t type; + + static const njs_value_t oct_str = njs_string("oct"); + + njs_assert(key->raw.start != NULL) + + ret = njs_string_base64url(vm, &k, &key->raw); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + type = key->alg->type; + + if (key->alg->type == NJS_ALGORITHM_HMAC) { + nm = &njs_webcrypto_alg_name[type][key->hash]; + (void) njs_vm_value_string_set(vm, &alg, nm->start, nm->length); + + } else { + switch (key->raw.length) { + case 16: + case 24: + case 32: + nm = &njs_webcrypto_alg_aes_name + [type - NJS_ALGORITHM_AES_GCM][(key->raw.length - 16) / 8]; + (void) njs_vm_value_string_set(vm, &alg, nm->start, nm->length); + break; + + default: + njs_value_undefined_set(&alg); + break; + } + } + + ret = njs_key_ops(vm, &ops, key->usage); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + njs_value_boolean_set(&extractable, key->extractable); + + ret = njs_vm_object_alloc(vm, retval, &string_kty, &oct_str, &string_k, + &k, &key_ops, &ops, &string_ext, &extractable, + NULL); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + if (njs_is_defined(&alg)) { + ret = njs_value_property_set(vm, retval, njs_value_arg(&string_alg), + &alg); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + + return NJS_OK; +} + + +static njs_int_t njs_ext_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { @@ -2081,6 +2181,17 @@ njs_ext_export_key(njs_vm_t *vm, njs_val break; + case NJS_ALGORITHM_AES_GCM: + case NJS_ALGORITHM_AES_CTR: + case NJS_ALGORITHM_AES_CBC: + case NJS_ALGORITHM_HMAC: + ret = njs_export_jwk_oct(vm, key, &value); + if (njs_slow_path(ret != NJS_OK)) { + goto fail; + } + + break; + default: break; } @@ -2175,9 +2286,13 @@ njs_ext_export_key(njs_vm_t *vm, njs_val break; } - njs_internal_error(vm, "exporting as \"%V\" fmt is not implemented", - njs_format_string(fmt)); - goto fail; + ret = njs_vm_value_array_buffer_set(vm, &value, key->raw.start, + key->raw.length); + if (njs_slow_path(ret != NJS_OK)) { + goto fail; + } + + break; } return njs_webcrypto_result(vm, &value, NJS_OK); @@ -2386,6 +2501,57 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v break; + case NJS_ALGORITHM_AES_GCM: + case NJS_ALGORITHM_AES_CTR: + case NJS_ALGORITHM_AES_CBC: + case NJS_ALGORITHM_HMAC: + + if (alg->type == NJS_ALGORITHM_HMAC) { + ret = njs_algorithm_hash(vm, aobject, &key->hash); + if (njs_slow_path(ret == NJS_ERROR)) { + goto fail; + } + + key->raw.length = EVP_MD_size(njs_algorithm_hash_digest(key->hash)); + + } else { + ret = njs_value_property(vm, aobject, njs_value_arg(&string_length), + &value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto fail; + } + + key->raw.length = njs_number(&value) / 8; + + if (key->raw.length != 16 + && key->raw.length != 24 + && key->raw.length != 32) + { + njs_type_error(vm, "length for \"%V\" key should be one of " + "128, 192, 256", njs_algorithm_string(alg)); + goto fail; + } + } + + key->raw.start = njs_mp_alloc(njs_vm_memory_pool(vm), key->raw.length); + if (njs_slow_path(key->raw.start == NULL)) { + njs_memory_error(vm); + goto fail; + } + + if (RAND_bytes(key->raw.start, key->raw.length) <= 0) { + njs_webcrypto_error(vm, "RAND_bytes() failed"); + goto fail; + } + + ret = njs_vm_external_create(vm, &value, + njs_webcrypto_crypto_key_proto_id, key, 0); + if (njs_slow_path(ret != NJS_OK)) { + goto fail; + } + + break; + default: njs_internal_error(vm, "not implemented generateKey" "algorithm: \"%V\"", njs_algorithm_string(alg)); @@ -2901,6 +3067,124 @@ fail: static njs_int_t +njs_import_jwk_oct(njs_vm_t *vm, njs_value_t *jwk, njs_webcrypto_key_t *key) +{ + size_t size; + unsigned usage; + njs_int_t ret; + njs_str_t *a, alg, b64; + njs_value_t value; + njs_webcrypto_alg_t type; + njs_webcrypto_entry_t *w; + + static njs_webcrypto_entry_t hashes[] = { + { njs_str("HS1"), NJS_HASH_SHA1 }, + { njs_str("HS256"), NJS_HASH_SHA256 }, + { njs_str("HS384"), NJS_HASH_SHA384 }, + { njs_str("HS512"), NJS_HASH_SHA512 }, + { njs_null_str, 0 } + }; + + ret = njs_value_property(vm, jwk, njs_value_arg(&string_k), &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (!njs_value_is_string(&value)) { + njs_type_error(vm, "Invalid JWK oct key"); + return NJS_ERROR; + } + + njs_string_get(&value, &b64); + + (void) njs_decode_base64url_length(&b64, &key->raw.length); + + key->raw.start = njs_mp_alloc(njs_vm_memory_pool(vm), key->raw.length); + if (njs_slow_path(key->raw.start == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + njs_decode_base64url(&key->raw, &b64); + + ret = njs_value_property(vm, jwk, njs_value_arg(&string_alg), &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + size = 16; + + if (njs_value_is_string(&value)) { + njs_string_get(&value, &alg); + + if (key->alg->type == NJS_ALGORITHM_HMAC) { + for (w = &hashes[0]; w->name.length != 0; w++) { + if (njs_strstr_eq(&alg, &w->name)) { + key->hash = w->value; + goto done; + } + } + + } else { + type = key->alg->type; + a = &njs_webcrypto_alg_aes_name[type - NJS_ALGORITHM_AES_GCM][0]; + for (; a->length != 0; a++) { + if (njs_strstr_eq(&alg, a)) { + goto done; + } + + size += 8; + } + } + + njs_type_error(vm, "unexpected \"alg\" value \"%V\" for JWK key", &alg); + return NJS_ERROR; + } + +done: + + if (key->alg->type != NJS_ALGORITHM_HMAC) { + if (key->raw.length != size) { + njs_type_error(vm, "key size and \"alg\" value \"%V\" mismatch", + &alg); + return NJS_ERROR; + } + } + + ret = njs_value_property(vm, jwk, njs_value_arg(&key_ops), &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (njs_is_defined(&value)) { + ret = njs_key_usage(vm, &value, &usage); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + if ((key->usage & usage) != key->usage) { + njs_type_error(vm, "Key operations and usage mismatch"); + return NJS_ERROR; + } + } + + if (key->extractable) { + ret = njs_value_property(vm, jwk, njs_value_arg(&string_ext), &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (njs_is_defined(&value) && !njs_value_bool(&value)) { + njs_type_error(vm, "JWK oct is not extractable"); + return NJS_ERROR; + } + } + + return NJS_OK; +} + + +static njs_int_t njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { @@ -3057,6 +3341,12 @@ njs_ext_import_key(njs_vm_t *vm, njs_val goto fail; } + } else if (njs_strstr_eq(&kty, &njs_str_value("oct"))) { + ret = njs_import_jwk_oct(vm, jwk, key); + if (njs_slow_path(ret != NJS_OK)) { + goto fail; + } + } else { njs_type_error(vm, "invalid JWK key type: %V", &kty); goto fail; @@ -3182,34 +3472,54 @@ njs_ext_import_key(njs_vm_t *vm, njs_val break; case NJS_ALGORITHM_HMAC: - ret = njs_algorithm_hash(vm, options, &key->hash); - if (njs_slow_path(ret == NJS_ERROR)) { - goto fail; + if (fmt == NJS_KEY_FORMAT_RAW) { + ret = njs_algorithm_hash(vm, options, &key->hash); + if (njs_slow_path(ret == NJS_ERROR)) { + goto fail; + } + + key->raw = key_data; + + } else { + /* NJS_KEY_FORMAT_JWK. */ + + ret = njs_algorithm_hash(vm, options, &hash); + if (njs_slow_path(ret == NJS_ERROR)) { + goto fail; + } + + if (key->hash != NJS_HASH_UNSET && key->hash != hash) { + njs_type_error(vm, "HMAC JWK hash mismatch"); + goto fail; + } } - key->raw = key_data; break; case NJS_ALGORITHM_AES_GCM: case NJS_ALGORITHM_AES_CTR: case NJS_ALGORITHM_AES_CBC: - switch (key_data.length) { - case 16: - case 24: - case 32: - break; - - default: - njs_type_error(vm, "Invalid key length"); - goto fail; + if (fmt == NJS_KEY_FORMAT_RAW) { + switch (key_data.length) { + case 16: + case 24: + case 32: + break; + + default: + njs_type_error(vm, "AES Invalid key length"); + goto fail; + } + + key->raw = key_data; } - /* Fall through. */ + break; case NJS_ALGORITHM_PBKDF2: case NJS_ALGORITHM_HKDF: + default: key->raw = key_data; - default: break; } @@ -3869,6 +4179,11 @@ njs_key_usage(njs_vm_t *vm, njs_value_t njs_int_t ret; njs_iterator_args_t args; + if (!njs_value_is_object(value)) { + njs_type_error(vm, "\"keyUsages\" argument must be an Array"); + return NJS_ERROR; + } + ret = njs_object_length(vm, value, &length); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; diff -r 0681bf662222 -r 2e3bbe8743af test/ts/test.ts --- a/test/ts/test.ts Wed Jan 04 17:49:22 2023 -0800 +++ b/test/ts/test.ts Wed Jan 04 18:07:30 2023 -0800 @@ -188,6 +188,14 @@ async function crypto_object(keyData: Ar modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1])}, true, ['sign', 'verify']); + + let hkey = await crypto.subtle.generateKey({name: "HMAC", + hash: "SHA-384"}, + true, ['sign', 'verify']); + + let akey = await crypto.subtle.generateKey({name: "AES-GCM", + length: 256}, + true, ['encrypt', 'decrypt']); } function buffer(b: Buffer) { diff -r 0681bf662222 -r 2e3bbe8743af test/webcrypto/export.t.js --- a/test/webcrypto/export.t.js Wed Jan 04 17:49:22 2023 -0800 +++ b/test/webcrypto/export.t.js Wed Jan 04 18:07:30 2023 -0800 @@ -17,6 +17,12 @@ async function load_key(params) { return params.generate_keys.keys[type]; } + if (params.generate_key) { + return await crypto.subtle.generateKey(params.generate_key.alg, + params.generate_key.extractable, + params.generate_key.usage); + } + return await crypto.subtle.importKey(params.key.fmt, params.key.key, params.key.alg, @@ -43,6 +49,7 @@ async function test(params) { } if (!params.generate_keys + && !params.generate_key && (exp.startsWith && !exp.startsWith("ArrayBuffer:"))) { /* Check that exported key can be imported back. */ @@ -73,6 +80,9 @@ function p(args, default_opts) { case "jwk": key = load_jwk(params.key.key); break; + case "raw": + key = Buffer.from(params.key.key, "base64url"); + break; default: throw Error("Unknown encoding key format"); } @@ -291,8 +301,178 @@ let ec_tsuite = { expected: { kty: "EC", ext: true, key_ops: [ "sign" ], crv: "P-384" } }, ]}; +function validate_hmac_jwk(exp, params) { + let hash = params.generate_key.alg.hash; + let expected_len = Number(hash.slice(2)) / 8 * (4 / 3); + expected_len = Math.round(expected_len); + + validate_property(exp, 'k', expected_len); + + return true; +} + +let hmac_tsuite = { + name: "HMAC exporting", + skip: () => (!has_fs() || !has_webcrypto()), + T: test, + prepare_args: p, + opts: { + key: { fmt: "raw", + key: "c2VjcmV0LUtleTE", + alg: { name: "HMAC", hash: "SHA-256" }, + extractable: true, + usage: [ "sign", "verify" ] }, + export: { fmt: "jwk" }, + expected: { kty: "oct", ext: true }, + }, + + tests: [ + { expected: { key_ops: [ "sign", "verify" ], + alg: "HS256", + k: "c2VjcmV0LUtleTE" } }, + { export: { fmt: "raw" }, + expected: "ArrayBuffer:c2VjcmV0LUtleTE" }, + { export: { fmt: "spki" }, + exception: "TypeError: unsupported key fmt \"spki\" for \"HMAC\"" }, + { export: { fmt: "pksc8" }, + exception: "TypeError: unsupported key fmt \"pksc8\" for \"HMAC\"" }, + { key: { key: "cDBzc3dE", + alg: { hash: "SHA-384" } }, + expected: { key_ops: [ "sign", "verify" ], + alg: "HS384", + k: "cDBzc3dE" } }, + { key: { extractable: false }, + exception: "TypeError: provided key cannot be extracted" }, + + { key: { fmt: "jwk", + key: { kty: "oct", ext: true, k: "c2VjcmV0LUtleTE", alg: "HS256" } }, + expected: { key_ops: [ "sign", "verify" ], + alg: "HS256", + k: "c2VjcmV0LUtleTE" } }, + { key: { fmt: "jwk", + alg: { hash: "SHA-512" }, + key: { kty: "oct", ext: true, k: "c2VjcmV0LUtleTE", alg: "HS512" } }, + expected: { key_ops: [ "sign", "verify" ], + alg: "HS512", + k: "c2VjcmV0LUtleTE" } }, + { key: { fmt: "jwk", + key: { kty: "oct", ext: true, k: "c2VjcmV0LUtleTE", alg: "HS256" }, + alg: { hash: "SHA-384" } }, + exception: "TypeError: HMAC JWK hash mismatch" }, + + { generate_key: { alg: { name: "HMAC", + hash: "SHA-256" }, + extractable: true, + usage: [ "sign", "verify" ] }, + check: validate_hmac_jwk, + expected: { key_ops: [ "sign", "verify" ], alg: "HS256" } }, + { generate_key: { alg: { name: "HMAC", + hash: "SHA-1" }, + extractable: true, + usage: [ "verify" ] }, + check: validate_hmac_jwk, + expected: { key_ops: [ "verify" ], alg: "HS1" } }, + { generate_key: { alg: { name: "HMAC", + hash: "SHA-384" }, + extractable: true, + usage: [ "sign" ] }, + check: validate_hmac_jwk, + expected: { key_ops: [ "sign" ], alg: "HS384" } }, + { generate_key: { alg: { name: "HMAC", + hash: "SHA-512" }, + extractable: true, + usage: [ "sign" ] }, + check: validate_hmac_jwk, + expected: { key_ops: [ "sign" ], alg: "HS512" } }, +]}; + +function validate_aes_jwk(exp, params) { + let expected_len = params.generate_key.alg.length; + expected_len = expected_len / 8 * (4 / 3); + expected_len = Math.round(expected_len); + + validate_property(exp, 'k', expected_len); + + return true; +} + +let aes_tsuite = { + name: "AES exporting", + skip: () => (!has_fs() || !has_webcrypto()), + T: test, + prepare_args: p, + opts: { + key: { fmt: "raw", + key: "ABEiMwARIjMAESIzABEiMw", + alg: { name: "AES-GCM" }, + extractable: true, + usage: [ "encrypt" ] }, + export: { fmt: "jwk" }, + expected: { kty: "oct", ext: true }, + }, + + tests: [ + { expected: { key_ops: [ "encrypt" ], + alg: "A128GCM", + k: "ABEiMwARIjMAESIzABEiMw" } }, + { export: { fmt: "raw" }, + expected: "ArrayBuffer:ABEiMwARIjMAESIzABEiMw" }, + { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIz", + alg: { name: "AES-CBC" }, + usage: [ "decrypt" ] }, + expected: { key_ops: [ "decrypt" ], + alg: "A192CBC", + k: "ABEiMwARIjMAESIzABEiMwARIjMAESIz" } }, + { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIz", + alg: { name: "AES-CBC" }, + usage: [ "decrypt" ] }, + export: { fmt: "raw" }, + expected: "ArrayBuffer:ABEiMwARIjMAESIzABEiMwARIjMAESIz" }, + { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM", + alg: { name: "AES-CTR" }, + usage: [ "decrypt" ] }, + expected: { key_ops: [ "decrypt" ], + alg: "A256CTR", + k: "ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM" } }, + { key: { key: "ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM", + alg: { name: "AES-CTR" }, + usage: [ "decrypt" ] }, + export: { fmt: "raw" }, + expected: "ArrayBuffer:ABEiMwARIjMAESIzABEiMwARIjMAESIzABEiMwARIjM" }, + + { generate_key: { alg: { name: "AES-GCM", length: 128 }, + extractable: true, + usage: [ "encrypt" ] }, + check: validate_aes_jwk, + expected: { key_ops: [ "encrypt" ], alg: "A128GCM" } }, + { generate_key: { alg: { name: "AES-CTR", length: 192 }, + extractable: true, + usage: [ "decrypt" ] }, + check: validate_aes_jwk, + expected: { key_ops: [ "decrypt" ], alg: "A192CTR" } }, + { generate_key: { alg: { name: "AES-CBC", length: 256 }, + extractable: true, + usage: [ "decrypt" ] }, + check: validate_aes_jwk, + expected: { key_ops: [ "decrypt" ], alg: "A256CBC" } }, + { generate_key: { alg: { name: "AES-GCM", length: 128 }, + extractable: false, + usage: [ "decrypt" ] }, + exception: "TypeError: provided key cannot be extracted" }, + { generate_key: { alg: { name: "AES-GCM" }, + extractable: false, + usage: [ "decrypt" ] }, + exception: "TypeError: length for \"AES-GCM\" key should be one of 128, 192, 256" }, + { generate_key: { alg: { name: "AES-GCM", length: 25 }, + extractable: false, + usage: [ "decrypt" ] }, + exception: "TypeError: length for \"AES-GCM\" key should be one of 128, 192, 256" }, +]}; + run([ rsa_tsuite, ec_tsuite, + hmac_tsuite, + aes_tsuite, ]) .then($DONE, $DONE); diff -r 0681bf662222 -r 2e3bbe8743af test/webcrypto/sign.t.js --- a/test/webcrypto/sign.t.js Wed Jan 04 17:49:22 2023 -0800 +++ b/test/webcrypto/sign.t.js Wed Jan 04 18:07:30 2023 -0800 @@ -180,6 +180,25 @@ let hmac_tsuite = { expected: "0540c587e7ee607fb4fd5e814438ed50f261c244" }, { sign_alg: { name: "ECDSA" }, exception: "TypeError: cannot sign using \"HMAC\" with \"ECDSA\" key" }, + { sign_key: { fmt: "jwk", + key: { kty: "oct", + alg: "HS256", + k: "c2VjcmV0S0VZ" } }, + expected: "76d4f1b22d7544c34e86380c9ab7c756311810dc31e4af3b705045d263db1212" }, + { sign_key: { fmt: "jwk", + key: { kty: "oct", + alg: "HS256", + key_ops: [ "sign" ], + k: "c2VjcmV0S0VZ" } }, + verify: true, + expected: true }, + { sign_key: { fmt: "jwk", + key: { kty: "oct", + alg: "HS256", + key_ops: [ "verify" ], + k: "c2VjcmV0S0VZ" } }, + exception: "TypeError: Key operations and usage mismatch" }, + { verify: true, expected: true }, { verify: true, import_alg: { hash: "SHA-384" }, expected: true }, { verify: true, import_alg: { hash: "SHA-512" }, expected: true }, diff -r 0681bf662222 -r 2e3bbe8743af ts/njs_webcrypto.d.ts --- a/ts/njs_webcrypto.d.ts Wed Jan 04 17:49:22 2023 -0800 +++ b/ts/njs_webcrypto.d.ts Wed Jan 04 18:07:30 2023 -0800 @@ -72,11 +72,14 @@ type ImportAlgorithm = type GenerateAlgorithm = | RsaHashedKeyGenParams - | EcKeyGenParams; + | EcKeyGenParams + | HmacKeyGenParams + | AesKeyGenParams; type JWK = | { kty: "RSA"; } - | { kty: "EC"; }; + | { kty: "EC"; } + | { kty: "oct"; }; type KeyData = | NjsStringOrBuffer @@ -230,7 +233,8 @@ interface SubtleCrypto { key: CryptoKey): Promise<ArrayBuffer|Object>; /** - * Generates a keypair for asymmetric algorithms. + * Generates a key for symmetric algorithms or a keypair + * for asymmetric algorithms. * * @since 0.7.10 * @param algorithm Dictionary object defining the type of key to generate @@ -242,7 +246,7 @@ interface SubtleCrypto { */ generateKey(algorithm: GenerateAlgorithm, extractable: boolean, - usage: Array<string>): Promise<CryptoKeyPair>; + usage: Array<string>): Promise<CryptoKey|CryptoKeyPair>; /** * Generates a digital signature. _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel