]> git.kaiwu.me - njs.git/commitdiff
WebCrypto: extended support for asymmetric keys.
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 5 Jan 2023 01:49:22 +0000 (17:49 -0800)
committerDmitry Volyntsev <xeioex@nginx.com>
Thu, 5 Jan 2023 01:49:22 +0000 (17:49 -0800)
The following functionality for RSA and EC keys were added:
    importKey() supporting 'jwk' format,
                also 'raw' format for EC public keys.
    exportKey() supporting 'pksc8', 'spki', 'jwk' format,
                also 'raw' format for EC public keys.
    generateKey().

16 files changed:
external/njs_openssl.h
external/njs_webcrypto_module.c
test/harness/compareObjects.js [new file with mode: 0644]
test/harness/runTsuite.js
test/harness/webCryptoUtils.js
test/ts/test.ts
test/webcrypto/ec.jwk [new file with mode: 0644]
test/webcrypto/ec.pub.jwk [new file with mode: 0644]
test/webcrypto/export.t.js [new file with mode: 0644]
test/webcrypto/rsa.dec.jwk [new file with mode: 0644]
test/webcrypto/rsa.enc.pub.jwk [new file with mode: 0644]
test/webcrypto/rsa.jwk [new file with mode: 0644]
test/webcrypto/rsa.pub.jwk [new file with mode: 0644]
test/webcrypto/rsa.t.js
test/webcrypto/sign.t.js
ts/njs_webcrypto.d.ts

index 8289d7c41c3ed10d54176a3ecbef6861e17c9e68..87d425ded45263c10a64c0631e0f051931b4952c 100644 (file)
 #endif
 
 
+njs_inline int
+njs_bn_bn2binpad(const BIGNUM *bn, unsigned char *to, int tolen)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return BN_bn2binpad(bn, to, tolen);
+#else
+    return BN_bn2bin(bn, &to[tolen - BN_num_bytes(bn)]);
+#endif
+}
+
+
+njs_inline int
+njs_pkey_up_ref(EVP_PKEY *pkey)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return EVP_PKEY_up_ref(pkey);
+#else
+    CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
+    return 1;
+#endif
+}
+
+
+njs_inline const RSA *
+njs_pkey_get_rsa_key(EVP_PKEY *pkey)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return EVP_PKEY_get0_RSA(pkey);
+#else
+    return EVP_PKEY_get0(pkey);
+#endif
+}
+
+
+njs_inline void
+njs_rsa_get0_key(const RSA *rsa, const BIGNUM **n, const BIGNUM **e,
+    const BIGNUM **d)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    RSA_get0_key(rsa, n, e, d);
+#else
+    if (n != NULL) {
+        *n = rsa->n;
+    }
+
+    if (e != NULL) {
+        *e = rsa->e;
+    }
+
+    if (d != NULL) {
+        *d = rsa->d;
+    }
+#endif
+}
+
+
+njs_inline void
+njs_rsa_get0_factors(const RSA *rsa, const BIGNUM **p, const BIGNUM **q)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    RSA_get0_factors(rsa, p, q);
+#else
+    if (p != NULL) {
+        *p = rsa->p;
+    }
+
+    if (q != NULL) {
+        *q = rsa->q;
+    }
+#endif
+}
+
+
+
+njs_inline void
+njs_rsa_get0_ctr_params(const RSA *rsa, const BIGNUM **dp, const BIGNUM **dq,
+    const BIGNUM **qi)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    RSA_get0_crt_params(rsa, dp, dq, qi);
+#else
+    if (dp != NULL) {
+        *dp = rsa->dmp1;
+    }
+
+    if (dq != NULL) {
+        *dq = rsa->dmq1;
+    }
+
+    if (qi != NULL) {
+        *qi = rsa->iqmp;
+    }
+#endif
+}
+
+
+njs_inline int
+njs_rsa_set0_key(RSA *rsa, BIGNUM *n, BIGNUM *e, BIGNUM *d)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return RSA_set0_key(rsa, n, e, d);
+#else
+    if ((rsa->n == NULL && n == NULL) || (rsa->e == NULL && e == NULL)) {
+        return 0;
+    }
+
+    if (n != NULL) {
+        BN_free(rsa->n);
+        rsa->n = n;
+    }
+
+    if (e != NULL) {
+        BN_free(rsa->e);
+        rsa->e = e;
+    }
+
+    if (d != NULL) {
+        BN_clear_free(rsa->d);
+        rsa->d = d;
+        BN_set_flags(rsa->d, BN_FLG_CONSTTIME);
+    }
+
+    return 1;
+#endif
+}
+
+
+njs_inline int
+njs_rsa_set0_factors(RSA *rsa, BIGNUM *p, BIGNUM *q)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return RSA_set0_factors(rsa, p, q);
+#else
+    if ((rsa->p == NULL && p == NULL) || (rsa->q == NULL && q == NULL)) {
+        return 0;
+    }
+
+    if (p != NULL) {
+        BN_clear_free(rsa->p);
+        rsa->p = p;
+        BN_set_flags(rsa->p, BN_FLG_CONSTTIME);
+    }
+
+    if (q != NULL) {
+        BN_clear_free(rsa->q);
+        rsa->q = q;
+        BN_set_flags(rsa->q, BN_FLG_CONSTTIME);
+    }
+
+    return 1;
+#endif
+}
+
+
+njs_inline int
+njs_rsa_set0_ctr_params(RSA *rsa, BIGNUM *dp, BIGNUM *dq, BIGNUM *qi)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return RSA_set0_crt_params(rsa, dp, dq, qi);
+#else
+    if ((rsa->dmp1 == NULL && dp == NULL)
+        || (rsa->dmq1 == NULL && dq == NULL)
+        || (rsa->iqmp == NULL && qi == NULL))
+    {
+        return 0;
+    }
+
+    if (dp != NULL) {
+        BN_clear_free(rsa->dmp1);
+        rsa->dmp1 = dp;
+        BN_set_flags(rsa->dmp1, BN_FLG_CONSTTIME);
+    }
+
+    if (dq != NULL) {
+        BN_clear_free(rsa->dmq1);
+        rsa->dmq1 = dq;
+        BN_set_flags(rsa->dmq1, BN_FLG_CONSTTIME);
+    }
+
+    if (qi != NULL) {
+        BN_clear_free(rsa->iqmp);
+        rsa->iqmp = qi;
+        BN_set_flags(rsa->iqmp, BN_FLG_CONSTTIME);
+    }
+
+    return 1;
+#endif
+}
+
+
+njs_inline const EC_KEY *
+njs_pkey_get_ec_key(EVP_PKEY *pkey)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return EVP_PKEY_get0_EC_KEY(pkey);
+#else
+    if (pkey->type != EVP_PKEY_EC) {
+        return NULL;
+    }
+
+    return pkey->pkey.ec;
+#endif
+}
+
+
+njs_inline int
+njs_ec_group_order_bits(const EC_GROUP *group)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return EC_GROUP_order_bits(group);
+#else
+    int     bits;
+    BIGNUM  *order;
+
+    order = BN_new();
+    if (order == NULL) {
+        return 0;
+    }
+
+    if (EC_GROUP_get_order(group, order, NULL) == 0) {
+        return 0;
+    }
+
+    bits = BN_num_bits(order);
+
+    BN_free(order);
+
+    return bits;
+#endif
+}
+
+
+njs_inline int
+njs_ec_point_get_affine_coordinates(const EC_GROUP *group, const EC_POINT *p,
+    BIGNUM *x, BIGNUM *y)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100001L)
+    return EC_POINT_get_affine_coordinates(group, p, x, y, NULL);
+#else
+    return EC_POINT_get_affine_coordinates_GFp(group, p, x, y, NULL);
+#endif
+}
+
+
+njs_inline int
+njs_ecdsa_sig_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    return ECDSA_SIG_set0(sig, r, s);
+#else
+    if (r == NULL || s == NULL) {
+        return 0;
+    }
+
+    BN_clear_free(sig->r);
+    BN_clear_free(sig->s);
+
+    sig->r = r;
+    sig->s = s;
+
+    return 1;
+#endif
+}
+
+
 #endif /* _NJS_EXTERNAL_OPENSSL_H_INCLUDED_ */
