if [ $njs_found = yes ]; then
+ njs_feature="EVP_aes_128_wrap()"
+ njs_feature_name=NJS_HAVE_AES_WRAP
+ njs_feature_run=
+ njs_feature_test="#include <openssl/evp.h>
+
+ int main() {
+ (void) EVP_aes_128_wrap();
+ return 0;
+ }"
+ . auto/feature
+
njs_feature="OpenSSL version"
njs_feature_name=NJS_OPENSSL_VERSION
njs_feature_run=value
NJS_ALGORITHM_AES_GCM,
NJS_ALGORITHM_AES_CTR,
NJS_ALGORITHM_AES_CBC,
+ NJS_ALGORITHM_AES_KW,
NJS_ALGORITHM_ECDSA,
NJS_ALGORITHM_ECDH,
NJS_ALGORITHM_PBKDF2,
static njs_int_t njs_cipher_aes_cbc(njs_vm_t *vm, njs_str_t *data,
njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt,
njs_value_t *retval);
+#if (NJS_HAVE_AES_WRAP)
+static njs_int_t njs_cipher_aes_kw(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_bool_t encrypt, njs_value_t *retval);
+#endif
static njs_int_t njs_ext_derive(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t derive_key, njs_value_t *retval);
static njs_int_t njs_ext_digest(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_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,
+ njs_webcrypto_key_t *key, njs_webcrypto_key_format_t fmt,
+ njs_value_t *retval);
+static njs_int_t njs_webcrypto_cipher_core(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options,
+ njs_webcrypto_algorithm_t *alg, njs_bool_t encrypt, njs_value_t *retval);
static njs_int_t njs_ext_unwrap_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_ext_wrap_key(njs_vm_t *vm, njs_value_t *args,
1)
},
+#if (NJS_HAVE_AES_WRAP)
+ {
+ njs_str("AES-KW"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_AES_KW,
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_RAW |
+ NJS_KEY_FORMAT_JWK,
+ 1)
+ },
+#endif
+
{
njs_str("ECDSA"),
njs_webcrypto_algorithm(NJS_ALGORITHM_ECDSA,
},
};
-static njs_str_t njs_webcrypto_alg_aes_name[3][3 + 1] = {
+static njs_str_t njs_webcrypto_alg_aes_name[4][3 + 1] = {
{
njs_str("A128GCM"),
njs_str("A192GCM"),
njs_str("A256CBC"),
njs_null_str,
},
+
+ {
+ njs_str("A128KW"),
+ njs_str("A192KW"),
+ njs_str("A256KW"),
+ njs_null_str,
+ },
};
}
mask = encrypt ? NJS_KEY_USAGE_ENCRYPT : NJS_KEY_USAGE_DECRYPT;
+
+ if (alg->type == NJS_ALGORITHM_AES_KW) {
+ mask = encrypt ? NJS_KEY_USAGE_WRAP_KEY : NJS_KEY_USAGE_UNWRAP_KEY;
+ }
+
if (njs_slow_path(!(key->usage & mask))) {
njs_vm_type_error(vm, "provide key does not support %s operation",
encrypt ? "encrypt" : "decrypt");
goto fail;
}
- switch (alg->type) {
- case NJS_ALGORITHM_RSA_OAEP:
- ret = njs_cipher_pkey(vm, &data, key, encrypt, njs_value_arg(&result));
- break;
-
- case NJS_ALGORITHM_AES_GCM:
- ret = njs_cipher_aes_gcm(vm, &data, key, options, encrypt,
- njs_value_arg(&result));
- break;
-
- case NJS_ALGORITHM_AES_CTR:
- ret = njs_cipher_aes_ctr(vm, &data, key, options, encrypt,
- njs_value_arg(&result));
- break;
-
- case NJS_ALGORITHM_AES_CBC:
- default:
- ret = njs_cipher_aes_cbc(vm, &data, key, options, encrypt,
- njs_value_arg(&result));
- }
+ ret = njs_webcrypto_cipher_core(vm, &data, key, options, alg, encrypt,
+ njs_value_arg(&result));
return njs_webcrypto_result(vm, &result, ret, retval);
}
+#if (NJS_HAVE_AES_WRAP)
+static njs_int_t
+njs_cipher_aes_kw(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key,
+ njs_bool_t encrypt, njs_value_t *retval)
+{
+ int olen, dstlen;
+ u_char *dst;
+ njs_int_t ret;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+
+ if (encrypt) {
+ if (data->length < 16) {
+ njs_vm_type_error(vm, "AES-KW data must be at least 16 bytes");
+ return NJS_ERROR;
+ }
+
+ if (data->length % 8 != 0) {
+ njs_vm_type_error(vm, "AES-KW data must be a multiple of 8 bytes");
+ return NJS_ERROR;
+ }
+
+ } else {
+ if (data->length < 24) {
+ njs_vm_type_error(vm, "AES-KW data must be at least 24 bytes");
+ return NJS_ERROR;
+ }
+
+ if (data->length % 8 != 0) {
+ njs_vm_type_error(vm, "AES-KW data must be a multiple of 8 bytes");
+ return NJS_ERROR;
+ }
+ }
+
+ switch (key->u.s.raw.length) {
+ case 16:
+ cipher = EVP_aes_128_wrap();
+ break;
+
+ case 24:
+ cipher = EVP_aes_192_wrap();
+ break;
+
+ case 32:
+ cipher = EVP_aes_256_wrap();
+ break;
+
+ default:
+ njs_vm_type_error(vm, "AES-KW Invalid key length");
+ return NJS_ERROR;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+
+ ret = EVP_CipherInit_ex(ctx, cipher, NULL, key->u.s.raw.start, NULL,
+ encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ dstlen = data->length + (encrypt ? 8 : 0);
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm), dstlen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_vm_memory_error(vm);
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CipherUpdate(ctx, dst, &olen, data->start, data->length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = njs_vm_value_array_buffer_set(vm, retval, dst, olen);
+
+fail:
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ret;
+}
+#endif
+
+
static njs_int_t
njs_ext_derive_ecdh(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t derive_key, njs_webcrypto_key_t *key, njs_value_t *retval)
case NJS_ALGORITHM_AES_GCM:
case NJS_ALGORITHM_AES_CTR:
case NJS_ALGORITHM_AES_CBC:
+ case NJS_ALGORITHM_AES_KW:
- if (length != 16 && length != 32) {
+ if (length != 16 && length != 24 && length != 32) {
njs_vm_type_error(vm, "deriveKey \"%V\" length must be "
- "128 or 256", njs_algorithm_string(dalg));
+ "128, 192, or 256",
+ njs_algorithm_string(dalg));
goto fail;
}
njs_ext_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused, njs_value_t *retval)
{
- BIO *bio;
- BUF_MEM *mem;
njs_int_t ret;
njs_webcrypto_key_t *key;
- PKCS8_PRIV_KEY_INFO *pkcs8;
njs_opaque_value_t value;
njs_webcrypto_key_format_t fmt;
goto fail;
}
- switch (fmt) {
- case NJS_KEY_FORMAT_JWK:
- switch (key->alg->type) {
- case NJS_ALGORITHM_RSASSA_PKCS1_v1_5:
- case NJS_ALGORITHM_RSA_PSS:
- case NJS_ALGORITHM_RSA_OAEP:
- case NJS_ALGORITHM_ECDSA:
- case NJS_ALGORITHM_ECDH:
- ret = njs_export_jwk_asymmetric(vm, key, njs_value_arg(&value));
- if (njs_slow_path(ret != NJS_OK)) {
- goto fail;
- }
-
- 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, njs_value_arg(&value));
- if (njs_slow_path(ret != NJS_OK)) {
- goto fail;
- }
-
- break;
-
- default:
- break;
- }
-
- break;
-
- case NJS_KEY_FORMAT_PKCS8:
- if (!key->u.a.privat) {
- njs_vm_type_error(vm, "public key of \"%V\" cannot be exported "
- "as PKCS8", njs_algorithm_string(key->alg));
- goto fail;
- }
-
- bio = BIO_new(BIO_s_mem());
- if (njs_slow_path(bio == NULL)) {
- njs_webcrypto_error(vm, "BIO_new(BIO_s_mem()) failed");
- goto fail;
- }
-
- njs_assert(key->u.a.pkey != NULL);
-
- pkcs8 = EVP_PKEY2PKCS8(key->u.a.pkey);
- if (njs_slow_path(pkcs8 == NULL)) {
- BIO_free(bio);
- njs_webcrypto_error(vm, "EVP_PKEY2PKCS8() failed");
- goto fail;
- }
-
- if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio, pkcs8)) {
- BIO_free(bio);
- PKCS8_PRIV_KEY_INFO_free(pkcs8);
- njs_webcrypto_error(vm, "i2d_PKCS8_PRIV_KEY_INFO_bio() failed");
- goto fail;
- }
-
- BIO_get_mem_ptr(bio, &mem);
-
- ret = njs_webcrypto_array_buffer(vm, njs_value_arg(&value),
- (u_char *) mem->data, mem->length);
-
- BIO_free(bio);
- PKCS8_PRIV_KEY_INFO_free(pkcs8);
-
- if (njs_slow_path(ret != NJS_OK)) {
- goto fail;
- }
-
- break;
-
- case NJS_KEY_FORMAT_SPKI:
- if (key->u.a.privat) {
- njs_vm_type_error(vm, "private key of \"%V\" cannot be exported "
- "as SPKI", njs_algorithm_string(key->alg));
- goto fail;
- }
-
- bio = BIO_new(BIO_s_mem());
- if (njs_slow_path(bio == NULL)) {
- njs_webcrypto_error(vm, "BIO_new(BIO_s_mem()) failed");
- goto fail;
- }
-
- njs_assert(key->u.a.pkey != NULL);
-
- if (!i2d_PUBKEY_bio(bio, key->u.a.pkey)) {
- BIO_free(bio);
- njs_webcrypto_error(vm, "i2d_PUBKEY_bio() failed");
- goto fail;
- }
-
- BIO_get_mem_ptr(bio, &mem);
-
- ret = njs_webcrypto_array_buffer(vm, njs_value_arg(&value),
- (u_char *) mem->data, mem->length);
-
- BIO_free(bio);
-
- if (njs_slow_path(ret != NJS_OK)) {
- goto fail;
- }
-
- break;
-
- case NJS_KEY_FORMAT_RAW:
- default:
- if (key->alg->type == NJS_ALGORITHM_ECDSA
- || key->alg->type == NJS_ALGORITHM_ECDH)
- {
- ret = njs_export_raw_ec(vm, key, njs_value_arg(&value));
- if (njs_slow_path(ret != NJS_OK)) {
- goto fail;
- }
-
- break;
- }
-
- ret = njs_vm_value_array_buffer_set(vm, njs_value_arg(&value),
- key->u.s.raw.start,
- key->u.s.raw.length);
- if (njs_slow_path(ret != NJS_OK)) {
- goto fail;
- }
-
- break;
+ ret = njs_webcrypto_export_key_raw(vm, key, fmt, njs_value_arg(&value));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
}
return njs_webcrypto_result(vm, &value, NJS_OK, retval);
case NJS_ALGORITHM_AES_GCM:
case NJS_ALGORITHM_AES_CTR:
case NJS_ALGORITHM_AES_CBC:
+ case NJS_ALGORITHM_AES_KW:
case NJS_ALGORITHM_HMAC:
if (alg->type == NJS_ALGORITHM_HMAC) {
case NJS_ALGORITHM_AES_GCM:
case NJS_ALGORITHM_AES_CTR:
case NJS_ALGORITHM_AES_CBC:
+ case NJS_ALGORITHM_AES_KW:
if (fmt == NJS_KEY_FORMAT_RAW) {
switch (key_data.length) {
case 16:
static njs_int_t
-njs_ext_unwrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
- njs_index_t unused, njs_value_t *retval)
+njs_webcrypto_export_key_raw(njs_vm_t *vm, njs_webcrypto_key_t *key,
+ njs_webcrypto_key_format_t fmt, njs_value_t *retval)
{
- njs_vm_internal_error(vm, "\"unwrapKey\" not implemented");
+ BIO *bio;
+ BUF_MEM *mem;
+ njs_int_t ret;
+ PKCS8_PRIV_KEY_INFO *pkcs8;
+
+ switch (fmt) {
+ case NJS_KEY_FORMAT_JWK:
+ switch (key->alg->type) {
+ case NJS_ALGORITHM_RSASSA_PKCS1_v1_5:
+ case NJS_ALGORITHM_RSA_PSS:
+ case NJS_ALGORITHM_RSA_OAEP:
+ case NJS_ALGORITHM_ECDSA:
+ case NJS_ALGORITHM_ECDH:
+ return njs_export_jwk_asymmetric(vm, key, retval);
+
+ case NJS_ALGORITHM_AES_GCM:
+ case NJS_ALGORITHM_AES_CTR:
+ case NJS_ALGORITHM_AES_CBC:
+ case NJS_ALGORITHM_AES_KW:
+ case NJS_ALGORITHM_HMAC:
+ return njs_export_jwk_oct(vm, key, retval);
+
+ default:
+ break;
+ }
+
+ break;
+
+ case NJS_KEY_FORMAT_PKCS8:
+ if (!key->u.a.privat) {
+ njs_vm_type_error(vm, "public key of \"%V\" cannot be exported "
+ "as PKCS8", njs_algorithm_string(key->alg));
+ return NJS_ERROR;
+ }
+
+ bio = BIO_new(BIO_s_mem());
+ if (njs_slow_path(bio == NULL)) {
+ njs_webcrypto_error(vm, "BIO_new(BIO_s_mem()) failed");
+ return NJS_ERROR;
+ }
+
+ njs_assert(key->u.a.pkey != NULL);
+
+ pkcs8 = EVP_PKEY2PKCS8(key->u.a.pkey);
+ if (njs_slow_path(pkcs8 == NULL)) {
+ BIO_free(bio);
+ njs_webcrypto_error(vm, "EVP_PKEY2PKCS8() failed");
+ return NJS_ERROR;
+ }
+
+ if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio, pkcs8)) {
+ BIO_free(bio);
+ PKCS8_PRIV_KEY_INFO_free(pkcs8);
+ njs_webcrypto_error(vm,
+ "i2d_PKCS8_PRIV_KEY_INFO_bio() failed");
+ return NJS_ERROR;
+ }
+
+ BIO_get_mem_ptr(bio, &mem);
+
+ ret = njs_webcrypto_array_buffer(vm, retval, (u_char *) mem->data,
+ mem->length);
+ BIO_free(bio);
+ PKCS8_PRIV_KEY_INFO_free(pkcs8);
+
+ return ret;
+
+ case NJS_KEY_FORMAT_SPKI:
+ if (key->u.a.privat) {
+ njs_vm_type_error(vm, "private key of \"%V\" cannot be exported "
+ "as SPKI", njs_algorithm_string(key->alg));
+ return NJS_ERROR;
+ }
+
+ bio = BIO_new(BIO_s_mem());
+ if (njs_slow_path(bio == NULL)) {
+ njs_webcrypto_error(vm, "BIO_new(BIO_s_mem()) failed");
+ return NJS_ERROR;
+ }
+
+ njs_assert(key->u.a.pkey != NULL);
+
+ if (!i2d_PUBKEY_bio(bio, key->u.a.pkey)) {
+ BIO_free(bio);
+ njs_webcrypto_error(vm, "i2d_PUBKEY_bio() failed");
+ return NJS_ERROR;
+ }
+
+ BIO_get_mem_ptr(bio, &mem);
+
+ ret = njs_webcrypto_array_buffer(vm, retval, (u_char *) mem->data,
+ mem->length);
+ BIO_free(bio);
+
+ return ret;
+
+ case NJS_KEY_FORMAT_RAW:
+ default:
+ if (key->alg->type == NJS_ALGORITHM_ECDSA
+ || key->alg->type == NJS_ALGORITHM_ECDH)
+ {
+ return njs_export_raw_ec(vm, key, retval);
+ }
+
+ return njs_vm_value_array_buffer_set(vm, retval,
+ key->u.s.raw.start,
+ key->u.s.raw.length);
+ }
+
return NJS_ERROR;
}
+static njs_int_t
+njs_webcrypto_cipher_core(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options,
+ njs_webcrypto_algorithm_t *alg, njs_bool_t encrypt, njs_value_t *retval)
+{
+ switch (alg->type) {
+ case NJS_ALGORITHM_RSA_OAEP:
+ return njs_cipher_pkey(vm, data, key, encrypt, retval);
+
+ case NJS_ALGORITHM_AES_GCM:
+ return njs_cipher_aes_gcm(vm, data, key, options, encrypt, retval);
+
+ case NJS_ALGORITHM_AES_CTR:
+ return njs_cipher_aes_ctr(vm, data, key, options, encrypt, retval);
+
+ case NJS_ALGORITHM_AES_CBC:
+ return njs_cipher_aes_cbc(vm, data, key, options, encrypt, retval);
+
+#if (NJS_HAVE_AES_WRAP)
+ case NJS_ALGORITHM_AES_KW:
+ return njs_cipher_aes_kw(vm, data, key, encrypt, retval);
+#endif
+
+ default:
+ njs_vm_type_error(vm, "not implemented");
+ return NJS_ERROR;
+ }
+}
+
+
static njs_int_t
njs_ext_wrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused, njs_value_t *retval)
{
- njs_vm_internal_error(vm, "\"wrapKey\" not implemented");
- return NJS_ERROR;
+ njs_int_t ret;
+ njs_str_t data;
+ njs_value_t *options;
+ njs_opaque_value_t exported, result;
+ njs_webcrypto_key_t *key, *wrapping_key;
+ 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;
+ }
+
+ key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id,
+ njs_arg(args, nargs, 2));
+ if (njs_slow_path(key == NULL)) {
+ njs_vm_type_error(vm, "\"key\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ wrapping_key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id,
+ njs_arg(args, nargs, 3));
+ if (njs_slow_path(wrapping_key == NULL)) {
+ njs_vm_type_error(vm, "\"wrappingKey\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ options = njs_arg(args, nargs, 4);
+ alg = njs_key_algorithm(vm, options);
+ if (njs_slow_path(alg == NULL)) {
+ goto fail;
+ }
+
+ if (njs_slow_path(!key->extractable)) {
+ njs_vm_type_error(vm, "provided key cannot be extracted");
+ goto fail;
+ }
+
+ if (njs_slow_path(!(wrapping_key->usage & NJS_KEY_USAGE_WRAP_KEY))) {
+ njs_vm_type_error(vm, "wrapping key does not support wrapKey");
+ goto fail;
+ }
+
+ if (njs_slow_path(wrapping_key->alg != alg)) {
+ njs_vm_type_error(vm, "cannot wrap using \"%V\" with \"%V\" key",
+ njs_algorithm_string(wrapping_key->alg),
+ njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ ret = njs_webcrypto_export_key_raw(vm, key, fmt,
+ njs_value_arg(&exported));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ if (fmt == NJS_KEY_FORMAT_JWK) {
+ njs_opaque_value_t json_str;
+
+ ret = njs_vm_json_stringify(vm, njs_value_arg(&exported), 1,
+ njs_value_arg(&json_str));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ njs_value_assign(&exported, &json_str);
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &data, njs_value_arg(&exported));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ ret = njs_webcrypto_cipher_core(vm, &data, wrapping_key, options,
+ alg, 1, njs_value_arg(&result));
+
+ return njs_webcrypto_result(vm, &result, ret, retval);
+
+fail:
+
+ return njs_webcrypto_result(vm, NULL, NJS_ERROR, retval);
+}
+
+
+static njs_int_t
+njs_ext_unwrap_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_str_t data, key_data;
+ njs_value_t *options;
+ njs_bool_t extractable;
+ njs_opaque_value_t decrypted, value;
+ njs_webcrypto_key_t *wrapping_key, *ikey;
+ njs_webcrypto_algorithm_t *alg, *key_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;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 2));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ wrapping_key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id,
+ njs_arg(args, nargs, 3));
+ if (njs_slow_path(wrapping_key == NULL)) {
+ njs_vm_type_error(vm, "\"unwrappingKey\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ options = njs_arg(args, nargs, 4);
+ alg = njs_key_algorithm(vm, options);
+ if (njs_slow_path(alg == NULL)) {
+ goto fail;
+ }
+
+ key_alg = njs_key_algorithm(vm, njs_arg(args, nargs, 5));
+ if (njs_slow_path(key_alg == NULL)) {
+ goto fail;
+ }
+
+ extractable = njs_value_bool(njs_arg(args, nargs, 6));
+
+ ret = njs_key_usage(vm, njs_arg(args, nargs, 7), &usage);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ if (njs_slow_path(!(wrapping_key->usage & NJS_KEY_USAGE_UNWRAP_KEY))) {
+ njs_vm_type_error(vm, "unwrapping key does not support unwrapKey");
+ goto fail;
+ }
+
+ if (njs_slow_path(wrapping_key->alg != alg)) {
+ njs_vm_type_error(vm, "cannot unwrap using \"%V\" with \"%V\" key",
+ njs_algorithm_string(wrapping_key->alg),
+ njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ ret = njs_webcrypto_cipher_core(vm, &data, wrapping_key, options,
+ alg, 0, njs_value_arg(&decrypted));
+ if (njs_slow_path(ret != NJS_OK)) {
+ 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_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;
+ }
+
+ ikey->u.s.raw = key_data;
+ break;
+
+ case NJS_ALGORITHM_HMAC:
+ default:
+ ikey->u.s.raw = key_data;
+ break;
+ }
+
+ } else {
+ njs_vm_type_error(vm, "unwrapKey: unsupported format \"%V\"",
+ njs_format_string(fmt));
+ goto fail;
+ }
+
+ ret = njs_vm_external_create(vm, njs_value_arg(&value),
+ njs_webcrypto_crypto_key_proto_id, ikey, 0);
+ 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);
}
case NJS_ALGORITHM_AES_GCM:
case NJS_ALGORITHM_AES_CTR:
case NJS_ALGORITHM_AES_CBC:
+ case NJS_ALGORITHM_AES_KW:
/* AesKeyGenParams */
njs_value_number_set(njs_value_arg(&val), key->u.s.raw.length * 8);
QJS_ALGORITHM_AES_GCM,
QJS_ALGORITHM_AES_CTR,
QJS_ALGORITHM_AES_CBC,
+ QJS_ALGORITHM_AES_KW,
QJS_ALGORITHM_ECDSA,
QJS_ALGORITHM_ECDH,
QJS_ALGORITHM_PBKDF2,
qjs_webcrypto_key_t *key, JSValue options, int encrypt);
static JSValue qjs_cipher_aes_cbc(JSContext *cx, njs_str_t *data,
qjs_webcrypto_key_t *key, JSValue options, int encrypt);
+#if (NJS_HAVE_AES_WRAP)
+static JSValue qjs_cipher_aes_kw(JSContext *cx, njs_str_t *data,
+ qjs_webcrypto_key_t *key, int encrypt);
+#endif
static JSValue qjs_derive_ecdh(JSContext *cx, JSValueConst *argv, int argc,
int derive_key, qjs_webcrypto_key_t *key);
static JSValue qjs_webcrypto_derive(JSContext *cx, JSValueConst this_val,
int argc, JSValueConst *argv);
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,
+ int argc, JSValueConst *argv);
+static JSValue qjs_webcrypto_unwrap_key(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
static JSValue qjs_webcrypto_key_algorithm(JSContext *cx,
JSValueConst this_val);
static JSValue qjs_webcrypto_key_type(JSContext *cx, JSValueConst this_val);
static JSValue qjs_webcrypto_key_usages(JSContext *cx, JSValueConst this_val);
+static JSValue qjs_webcrypto_export_key_raw(JSContext *cx,
+ qjs_webcrypto_key_t *key, qjs_webcrypto_key_format_t fmt);
+static JSValue qjs_webcrypto_cipher_core(JSContext *cx, njs_str_t *data,
+ qjs_webcrypto_key_t *key, JSValue options,
+ qjs_webcrypto_algorithm_t *alg, int encrypt);
static JSValue qjs_get_random_values(JSContext *cx, JSValueConst this_val,
int argc, JSValueConst *argv);
static JSValue qjs_random_uuid(JSContext *cx, JSValueConst this_val,
1)
},
+#if (NJS_HAVE_AES_WRAP)
+ {
+ njs_str("AES-KW"),
+ qjs_webcrypto_algorithm(QJS_ALGORITHM_AES_KW,
+ QJS_KEY_USAGE_WRAP_KEY |
+ QJS_KEY_USAGE_UNWRAP_KEY |
+ QJS_KEY_USAGE_GENERATE_KEY,
+ QJS_KEY_FORMAT_RAW |
+ QJS_KEY_FORMAT_JWK,
+ 1)
+ },
+#endif
+
{
njs_str("ECDSA"),
qjs_webcrypto_algorithm(QJS_ALGORITHM_ECDSA,
},
};
-static njs_str_t qjs_webcrypto_alg_aes_name[3][3 + 1] = {
+static njs_str_t qjs_webcrypto_alg_aes_name[4][3 + 1] = {
{
njs_str("A128GCM"),
njs_str("A192GCM"),
njs_str("A256CBC"),
njs_null_str,
},
+
+ {
+ njs_str("A128KW"),
+ njs_str("A192KW"),
+ njs_str("A256KW"),
+ njs_null_str,
+ },
};
JS_CFUNC_DEF("exportKey", 3, qjs_webcrypto_export_key),
JS_CFUNC_DEF("generateKey", 3, qjs_webcrypto_generate_key),
JS_CFUNC_MAGIC_DEF("sign", 4, qjs_webcrypto_sign, 0),
+ JS_CFUNC_DEF("unwrapKey", 7, qjs_webcrypto_unwrap_key),
JS_CFUNC_MAGIC_DEF("verify", 4, qjs_webcrypto_sign, 1),
+ JS_CFUNC_DEF("wrapKey", 4, qjs_webcrypto_wrap_key),
};
}
mask = encrypt ? QJS_KEY_USAGE_ENCRYPT : QJS_KEY_USAGE_DECRYPT;
+
+ if (alg->type == QJS_ALGORITHM_AES_KW) {
+ mask = encrypt ? QJS_KEY_USAGE_WRAP_KEY : QJS_KEY_USAGE_UNWRAP_KEY;
+ }
+
if ((key->usage & mask) != mask) {
JS_ThrowTypeError(cx, "key does not support %s operation",
encrypt ? "encrypt" : "decrypt");
return ret;
}
- switch (alg->type) {
- case QJS_ALGORITHM_RSA_OAEP:
- ret = qjs_cipher_pkey(cx, &data, key, encrypt);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
-
- case QJS_ALGORITHM_AES_GCM:
- ret = qjs_cipher_aes_gcm(cx, &data, key, options, encrypt);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
-
- case QJS_ALGORITHM_AES_CTR:
- ret = qjs_cipher_aes_ctr(cx, &data, key, options, encrypt);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
-
- case QJS_ALGORITHM_AES_CBC:
- default:
- ret = qjs_cipher_aes_cbc(cx, &data, key, options, encrypt);
- if (JS_IsException(ret)) {
- goto fail;
- }
+ ret = qjs_webcrypto_cipher_core(cx, &data, key, options, alg, encrypt);
+ if (JS_IsException(ret)) {
+ goto fail;
}
return qjs_promise_result(cx, ret);
}
+#if (NJS_HAVE_AES_WRAP)
+static JSValue
+qjs_cipher_aes_kw(JSContext *cx, njs_str_t *data, qjs_webcrypto_key_t *key,
+ int encrypt)
+{
+ int olen, dstlen;
+ u_char *dst;
+ JSValue ret;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+
+ if (encrypt) {
+ if (data->length < 16) {
+ JS_ThrowTypeError(cx, "AES-KW data must be at least 16 bytes");
+ return JS_EXCEPTION;
+ }
+
+ if (data->length % 8 != 0) {
+ JS_ThrowTypeError(cx, "AES-KW data must be a multiple of 8 bytes");
+ return JS_EXCEPTION;
+ }
+
+ } else {
+ if (data->length < 24) {
+ JS_ThrowTypeError(cx, "AES-KW data must be at least 24 bytes");
+ return JS_EXCEPTION;
+ }
+
+ if (data->length % 8 != 0) {
+ JS_ThrowTypeError(cx, "AES-KW data must be a multiple of 8 bytes");
+ return JS_EXCEPTION;
+ }
+ }
+
+ switch (key->u.s.raw.length) {
+ case 16:
+ cipher = EVP_aes_128_wrap();
+ break;
+
+ case 24:
+ cipher = EVP_aes_192_wrap();
+ break;
+
+ case 32:
+ cipher = EVP_aes_256_wrap();
+ break;
+
+ default:
+ JS_ThrowTypeError(cx, "AES-KW Invalid key length");
+ return JS_EXCEPTION;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ qjs_webcrypto_error(cx, "EVP_CIPHER_CTX_new() failed");
+ return JS_EXCEPTION;
+ }
+
+ EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+
+ if (EVP_CipherInit_ex(ctx, cipher, NULL, key->u.s.raw.start, NULL,
+ encrypt) <= 0)
+ {
+ qjs_webcrypto_error(cx, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = JS_EXCEPTION;
+ goto fail;
+ }
+
+ dstlen = data->length + (encrypt ? 8 : 0);
+ dst = js_malloc(cx, dstlen);
+ if (dst == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ ret = JS_EXCEPTION;
+ goto fail;
+ }
+
+ if (EVP_CipherUpdate(ctx, dst, &olen, data->start, data->length) <= 0) {
+ qjs_webcrypto_error(cx, "EVP_%sUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ js_free(cx, dst);
+ ret = JS_EXCEPTION;
+ goto fail;
+ }
+
+ ret = qjs_new_array_buffer(cx, dst, olen);
+
+fail:
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ret;
+}
+#endif
+
+
static JSValue
qjs_export_base64url_bignum(JSContext *cx, const BIGNUM *v, size_t size)
{
case QJS_ALGORITHM_AES_GCM:
case QJS_ALGORITHM_AES_CTR:
case QJS_ALGORITHM_AES_CBC:
+ case QJS_ALGORITHM_AES_KW:
- if (length != 16 && length != 32) {
+ if (length != 16 && length != 24 && length != 32) {
JS_ThrowTypeError(cx, "deriveKey \"%s\" length must be "
- "128 or 256", qjs_algorithm_string(dalg));
+ "128, 192, or 256",
+ qjs_algorithm_string(dalg));
return JS_EXCEPTION;
}
static JSValue
-qjs_webcrypto_export_key(JSContext *cx, JSValueConst this_val, int argc,
- JSValueConst *argv)
+qjs_webcrypto_export_key_raw(JSContext *cx, qjs_webcrypto_key_t *key,
+ qjs_webcrypto_key_format_t fmt)
{
- BIO *bio;
- BUF_MEM *mem;
- JSValue ret;
- qjs_webcrypto_key_t *key;
- PKCS8_PRIV_KEY_INFO *pkcs8;
- qjs_webcrypto_key_format_t fmt;
-
- fmt = qjs_key_format(cx, argv[0]);
-
- key = JS_GetOpaque2(cx, argv[1], QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
- if (key == NULL) {
- JS_ThrowTypeError(cx, "\"key\" is not a CryptoKey object");
- return JS_EXCEPTION;
- }
-
- if (!(fmt & key->alg->fmt)) {
- JS_ThrowTypeError(cx, "unsupported key fmt \"%s\" for \"%s\" key",
- qjs_format_string(fmt),
- qjs_algorithm_string(key->alg));
- return JS_EXCEPTION;
- }
-
- if (!key->extractable) {
- JS_ThrowTypeError(cx, "provided key cannot be extracted");
- return JS_EXCEPTION;
- }
+ BIO *bio;
+ BUF_MEM *mem;
+ JSValue ret;
+ PKCS8_PRIV_KEY_INFO *pkcs8;
switch (fmt) {
case QJS_KEY_FORMAT_JWK:
case QJS_ALGORITHM_RSA_OAEP:
case QJS_ALGORITHM_ECDSA:
case QJS_ALGORITHM_ECDH:
- ret = qjs_export_jwk_asymmetric(cx, key);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
+ return qjs_export_jwk_asymmetric(cx, key);
case QJS_ALGORITHM_AES_GCM:
case QJS_ALGORITHM_AES_CTR:
case QJS_ALGORITHM_AES_CBC:
+ case QJS_ALGORITHM_AES_KW:
case QJS_ALGORITHM_HMAC:
- ret = qjs_export_jwk_oct(cx, key);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
+ return qjs_export_jwk_oct(cx, key);
default:
JS_ThrowTypeError(cx, "provided key of \"%s\" cannot be exported "
"as JWK", qjs_algorithm_string(key->alg));
- goto fail;
+ return JS_EXCEPTION;
}
- break;
-
case QJS_KEY_FORMAT_PKCS8:
if (!key->u.a.privat) {
JS_ThrowTypeError(cx, "public key of \"%s\" cannot be exported "
"as PKCS8", qjs_algorithm_string(key->alg));
- goto fail;
+ return JS_EXCEPTION;
}
bio = BIO_new(BIO_s_mem());
if (bio == NULL) {
qjs_webcrypto_error(cx, "BIO_new(BIO_s_mem()) failed");
- goto fail;
+ return JS_EXCEPTION;
}
njs_assert(key->u.a.pkey != NULL);
if (pkcs8 == NULL) {
BIO_free(bio);
qjs_webcrypto_error(cx, "EVP_PKEY2PKCS8() failed");
- goto fail;
+ return JS_EXCEPTION;
}
if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio, pkcs8)) {
BIO_free(bio);
PKCS8_PRIV_KEY_INFO_free(pkcs8);
- qjs_webcrypto_error(cx, "i2d_PKCS8_PRIV_KEY_INFO_bio() failed");
- goto fail;
+ qjs_webcrypto_error(cx,
+ "i2d_PKCS8_PRIV_KEY_INFO_bio() failed");
+ return JS_EXCEPTION;
}
BIO_get_mem_ptr(bio, &mem);
ret = JS_NewArrayBufferCopy(cx, (const uint8_t *) mem->data,
mem->length);
-
BIO_free(bio);
PKCS8_PRIV_KEY_INFO_free(pkcs8);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
+ return ret;
case QJS_KEY_FORMAT_SPKI:
if (key->u.a.privat) {
JS_ThrowTypeError(cx, "private key of \"%s\" cannot be exported "
"as SPKI", qjs_algorithm_string(key->alg));
- goto fail;
+ return JS_EXCEPTION;
}
bio = BIO_new(BIO_s_mem());
if (bio == NULL) {
qjs_webcrypto_error(cx, "BIO_new(BIO_s_mem()) failed");
- goto fail;
+ return JS_EXCEPTION;
}
njs_assert(key->u.a.pkey != NULL);
if (!i2d_PUBKEY_bio(bio, key->u.a.pkey)) {
BIO_free(bio);
qjs_webcrypto_error(cx, "i2d_PUBKEY_bio() failed");
- goto fail;
+ return JS_EXCEPTION;
}
BIO_get_mem_ptr(bio, &mem);
ret = JS_NewArrayBufferCopy(cx, (const uint8_t *) mem->data,
mem->length);
-
BIO_free(bio);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
+ return ret;
case QJS_KEY_FORMAT_RAW:
default:
if (key->alg->type == QJS_ALGORITHM_ECDSA
|| key->alg->type == QJS_ALGORITHM_ECDH)
{
- ret = qjs_export_raw_ec(cx, key);
- if (JS_IsException(ret)) {
- goto fail;
- }
-
- break;
- } else {
- ret = JS_NewArrayBufferCopy(cx, key->u.s.raw.start,
- key->u.s.raw.length);
- if (JS_IsException(ret)) {
- goto fail;
- }
+ return qjs_export_raw_ec(cx, key);
}
+ return JS_NewArrayBufferCopy(cx, key->u.s.raw.start,
+ key->u.s.raw.length);
}
+}
- return qjs_promise_result(cx, ret);
-fail:
+static JSValue
+qjs_webcrypto_cipher_core(JSContext *cx, njs_str_t *data,
+ qjs_webcrypto_key_t *key, JSValue options,
+ qjs_webcrypto_algorithm_t *alg, int encrypt)
+{
+ switch (alg->type) {
+ case QJS_ALGORITHM_RSA_OAEP:
+ return qjs_cipher_pkey(cx, data, key, encrypt);
- return qjs_promise_result(cx, JS_EXCEPTION);
+ case QJS_ALGORITHM_AES_GCM:
+ return qjs_cipher_aes_gcm(cx, data, key, options, encrypt);
+
+ case QJS_ALGORITHM_AES_CTR:
+ return qjs_cipher_aes_ctr(cx, data, key, options, encrypt);
+
+ case QJS_ALGORITHM_AES_CBC:
+ return qjs_cipher_aes_cbc(cx, data, key, options, encrypt);
+
+#if (NJS_HAVE_AES_WRAP)
+ case QJS_ALGORITHM_AES_KW:
+ return qjs_cipher_aes_kw(cx, data, key, encrypt);
+#endif
+
+ default:
+ JS_ThrowTypeError(cx, "not implemented");
+ return JS_EXCEPTION;
+ }
+}
+
+
+static JSValue
+qjs_webcrypto_export_key(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue ret;
+ qjs_webcrypto_key_t *key;
+ qjs_webcrypto_key_format_t fmt;
+
+ fmt = qjs_key_format(cx, argv[0]);
+
+ key = JS_GetOpaque2(cx, argv[1], QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
+ if (key == NULL) {
+ JS_ThrowTypeError(cx, "\"key\" is not a CryptoKey object");
+ return JS_EXCEPTION;
+ }
+
+ if (!(fmt & key->alg->fmt)) {
+ JS_ThrowTypeError(cx, "unsupported key fmt \"%s\" for \"%s\" key",
+ qjs_format_string(fmt),
+ qjs_algorithm_string(key->alg));
+ return JS_EXCEPTION;
+ }
+
+ if (!key->extractable) {
+ JS_ThrowTypeError(cx, "provided key cannot be extracted");
+ return JS_EXCEPTION;
+ }
+
+ ret = qjs_webcrypto_export_key_raw(cx, key, fmt);
+ if (JS_IsException(ret)) {
+ return qjs_promise_result(cx, JS_EXCEPTION);
+ }
+
+ return qjs_promise_result(cx, ret);
}
case QJS_ALGORITHM_AES_GCM:
case QJS_ALGORITHM_AES_CTR:
case QJS_ALGORITHM_AES_CBC:
+ case QJS_ALGORITHM_AES_KW:
case QJS_ALGORITHM_HMAC:
if (alg->type == QJS_ALGORITHM_HMAC) {
ret = qjs_algorithm_hash(cx, options, &wkey->hash);
case QJS_ALGORITHM_AES_GCM:
case QJS_ALGORITHM_AES_CTR:
case QJS_ALGORITHM_AES_CBC:
+ case QJS_ALGORITHM_AES_KW:
if (fmt == QJS_KEY_FORMAT_RAW) {
switch (key_data.length) {
case 16:
}
+static JSValue
+qjs_webcrypto_wrap_key(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ unsigned mask;
+ JSValue exported, options, ret;
+ njs_str_t data;
+ qjs_webcrypto_key_t *key, *wrapping_key;
+ qjs_webcrypto_algorithm_t *alg;
+ qjs_webcrypto_key_format_t fmt;
+
+ fmt = qjs_key_format(cx, argv[0]);
+
+ key = JS_GetOpaque2(cx, argv[1], QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
+ if (key == NULL) {
+ JS_ThrowTypeError(cx, "\"key\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ wrapping_key = JS_GetOpaque2(cx, argv[2],
+ QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
+ if (wrapping_key == NULL) {
+ JS_ThrowTypeError(cx, "\"wrappingKey\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ options = argv[3];
+ alg = qjs_key_algorithm(cx, options);
+ if (alg == NULL) {
+ goto fail;
+ }
+
+ if (!key->extractable) {
+ JS_ThrowTypeError(cx, "provided key cannot be extracted");
+ goto fail;
+ }
+
+ mask = QJS_KEY_USAGE_WRAP_KEY;
+ if (!(wrapping_key->usage & mask)) {
+ JS_ThrowTypeError(cx, "wrapping key does not support wrapKey");
+ goto fail;
+ }
+
+ if (wrapping_key->alg != alg) {
+ JS_ThrowTypeError(cx, "cannot wrap using \"%s\" with \"%s\" key",
+ qjs_algorithm_string(wrapping_key->alg),
+ qjs_algorithm_string(alg));
+ goto fail;
+ }
+
+ exported = qjs_webcrypto_export_key_raw(cx, key, fmt);
+ if (JS_IsException(exported)) {
+ goto fail;
+ }
+
+ if (fmt == QJS_KEY_FORMAT_JWK) {
+ JSValue json;
+ const char *str;
+
+ json = JS_JSONStringify(cx, exported, JS_UNDEFINED, JS_UNDEFINED);
+ JS_FreeValue(cx, exported);
+
+ if (JS_IsException(json)) {
+ goto fail;
+ }
+
+ str = JS_ToCStringLen(cx, &data.length, json);
+ JS_FreeValue(cx, json);
+
+ if (str == NULL) {
+ goto fail;
+ }
+
+ data.start = (u_char *) str;
+
+ ret = qjs_webcrypto_cipher_core(cx, &data, wrapping_key, options,
+ alg, 1);
+ JS_FreeCString(cx, str);
+
+ } else {
+ ret = qjs_typed_array_data(cx, exported, &data);
+ if (JS_IsException(ret)) {
+ JS_FreeValue(cx, exported);
+ goto fail;
+ }
+
+ ret = qjs_webcrypto_cipher_core(cx, &data, wrapping_key, options,
+ alg, 1);
+ JS_FreeValue(cx, exported);
+ }
+
+ if (JS_IsException(ret)) {
+ goto fail;
+ }
+
+ return qjs_promise_result(cx, ret);
+
+fail:
+
+ return qjs_promise_result(cx, JS_EXCEPTION);
+}
+
+
+static JSValue
+qjs_webcrypto_unwrap_key(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ unsigned mask, usage;
+ JSValue options, ret, decrypted, key_value;
+ njs_str_t data, key_data;
+ qjs_webcrypto_key_t *wrapping_key, *ikey;
+ qjs_webcrypto_algorithm_t *alg, *key_alg;
+ qjs_webcrypto_key_format_t fmt;
+
+ fmt = qjs_key_format(cx, argv[0]);
+
+ ret = qjs_typed_array_data(cx, argv[1], &data);
+ if (JS_IsException(ret)) {
+ return JS_EXCEPTION;
+ }
+
+ wrapping_key = JS_GetOpaque2(cx, argv[2],
+ QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
+ if (wrapping_key == NULL) {
+ JS_ThrowTypeError(cx, "\"unwrappingKey\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ options = argv[3];
+ alg = qjs_key_algorithm(cx, options);
+ if (alg == NULL) {
+ goto fail;
+ }
+
+ key_alg = qjs_key_algorithm(cx, argv[4]);
+ if (key_alg == NULL) {
+ goto fail;
+ }
+
+ ret = qjs_key_usage(cx, argv[6], &usage);
+ if (JS_IsException(ret)) {
+ goto fail;
+ }
+
+ mask = QJS_KEY_USAGE_UNWRAP_KEY;
+ if (!(wrapping_key->usage & mask)) {
+ JS_ThrowTypeError(cx, "unwrapping key does not support unwrapKey");
+ goto fail;
+ }
+
+ if (wrapping_key->alg != alg) {
+ JS_ThrowTypeError(cx, "cannot unwrap using \"%s\" with \"%s\" key",
+ qjs_algorithm_string(wrapping_key->alg),
+ qjs_algorithm_string(alg));
+ goto fail;
+ }
+
+ decrypted = qjs_webcrypto_cipher_core(cx, &data, wrapping_key, options,
+ alg, 0);
+ if (JS_IsException(decrypted)) {
+ 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;
+ }
+
+ key_value = qjs_webcrypto_key_make(cx, key_alg, usage,
+ JS_ToBool(cx, argv[5]));
+ if (JS_IsException(key_value)) {
+ 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;
+ }
+
+ 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;
+ }
+
+ ikey->u.s.raw.length = key_data.length;
+ memcpy(ikey->u.s.raw.start, key_data.start, key_data.length);
+ break;
+
+ 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;
+ }
+
+ ikey->u.s.raw.length = key_data.length;
+ memcpy(ikey->u.s.raw.start, key_data.start, key_data.length);
+ break;
+ }
+
+ } else {
+ JS_FreeValue(cx, decrypted);
+ JS_FreeValue(cx, key_value);
+ JS_ThrowTypeError(cx, "unwrapKey: unsupported format");
+ goto fail;
+ }
+
+ JS_FreeValue(cx, decrypted);
+
+ return qjs_promise_result(cx, key_value);
+
+fail:
+
+ return qjs_promise_result(cx, JS_EXCEPTION);
+}
+
+
static JSValue
qjs_webcrypto_key_algorithm(JSContext *cx, JSValueConst this_val)
{
case QJS_ALGORITHM_AES_GCM:
case QJS_ALGORITHM_AES_CTR:
case QJS_ALGORITHM_AES_CBC:
+ case QJS_ALGORITHM_AES_KW:
/* AesKeyGenParams. */
if (JS_DefinePropertyValueStr(cx, obj, "length",
--- /dev/null
+/*---
+includes: [compatFs.js, compatBuffer.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js]
+flags: [async]
+---*/
+
+async function generate_kek(usages) {
+ return await crypto.subtle.generateKey(
+ {name: "AES-KW", length: 128}, true, usages);
+}
+
+
+async function test(params) {
+ try {
+ await crypto.subtle.generateKey(
+ {name: "AES-KW", length: 128}, true, ["wrapKey", "unwrapKey"]);
+ } catch (e) {
+ if (e.message.indexOf("AES-KW") !== -1) {
+ return 'SKIPPED';
+ }
+
+ throw e;
+ }
+
+ let key;
+
+ if (params.generate) {
+ key = await crypto.subtle.generateKey(
+ {name: "AES-KW", length: params.generate},
+ true, ["wrapKey", "unwrapKey"]);
+
+ let raw = await crypto.subtle.exportKey("raw", key);
+ if (raw.byteLength !== params.generate / 8) {
+ throw Error(`generateKey length mismatch: ${raw.byteLength}`);
+ }
+
+ return 'SUCCESS';
+ }
+
+ let kek;
+
+ if (params.jwk_import) {
+ kek = await crypto.subtle.importKey("jwk", params.jwk_import,
+ {name: "AES-KW"}, false, ["wrapKey", "unwrapKey"]);
+
+ } else {
+ let extractable = params.jwk_roundtrip ? true : false;
+
+ kek = await crypto.subtle.importKey("raw",
+ Buffer.from(params.kek, "hex"),
+ {name: "AES-KW"}, extractable, ["wrapKey", "unwrapKey"]);
+ }
+
+ if (params.jwk_roundtrip) {
+ let jwk = await crypto.subtle.exportKey("jwk", kek);
+ if (jwk.kty !== "oct") {
+ throw Error(`JWK kty mismatch: ${jwk.kty}`);
+ }
+
+ return 'SUCCESS';
+ }
+
+ let data = Buffer.from(params.data, "hex");
+ let expected = Buffer.from(params.expected, "hex");
+
+ /* import the plaintext as an AES key to wrap */
+ let inner = await crypto.subtle.importKey("raw", data,
+ {name: "AES-GCM"}, true, ["encrypt"]);
+
+ /* wrap */
+ let wrapped = await crypto.subtle.wrapKey("raw", inner, kek,
+ {name: "AES-KW"});
+ wrapped = Buffer.from(wrapped);
+
+ if (wrapped.compare(expected) != 0) {
+ throw Error(`AES-KW wrap failed: ${wrapped.toString("hex")}`
+ + ` != ${params.expected}`);
+ }
+
+ /* unwrap */
+ let unwrapped = await crypto.subtle.unwrapKey("raw", expected, kek,
+ {name: "AES-KW"}, {name: "AES-GCM"}, true, ["encrypt"]);
+
+ let raw = await crypto.subtle.exportKey("raw", unwrapped);
+ if (Buffer.from(raw).compare(data) != 0) {
+ throw Error(`AES-KW unwrap failed`);
+ }
+
+ return 'SUCCESS';
+}
+
+async function test_wrap_short_data() {
+ let kek = await generate_kek(["wrapKey", "unwrapKey"]);
+ let key = await crypto.subtle.importKey("raw",
+ Buffer.from("0011223344556677", "hex"),
+ {name: "HMAC", hash: "SHA-256"}, true, ["sign"]);
+
+ await crypto.subtle.wrapKey("raw", key, kek, {name: "AES-KW"});
+}
+
+
+async function test_wrap_non_multiple() {
+ let kek = await generate_kek(["wrapKey", "unwrapKey"]);
+ let key = await crypto.subtle.importKey("raw",
+ Buffer.from("00112233445566778899AABBCCDDEEFF00112233", "hex"),
+ {name: "HMAC", hash: "SHA-256"}, true, ["sign"]);
+
+ await crypto.subtle.wrapKey("raw", key, kek, {name: "AES-KW"});
+}
+
+
+async function test_unwrap_short_data() {
+ let kek = await generate_kek(["wrapKey", "unwrapKey"]);
+
+ await crypto.subtle.unwrapKey("raw",
+ crypto.getRandomValues(new Uint8Array(23)),
+ kek, {name: "AES-KW"}, {name: "AES-GCM"}, true, ["encrypt"]);
+}
+
+
+async function test_unwrap_non_multiple() {
+ let kek = await generate_kek(["wrapKey", "unwrapKey"]);
+
+ await crypto.subtle.unwrapKey("raw",
+ crypto.getRandomValues(new Uint8Array(25)),
+ kek, {name: "AES-KW"}, {name: "AES-GCM"}, true, ["encrypt"]);
+}
+
+
+async function test_no_wrapkey_usage() {
+ let kek = await generate_kek(["unwrapKey"]);
+ let key = await crypto.subtle.importKey("raw",
+ Buffer.from("00112233445566778899AABBCCDDEEFF", "hex"),
+ {name: "AES-GCM"}, true, ["encrypt"]);
+
+ await crypto.subtle.wrapKey("raw", key, kek, {name: "AES-KW"});
+}
+
+
+async function test_no_unwrapkey_usage() {
+ let kek = await generate_kek(["wrapKey"]);
+ let wrapped = Buffer.from(
+ "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5",
+ "hex");
+
+ await crypto.subtle.unwrapKey("raw", wrapped, kek,
+ {name: "AES-KW"}, {name: "AES-GCM"}, true, ["encrypt"]);
+}
+
+
+let aes_kw_tsuite = {
+ name: "AES-KW wrap/unwrap",
+ skip: () => (!has_buffer() || !has_webcrypto()),
+ T: test,
+ prepare_args: (args) => args,
+
+ tests: [
+ /* generateKey */
+ { generate: 128 },
+ { generate: 192 },
+ { generate: 256 },
+
+ /* RFC 3394 4.1: 128-bit KEK, 128-bit data */
+ { kek: "000102030405060708090A0B0C0D0E0F",
+ data: "00112233445566778899AABBCCDDEEFF",
+ expected: "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5" },
+
+ /* RFC 3394 4.3: 192-bit KEK, 128-bit data */
+ { kek: "000102030405060708090A0B0C0D0E0F1011121314151617",
+ data: "00112233445566778899AABBCCDDEEFF",
+ expected: "96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D" },
+
+ /* RFC 3394 4.5: 256-bit KEK, 128-bit data */
+ { kek: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
+ data: "00112233445566778899AABBCCDDEEFF",
+ expected: "64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7" },
+
+ /* RFC 3394 4.6: 256-bit KEK, 256-bit data */
+ { kek: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
+ data: "00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F",
+ expected: "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21" },
+
+ /* JWK roundtrip: export + reimport */
+ { kek: "000102030405060708090A0B0C0D0E0F",
+ jwk_roundtrip: true },
+
+ /* JWK import 128-bit */
+ { jwk_import: { kty: "oct", k: "AAECAwQFBgcICQoLDA0ODw",
+ alg: "A128KW", key_ops: ["wrapKey", "unwrapKey"] },
+ data: "00112233445566778899AABBCCDDEEFF",
+ expected: "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5" },
+
+ /* JWK import 256-bit */
+ { jwk_import: { kty: "oct",
+ k: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8",
+ alg: "A256KW", key_ops: ["wrapKey", "unwrapKey"] },
+ data: "00112233445566778899AABBCCDDEEFF",
+ expected: "64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7" },
+]};
+
+let aes_kw_error_tsuite = {
+ name: "AES-KW errors",
+ skip: () => (!has_buffer() || !has_webcrypto()),
+ T: (params) => params.T(),
+ prepare_args: (args) => args,
+
+ tests: [
+ { T: test_wrap_short_data,
+ exception: "TypeError: AES-KW data must be at least 16 bytes" },
+ { T: test_wrap_non_multiple,
+ exception: "TypeError: AES-KW data must be a multiple of 8 bytes" },
+ { T: test_unwrap_short_data,
+ exception: "TypeError: AES-KW data must be at least 24 bytes" },
+ { T: test_unwrap_non_multiple,
+ exception: "TypeError: AES-KW data must be a multiple of 8 bytes" },
+ { T: test_no_wrapkey_usage,
+ exception: "TypeError: wrapping key does not support wrapKey" },
+ { T: test_no_unwrapkey_usage,
+ exception: "TypeError: unwrapping key does not support unwrapKey" },
+]};
+
+run([aes_kw_tsuite, aes_kw_error_tsuite])
+.then($DONE, $DONE);
--- /dev/null
+/*---
+includes: [compatFs.js, compatBuffer.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js]
+flags: [async]
+---*/
+
+async function test(params) {
+ let wrapping_key;
+
+ try {
+ wrapping_key = await crypto.subtle.generateKey(
+ params.wrap_alg, true, ["wrapKey", "unwrapKey"]);
+ } catch (e) {
+ if (e.message.indexOf("unknown algorithm") !== -1
+ || e.message.indexOf("Unrecognized algorithm") !== -1)
+ {
+ return 'SKIPPED';
+ }
+
+ throw e;
+ }
+
+ let key = await crypto.subtle.generateKey(
+ params.key_alg, true, params.key_usage);
+
+ let wrapped = await crypto.subtle.wrapKey(
+ params.format, key, wrapping_key, params.wrap_params);
+
+ let unwrapped = await crypto.subtle.unwrapKey(
+ params.format, wrapped, wrapping_key, params.wrap_params,
+ params.key_alg, true, params.key_usage);
+
+ /* verify the unwrapped key works */
+ if (params.verify_encrypt) {
+ let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16]);
+ let enc = await crypto.subtle.encrypt(
+ params.verify_encrypt, unwrapped, data);
+ let dec = await crypto.subtle.decrypt(
+ params.verify_encrypt, unwrapped, enc);
+
+ if (Buffer.from(dec).compare(Buffer.from(data)) != 0) {
+ throw Error("unwrapped key encrypt/decrypt roundtrip failed");
+ }
+ }
+
+ if (params.verify_sign) {
+ let data = new Uint8Array([1, 2, 3, 4]);
+ let sig = await crypto.subtle.sign(
+ params.verify_sign, unwrapped, data);
+
+ if (sig.byteLength === 0) {
+ throw Error("unwrapped key sign produced empty signature");
+ }
+ }
+
+ /* verify raw key material matches */
+ if (params.format === "raw" && params.check_raw) {
+ let orig = await crypto.subtle.exportKey("raw", key);
+ let unwrapped_raw = await crypto.subtle.exportKey("raw", unwrapped);
+ if (Buffer.from(orig).compare(Buffer.from(unwrapped_raw)) != 0) {
+ throw Error("unwrapped key raw material mismatch");
+ }
+ }
+
+ return 'SUCCESS';
+}
+
+async function test_non_extractable() {
+ let wrapping_key = await crypto.subtle.generateKey(
+ {name: "AES-KW", length: 256}, false,
+ ["wrapKey", "unwrapKey"]);
+ let key = await crypto.subtle.generateKey(
+ {name: "AES-GCM", length: 128}, false,
+ ["encrypt", "decrypt"]);
+
+ await crypto.subtle.wrapKey("raw", key, wrapping_key,
+ {name: "AES-KW"});
+}
+
+async function test_no_wrapKey_usage() {
+ let wrapping_key = await crypto.subtle.importKey("raw",
+ crypto.getRandomValues(new Uint8Array(16)),
+ {name: "AES-KW"}, false, ["unwrapKey"]);
+ let key = await crypto.subtle.generateKey(
+ {name: "AES-GCM", length: 128}, true,
+ ["encrypt", "decrypt"]);
+
+ await crypto.subtle.wrapKey("raw", key, wrapping_key,
+ {name: "AES-KW"});
+}
+
+async function test_no_unwrapKey_usage() {
+ let wrapping_key = await crypto.subtle.importKey("raw",
+ crypto.getRandomValues(new Uint8Array(16)),
+ {name: "AES-KW"}, false, ["wrapKey"]);
+
+ let data = crypto.getRandomValues(new Uint8Array(24));
+ await crypto.subtle.unwrapKey("raw", data, wrapping_key,
+ {name: "AES-KW"}, {name: "AES-GCM"}, true,
+ ["encrypt", "decrypt"]);
+}
+
+let wrap_tsuite = {
+ name: "wrapKey/unwrapKey",
+ skip: () => (!has_buffer() || !has_webcrypto()),
+ T: test,
+ prepare_args: (args) => args,
+
+ tests: [
+ /* AES-KW wrapping an AES-GCM key (raw format) */
+ { wrap_alg: {name: "AES-KW", length: 256},
+ key_alg: {name: "AES-GCM", length: 128},
+ key_usage: ["encrypt", "decrypt"],
+ format: "raw",
+ wrap_params: {name: "AES-KW"},
+ check_raw: true,
+ verify_encrypt: {name: "AES-GCM",
+ iv: crypto.getRandomValues(new Uint8Array(12))} },
+
+ /* AES-KW wrapping an AES-CBC key */
+ { wrap_alg: {name: "AES-KW", length: 128},
+ key_alg: {name: "AES-CBC", length: 256},
+ key_usage: ["encrypt", "decrypt"],
+ format: "raw",
+ wrap_params: {name: "AES-KW"},
+ check_raw: true },
+
+ /* AES-KW wrapping an HMAC key */
+ { wrap_alg: {name: "AES-KW", length: 256},
+ key_alg: {name: "HMAC", hash: "SHA-256"},
+ key_usage: ["sign", "verify"],
+ format: "raw",
+ wrap_params: {name: "AES-KW"},
+ check_raw: true,
+ verify_sign: {name: "HMAC"} },
+
+ /* AES-GCM wrapping an AES key */
+ { wrap_alg: {name: "AES-GCM", length: 256},
+ key_alg: {name: "AES-GCM", length: 128},
+ key_usage: ["encrypt", "decrypt"],
+ format: "raw",
+ wrap_params: {name: "AES-GCM",
+ iv: crypto.getRandomValues(new Uint8Array(12))},
+ check_raw: true },
+
+ /* AES-CBC wrapping an AES key */
+ { wrap_alg: {name: "AES-CBC", length: 256},
+ key_alg: {name: "AES-CBC", length: 128},
+ key_usage: ["encrypt", "decrypt"],
+ format: "raw",
+ wrap_params: {name: "AES-CBC",
+ iv: crypto.getRandomValues(new Uint8Array(16))},
+ check_raw: true },
+
+ /* AES-GCM wrapping an HMAC key (covers HMAC unwrap path) */
+ { wrap_alg: {name: "AES-GCM", length: 256},
+ key_alg: {name: "HMAC", hash: "SHA-256"},
+ key_usage: ["sign", "verify"],
+ format: "raw",
+ wrap_params: {name: "AES-GCM",
+ iv: crypto.getRandomValues(new Uint8Array(12))},
+ check_raw: true,
+ verify_sign: {name: "HMAC"} },
+
+ /* AES-CTR wrapping an AES key */
+ { wrap_alg: {name: "AES-CTR", length: 256},
+ key_alg: {name: "AES-GCM", length: 128},
+ key_usage: ["encrypt", "decrypt"],
+ format: "raw",
+ wrap_params: {name: "AES-CTR",
+ counter: crypto.getRandomValues(new Uint8Array(16)),
+ length: 64},
+ check_raw: true },
+]};
+
+let wrap_error_tsuite = {
+ name: "wrapKey/unwrapKey errors",
+ skip: () => (!has_buffer() || !has_webcrypto()),
+ T: (params) => params.T(),
+ prepare_args: (args) => args,
+
+ tests: [
+ { T: test_non_extractable, exception: true },
+ { T: test_no_wrapKey_usage, exception: true },
+ { T: test_no_unwrapKey_usage, exception: true },
+]};
+
+run([wrap_tsuite, wrap_error_tsuite])
+.then($DONE, $DONE);
hash: HashVariants;
}
-type AesVariants = "AES-CTR" | "AES-CBC" | "AES-GCM";
+type AesVariants = "AES-CTR" | "AES-CBC" | "AES-GCM" | "AES-KW";
interface AesImportParams {
name: AesVariants;
key: CryptoKey,
signature: NjsStringOrBuffer,
data: NjsStringOrBuffer): Promise<boolean>;
+
+ wrapKey(format: "raw" | "pkcs8" | "spki" | "jwk",
+ key: CryptoKey,
+ wrappingKey: CryptoKey,
+ wrapAlgorithm: CipherAlgorithm | "AES-KW"): Promise<ArrayBuffer>;
+
+ unwrapKey(format: "raw" | "pkcs8" | "spki" | "jwk",
+ wrappedKey: NjsStringOrBuffer,
+ unwrappingKey: CryptoKey,
+ unwrapAlgorithm: CipherAlgorithm | "AES-KW",
+ unwrappedKeyAlgorithm: ImportAlgorithm,
+ extractable: boolean,
+ keyUsages: Array<string>): Promise<CryptoKey>;
}
interface Crypto {