[njs] WebCrypto: extended support for symmetric keys.
Dmitry Volyntsev
xeioex at nginx.com
Thu Jan 5 04:42:47 UTC 2023
details: https://hg.nginx.org/njs/rev/2e3bbe8743af
branches:
changeset: 2021:2e3bbe8743af
user: Dmitry Volyntsev <xeioex at 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.
More information about the nginx-devel
mailing list