index 4c03609c1038250a93b1a0b954dfadcc61576854..8d4db05ae00543f0c79c52bbe648063fdc52dbac 100644 (file)
@@ -32,21 +32,22 @@ typedef enum {
 
 
 typedef enum {
+    NJS_ALGORITHM_RSASSA_PKCS1_v1_5 = 0,
+    NJS_ALGORITHM_RSA_PSS,
     NJS_ALGORITHM_RSA_OAEP,
+    NJS_ALGORITHM_HMAC,
     NJS_ALGORITHM_AES_GCM,
     NJS_ALGORITHM_AES_CTR,
     NJS_ALGORITHM_AES_CBC,
-    NJS_ALGORITHM_RSASSA_PKCS1_v1_5,
-    NJS_ALGORITHM_RSA_PSS,
     NJS_ALGORITHM_ECDSA,
     NJS_ALGORITHM_ECDH,
     NJS_ALGORITHM_PBKDF2,
     NJS_ALGORITHM_HKDF,
-    NJS_ALGORITHM_HMAC,
 } njs_webcrypto_alg_t;
 
 
 typedef enum {
+    NJS_HASH_UNSET = 0,
     NJS_HASH_SHA1,
     NJS_HASH_SHA256,
     NJS_HASH_SHA384,
@@ -54,13 +55,6 @@ typedef enum {
 } njs_webcrypto_hash_t;
 
 
-typedef enum {
-    NJS_CURVE_P256,
-    NJS_CURVE_P384,
-    NJS_CURVE_P521,
-} njs_webcrypto_curve_t;
-
-
 typedef struct {
     njs_str_t                  name;
     uintptr_t                  value;
@@ -76,12 +70,15 @@ typedef struct {
 
 typedef struct {
     njs_webcrypto_algorithm_t  *alg;
-    unsigned                   usage;
     njs_webcrypto_hash_t       hash;
-    njs_webcrypto_curve_t      curve;
+    int                        curve;
 
     EVP_PKEY                   *pkey;
     njs_str_t                  raw;
+
+    unsigned                   usage;
+    njs_bool_t                 extractable;
+    njs_bool_t                 privat;
 } njs_webcrypto_key_t;
 
 
@@ -119,12 +116,14 @@ static njs_int_t njs_ext_wrap_key(njs_vm_t *vm, njs_value_t *args,
 static njs_int_t njs_ext_get_random_values(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused);
 
-static void njs_webcrypto_cleanup_pkey(void *data);
+static njs_webcrypto_key_t *njs_webcrypto_key_alloc(njs_vm_t *vm,
+    njs_webcrypto_algorithm_t *alg, unsigned usage, njs_bool_t extractable);
 static njs_webcrypto_key_format_t njs_key_format(njs_vm_t *vm,
     njs_value_t *value);
 static njs_str_t *njs_format_string(njs_webcrypto_key_format_t fmt);
 static njs_int_t njs_key_usage(njs_vm_t *vm, njs_value_t *value,
     unsigned *mask);
+static njs_int_t njs_key_ops(njs_vm_t *vm, njs_value_t *retval, unsigned mask);
 static njs_webcrypto_algorithm_t *njs_key_algorithm(njs_vm_t *vm,
     njs_value_t *value);
 static njs_str_t *njs_algorithm_string(njs_webcrypto_algorithm_t *algorithm);
@@ -132,10 +131,12 @@ static njs_int_t njs_algorithm_hash(njs_vm_t *vm, njs_value_t *value,
     njs_webcrypto_hash_t *hash);
 static const EVP_MD *njs_algorithm_hash_digest(njs_webcrypto_hash_t hash);
 static njs_int_t njs_algorithm_curve(njs_vm_t *vm, njs_value_t *value,
-    njs_webcrypto_curve_t *curve);
+    int *curve);
 
 static njs_int_t njs_webcrypto_result(njs_vm_t *vm, njs_value_t *result,
     njs_int_t rc);
+static njs_int_t njs_webcrypto_array_buffer(njs_vm_t *vm, njs_value_t *retval,
+    u_char *start, size_t length);
 static void njs_webcrypto_error(njs_vm_t *vm, const char *fmt, ...);
 
 static njs_int_t njs_webcrypto_init(njs_vm_t *vm);
@@ -154,7 +155,8 @@ static njs_webcrypto_entry_t njs_webcrypto_alg[] = {
                               NJS_KEY_USAGE_UNWRAP_KEY |
                               NJS_KEY_USAGE_GENERATE_KEY,
                               NJS_KEY_FORMAT_PKCS8 |
-                              NJS_KEY_FORMAT_SPKI)
+                              NJS_KEY_FORMAT_SPKI |
+                              NJS_KEY_FORMAT_JWK)
     },
 
     {
@@ -197,7 +199,8 @@ static njs_webcrypto_entry_t njs_webcrypto_alg[] = {
                               NJS_KEY_USAGE_VERIFY |
                               NJS_KEY_USAGE_GENERATE_KEY,
                               NJS_KEY_FORMAT_PKCS8 |
-                              NJS_KEY_FORMAT_SPKI)
+                              NJS_KEY_FORMAT_SPKI |
+                              NJS_KEY_FORMAT_JWK)
     },
 
     {
@@ -207,7 +210,8 @@ static njs_webcrypto_entry_t njs_webcrypto_alg[] = {
                               NJS_KEY_USAGE_VERIFY |
                               NJS_KEY_USAGE_GENERATE_KEY,
                               NJS_KEY_FORMAT_PKCS8 |
-                              NJS_KEY_FORMAT_SPKI)
+                              NJS_KEY_FORMAT_SPKI |
+                              NJS_KEY_FORMAT_JWK)
     },
 
     {
@@ -217,7 +221,9 @@ static njs_webcrypto_entry_t njs_webcrypto_alg[] = {
                               NJS_KEY_USAGE_VERIFY |
                               NJS_KEY_USAGE_GENERATE_KEY,
                               NJS_KEY_FORMAT_PKCS8 |
-                              NJS_KEY_FORMAT_SPKI)
+                              NJS_KEY_FORMAT_SPKI |
+                              NJS_KEY_FORMAT_RAW |
+                              NJS_KEY_FORMAT_JWK)
     },
 
     {
@@ -272,9 +278,9 @@ static njs_webcrypto_entry_t njs_webcrypto_hash[] = {
 
 
 static njs_webcrypto_entry_t njs_webcrypto_curve[] = {
-    { njs_str("P-256"), NJS_CURVE_P256 },
-    { njs_str("P-384"), NJS_CURVE_P384 },
-    { njs_str("P-521"), NJS_CURVE_P521 },
+    { njs_str("P-256"), NID_X9_62_prime256v1 },
+    { njs_str("P-384"), NID_secp384r1 },
+    { njs_str("P-521"), NID_secp521r1 },
     { njs_null_str, 0 }
 };
 
@@ -301,6 +307,51 @@ static njs_webcrypto_entry_t njs_webcrypto_usage[] = {
 };
 
 
+static njs_webcrypto_entry_t njs_webcrypto_alg_hash[] = {
+    { njs_str("RS1"), NJS_HASH_SHA1 },
+    { njs_str("RS256"), NJS_HASH_SHA256 },
+    { njs_str("RS384"), NJS_HASH_SHA384 },
+    { njs_str("RS512"), NJS_HASH_SHA512 },
+    { njs_str("PS1"), NJS_HASH_SHA1 },
+    { njs_str("PS256"), NJS_HASH_SHA256 },
+    { njs_str("PS384"), NJS_HASH_SHA384 },
+    { njs_str("PS512"), NJS_HASH_SHA512 },
+    { njs_str("RSA-OAEP"), NJS_HASH_SHA1 },
+    { njs_str("RSA-OAEP-256"), NJS_HASH_SHA256 },
+    { njs_str("RSA-OAEP-384"), NJS_HASH_SHA384 },
+    { njs_str("RSA-OAEP-512"), NJS_HASH_SHA512 },
+    { njs_null_str, 0 }
+};
+
+
+static njs_str_t
+    njs_webcrypto_alg_name[NJS_ALGORITHM_RSA_OAEP + 1][NJS_HASH_SHA512 + 1] = {
+    {
+        njs_null_str,
+        njs_str("RS1"),
+        njs_str("RS256"),
+        njs_str("RS384"),
+        njs_str("RS512"),
+    },
+
+    {
+        njs_null_str,
+        njs_str("PS1"),
+        njs_str("PS256"),
+        njs_str("PS384"),
+        njs_str("PS512"),
+    },
+
+    {
+        njs_null_str,
+        njs_str("RSA-OAEP"),
+        njs_str("RSA-OAEP-256"),
+        njs_str("RSA-OAEP-384"),
+        njs_str("RSA-OAEP-512"),
+    },
+};
+
+
 static njs_external_t  njs_ext_webcrypto_crypto_key[] = {
 
     {
@@ -504,6 +555,23 @@ njs_module_t  njs_webcrypto_module = {
 };
 
 
+static const njs_value_t  string_alg = njs_string("alg");
+static const njs_value_t  string_d = njs_string("d");
+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_n = njs_string("n");
+static const njs_value_t  string_p = njs_string("p");
+static const njs_value_t  string_q = njs_string("q");
+static const njs_value_t  string_qi = njs_string("qi");
+static const njs_value_t  string_x = njs_string("x");
+static const njs_value_t  string_y = njs_string("y");
+static const njs_value_t  string_ext = njs_string("ext");
+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 njs_int_t    njs_webcrypto_crypto_key_proto_id;
 
 
@@ -1639,231 +1707,1487 @@ fail:
 
 
 static njs_int_t
-njs_ext_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+njs_export_base64url_bignum(njs_vm_t *vm, njs_value_t *retval, const BIGNUM *v,
+    size_t size)
 {
-    njs_internal_error(vm, "\"exportKey\" not implemented");
-    return NJS_ERROR;
+    njs_str_t  src;
+    u_char     buf[512];
+
+    if (size == 0) {
+        size = BN_num_bytes(v);
+    }
+
+    if (njs_bn_bn2binpad(v, &buf[0], size) <= 0) {
+        return NJS_ERROR;
+    }
+
+    src.start = buf;
+    src.length = size;
+
+    return njs_string_base64url(vm, retval, &src);
 }
 
 
 static njs_int_t
-njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+njs_base64url_bignum_set(njs_vm_t *vm, njs_value_t *jwk, njs_value_t *key,
+    const BIGNUM *v, size_t size)
 {
-    njs_internal_error(vm, "\"generateKey\" not implemented");
-    return NJS_ERROR;
+    njs_int_t    ret;
+    njs_value_t  value;
+
+    ret = njs_export_base64url_bignum(vm, &value, v, size);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    return njs_value_property_set(vm, jwk, key, &value);
 }
 
 
 static njs_int_t
-njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+njs_export_jwk_rsa(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval)
 {
-    int                         nid;
-    BIO                         *bio;
-#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
-    RSA                         *rsa;
-    EC_KEY                      *ec;
-#else
-    char                        gname[80];
-#endif
-    unsigned                    usage;
-    EVP_PKEY                    *pkey;
-    njs_int_t                   ret;
-    njs_str_t                   key_data;
-    njs_value_t                 value, *options;
-    const u_char                *start;
-#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
-    const EC_GROUP              *group;
-#endif
-    njs_mp_cleanup_t            *cln;
-    njs_webcrypto_key_t         *key;
-    PKCS8_PRIV_KEY_INFO         *pkcs8;
-    njs_webcrypto_algorithm_t   *alg;
-    njs_webcrypto_key_format_t  fmt;
+    njs_int_t     ret;
+    const RSA     *rsa;
+    njs_str_t     *nm;
+    njs_value_t   nvalue, evalue, alg;
+    const BIGNUM  *n_bn, *e_bn, *d_bn, *p_bn, *q_bn, *dp_bn, *dq_bn, *qi_bn;
 
-    static const int curves[] = {
-        NID_X9_62_prime256v1,
-        NID_secp384r1,
-        NID_secp521r1,
-    };
+    static const njs_value_t  rsa_str = njs_string("RSA");
 
-    pkey = NULL;
+    rsa = njs_pkey_get_rsa_key(key->pkey);
 
-    fmt = njs_key_format(vm, njs_arg(args, nargs, 1));
-    if (njs_slow_path(fmt == NJS_KEY_FORMAT_UNKNOWN)) {
-        goto fail;
-    }
+    njs_rsa_get0_key(rsa, &n_bn, &e_bn, &d_bn);
 
-    options = njs_arg(args, nargs, 3);
-    alg = njs_key_algorithm(vm, options);
-    if (njs_slow_path(alg == NULL)) {
-        goto fail;
+    ret = njs_export_base64url_bignum(vm, &nvalue, n_bn, 0);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
     }
 
-    if (njs_slow_path(!(fmt & alg->fmt))) {
-        njs_type_error(vm, "unsupported key fmt \"%V\" for \"%V\" key",
-                       njs_format_string(fmt),
-                       njs_algorithm_string(alg));
-        goto fail;
+    ret = njs_export_base64url_bignum(vm, &evalue, e_bn, 0);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
     }
 
-    ret = njs_key_usage(vm, njs_arg(args, nargs, 5), &usage);
-    if (njs_slow_path(ret != NJS_OK)) {
-        goto fail;
+    ret = njs_vm_object_alloc(vm, retval, &string_kty, &rsa_str, &string_n,
+                              &nvalue, &string_e, &evalue, NULL);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
     }
 
-    if (njs_slow_path(usage & ~alg->usage)) {
-        njs_type_error(vm, "unsupported key usage for \"%V\" key",
-                       njs_algorithm_string(alg));
-        goto fail;
-    }
+    if (key->privat) {
+        njs_rsa_get0_factors(rsa, &p_bn, &q_bn);
+        njs_rsa_get0_ctr_params(rsa, &dp_bn, &dq_bn, &qi_bn);
 
-    ret = njs_vm_value_to_bytes(vm, &key_data, njs_arg(args, nargs, 2));
-    if (njs_slow_path(ret != NJS_OK)) {
-        goto fail;
-    }
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_d),
+                                       d_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
 
-    start = key_data.start;
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_p),
+                                       p_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
 
-    switch (fmt) {
-    case NJS_KEY_FORMAT_PKCS8:
-        bio = njs_bio_new_mem_buf(start, key_data.length);
-        if (njs_slow_path(bio == NULL)) {
-            njs_webcrypto_error(vm, "BIO_new_mem_buf() failed");
-            goto fail;
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_q),
+                                       q_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
         }
 
-        pkcs8 = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL);
-        if (njs_slow_path(pkcs8 == NULL)) {
-            BIO_free(bio);
-            njs_webcrypto_error(vm, "d2i_PKCS8_PRIV_KEY_INFO_bio() failed");
-            goto fail;
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_dp),
+                                       dp_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
         }
 
-        pkey = EVP_PKCS82PKEY(pkcs8);
-        if (njs_slow_path(pkey == NULL)) {
-            PKCS8_PRIV_KEY_INFO_free(pkcs8);
-            BIO_free(bio);
-            njs_webcrypto_error(vm, "EVP_PKCS82PKEY() failed");
-            goto fail;
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_dq),
+                                       dq_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
         }
 
-        PKCS8_PRIV_KEY_INFO_free(pkcs8);
-        BIO_free(bio);
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_qi),
+                                       qi_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+    }
 
-        break;
+    nm = &njs_webcrypto_alg_name[key->alg->type][key->hash];
 
-    case NJS_KEY_FORMAT_SPKI:
-        pkey = d2i_PUBKEY(NULL, &start, key_data.length);
-        if (njs_slow_path(pkey == NULL)) {
-            njs_webcrypto_error(vm, "d2i_PUBKEY() failed");
-            goto fail;
-        }
+    (void) njs_vm_value_string_set(vm, &alg, nm->start, nm->length);
 
-        break;
+    return njs_value_property_set(vm, retval, njs_value_arg(&string_alg), &alg);
+}
 
-    case NJS_KEY_FORMAT_RAW:
-        break;
 
-    default:
-        njs_internal_error(vm, "not implemented key format: \"jwk\"");
+static njs_int_t
+njs_export_jwk_ec(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval)
+{
+    int                    nid, group_bits, group_bytes;
+    BIGNUM                 *x_bn, *y_bn;
+    njs_int_t              ret;
+    njs_value_t            xvalue, yvalue, dvalue, name;
+    const EC_KEY           *ec;
+    const BIGNUM           *d_bn;
+    const EC_POINT         *pub;
+    const EC_GROUP         *group;
+    njs_webcrypto_entry_t  *e;
+
+    static const njs_value_t  ec_str = njs_string("EC");
+
+    x_bn = NULL;
+    y_bn = NULL;
+    d_bn = NULL;
+
+    ec = njs_pkey_get_ec_key(key->pkey);
+
+    pub = EC_KEY_get0_public_key(ec);
+    group = EC_KEY_get0_group(ec);
+
+    group_bits = EC_GROUP_get_degree(group);
+    group_bytes = (group_bits / CHAR_BIT) + (7 + (group_bits % CHAR_BIT)) / 8;
+
+    x_bn = BN_new();
+    if (x_bn == NULL) {
         goto fail;
     }
 
-    key = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(njs_webcrypto_key_t));
-    if (njs_slow_path(key == NULL)) {
-        njs_memory_error(vm);
+    y_bn = BN_new();
+    if (y_bn == NULL) {
         goto fail;
     }
 
-    key->alg = alg;
-    key->usage = usage;
+    if (!njs_ec_point_get_affine_coordinates(group, pub, x_bn, y_bn)) {
+        njs_webcrypto_error(vm, "EC_POINT_get_affine_coordinates() failed");
+        goto fail;
+    }
 
-    switch (alg->type) {
-    case NJS_ALGORITHM_RSA_OAEP:
-    case NJS_ALGORITHM_RSA_PSS:
-    case NJS_ALGORITHM_RSASSA_PKCS1_v1_5:
+    ret = njs_export_base64url_bignum(vm, &xvalue, x_bn, group_bytes);
+    if (ret != NJS_OK) {
+        goto fail;
+    }
 
-#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+    BN_free(x_bn);
+    x_bn = NULL;
 
-        rsa = EVP_PKEY_get1_RSA(pkey);
-        if (njs_slow_path(rsa == NULL)) {
-            njs_webcrypto_error(vm, "RSA key is not found");
-            goto fail;
+    ret = njs_export_base64url_bignum(vm, &yvalue, y_bn, group_bytes);
+    if (ret != NJS_OK) {
+        goto fail;
+    }
+
+    BN_free(y_bn);
+    y_bn = NULL;
+
+    nid = EC_GROUP_get_curve_name(group);
+
+    for (e = &njs_webcrypto_curve[0]; e->name.length != 0; e++) {
+        if ((uintptr_t) nid == e->value) {
+            (void) njs_vm_value_string_set(vm, &name, e->name.start,
+                                           e->name.length);
+            break;
         }
+    }
 
-        RSA_free(rsa);
+    if (e->name.length == 0) {
+        njs_type_error(vm, "Unsupported JWK EC curve: %s", OBJ_nid2sn(nid));
+        goto fail;
+    }
 
-#else
-        if (!EVP_PKEY_is_a(pkey, "RSA")) {
-            njs_webcrypto_error(vm, "RSA key is not found");
+    ret = njs_vm_object_alloc(vm, retval, &string_kty, &ec_str, &string_x,
+                              &xvalue, &string_y, &yvalue, &string_crv, &name,
+                              NULL);
+    if (ret != NJS_OK) {
+        goto fail;
+    }
+
+    if (key->privat) {
+        d_bn = EC_KEY_get0_private_key(ec);
+
+        ret = njs_export_base64url_bignum(vm, &dvalue, d_bn, group_bytes);
+        if (ret != NJS_OK) {
             goto fail;
         }
-#endif
 
-        ret = njs_algorithm_hash(vm, options, &key->hash);
-        if (njs_slow_path(ret == NJS_ERROR)) {
+        ret = njs_value_property_set(vm, retval, njs_value_arg(&string_d),
+                                     &dvalue);
+        if (ret != NJS_OK) {
             goto fail;
         }
+    }
 
-        key->pkey = pkey;
+    return NJS_OK;
 
-        break;
+fail:
 
-    case NJS_ALGORITHM_ECDSA:
-    case NJS_ALGORITHM_ECDH:
+    if (x_bn != NULL) {
+        BN_free(x_bn);
+    }
 
-#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+    if (y_bn != NULL) {
+        BN_free(y_bn);
+    }
 
-        ec = EVP_PKEY_get1_EC_KEY(pkey);
-        if (njs_slow_path(ec == NULL)) {
-            njs_webcrypto_error(vm, "EC key is not found");
-            goto fail;
-        }
+    return NJS_ERROR;
+}
 
-        group = EC_KEY_get0_group(ec);
-        nid = EC_GROUP_get_curve_name(group);
-        EC_KEY_free(ec);
 
-#else
+static njs_int_t
+njs_export_raw_ec(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval)
+{
+    size_t                   size;
+    u_char                   *dst;
+    const EC_KEY             *ec;
+    const EC_GROUP           *group;
+    const EC_POINT           *point;
+    point_conversion_form_t  form;
+
+    njs_assert(key->pkey != NULL);
+
+    if (key->privat) {
+        njs_type_error(vm, "private key of \"%V\" cannot be exported "
+                       "in \"raw\" format", njs_algorithm_string(key->alg));
+        return NJS_ERROR;
+    }
 
-        if (!EVP_PKEY_is_a(pkey, "EC")) {
-            njs_webcrypto_error(vm, "EC key is not found");
-            goto fail;
-        }
+    ec = njs_pkey_get_ec_key(key->pkey);
 
-        if (EVP_PKEY_get_group_name(pkey, gname, sizeof(gname), NULL) != 1) {
-            njs_webcrypto_error(vm, "EVP_PKEY_get_group_name() failed");
-            goto fail;
-        }
+    group = EC_KEY_get0_group(ec);
+    point = EC_KEY_get0_public_key(ec);
+    form = POINT_CONVERSION_UNCOMPRESSED;
 
-        nid = OBJ_txt2nid(gname);
+    size = EC_POINT_point2oct(group, point, form, NULL, 0, NULL);
+    if (njs_slow_path(size == 0)) {
+        njs_webcrypto_error(vm, "EC_POINT_point2oct() failed");
+        return NJS_ERROR;
+    }
 
-#endif
+    dst = njs_mp_alloc(njs_vm_memory_pool(vm), size);
+    if (njs_slow_path(dst == NULL)) {
+        return NJS_ERROR;
+    }
 
-        ret = njs_algorithm_curve(vm, options, &key->curve);
-        if (njs_slow_path(ret == NJS_ERROR)) {
-            goto fail;
-        }
+    size = EC_POINT_point2oct(group, point, form, dst, size, NULL);
+    if (njs_slow_path(size == 0)) {
+        njs_webcrypto_error(vm, "EC_POINT_point2oct() failed");
+        return NJS_ERROR;
+    }
 
-        if (njs_slow_path(curves[key->curve] != nid)) {
-            njs_webcrypto_error(vm, "name curve mismatch");
-            goto fail;
-        }
+    return njs_vm_value_array_buffer_set(vm, retval, dst, size);
+}
 
-        key->pkey = pkey;
 
-        break;
+static njs_int_t
+njs_export_jwk_asymmetric(njs_vm_t *vm, njs_webcrypto_key_t *key,
+    njs_value_t *retval)
+{
+    njs_int_t    ret;
+    njs_value_t  ops, extractable;
 
-    case NJS_ALGORITHM_HMAC:
-        ret = njs_algorithm_hash(vm, options, &key->hash);
-        if (njs_slow_path(ret == NJS_ERROR)) {
-            goto fail;
-        }
+    njs_assert(key->pkey != NULL);
 
-        key->raw = key_data;
+    switch (EVP_PKEY_id(key->pkey)) {
+    case EVP_PKEY_RSA:
+#if (OPENSSL_VERSION_NUMBER >= 0x10100001L)
+    case EVP_PKEY_RSA_PSS:
+#endif
+        ret = njs_export_jwk_rsa(vm, key, retval);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+
+        break;
+
+    case EVP_PKEY_EC:
+        ret = njs_export_jwk_ec(vm, key, retval);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+
+        break;
+
+    default:
+        njs_type_error(vm, "provided key cannot be exported as JWK");
+        return NJS_ERROR;
+    }
+
+    ret = njs_key_ops(vm, &ops, key->usage);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_value_property_set(vm, retval, njs_value_arg(&key_ops), &ops);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    njs_value_boolean_set(&extractable, key->extractable);
+
+    return njs_value_property_set(vm, retval, njs_value_arg(&string_ext),
+                                  &extractable);
+}
+
+
+static njs_int_t
+njs_ext_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
+{
+    BIO                         *bio;
+    BUF_MEM                     *mem;
+    njs_int_t                   ret;
+    njs_value_t                 value;
+    njs_webcrypto_key_t         *key;
+    PKCS8_PRIV_KEY_INFO         *pkcs8;
+    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_type_error(vm, "\"key\" is not a CryptoKey object");
+        goto fail;
+    }
+
+    if (njs_slow_path(!(fmt & key->alg->fmt))) {
+        njs_type_error(vm, "unsupported key fmt \"%V\" for \"%V\" key",
+                       njs_format_string(fmt),
+                       njs_algorithm_string(key->alg));
+        goto fail;
+    }
+
+    if (njs_slow_path(!key->extractable)) {
+        njs_type_error(vm, "provided key cannot be extracted");
+        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:
+            ret = njs_export_jwk_asymmetric(vm, key, &value);
+            if (njs_slow_path(ret != NJS_OK)) {
+                goto fail;
+            }
+
+            break;
+
+        default:
+            break;
+        }
+
+        break;
+
+    case NJS_KEY_FORMAT_PKCS8:
+        if (!key->privat) {
+            njs_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->pkey != NULL);
+
+        pkcs8 = EVP_PKEY2PKCS8(key->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, &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->privat) {
+            njs_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->pkey != NULL);
+
+        if (!i2d_PUBKEY_bio(bio, key->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, &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) {
+            ret = njs_export_raw_ec(vm, key, &value);
+            if (njs_slow_path(ret != NJS_OK)) {
+                goto fail;
+            }
+
+            break;
+        }
+
+        njs_internal_error(vm, "exporting as \"%V\" fmt is not implemented",
+                           njs_format_string(fmt));
+        goto fail;
+    }
+
+    return njs_webcrypto_result(vm, &value, NJS_OK);
+
+fail:
+
+    return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR);
+}
+
+
+static njs_int_t
+njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
+{
+    int                        nid;
+    unsigned                   usage;
+    njs_int_t                  ret;
+    njs_bool_t                 extractable;
+    njs_value_t                value, pub, priv, *aobject;
+    EVP_PKEY_CTX               *ctx;
+    njs_webcrypto_key_t        *key, *keypub;
+    njs_webcrypto_algorithm_t  *alg;
+
+    static const njs_value_t  string_ml = njs_string("modulusLength");
+    static const njs_value_t  string_priv = njs_string("privateKey");
+    static const njs_value_t  string_pub = njs_string("publicKey");
+
+    ctx = NULL;
+
+    aobject = njs_arg(args, nargs, 1);
+    extractable = njs_value_bool(njs_arg(args, nargs, 2));
+
+    alg = njs_key_algorithm(vm, aobject);
+    if (njs_slow_path(alg == NULL)) {
+        goto fail;
+    }
+
+    ret = njs_key_usage(vm, njs_arg(args, nargs, 3), &usage);
+    if (njs_slow_path(ret != NJS_OK)) {
+        goto fail;
+    }
+
+    key = njs_webcrypto_key_alloc(vm, alg, usage, extractable);
+    if (njs_slow_path(key == NULL)) {
+        goto fail;
+    }
+
+    if (njs_slow_path(usage & ~alg->usage)) {
+        njs_type_error(vm, "unsupported key usage for \"%V\" key",
+                       njs_algorithm_string(alg));
+        goto fail;
+    }
+
+    switch (alg->type) {
+    case NJS_ALGORITHM_RSASSA_PKCS1_v1_5:
+    case NJS_ALGORITHM_RSA_PSS:
+    case NJS_ALGORITHM_RSA_OAEP:
+        ret = njs_algorithm_hash(vm, aobject, &key->hash);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto fail;
+        }
+
+        ret = njs_value_property(vm, aobject, njs_value_arg(&string_ml),
+                                 &value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto fail;
+        }
+
+        if (!njs_is_number(&value)) {
+            njs_type_error(vm, "\"modulusLength\" is not a number");
+            goto fail;
+        }
+
+        ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+        if (njs_slow_path(ctx == NULL)) {
+            njs_webcrypto_error(vm, "EVP_PKEY_CTX_new_id() failed");
+            goto fail;
+        }
+
+        if (EVP_PKEY_keygen_init(ctx) <= 0) {
+            njs_webcrypto_error(vm, "EVP_PKEY_keygen_init() failed");
+            goto fail;
+        }
+
+        if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, njs_number(&value)) <= 0) {
+            njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_rsa_keygen_bits() "
+                                "failed");
+            goto fail;
+        }
+
+        if (EVP_PKEY_keygen(ctx, &key->pkey) <= 0) {
+            njs_webcrypto_error(vm, "EVP_PKEY_keygen() failed");
+            goto fail;
+        }
+
+        EVP_PKEY_CTX_free(ctx);
+        ctx = NULL;
+
+        key->privat = 1;
+        key->usage = (alg->type == NJS_ALGORITHM_RSA_OAEP)
+                        ? NJS_KEY_USAGE_DECRYPT
+                        : NJS_KEY_USAGE_SIGN;
+
+        keypub = njs_webcrypto_key_alloc(vm, alg, usage, extractable);
+        if (njs_slow_path(keypub == NULL)) {
+            goto fail;
+        }
+
+        if (njs_pkey_up_ref(key->pkey) <= 0) {
+            njs_webcrypto_error(vm, "njs_pkey_up_ref() failed");
+            goto fail;
+        }
+
+        keypub->pkey = key->pkey;
+        keypub->hash = key->hash;
+        keypub->usage = (alg->type == NJS_ALGORITHM_RSA_OAEP)
+                          ? NJS_KEY_USAGE_ENCRYPT
+                          : NJS_KEY_USAGE_VERIFY;
+
+        ret = njs_vm_external_create(vm, &priv,
+                                     njs_webcrypto_crypto_key_proto_id, key, 0);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
+
+        ret = njs_vm_external_create(vm, &pub,
+                                  njs_webcrypto_crypto_key_proto_id, keypub, 0);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
+
+        ret = njs_vm_object_alloc(vm, &value, &string_priv, &priv, &string_pub,
+                                  &pub, NULL);
+        if (ret != NJS_OK) {
+            goto fail;
+        }
+
+        break;
+
+    case NJS_ALGORITHM_ECDSA:
+        nid = 0;
+        ret = njs_algorithm_curve(vm, aobject, &nid);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto fail;
+        }
+
+        ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+        if (njs_slow_path(ctx == NULL)) {
+            njs_webcrypto_error(vm, "EVP_PKEY_CTX_new_id() failed");
+            goto fail;
+        }
+
+        if (EVP_PKEY_keygen_init(ctx) <= 0) {
+            njs_webcrypto_error(vm, "EVP_PKEY_keygen_init() failed");
+            goto fail;
+        }
+
+        if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) <= 0) {
+            njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_ec_paramgen_curve_nid() "
+                                "failed");
+            goto fail;
+        }
+
+        if (EVP_PKEY_keygen(ctx, &key->pkey) <= 0) {
+            njs_webcrypto_error(vm, "EVP_PKEY_keygen() failed");
+            goto fail;
+        }
+
+        EVP_PKEY_CTX_free(ctx);
+        ctx = NULL;
+
+        key->privat = 1;
+        key->usage = NJS_KEY_USAGE_SIGN;
+
+        keypub = njs_webcrypto_key_alloc(vm, alg, usage, extractable);
+        if (njs_slow_path(keypub == NULL)) {
+            goto fail;
+        }
+
+        if (njs_pkey_up_ref(key->pkey) <= 0) {
+            njs_webcrypto_error(vm, "njs_pkey_up_ref() failed");
+            goto fail;
+        }
+
+        keypub->pkey = key->pkey;
+        keypub->curve = key->curve;
+        keypub->usage = NJS_KEY_USAGE_VERIFY;
+
+        ret = njs_vm_external_create(vm, &priv,
+                                     njs_webcrypto_crypto_key_proto_id, key, 0);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
+
+        ret = njs_vm_external_create(vm, &pub,
+                                  njs_webcrypto_crypto_key_proto_id, keypub, 0);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
+
+        ret = njs_vm_object_alloc(vm, &value, &string_priv, &priv, &string_pub,
+                                  &pub, NULL);
+        if (ret != NJS_OK) {
+            goto fail;
+        }
+
+        break;
+
+    default:
+        njs_internal_error(vm, "not implemented generateKey"
+                           "algorithm: \"%V\"", njs_algorithm_string(alg));
+        return NJS_ERROR;
+    }
+
+    return njs_webcrypto_result(vm, &value, NJS_OK);
+
+fail:
+
+    if (ctx != NULL) {
+        EVP_PKEY_CTX_free(ctx);
+    }
+
+    return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR);
+}
+
+
+static BIGNUM *
+njs_import_base64url_bignum(njs_vm_t *vm, njs_value_t *value)
+{
+    njs_int_t  ret;
+    njs_str_t  data, decoded;
+    u_char     buf[512];
+
+    ret = njs_vm_value_to_bytes(vm, &data, value);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NULL;
+    }
+
+    (void) njs_decode_base64url_length(&data, &decoded.length);
+
+    if (njs_slow_path(decoded.length > sizeof(buf))) {
+        return NULL;
+    }
+
+    decoded.start = buf;
+
+    njs_decode_base64url(&decoded, &data);
+
+    return BN_bin2bn(decoded.start, decoded.length, NULL);
+}
+
+
+static EVP_PKEY *
+njs_import_jwk_rsa(njs_vm_t *vm, njs_value_t *jwk, njs_webcrypto_key_t *key)
+{
+    RSA                    *rsa;
+    BIGNUM                 *n_bn, *e_bn, *d_bn, *p_bn, *q_bn, *dp_bn, *dq_bn,
+                           *qi_bn;
+    njs_str_t              alg;
+    unsigned               usage;
+    EVP_PKEY               *pkey;
+    njs_int_t              ret;
+    njs_value_t            n, e, d, p, q, dp, dq, qi, value;
+    njs_webcrypto_entry_t  *w;
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_n), &n);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_e), &e);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_d), &d);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    if (!njs_value_is_string(&n)
+        || !njs_value_is_string(&e)
+        || (!njs_value_is_undefined(&d) && !njs_value_is_string(&d)))
+    {
+        njs_type_error(vm, "Invalid JWK RSA key");
+        return NULL;
+    }
+
+    key->privat = njs_value_is_string(&d);
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&key_ops), &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    if (njs_is_defined(&value)) {
+        ret = njs_key_usage(vm, &value, &usage);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NULL;
+        }
+
+        if ((key->usage & usage) != key->usage) {
+            njs_type_error(vm, "Key operations and usage mismatch");
+            return NULL;
+        }
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_alg), &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    if (njs_is_defined(&value)) {
+        ret = njs_value_to_string(vm, &value, &value);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NULL;
+        }
+
+        njs_string_get(&value, &alg);
+
+        for (w = &njs_webcrypto_alg_hash[0]; w->name.length != 0; w++) {
+            if (njs_strstr_eq(&alg, &w->name)) {
+                key->hash = w->value;
+                break;
+            }
+        }
+    }
+
+    if (key->extractable) {
+        ret = njs_value_property(vm, jwk, njs_value_arg(&string_ext), &value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return NULL;
+        }
+
+        if (njs_is_defined(&value) && !njs_value_bool(&value)) {
+            njs_type_error(vm, "JWK RSA is not extractable");
+            return NULL;
+        }
+    }
+
+    rsa = RSA_new();
+    if (rsa == NULL) {
+        njs_webcrypto_error(vm, "RSA_new() failed");
+        return NULL;
+    }
+
+    n_bn = njs_import_base64url_bignum(vm, &n);
+    if (njs_slow_path(n_bn == NULL)) {
+        goto fail;
+    }
+
+    e_bn = njs_import_base64url_bignum(vm, &e);
+    if (njs_slow_path(e_bn == NULL)) {
+        goto fail;
+    }
+
+    if (!njs_rsa_set0_key(rsa, n_bn, e_bn, NULL)) {
+        BN_free(n_bn);
+        BN_free(e_bn);
+
+        njs_webcrypto_error(vm, "RSA_set0_key() failed");
+        goto fail;
+    }
+
+    if (!key->privat) {
+        goto done;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_p), &p);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        goto fail;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_q), &q);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        goto fail;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_dp), &dp);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        goto fail;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_dq), &dq);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        goto fail;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_qi), &qi);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        goto fail;
+    }
+
+    if (!njs_value_is_string(&d)
+        || !njs_value_is_string(&p)
+        || !njs_value_is_string(&q)
+        || !njs_value_is_string(&dp)
+        || !njs_value_is_string(&dq)
+        || !njs_value_is_string(&qi))
+    {
+        njs_type_error(vm, "Invalid JWK RSA key");
+        goto fail;
+    }
+
+    d_bn = njs_import_base64url_bignum(vm, &d);
+    if (njs_slow_path(d_bn == NULL)) {
+        goto fail;
+    }
+
+    if (!njs_rsa_set0_key(rsa, NULL, NULL, d_bn)) {
+        BN_free(d_bn);
+
+        njs_webcrypto_error(vm, "RSA_set0_key() failed");
+        goto fail;
+    }
+
+    p_bn = njs_import_base64url_bignum(vm, &p);
+    if (njs_slow_path(p_bn == NULL)) {
+        goto fail;
+    }
+
+    q_bn = njs_import_base64url_bignum(vm, &q);
+    if (njs_slow_path(q_bn == NULL)) {
+        BN_free(p_bn);
+        goto fail;
+    }
+
+    if (!njs_rsa_set0_factors(rsa, p_bn, q_bn)) {
+        BN_free(p_bn);
+        BN_free(q_bn);
+
+        njs_webcrypto_error(vm, "RSA_set0_factors() failed");
+        goto fail;
+    }
+
+    dp_bn = njs_import_base64url_bignum(vm, &dp);
+    if (njs_slow_path(dp_bn == NULL)) {
+        goto fail;
+    }
+
+    dq_bn = njs_import_base64url_bignum(vm, &dq);
+    if (njs_slow_path(dq_bn == NULL)) {
+        BN_free(dp_bn);
+        goto fail;
+    }
+
+    qi_bn = njs_import_base64url_bignum(vm, &qi);
+    if (njs_slow_path(qi_bn == NULL)) {
+        BN_free(dp_bn);
+        BN_free(dq_bn);
+        goto fail;
+    }
+
+    if (!njs_rsa_set0_ctr_params(rsa, dp_bn, dq_bn, qi_bn)) {
+        BN_free(dp_bn);
+        BN_free(dq_bn);
+        BN_free(qi_bn);
+        njs_webcrypto_error(vm, "RSA_set0_crt_params() failed");
+        goto fail;
+    }
+
+done:
+
+    pkey = EVP_PKEY_new();
+    if (njs_slow_path(pkey == NULL)) {
+        goto fail;
+    }
+
+    if (!EVP_PKEY_set1_RSA(pkey, rsa)) {
+        EVP_PKEY_free(pkey);
+        goto fail;
+    }
+
+    RSA_free(rsa);
+
+    return pkey;
+
+fail:
+
+    RSA_free(rsa);
+
+    return NULL;
+}
+
+
+static EVP_PKEY *
+njs_import_raw_ec(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key)
+{
+    EC_KEY          *ec;
+    EVP_PKEY        *pkey;
+    EC_POINT        *pub;
+    const EC_GROUP  *group;
+
+    ec = EC_KEY_new_by_curve_name(key->curve);
+    if (njs_slow_path(ec == NULL)) {
+        njs_webcrypto_error(vm, "EC_KEY_new_by_curve_name() failed");
+        return NULL;
+    }
+
+    group = EC_KEY_get0_group(ec);
+
+    pub = EC_POINT_new(group);
+    if (njs_slow_path(pub == NULL)) {
+        EC_KEY_free(ec);
+        njs_webcrypto_error(vm, "EC_POINT_new() failed");
+        return NULL;
+    }
+
+    if (!EC_POINT_oct2point(group, pub, data->start, data->length, NULL)) {
+        EC_KEY_free(ec);
+        EC_POINT_free(pub);
+        njs_webcrypto_error(vm, "EC_POINT_oct2point() failed");
+        return NULL;
+    }
+
+    if (!EC_KEY_set_public_key(ec, pub)) {
+        EC_KEY_free(ec);
+        EC_POINT_free(pub);
+        njs_webcrypto_error(vm, "EC_KEY_set_public_key() failed");
+        return NULL;
+    }
+
+    pkey = EVP_PKEY_new();
+    if (njs_slow_path(pkey == NULL)) {
+        EC_KEY_free(ec);
+        EC_POINT_free(pub);
+        njs_webcrypto_error(vm, "EVP_PKEY_new() failed");
+        return NULL;
+    }
+
+    if (!EVP_PKEY_set1_EC_KEY(pkey, ec)) {
+        EC_KEY_free(ec);
+        EC_POINT_free(pub);
+        EVP_PKEY_free(pkey);
+        njs_webcrypto_error(vm, "EVP_PKEY_set1_EC_KEY() failed");
+        return NULL;
+    }
+
+    EC_KEY_free(ec);
+    EC_POINT_free(pub);
+
+    return pkey;
+}
+
+
+static EVP_PKEY *
+njs_import_jwk_ec(njs_vm_t *vm, njs_value_t *jwk, njs_webcrypto_key_t *key)
+{
+    int                    curve;
+    EC_KEY                 *ec;
+    BIGNUM                 *x_bn, *y_bn, *d_bn;
+    unsigned               usage;
+    EVP_PKEY               *pkey;
+    njs_str_t              name;
+    njs_int_t              ret;
+    njs_value_t            x, y, d, value;
+    njs_webcrypto_entry_t  *e;
+
+    ec = NULL;
+    x_bn = NULL;
+    y_bn = NULL;
+    d_bn = NULL;
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_x), &x);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_y), &y);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_d), &d);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    if (!njs_value_is_string(&x)
+        || !njs_value_is_string(&y)
+        || (!njs_value_is_undefined(&d) && !njs_value_is_string(&d)))
+    {
+        njs_type_error(vm, "Invalid JWK EC key");
+        return NULL;
+    }
+
+    key->privat = njs_value_is_string(&d);
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&key_ops), &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    if (njs_is_defined(&value)) {
+        ret = njs_key_usage(vm, &value, &usage);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NULL;
+        }
+
+        if ((key->usage & usage) != key->usage) {
+            njs_type_error(vm, "Key operations and usage mismatch");
+            return NULL;
+        }
+    }
+
+    if (key->extractable) {
+        ret = njs_value_property(vm, jwk, njs_value_arg(&string_ext), &value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return NULL;
+        }
+
+        if (njs_is_defined(&value) && !njs_value_bool(&value)) {
+            njs_type_error(vm, "JWK EC is not extractable");
+            return NULL;
+        }
+    }
+
+    ret = njs_value_property(vm, jwk, njs_value_arg(&string_crv), &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NULL;
+    }
+
+    curve = 0;
+    njs_string_get(&value, &name);
+
+    for (e = &njs_webcrypto_curve[0]; e->name.length != 0; e++) {
+        if (njs_strstr_eq(&name, &e->name)) {
+            curve = e->value;
+            break;
+        }
+    }
+
+    if (curve != key->curve) {
+        njs_type_error(vm, "JWK EC curve mismatch");
+        return NULL;
+    }
+
+    ec = EC_KEY_new_by_curve_name(key->curve);
+    if (njs_slow_path(ec == NULL)) {
+        njs_webcrypto_error(vm, "EC_KEY_new_by_curve_name() failed");
+        return NULL;
+    }
+
+    x_bn = njs_import_base64url_bignum(vm, &x);
+    if (njs_slow_path(x_bn == NULL)) {
+        goto fail;
+    }
+
+    y_bn = njs_import_base64url_bignum(vm, &y);
+    if (njs_slow_path(y_bn == NULL)) {
+        goto fail;
+    }
+
+    if (key->privat) {
+        d_bn = njs_import_base64url_bignum(vm, &d);
+        if (njs_slow_path(d_bn == NULL)) {
+            goto fail;
+        }
+    }
+
+    if (!EC_KEY_set_public_key_affine_coordinates(ec, x_bn, y_bn)) {
+        njs_webcrypto_error(vm, "EC_KEY_set_public_key_affine_coordinates() "
+                            "failed");
+        goto fail;
+    }
+
+    BN_free(x_bn);
+    x_bn = NULL;
+
+    BN_free(y_bn);
+    y_bn = NULL;
+
+    pkey = EVP_PKEY_new();
+    if (njs_slow_path(pkey == NULL)) {
+        goto fail;
+    }
+
+    if (!EVP_PKEY_set1_EC_KEY(pkey, ec)) {
+        njs_webcrypto_error(vm, "EVP_PKEY_set1_EC_KEY() failed");
+        goto fail_pkey;
+    }
+
+    if (key->privat) {
+        if (!EC_KEY_set_private_key(ec, d_bn)) {
+            njs_webcrypto_error(vm, "EC_KEY_set_private_key() failed");
+            goto fail_pkey;
+        }
+
+        BN_free(d_bn);
+        d_bn = NULL;
+    }
+
+    EC_KEY_free(ec);
+
+    return pkey;
+
+fail_pkey:
+
+    EVP_PKEY_free(pkey);
+    EC_KEY_free(ec);
+    ec = NULL;
+
+fail:
+
+    EC_KEY_free(ec);
+
+    if (x_bn != NULL) {
+        BN_free(x_bn);
+    }
+
+    if (y_bn != NULL) {
+        BN_free(y_bn);
+    }
+
+    if (d_bn != NULL) {
+        BN_free(d_bn);
+    }
+
+    return NULL;
+}
+
+
+static njs_int_t
+njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
+{
+    int                         nid;
+    BIO                         *bio;
+#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+    RSA                         *rsa;
+    EC_KEY                      *ec;
+#else
+    char                        gname[80];
+#endif
+    unsigned                    mask, usage;
+    EVP_PKEY                    *pkey;
+    njs_int_t                   ret;
+    njs_str_t                   key_data, kty;
+    njs_value_t                 value, *options, *jwk;
+    const u_char                *start;
+#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+    const EC_GROUP              *group;
+#endif
+    njs_webcrypto_key_t         *key;
+    PKCS8_PRIV_KEY_INFO         *pkcs8;
+    njs_webcrypto_hash_t        hash;
+    njs_webcrypto_algorithm_t   *alg;
+    njs_webcrypto_key_format_t  fmt;
+
+    pkey = NULL;
+
+    fmt = njs_key_format(vm, njs_arg(args, nargs, 1));
+    if (njs_slow_path(fmt == NJS_KEY_FORMAT_UNKNOWN)) {
+        goto fail;
+    }
+
+    options = njs_arg(args, nargs, 3);
+    alg = njs_key_algorithm(vm, options);
+    if (njs_slow_path(alg == NULL)) {
+        goto fail;
+    }
+
+    if (njs_slow_path(!(fmt & alg->fmt))) {
+        njs_type_error(vm, "unsupported key fmt \"%V\" for \"%V\" key",
+                       njs_format_string(fmt),
+                       njs_algorithm_string(alg));
+        goto fail;
+    }
+
+    ret = njs_key_usage(vm, njs_arg(args, nargs, 5), &usage);
+    if (njs_slow_path(ret != NJS_OK)) {
+        goto fail;
+    }
+
+    if (njs_slow_path(usage & ~alg->usage)) {
+        njs_type_error(vm, "unsupported key usage for \"%V\" key",
+                       njs_algorithm_string(alg));
+        goto fail;
+    }
+
+    if (fmt != NJS_KEY_FORMAT_JWK) {
+        ret = njs_vm_value_to_bytes(vm, &key_data, njs_arg(args, nargs, 2));
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
+    }
+
+    key = njs_webcrypto_key_alloc(vm, alg, usage,
+                                  njs_value_bool(njs_arg(args, nargs, 4)));
+    if (njs_slow_path(key == NULL)) {
+        goto fail;
+    }
+
+    /*
+     * set by njs_webcrypto_key_alloc():
+     *
+     *  key->pkey = NULL;
+     *  key->raw.length = 0;
+     *  key->raw.start = NULL;
+     *  key->curve = 0;
+     *  key->privat = 0;
+     *  key->hash = NJS_HASH_UNSET;
+     */
+
+    start = key_data.start;
+
+    switch (fmt) {
+    case NJS_KEY_FORMAT_PKCS8:
+        bio = njs_bio_new_mem_buf(start, key_data.length);
+        if (njs_slow_path(bio == NULL)) {
+            njs_webcrypto_error(vm, "BIO_new_mem_buf() failed");
+            goto fail;
+        }
+
+        pkcs8 = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL);
+        if (njs_slow_path(pkcs8 == NULL)) {
+            BIO_free(bio);
+            njs_webcrypto_error(vm, "d2i_PKCS8_PRIV_KEY_INFO_bio() failed");
+            goto fail;
+        }
+
+        pkey = EVP_PKCS82PKEY(pkcs8);
+        if (njs_slow_path(pkey == NULL)) {
+            PKCS8_PRIV_KEY_INFO_free(pkcs8);
+            BIO_free(bio);
+            njs_webcrypto_error(vm, "EVP_PKCS82PKEY() failed");
+            goto fail;
+        }
+
+        PKCS8_PRIV_KEY_INFO_free(pkcs8);
+        BIO_free(bio);
+
+        key->privat = 1;
+
+        break;
+
+    case NJS_KEY_FORMAT_SPKI:
+        pkey = d2i_PUBKEY(NULL, &start, key_data.length);
+        if (njs_slow_path(pkey == NULL)) {
+            njs_webcrypto_error(vm, "d2i_PUBKEY() failed");
+            goto fail;
+        }
+
+        break;
+
+    case NJS_KEY_FORMAT_JWK:
+        jwk = njs_arg(args, nargs, 2);
+        if (!njs_value_is_object(jwk)) {
+            njs_type_error(vm, "invalid JWK key data: object value expected");
+            goto fail;
+        }
+
+        ret = njs_value_property(vm, jwk, njs_value_arg(&string_kty), &value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto fail;
+        }
+
+        ret = njs_vm_value_to_bytes(vm, &kty, &value);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto fail;
+        }
+
+        if (njs_strstr_eq(&kty, &njs_str_value("RSA"))) {
+            pkey = njs_import_jwk_rsa(vm, jwk, key);
+            if (njs_slow_path(pkey == NULL)) {
+                goto fail;
+            }
+
+        } else if (njs_strstr_eq(&kty, &njs_str_value("EC"))) {
+            ret = njs_algorithm_curve(vm, options, &key->curve);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                goto fail;
+            }
+
+            pkey = njs_import_jwk_ec(vm, jwk, key);
+            if (njs_slow_path(pkey == NULL)) {
+                goto fail;
+            }
+
+        } else {
+            njs_type_error(vm, "invalid JWK key type: %V", &kty);
+            goto fail;
+        }
+
+        break;
+
+    case NJS_KEY_FORMAT_RAW:
+    default:
+        break;
+    }
+
+    switch (alg->type) {
+    case NJS_ALGORITHM_RSA_OAEP:
+    case NJS_ALGORITHM_RSA_PSS:
+    case NJS_ALGORITHM_RSASSA_PKCS1_v1_5:
+
+#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+
+        rsa = EVP_PKEY_get1_RSA(pkey);
+        if (njs_slow_path(rsa == NULL)) {
+            njs_webcrypto_error(vm, "RSA key is not found");
+            goto fail;
+        }
+
+        RSA_free(rsa);
+
+#else
+        if (!EVP_PKEY_is_a(pkey, "RSA")) {
+            njs_webcrypto_error(vm, "RSA key is not found");
+            goto fail;
+        }
+#endif
+
+        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, "RSA JWK hash mismatch");
+            goto fail;
+        }
+
+        if (key->privat) {
+            mask = (alg->type == NJS_ALGORITHM_RSA_OAEP)
+                         ? ~(NJS_KEY_USAGE_DECRYPT | NJS_KEY_USAGE_UNWRAP_KEY)
+                         : ~(NJS_KEY_USAGE_SIGN);
+        } else {
+            mask = (alg->type == NJS_ALGORITHM_RSA_OAEP)
+                         ? ~(NJS_KEY_USAGE_ENCRYPT | NJS_KEY_USAGE_WRAP_KEY)
+                         : ~(NJS_KEY_USAGE_VERIFY);
+        }
+
+        if (key->usage & mask) {
+            njs_type_error(vm, "key usage mismatch for \"%V\" key",
+                           njs_algorithm_string(alg));
+            goto fail;
+        }
+
+        key->hash = hash;
+        key->pkey = pkey;
+
+        break;
+
+    case NJS_ALGORITHM_ECDSA:
+    case NJS_ALGORITHM_ECDH:
+        ret = njs_algorithm_curve(vm, options, &key->curve);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto fail;
+        }
+
+        if (fmt == NJS_KEY_FORMAT_RAW) {
+            pkey = njs_import_raw_ec(vm, &key_data, key);
+            if (njs_slow_path(pkey == NULL)) {
+                goto fail;
+            }
+        }
+
+#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+
+        ec = EVP_PKEY_get1_EC_KEY(pkey);
+        if (njs_slow_path(ec == NULL)) {
+            njs_webcrypto_error(vm, "EC key is not found");
+            goto fail;
+        }
+
+        group = EC_KEY_get0_group(ec);
+        nid = EC_GROUP_get_curve_name(group);
+        EC_KEY_free(ec);
+
+#else
+
+        if (!EVP_PKEY_is_a(pkey, "EC")) {
+            njs_webcrypto_error(vm, "EC key is not found");
+            goto fail;
+        }
+
+        if (EVP_PKEY_get_group_name(pkey, gname, sizeof(gname), NULL) != 1) {
+            njs_webcrypto_error(vm, "EVP_PKEY_get_group_name() failed");
+            goto fail;
+        }
+
+        nid = OBJ_txt2nid(gname);
+
+#endif
+
+        if (njs_slow_path(key->curve != nid)) {
+            njs_webcrypto_error(vm, "name curve mismatch");
+            goto fail;
+        }
+
+        mask = key->privat ? ~NJS_KEY_USAGE_SIGN : ~NJS_KEY_USAGE_VERIFY;
+
+        if (key->usage & mask) {
+            njs_type_error(vm, "key usage mismatch for \"%V\" key",
+                           njs_algorithm_string(alg));
+            goto fail;
+        }
+
+        key->pkey = pkey;
+
+        break;
+
+    case NJS_ALGORITHM_HMAC:
+        ret = njs_algorithm_hash(vm, options, &key->hash);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto fail;
+        }
+
+        key->raw = key_data;
         break;
 
     case NJS_ALGORITHM_AES_GCM:
@@ -1889,18 +3213,6 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         break;
     }
 
-    if (pkey != NULL) {
-        cln = njs_mp_cleanup_add(njs_vm_memory_pool(vm), 0);
-        if (cln == NULL) {
-            njs_memory_error(vm);
-            goto fail;
-        }
-
-        cln->handler = njs_webcrypto_cleanup_pkey;
-        cln->data = key;
-        pkey = NULL;
-    }
-
     ret = njs_vm_external_create(vm, &value, njs_webcrypto_crypto_key_proto_id,
                                  key, 0);
     if (njs_slow_path(ret != NJS_OK)) {
@@ -1971,48 +3283,6 @@ njs_set_rsa_padding(njs_vm_t *vm, njs_value_t *options, EVP_PKEY *pkey,
 }
 
 
-static const EC_KEY *
-njs_pkey_get_ec_key(EVP_PKEY *pkey)
-{
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-    return EVP_PKEY_get0_EC_KEY(pkey);
-#else
-    if (pkey->type != EVP_PKEY_EC) {
-        return NULL;
-    }
-
-    return pkey->pkey.ec;
-#endif
-}
-
-
-static int
-njs_ec_group_order_bits(const EC_GROUP *group)
-{
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-    return EC_GROUP_order_bits(group);
-#else
-    int     bits;
-    BIGNUM  *order;
-
-    order = BN_new();
-    if (order == NULL) {
-        return 0;
-    }
-
-    if (EC_GROUP_get_order(group, order, NULL) == 0) {
-        return 0;
-    }
-
-    bits = BN_num_bits(order);
-
-    BN_free(order);
-
-    return bits;
-#endif
-}
-
-
 static unsigned int
 njs_ec_rs_size(EVP_PKEY *pkey)
 {
@@ -2039,17 +3309,6 @@ njs_ec_rs_size(EVP_PKEY *pkey)
 }
 
 
-static int
-njs_bn_bn2binpad(const BIGNUM *bn, unsigned char *to, int tolen)
-{
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-    return BN_bn2binpad(bn, to, tolen);
-#else
-    return BN_bn2bin(bn, &to[tolen - BN_num_bytes(bn)]);
-#endif
-}
-
-
 static njs_int_t
 njs_convert_der_to_p1363(njs_vm_t *vm, EVP_PKEY *pkey, const u_char *der,
     size_t der_len, u_char **pout, size_t *out_len)
@@ -2114,27 +3373,6 @@ memory_error:
 }
 
 
-static int
-njs_ecdsa_sig_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
-{
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-    return ECDSA_SIG_set0(sig, r, s);
-#else
-    if (r == NULL || s == NULL) {
-        return 0;
-    }
-
-    BN_clear_free(sig->r);
-    BN_clear_free(sig->s);
-
-    sig->r = r;
-    sig->s = s;
-
-    return 1;
-#endif
-}
-
-
 static njs_int_t
 njs_convert_p1363_to_der(njs_vm_t *vm, EVP_PKEY *pkey, u_char *p1363,
     size_t p1363_len, u_char **pout, size_t *out_len)
@@ -2519,6 +3757,36 @@ njs_webcrypto_cleanup_pkey(void *data)
 }
 
 
+static njs_webcrypto_key_t *
+njs_webcrypto_key_alloc(njs_vm_t *vm, njs_webcrypto_algorithm_t *alg,
+    unsigned usage, njs_bool_t extractable)
+{
+    njs_mp_cleanup_t     *cln;
+    njs_webcrypto_key_t  *key;
+
+    key = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(njs_webcrypto_key_t));
+    if (njs_slow_path(key == NULL)) {
+        njs_memory_error(vm);
+        return NULL;
+    }
+
+    cln = njs_mp_cleanup_add(njs_vm_memory_pool(vm), 0);
+    if (cln == NULL) {
+        njs_memory_error(vm);
+        return NULL;
+    }
+
+    cln->handler = njs_webcrypto_cleanup_pkey;
+    cln->data = key;
+
+    key->alg = alg;
+    key->usage = usage;
+    key->extractable = extractable;
+
+    return key;
+}
+
+
 static njs_webcrypto_key_format_t
 njs_key_format(njs_vm_t *vm, njs_value_t *value)
 {
@@ -2617,6 +3885,37 @@ njs_key_usage(njs_vm_t *vm, njs_value_t *value, unsigned *mask)
 }
 
 
+static njs_int_t
+njs_key_ops(njs_vm_t *vm, njs_value_t *retval, unsigned mask)
+{
+    njs_int_t              ret;
+    njs_value_t            *value;
+    njs_webcrypto_entry_t  *e;
+
+    ret = njs_vm_array_alloc(vm, retval, 4);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    for (e = &njs_webcrypto_usage[0]; e->name.length != 0; e++) {
+        if (mask & e->value) {
+            value = njs_vm_array_push(vm, retval);
+            if (value == NULL) {
+                return NJS_ERROR;
+            }
+
+            ret = njs_vm_value_string_set(vm, value, e->name.start,
+                                          e->name.length);
+            if (ret != NJS_OK) {
+                return NJS_ERROR;
+            }
+        }
+    }
+
+    return NJS_OK;
+}
+
+
 static njs_webcrypto_algorithm_t *
 njs_key_algorithm(njs_vm_t *vm, njs_value_t *options)
 {
@@ -2750,8 +4049,7 @@ njs_algorithm_hash_digest(njs_webcrypto_hash_t hash)
 
 
 static njs_int_t
-njs_algorithm_curve(njs_vm_t *vm, njs_value_t *options,
-    njs_webcrypto_curve_t *curve)
+njs_algorithm_curve(njs_vm_t *vm, njs_value_t *options, int *curve)
 {
     njs_int_t              ret;
     njs_str_t              name;
@@ -2760,6 +4058,10 @@ njs_algorithm_curve(njs_vm_t *vm, njs_value_t *options,
 
     static const njs_value_t  string_curve = njs_string("namedCurve");
 
+    if (*curve != 0) {
+        return NJS_OK;
+    }
+
     ret = njs_value_property(vm, options, njs_value_arg(&string_curve),
                              &value);
     if (njs_slow_path(ret != NJS_OK)) {
@@ -2845,6 +4147,24 @@ error:
 }
 
 
+static njs_int_t
+njs_webcrypto_array_buffer(njs_vm_t *vm, njs_value_t *retval,
+    u_char *start, size_t length)
+{
+    u_char  *dst;
+
+    dst = njs_mp_alloc(njs_vm_memory_pool(vm), length);
+    if (njs_slow_path(dst == NULL)) {
+        njs_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    memcpy(dst, start, length);
+
+    return njs_vm_value_array_buffer_set(vm, retval, dst, length);
+}
+
+
 static u_char *
 njs_cpystrn(u_char *dst, u_char *src, size_t n)
 {
diff --git a/test/harness/compareObjects.js b/test/harness/compareObjects.js
new file mode 100644 (file)
index 0000000..d4a20c1
--- /dev/null
@@ -0,0 +1,17 @@
+function compareObjects(ref, obj) {
+    if (!isObject(ref) || !isObject(obj)) {
+        return ref === obj;
+    }
+
+    for (const key in ref) {
+        if (!compareObjects(ref[key], obj[key])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+function isObject(object) {
+    return object != null && typeof object === 'object';
+}
index 51b335ac1e2a9135be5a201668a3307ce3f8bdc4..3d82680727cfe0a0c8c19ec8859edc7ad879ffe8 100644 (file)
@@ -36,13 +36,13 @@ async function run(tlist) {
 }
 
 function merge(to, from) {
-    let r = Object.assign({}, to);
+    let r = Object.assign(Array.isArray(to) ? [] : {}, to);
     Object.keys(from).forEach(v => {
         if (typeof r[v] == 'object' && typeof from[v] == 'object') {
             r[v] = merge(r[v], from[v]);
 
         } else if (typeof from[v] == 'object') {
-            r[v] = Object.assign({}, from[v]);
+            r[v] = Object.assign(Array.isArray(from[v]) ? [] : {}, from[v]);
 
         } else {
             r[v] = from[v];
index 243098efe3e56faf2784073615c3af173dc8010a..d403f39aa4e48546f9341a0b175eb4239e91990b 100644 (file)
@@ -11,3 +11,11 @@ function base64decode(b64) {
     return Buffer.from(joined, 'base64');
 }
 
+function load_jwk(data) {
+    if (typeof data == 'string') {
+        let json = fs.readFileSync(`test/webcrypto/${data}`);
+        return JSON.parse(json);
+    }
+
+    return data;
+}
index 5848234a065df12c5190fb9e67da230cc9ae0a81..dcf7e58a6a263833604dc9c39139def90e2880d1 100644 (file)
@@ -166,6 +166,10 @@ async function crypto_object(keyData: ArrayBuffer, data: ArrayBuffer) {
                                              {name: 'RSA-OAEP', hash: "SHA-256"},
                                              false, ['decrypt']);
 
+    let jkey = await crypto.subtle.importKey("jwk", { kty: "RSA" },
+                                             {name: 'RSA-OAEP', hash: "SHA-256"},
+                                             true, ['decrypt']);
+
     let skey = await crypto.subtle.importKey("raw", keyData, 'AES-CBC',
                                              false, ['encrypt']);
 
@@ -176,6 +180,14 @@ async function crypto_object(keyData: ArrayBuffer, data: ArrayBuffer) {
 
     let r:boolean;
     r = await crypto.subtle.verify({name: 'RSA-PSS', saltLength:32}, skey, sig, data);
+
+    let jwk = await crypto.subtle.exportKey('jwk', ekey);
+
+    let pair = await crypto.subtle.generateKey({name: "RSASSA-PKCS1-v1_5",
+                                                hash: "SHA-512",
+                                                modulusLength: 2048,
+                                                publicExponent: new Uint8Array([1, 0, 1])},
+                                                true, ['sign', 'verify']);
 }
 
 function buffer(b: Buffer) {
diff --git a/test/webcrypto/ec.jwk b/test/webcrypto/ec.jwk
new file mode 100644 (file)
index 0000000..c90e234
--- /dev/null
@@ -0,0 +1 @@
+{"key_ops":["sign"],"ext":true,"kty":"EC","x":"cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw","y":"4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI","d":"E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A","crv":"P-256"}
diff --git a/test/webcrypto/ec.pub.jwk b/test/webcrypto/ec.pub.jwk
new file mode 100644 (file)
index 0000000..91d4f84
--- /dev/null
@@ -0,0 +1 @@
+{"key_ops":["verify"],"ext":true,"kty":"EC","x":"cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw","y":"4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI","crv":"P-256"}
diff --git a/test/webcrypto/export.t.js b/test/webcrypto/export.t.js
new file mode 100644 (file)
index 0000000..70bf4d4
--- /dev/null
@@ -0,0 +1,298 @@
+/*---
+includes: [compatFs.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js, compareObjects.js]
+flags: [async]
+---*/
+
+async function load_key(params) {
+    if (params.generate_keys) {
+        let type = params.generate_keys.type;
+        if (params.generate_keys.keys) {
+            return params.generate_keys.keys[type];
+        }
+
+        params.generate_keys.keys = await crypto.subtle.generateKey(params.generate_keys.alg,
+                                                                    params.generate_keys.extractable,
+                                                                    params.generate_keys.usage);
+
+        return params.generate_keys.keys[type];
+    }
+
+    return await crypto.subtle.importKey(params.key.fmt,
+                                         params.key.key,
+                                         params.key.alg,
+                                         params.key.extractable,
+                                         params.key.usage);
+
+}
+
+async function test(params) {
+    let key = await load_key(params);
+    let exp = await crypto.subtle.exportKey(params.export.fmt, key);
+
+    if (params.check && !params.check(exp, params)) {
+        throw Error(`failed check`);
+    }
+
+    if (exp[Symbol.toStringTag] == 'ArrayBuffer') {
+        let buf = Buffer.from(exp);
+        exp = "ArrayBuffer:" + buf.toString('base64url');
+    }
+
+    if (params.expected && !compareObjects(params.expected, exp)) {
+        throw Error(`unexpected export key: ${JSON.stringify(exp)}\n expected: ${JSON.stringify(params.expected)}`);
+    }
+
+    if (!params.generate_keys
+        && (exp.startsWith && !exp.startsWith("ArrayBuffer:")))
+    {
+        /* Check that exported key can be imported back. */
+        let imported = await crypto.subtle.importKey(params.export.fmt,
+                                                     exp,
+                                                     params.key.alg,
+                                                     params.key.extractable,
+                                                     params.key.usage);
+    }
+
+    return 'SUCCESS';
+}
+
+function p(args, default_opts) {
+    let key, pem;
+    let params = merge({}, default_opts);
+    params = merge(params, args);
+
+    switch (params.key.fmt) {
+    case "spki":
+        pem = fs.readFileSync(`test/webcrypto/${params.key.key}`);
+        key = pem_to_der(pem, "PUBLIC");
+        break;
+    case "pkcs8":
+        pem = fs.readFileSync(`test/webcrypto/${params.key.key}`);
+        key = pem_to_der(pem, "PRIVATE");
+        break;
+    case "jwk":
+        key = load_jwk(params.key.key);
+        break;
+    default:
+        throw Error("Unknown encoding key format");
+    }
+
+    params.key.key = key;
+
+    return params;
+}
+
+function validate_property(exp, p, exp_len) {
+    if (!exp[p]) {
+        throw Error(`"${p}" is not found in ${JSON.stringify(exp)}`);
+    }
+
+    if (typeof exp[p] != 'string') {
+        throw Error(`"${p}" is not a string`);
+    }
+
+    let len = exp[p].length;
+
+    if (len < exp_len - 4 || len > exp_len + 4) {
+        throw Error(`"${p}":"${exp[p]}" length is out of range [${exp_len - 4}, ${exp_len + 4}]`);
+    }
+}
+
+
+function validate_rsa_jwk(exp, params) {
+    let expected_len = params.generate_keys.alg.modulusLength / 8 * (4 / 3);
+    expected_len = Math.round(expected_len);
+
+    validate_property(exp, 'n', expected_len);
+
+    if (params.generate_keys.type == 'privateKey') {
+        validate_property(exp, 'd', expected_len);
+        validate_property(exp, 'p', expected_len / 2);
+        validate_property(exp, 'q', expected_len / 2);
+
+        validate_property(exp, 'dq', expected_len / 2);
+        validate_property(exp, 'dp', expected_len / 2);
+        validate_property(exp, 'qi', expected_len / 2);
+    }
+
+    return true;
+}
+
+let rsa_tsuite = {
+    name: "RSA exporting",
+    skip: () => (!has_fs() || !has_webcrypto()),
+    T: test,
+    prepare_args: p,
+    opts: {
+        key: {
+               fmt: "spki",
+               key: "rsa.spki",
+               alg: { name: "RSA-OAEP", hash: "SHA-256" },
+               extractable: true,
+               usage: [ "encrypt" ]
+        },
+        export: { fmt: "jwk" },
+        expected: {
+            ext: true,
+            kty: "RSA",
+            e: "AQAB",
+        },
+    },
+
+    tests: [
+      { expected: { key_ops: [ "encrypt" ],
+                    n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                    alg: "RSA-OAEP-256" } },
+      { export: { fmt: "spki" },
+        expected: "ArrayBuffer:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJSbGgkLxUAzmHJhlr5dQFmDU3XLAUw9SlawXWRpIZ533w_ddSA_OBfzTCnLN3mp0a4Auqd_aUxzs7w-sYG07liu9CUj3o1fSNSjltznDPbbShvcBQJ5CdN_H5QWozikLCOgNKjY-WmEl1aAVjKYYFoEImCcQ1Av0fU3_z_I2I3wIDAQAB" },
+
+      { key: { fmt: "pkcs8", key: "rsa.pkcs8", usage: [ "decrypt" ], alg: { hash: "SHA-512" } },
+        expected: { key_ops: [ "decrypt" ],
+                    n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                    alg: "RSA-OAEP-512" } },
+
+      { key: { fmt: "pkcs8", key: "rsa.pkcs8", usage: [ "decrypt" ], alg: { hash: "SHA-512" } },
+        export: { fmt: "pkcs8" },
+        expected: "ArrayBuffer:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMlJsaCQvFQDOYcmGWvl1AWYNTdcsBTD1KVrBdZGkhnnffD911ID84F_NMKcs3eanRrgC6p39pTHOzvD6xgbTuWK70JSPejV9I1KOW3OcM9ttKG9wFAnkJ038flBajOKQsI6A0qNj5aYSXVoBWMphgWgQiYJxDUC_R9Tf_P8jYjfAgMBAAECgYEAj06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_kCQQD0BJvbLDlvx3u6esW47LLgQNw9ufMSlu5UYBJ4c-qQ5HAeyp4Zt_AaWENhJitjQcLBSxIFIVw7dIN67RnTNK8VAkEA0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1IwJBAKVFydo0peJTljXDmc-aYb0JsSINo9jfaSS0vU3gFOt2DYqNaW-56WGujlRqadCcZbBNjDL1WWbbj4HevTMT59ECQEWaKgzPolykwN5XUNE0DCp1ZwIAH1kbBjfo-sMVt0f9S1TsN9SmBl-4l1X7CY5zU3RATMH5FR-8ns83fM1ZieMCQQDZEQ-dFAhouzJrnCXAXDTCHA9oBtNmnaN-C6G2DmCi79iu7sLHP9vzdgU-CgjrG4YTU5exaRFNOhLwW4hYKs0F" },
+
+      { key: { fmt: "pkcs8", key: "rsa.pkcs8", usage: [ "decrypt" ], alg: { hash: "SHA-512" } },
+        export: { fmt: "spki" },
+        exception: "TypeError: private key of \"RSA-OAEP\" cannot be exported as SPKI" },
+
+      { generate_keys: { alg: { name: "RSA-OAEP",
+                                modulusLength: 2048,
+                                publicExponent: new Uint8Array([1, 0, 1]),
+                                hash: "SHA-256" },
+                         extractable: true,
+                         type: "publicKey",
+                         usage: [ "encrypt", "decrypt" ] },
+        check: validate_rsa_jwk,
+        expected: { kty: "RSA", ext: true, key_ops: [ "encrypt" ], e: "AQAB", alg: "RSA-OAEP-256" } },
+      { generate_keys: { alg: { name: "RSA-OAEP",
+                                modulusLength: 1024,
+                                publicExponent: new Uint8Array([1, 0, 1]),
+                                hash: "SHA-1" },
+                         extractable: true,
+                         type: "privateKey",
+                         usage: [ "encrypt", "decrypt" ] },
+        check: validate_rsa_jwk,
+        expected: { kty: "RSA", ext: true, key_ops: [ "decrypt" ], e: "AQAB", alg: "RSA-OAEP" } },
+      { generate_keys: { alg: { name: "RSA-OAEP",
+                                modulusLength: 1024,
+                                publicExponent: new Uint8Array([1, 0, 1]),
+                                hash: "SHA-1" },
+                         extractable: false,
+                         type: "privateKey",
+                         usage: [ "encrypt", "decrypt" ] },
+        check: validate_rsa_jwk,
+        exception: "TypeError: provided key cannot be extracted" },
+      { generate_keys: { alg: { name: "RSASSA-PKCS1-v1_5",
+                                modulusLength: 1024,
+                                publicExponent: new Uint8Array([1, 0, 1]),
+                                hash: "SHA-512" },
+                         extractable: true,
+                         type: "publicKey",
+                         usage: [ "sign", "verify" ] },
+        check: validate_rsa_jwk,
+        expected: { kty: "RSA", ext: true, key_ops: [ "verify" ], e: "AQAB", alg: "RS512" } },
+      { generate_keys: { alg: { name: "RSASSA-PKCS1-v1_5",
+                                modulusLength: 1024,
+                                publicExponent: new Uint8Array([1, 0, 1]),
+                                hash: "SHA-256" },
+                         extractable: true,
+                         type: "privateKey",
+                         usage: [ "sign", "verify" ] },
+        check: validate_rsa_jwk,
+        expected: { kty: "RSA", ext: true, key_ops: [ "sign" ], e: "AQAB", alg: "RS256" } },
+      { generate_keys: { alg: { name: "RSA-PSS",
+                                modulusLength: 1024,
+                                publicExponent: new Uint8Array([1, 0, 1]),
+                                hash: "SHA-384" },
+                         extractable: true,
+                         type: "publicKey",
+                         usage: [ "sign", "verify" ] },
+        check: validate_rsa_jwk,
+        expected: { kty: "RSA", ext: true, key_ops: [ "verify" ], e: "AQAB", alg: "PS384" } },
+      { generate_keys: { alg: { name: "RSA-PSS",
+                                modulusLength: 1024,
+                                publicExponent: new Uint8Array([1, 0, 1]),
+                                hash: "SHA-1" },
+                         extractable: true,
+                         type: "privateKey",
+                         usage: [ "sign", "verify" ] },
+        check: validate_rsa_jwk,
+        expected: { kty: "RSA", ext: true, key_ops: [ "sign" ], e: "AQAB", alg: "PS1" } },
+]};
+
+function validate_ec_jwk(exp, params) {
+    let crv = params.generate_keys.alg.namedCurve;
+    let expected_len = Number(crv.slice(2)) / 8 * (4 / 3);
+    expected_len = Math.round(expected_len);
+
+    validate_property(exp, 'x', expected_len);
+    validate_property(exp, 'y', expected_len);
+
+    if (params.generate_keys.type == 'privateKey') {
+        validate_property(exp, 'd', expected_len);
+    }
+
+    return true;
+}
+
+let ec_tsuite = {
+    name: "EC exporting",
+    skip: () => (!has_fs() || !has_webcrypto()),
+    T: test,
+    prepare_args: p,
+    opts: {
+        key: { fmt: "spki",
+               key: "ec.spki",
+               alg: { name: "ECDSA", namedCurve: "P-256" },
+               extractable: true,
+               usage: [ "verify" ] },
+        export: { fmt: "jwk" },
+        expected: { ext: true, kty: "EC" },
+    },
+
+    tests: [
+      { expected: { key_ops: [ "verify" ],
+                    x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                    y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                    crv: "P-256" } },
+      { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
+        expected: { key_ops: [ "sign" ],
+                    x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                    y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                    d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A",
+                    crv: "P-256" } },
+      { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
+        export: { fmt: "pkcs8" },
+        expected: "ArrayBuffer:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6ChRANCAARxRSxlEa5VhF4aJNCX0ypHuKvp1kiDD7ykz4XSmElZ3ODc5_-7jc9AAN1OH4aX1cUg-FOUHIhshKDOK94wu24y" },
+      { export: { fmt: "pkcs8" },
+        exception: "TypeError: public key of \"ECDSA\" cannot be exported as PKCS8" },
+      { export: { fmt: "raw" },
+        expected: "ArrayBuffer:BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" },
+      { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
+        export: { fmt: "raw" },
+        exception: "TypeError: private key of \"ECDSA\" cannot be exported in \"raw\" format" },
+      { generate_keys: { alg: { name: "ECDSA",
+                                namedCurve: "P-256" },
+                         extractable: true,
+                         type: "publicKey",
+                         usage: [ "sign", "verify" ] },
+        check: validate_ec_jwk,
+        expected: { kty: "EC", ext: true, key_ops: [ "verify" ], crv: "P-256" } },
+      { generate_keys: { alg: { name: "ECDSA",
+                                namedCurve: "P-384" },
+                         extractable: true,
+                         type: "privateKey",
+                         usage: [ "sign", "verify" ] },
+        check: validate_ec_jwk,
+        expected: { kty: "EC", ext: true, key_ops: [ "sign" ], crv: "P-384" } },
+]};
+
+run([
+    rsa_tsuite,
+    ec_tsuite,
+])
+.then($DONE, $DONE);
diff --git a/test/webcrypto/rsa.dec.jwk b/test/webcrypto/rsa.dec.jwk
new file mode 100644 (file)
index 0000000..f1e092f
--- /dev/null
@@ -0,0 +1 @@
+{"key_ops":["decrypt"],"ext":true,"kty":"RSA","n":"yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8","e":"AQAB","d":"j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k","p":"9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ","q":"0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw","dp":"pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q","dq":"RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w","qi":"2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ","alg":"RS256"}
diff --git a/test/webcrypto/rsa.enc.pub.jwk b/test/webcrypto/rsa.enc.pub.jwk
new file mode 100644 (file)
index 0000000..2e9870d
--- /dev/null
@@ -0,0 +1 @@
+{"key_ops":["encrypt"],"ext":true,"kty":"RSA","n":"yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8","e":"AQAB","alg":"RS256"}
diff --git a/test/webcrypto/rsa.jwk b/test/webcrypto/rsa.jwk
new file mode 100644 (file)
index 0000000..bebce36
--- /dev/null
@@ -0,0 +1 @@
+{"key_ops":["sign"],"ext":true,"kty":"RSA","n":"yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8","e":"AQAB","d":"j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k","p":"9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ","q":"0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw","dp":"pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q","dq":"RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w","qi":"2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ","alg":"RS256"}
diff --git a/test/webcrypto/rsa.pub.jwk b/test/webcrypto/rsa.pub.jwk
new file mode 100644 (file)
index 0000000..201f124
--- /dev/null
@@ -0,0 +1 @@
+{"key_ops":["verify"],"ext":true,"kty":"RSA","n":"yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8","e":"AQAB","alg":"RS256"}
index 1e4c540984d804cd7636d8065f32e11ae0f56a38..b87a090947ea3601a3d2278ff237fb5a7f47fd7b 100644 (file)
@@ -3,20 +3,52 @@ includes: [compatFs.js, compatBuffer.js, compatWebcrypto.js, runTsuite.js, webCr
 flags: [async]
 ---*/
 
-async function test(params) {
-    let spki = await crypto.subtle.importKey("spki",
-                            pem_to_der(fs.readFileSync(`test/webcrypto/${params.spki}`), "PUBLIC"),
-                            {name:"RSA-OAEP", hash:params.spki_hash},
-                            false, ["encrypt"]);
+async function encrypt_key(params) {
+    if (params.generate_keys) {
+        if (params.generate_keys.publicKey) {
+            return params.generate_keys.publicKey;
+        }
+
+        params.generate_keys = await crypto.subtle.generateKey(params.generate_keys.alg,
+                                                               params.generate_keys.extractable,
+                                                               params.generate_keys.usage);
+
+        return params.generate_keys.publicKey;
+    }
+
+    return await crypto.subtle.importKey(params.enc.fmt,
+                                         params.enc.key,
+                                         { name: "RSA-OAEP", hash:params.enc.hash },
+                                         false, ["encrypt"]);
+
+}
+
+async function decrypt_key(params) {
+    if (params.generate_keys) {
+        if (params.generate_keys.privateKey) {
+            return params.generate_keys.privateKey;
+        }
+
+        params.generate_keys = await crypto.subtle.generateKey(params.generate_keys.alg,
+                                                               params.generate_keys.extractable,
+                                                               params.generate_keys.usage);
+
+        return params.generate_keys.privateKey;
+    }
 
-    let pkcs8 = await crypto.subtle.importKey("pkcs8",
-                            pem_to_der(fs.readFileSync(`test/webcrypto/${params.pkcs8}`), "PRIVATE"),
-                            {name:"RSA-OAEP", hash:params.pkcs8_hash},
-                            false, ["decrypt"]);
+    return await crypto.subtle.importKey(params.dec.fmt,
+                                         params.dec.key,
+                                         { name: "RSA-OAEP", hash:params.dec.hash },
+                                         false, ["decrypt"]);
+}
+
+async function test(params) {
+    let enc_key = await encrypt_key(params);
+    let dec_key = await decrypt_key(params);
 
-    let enc = await crypto.subtle.encrypt({name: "RSA-OAEP"}, spki, params.data);
+    let enc = await crypto.subtle.encrypt({name: "RSA-OAEP"}, enc_key, params.data);
 
-    let plaintext = await crypto.subtle.decrypt({name: "RSA-OAEP"}, pkcs8, enc);
+    let plaintext = await crypto.subtle.decrypt({name: "RSA-OAEP"}, dec_key, enc);
 
     plaintext = Buffer.from(plaintext);
 
@@ -28,7 +60,37 @@ async function test(params) {
 }
 
 function p(args, default_opts) {
-    let params = Object.assign({}, default_opts, args);
+    let key;
+    let params = merge({}, default_opts);
+    params = merge(params, args);
+
+    switch (params.enc.fmt) {
+    case "spki":
+        let pem = fs.readFileSync(`test/webcrypto/${params.enc.key}`);
+        key = pem_to_der(pem, "PUBLIC");
+        break;
+    case "jwk":
+        key = load_jwk(params.enc.key);
+        break;
+    default:
+        throw Error("Unknown encoding key format");
+    }
+
+    params.enc.key = key;
+
+    switch (params.dec.fmt) {
+    case "pkcs8":
+        let pem = fs.readFileSync(`test/webcrypto/${params.dec.key}`);
+        key = pem_to_der(pem, "PRIVATE");
+        break;
+    case "jwk":
+        key = load_jwk(params.dec.key);
+        break;
+    default:
+        throw Error("Unknown decoding key format");
+    }
+
+    params.dec.key = key;
 
     params.data = Buffer.from(params.data, "hex");
 
@@ -41,27 +103,35 @@ let rsa_tsuite = {
     T: test,
     prepare_args: p,
     opts: {
-        spki: "rsa.spki",
-        spki_hash: "SHA-256",
-        pkcs8: "rsa.pkcs8",
-        pkcs8_hash: "SHA-256",
+        enc: { fmt: "spki", key: "rsa.spki", hash: "SHA-256" },
+        dec: { fmt: "pkcs8", key: "rsa.pkcs8", hash: "SHA-256" },
     },
 
     tests: [
         { data: "aabbcc" },
         { data: "aabbccdd".repeat(4) },
         { data: "aabbccdd".repeat(7) },
-        { data: "aabbcc", spki_hash: "SHA-1", pkcs8_hash: "SHA-1" },
-        { data: "aabbccdd".repeat(4), spki_hash: "SHA-1", pkcs8_hash: "SHA-1" },
-        { data: "aabbccdd".repeat(7), spki_hash: "SHA-1", pkcs8_hash: "SHA-1" },
-        { data: "aabbcc", spki_hash: "SHA-384", pkcs8_hash: "SHA-384" },
-        { data: "aabbccdd".repeat(4), spki_hash: "SHA-384", pkcs8_hash: "SHA-384" },
-        { data: "aabbccdd".repeat(7), spki_hash: "SHA-384", pkcs8_hash: "SHA-384" },
-
-        { data: "aabbcc", spki_hash: "SHA-256", pkcs8_hash: "SHA-384", exception: "Error: EVP_PKEY_decrypt() failed" },
-        { data: "aabbcc", spki_hash: "XXX", exception: "TypeError: unknown hash name: \"XXX\"" },
-        { data: "aabbcc", spki: "rsa.spki.broken", exception: "Error: d2i_PUBKEY() failed" },
-        { data: "aabbcc", spki: "rsa2.spki", exception: "Error: EVP_PKEY_decrypt() failed" },
+        { data: "aabbcc",
+          generate_keys: { alg: { name: "RSA-OAEP",
+                                  modulusLength: 2048,
+                                  publicExponent: new Uint8Array([1, 0, 1]),
+                                  hash: "SHA-256" },
+                           extractable: true,
+                           usage: [ "encrypt", "decrypt" ] },
+          expected: true },
+        { data: "aabbcc", enc: { hash: "SHA-1" }, dec: { hash: "SHA-1" } },
+        { data: "aabbccdd".repeat(4), enc: { hash: "SHA-1" }, dec: { hash: "SHA-1" } },
+        { data: "aabbccdd".repeat(7), enc: { hash: "SHA-1" }, dec: { hash: "SHA-1" } },
+        { data: "aabbcc", enc: { hash: "SHA-384" }, dec: { hash: "SHA-384" } },
+        { data: "aabbccdd".repeat(4), enc: { hash: "SHA-384" }, dec: { hash: "SHA-384" } },
+        { data: "aabbccdd".repeat(7), enc: { hash: "SHA-384" }, dec: { hash: "SHA-384" } },
+
+        { data: "aabbcc", enc: { hash: "SHA-256" }, dec: { hash: "SHA-384" }, exception: "Error: EVP_PKEY_decrypt() failed" },
+        { data: "aabbcc", enc: { hash: "XXX" }, exception: "TypeError: unknown hash name: \"XXX\"" },
+        { data: "aabbcc", dec: { key: "rsa.spki.broken" }, exception: "Error: d2i_PUBKEY() failed" },
+        { data: "aabbcc", dec: { key: "rsa2.spki" }, exception: "Error: EVP_PKEY_decrypt() failed" },
+
+        { data: "aabbcc", enc: { fmt: "jwk", key: "rsa.enc.pub.jwk" }, dec: { fmt: "jwk", key: "rsa.dec.jwk" } },
 ]};
 
 run([rsa_tsuite])
index d72aa0034dfd8a5f8545d344aea8e63ba99da915..d7e1ebddd7f103d69fa7c34e5c06504d01a9e668 100644 (file)
@@ -3,15 +3,53 @@ includes: [compatFs.js, compatBuffer.js, compatWebcrypto.js, runTsuite.js, webCr
 flags: [async]
 ---*/
 
+async function sign_key(params) {
+    if (params.generate_keys) {
+        if (params.generate_keys.privateKey) {
+            return params.generate_keys.privateKey;
+        }
+
+        params.generate_keys = await crypto.subtle.generateKey(params.generate_keys.alg,
+                                                               params.generate_keys.extractable,
+                                                               params.generate_keys.usage);
+
+        return params.generate_keys.privateKey;
+    }
+
+    return await crypto.subtle.importKey(params.sign_key.fmt,
+                                  params.sign_key.key,
+                                  params.import_alg,
+                                  params.sign_key.extractable,
+                                  params.sign_key.key_ops);
+
+}
+
+async function verify_key(params) {
+    if (params.generate_keys) {
+        if (params.generate_keys.publicKey) {
+            return params.generate_keys.publicKey;
+        }
+
+        params.generate_keys = await crypto.subtle.generateKey(params.generate_keys.alg,
+                                                               params.generate_keys.extractable,
+                                                               params.generate_keys.usage);
+
+        return params.generate_keys.publicKey;
+    }
+
+    return await crypto.subtle.importKey(params.verify_key.fmt,
+                                         params.verify_key.key,
+                                         params.import_alg,
+                                         false, [ "verify" ]);
+
+}
+
 async function test(params) {
     let encoder = new TextEncoder();
-    let sign_key = await crypto.subtle.importKey(params.sign_key.fmt,
-                                                 params.sign_key.key,
-                                                 params.import_alg,
-                                                 false, [ "sign" ]);
+    let skey = await sign_key(params);
 
-    let sig = await crypto.subtle.sign(params.sign_alg, sign_key,
-                                     encoder.encode(params.text))
+    let sig = await crypto.subtle.sign(params.sign_alg, skey,
+                                       encoder.encode(params.text))
                     .catch (e => {
                         if (e.toString().startsWith("Error: EVP_PKEY_CTX_set_signature_md() failed")) {
                             /* Red Hat Enterprise Linux: SHA-1 is disabled */
@@ -24,12 +62,9 @@ async function test(params) {
     }
 
     if (params.verify) {
-        let verify_key = await crypto.subtle.importKey(params.verify_key.fmt,
-                                                       params.verify_key.key,
-                                                       params.import_alg,
-                                                       false, [ "verify" ]);
+        let vkey = await verify_key(params);
 
-        let r = await crypto.subtle.verify(params.sign_alg, verify_key, sig,
+        let r = await crypto.subtle.verify(params.sign_alg, vkey, sig,
                                            encoder.encode(params.text));
 
         if (params.expected !== r) {
@@ -40,7 +75,7 @@ async function test(params) {
             let broken_sig = Buffer.concat([Buffer.from(sig)]);
             broken_sig[8] = 255 - broken_sig[8];
 
-            r = await crypto.subtle.verify(params.sign_alg, verify_key, broken_sig,
+            r = await crypto.subtle.verify(params.sign_alg, vkey, broken_sig,
                                            encoder.encode(params.text));
             if (r !== false) {
                 throw Error(`${params.sign_alg.name} BROKEN SIG failed expected: "false" vs "${r}"`);
@@ -49,7 +84,7 @@ async function test(params) {
             let broken_text = encoder.encode(params.text);
             broken_text[0] = 255 - broken_text[0];
 
-            r = await crypto.subtle.verify(params.sign_alg, verify_key, sig,
+            r = await crypto.subtle.verify(params.sign_alg, vkey, sig,
                                            broken_text);
             if (r !== false) {
                 throw Error(`${params.sign_alg.name} BROKEN TEXT failed expected: "false" vs "${r}"`);
@@ -79,22 +114,33 @@ function p(args, default_opts) {
         let pem = fs.readFileSync(`test/webcrypto/${params.sign_key.key}`);
         key = pem_to_der(pem, "PRIVATE");
         break;
+    case "jwk":
+        key = load_jwk(params.sign_key.key);
+        break;
     case "raw":
-        key = encoder.encode(params.sign_key.key);
+        key = Buffer.from(params.sign_key.key, "base64url");
         break;
     default:
         throw Error("Unknown sign key format");
     }
 
     params.sign_key.key = key;
+    params.sign_key.extractable = Boolean(params.sign_key.extractable);
+
+    if (!params.sign_key.key_ops) {
+        params.sign_key.key_ops = [ "sign" ];
+    }
 
     switch (params.verify_key.fmt) {
     case "spki":
         let pem = fs.readFileSync(`test/webcrypto/${params.verify_key.key}`);
         key = pem_to_der(pem, "PUBLIC");
         break;
+    case "jwk":
+        key = load_jwk(params.verify_key.key);
+        break;
     case "raw":
-        key = encoder.encode(params.verify_key.key);
+        key = Buffer.from(params.verify_key.key, "base64url");
         break;
     default:
         throw Error("Unknown verify key format");
@@ -112,8 +158,8 @@ let hmac_tsuite = {
     prepare_args: p,
     opts: {
         text: "TExt-T0-SiGN",
-        sign_key: { key: "secretKEY", fmt: "raw" },
-        verify_key: { key: "secretKEY", fmt: "raw" },
+        sign_key: { key: "c2VjcmV0S0VZ", fmt: "raw" },
+        verify_key: { key: "c2VjcmV0S0VZ", fmt: "raw" },
         verify: false,
         import_alg: {
             name: "HMAC",
@@ -138,7 +184,7 @@ let hmac_tsuite = {
         { verify: true, import_alg: { hash: "SHA-384" }, expected: true },
         { verify: true, import_alg: { hash: "SHA-512" }, expected: true },
         { verify: true, import_alg: { hash: "SHA-1" }, expected: true },
-        { verify: true, verify_key: { key: "secretKEY2" }, expected: false },
+        { verify: true, verify_key: { key: "c2VjcmV0S0VZMg" }, expected: false },
 ]};
 
 let rsassa_pkcs1_v1_5_tsuite = {
@@ -170,6 +216,121 @@ let rsassa_pkcs1_v1_5_tsuite = {
         { verify: true, import_alg: { hash: "SHA-512" }, expected: true },
         { verify: true, import_alg: { hash: "SHA-1" }, expected: true },
         { verify: true, verify_key: { key: "rsa2.spki" }, expected: false },
+        { verify: true, sign_key: { key: "rsa.jwk", fmt: "jwk" }, expected: true },
+        { sign_key: { key: "rsa.jwk", fmt: "jwk" },
+          expected: "b126c528abd305dc2b7234de44ffa2190bd55f57087f75620196e8bdb05ba205e52ceca03e4799f30a6d61a6610878b1038a5dd869ab8c04ffe80d49d14407b2c2fe52ca78c9c409fcf7fee26188941f5072179c2bf2de43e637b089c32cf04f14ca01e7b9c33bbbec603b2815de0180b12a3269b0453aba158642e00303890d" },
+        { verify: true, sign_key: { key: "rsa.jwk", fmt: "jwk" },
+          verify_key: { key: "rsa.pub.jwk", fmt: "jwk" }, expected: true },
+        { verify: true,
+          generate_keys: { alg: { name: "RSASSA-PKCS1-v1_5",
+                                  modulusLength: 2048,
+                                  publicExponent: new Uint8Array([1, 0, 1]),
+                                  hash: "SHA-256" },
+                           extractable: true,
+                           usage: [ "sign", "verify" ] },
+          expected: true },
+
+        { sign_key: { key: 1, fmt: "jwk" }, exception: "TypeError: invalid JWK key data" },
+        { sign_key: { key: { kty: "RSA" }, fmt: "jwk" },
+          exception: "TypeError: Invalid JWK RSA key" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8" },
+                      fmt: "jwk" },
+          exception: "TypeError: Invalid JWK RSA key" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB" },
+                      fmt: "jwk" },
+          exception: "TypeError: key usage mismatch for a RSASSA-PKCS1-v1_5" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k" },
+                      fmt: "jwk" },
+          exception: "TypeError: Invalid JWK RSA key" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ" },
+                      fmt: "jwk" },
+          exception: "TypeError: Invalid JWK RSA key" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ",
+                             q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw" },
+                      fmt: "jwk" },
+          exception: "TypeError: Invalid JWK RSA key" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ",
+                             q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw",
+                             dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q" },
+                      fmt: "jwk" },
+          exception: "TypeError: Invalid JWK RSA key" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ",
+                             q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw",
+                             dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q",
+                             dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w" },
+                      fmt: "jwk" },
+          exception: "TypeError: Invalid JWK RSA key" },
+        { verify: true,
+          sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ",
+                             q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw",
+                             dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q",
+                             dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w",
+                             qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ" },
+                      fmt: "jwk" },
+          expected: true },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ",
+                             q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw",
+                             dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q",
+                             dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w",
+                             qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ",
+                             key_ops: [ "verify" ] },
+                      fmt: "jwk" },
+          exception: "TypeError: Key operations and usage mismatch" },
+        { sign_key: { key: { kty: "RSA",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ",
+                             q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw",
+                             dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q",
+                             dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w",
+                             qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ",
+                             ext: false },
+                      fmt: "jwk",
+                      extractable: true },
+          exception: "TypeError: JWK RSA is not extractable" },
+        { sign_key: { key: { kty: "RSA",
+                             alg: "RS384",
+                             n: "yUmxoJC8VAM5hyYZa-XUBZg1N1ywFMPUpWsF1kaSGed98P3XUgPzgX80wpyzd5qdGuALqnf2lMc7O8PrGBtO5YrvQlI96NX0jUo5bc5wz220ob3AUCeQnTfx-UFqM4pCwjoDSo2PlphJdWgFYymGBaBCJgnENQL9H1N_8_yNiN8",
+                             e: "AQAB",
+                             d: "j06DQyCopFujYoASi0oWmGEUSjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJTG5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH_kOf-znUc7eTvuzISs61x_k",
+                             p: "9ASb2yw5b8d7unrFuOyy4EDcPbnzEpbuVGASeHPqkORwHsqeGbfwGlhDYSYrY0HCwUsSBSFcO3SDeu0Z0zSvFQ",
+                             q: "0yvzzgHo_PGYSlVj-M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr-igqLHhzfynAQjjf39VrXuPuRL23REF1Iw",
+                             dp: "pUXJ2jSl4lOWNcOZz5phvQmxIg2j2N9pJLS9TeAU63YNio1pb7npYa6OVGpp0JxlsE2MMvVZZtuPgd69MxPn0Q",
+                             dq: "RZoqDM-iXKTA3ldQ0TQMKnVnAgAfWRsGN-j6wxW3R_1LVOw31KYGX7iXVfsJjnNTdEBMwfkVH7yezzd8zVmJ4w",
+                             qi: "2REPnRQIaLsya5wlwFw0whwPaAbTZp2jfguhtg5gou_Yru7Cxz_b83YFPgoI6xuGE1OXsWkRTToS8FuIWCrNBQ" },
+                      fmt: "jwk" },
+          exception: "TypeError: JWK hash mismatch" },
 ]};
 
 let rsa_pss_tsuite = {
@@ -202,6 +363,17 @@ let rsa_pss_tsuite = {
         { verify: true, import_alg: { hash: "SHA-512" }, sign_alg: { saltLength: 32 },
           expected: true },
         { verify: true, verify_key: { key: "rsa2.spki" }, expected: false },
+
+        { verify: true, sign_key: { key: "rsa.jwk", fmt: "jwk" },
+          verify_key: { key: "rsa.pub.jwk", fmt: "jwk" }, expected: true },
+        { verify: true,
+          generate_keys: { alg: { name: "RSA-PSS",
+                                  modulusLength: 2048,
+                                  publicExponent: new Uint8Array([1, 0, 1]),
+                                  hash: "SHA-256" },
+                           extractable: true,
+                           usage: [ "sign", "verify" ] },
+          expected: true },
 ]};
 
 let ecdsa_tsuite = {
@@ -231,6 +403,64 @@ let ecdsa_tsuite = {
         { verify: true, verify_key: { key: "ec2.spki" }, expected: false },
         { verify: true, verify_key: { key: "rsa.spki" }, exception: "Error: EC key is not found" },
         { verify: true, import_alg: { namedCurve: "P-384" }, exception: "Error: name curve mismatch" },
+
+        { verify: true,
+          verify_key: { key: "BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                        fmt: "raw"},
+         expected: true },
+
+        { verify: true, sign_key: { key: "ec.jwk", fmt: "jwk" }, expected: true },
+        { verify: true, sign_key: { key: "ec.jwk", fmt: "jwk" },
+          verify_key: { key: "ec.pub.jwk", fmt: "jwk" }, expected: true },
+        { verify: true, sign_key: { key: "ec.jwk", fmt: "jwk" },
+          import_alg: { namedCurve: "P-384" }, exception: "Error: JWK EC curve mismatch" },
+        { sign_key: { key: 1, fmt: "jwk" }, exception: "TypeError: Invalid JWK EC key" },
+        { sign_key: { key: { kty: "EC" }, fmt: "jwk" }, exception: "TypeError: Invalid JWK EC key" },
+        { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw"}, fmt: "jwk" },
+          exception: "TypeError: Invalid JWK EC key" },
+        { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                             y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" },
+                      fmt: "jwk" },
+          exception: "TypeError: Invalid JWK EC key" },
+        { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                              y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                              d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A" },
+                      fmt: "jwk" },
+          exception: "TypeError: JWK EC curve mismatch" },
+        { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                             y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                             d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-384" },
+                      fmt: "jwk" },
+          exception: "TypeError: JWK EC curve mismatch" },
+        { verify: true,
+          sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                             y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                             d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" },
+                      fmt: "jwk" },
+          expected: true },
+        { sign_key: { key: { kty: "EC", x: "_BROKEN_",
+                             y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                             d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" },
+                      fmt: "jwk" },
+          exception: "Error: EC_KEY_set_public_key_affine_coordinates() failed" },
+        { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                             y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                             d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256",
+                             key_ops: [ "verify" ]},
+                      fmt: "jwk" },
+          exception: "TypeError: Key operations and usage mismatch" },
+        { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                             y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI", ext: false,
+                             d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" },
+                      extractable: true,
+                      fmt: "jwk" },
+          exception: "TypeError: JWK is not extractable" },
+        { sign_key: { key: { kty: "EC", x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
+                             y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
+                             d: "E2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6A", crv: "P-256" },
+                      key_ops: [ 'verify', 'sign' ],
+                      fmt: "jwk" },
+          exception: "TypeError: Unsupported key usage for a ECDSA key" },
 ]};
 
 run([
index b67ee0d55367088299f72f0181d739655d26d912..ec0ab212e120a74408326529fe7e9510c85a1049 100644 (file)
@@ -33,11 +33,23 @@ interface  RsaHashedImportParams {
     hash: HashVariants;
 }
 
+interface  RsaHashedKeyGenParams {
+    name: "RSASSA-PKCS1-v1_5" | "RSA-PSS" | "RSA-OAEP";
+    hash: HashVariants;
+    modulusLength: number;
+    publicExponent: Uint8Array;
+}
+
 interface  EcKeyImportParams {
     name: "ECDSA";
     namedCurve: "P-256" | "P-384" | "P-521";
 }
 
+interface EcKeyGenParams {
+    name: "ECDSA";
+    namedCurve: "P-256" | "P-384" | "P-521";
+}
+
 interface  HmacImportParams {
     name: "HMAC";
     hash: HashVariants;
@@ -58,6 +70,18 @@ type ImportAlgorithm =
     | "PBKDF2"
     | "HKDF";
 
+type GenerateAlgorithm =
+    | RsaHashedKeyGenParams
+    | EcKeyGenParams;
+
+type JWK =
+    | { kty: "RSA"; }
+    | { kty: "EC"; };
+
+type KeyData =
+    | NjsStringOrBuffer
+    | JWK;
+
 interface   HkdfParams {
     name: "HKDF";
     hash: HashVariants;
@@ -111,6 +135,8 @@ type SignOrVerifyAlgorithm =
 interface CryptoKey {
 }
 
+type CryptoKeyPair = { privateKey: CryptoKey, publicKey: CryptoKey };
+
 interface SubtleCrypto {
     /**
      * Decrypts encrypted data.
@@ -177,20 +203,47 @@ interface SubtleCrypto {
      * Imports a key.
      *
      * @param format String describing the data format of the key to import.
+     * Possible values: "raw", "pkcs8", "spki", "jwk" (since 0.7.10).
      * @param keyData Object containing the key in the given format.
      * @param algorithm Dictionary object defining the type of key to import
      *  and providing extra algorithm-specific parameters.
-     * @param extractable Unsupported.
+     * @param extractable Boolean indicating whether a key can be exported.
      * @param usage Array indicating what can be done with the key.
      *  Possible array values: "encrypt", "decrypt", "sign", "verify",
      *  "deriveKey", "deriveBits", "wrapKey", "unwrapKey".
      */
-    importKey(format: "raw" | "pkcs8" | "spki",
-              keyData: NjsStringOrBuffer,
+    importKey(format: "raw" | "pkcs8" | "spki" | "jwk",
+              keyData: KeyData,
               algorithm: ImportAlgorithm,
               extractable: boolean,
               usage: Array<string>): Promise<CryptoKey>;
 
+    /**
+     * Exports a key.
+     *
+     * @since 0.7.10
+     * @param format String describing the data format of the key to export.
+     * Possible values: "raw", "pkcs8", "spki", "jwk".
+     * @param key CryptoKey containing the key to be exported.
+     */
+    exportKey(format: "raw" | "pkcs8" | "spki" | "jwk",
+              key: CryptoKey): Promise<ArrayBuffer|Object>;
+
+    /**
+     * Generates a keypair for asymmetric algorithms.
+     *
+     * @since 0.7.10
+     * @param algorithm Dictionary object defining the type of key to generate
+     *  and providing extra algorithm-specific parameters.
+     * @param extractable Boolean indicating whether a key can be exported.
+     * @param usage Array indicating what can be done with the key.
+     *  Possible array values: "encrypt", "decrypt", "sign", "verify",
+     *  "deriveKey", "deriveBits", "wrapKey", "unwrapKey".
+     */
+    generateKey(algorithm: GenerateAlgorithm,
+                extractable: boolean,
+                usage: Array<string>): Promise<CryptoKeyPair>;
+
     /**
      * Generates a digital signature.
      *