[njs] WebCrypto: added ECDH support.

noreply at nginx.com noreply at nginx.com
Fri May 16 19:16:02 UTC 2025


details:   https://github.com/nginx/njs/commit/1b69415d8c29bde08cc4c79dbb4b88827c55d8b9
branches:  master
commit:    1b69415d8c29bde08cc4c79dbb4b88827c55d8b9
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Thu, 8 May 2025 17:13:01 -0700
description:
WebCrypto: added ECDH support.


---
 external/njs_webcrypto_module.c    | 239 +++++++++++++++++++++++++++++++++---
 external/qjs_webcrypto_module.c    | 245 ++++++++++++++++++++++++++++++++++---
 test/harness/webCryptoUtils.js     |  16 +++
 test/webcrypto/derive_ecdh.t.mjs   | 188 ++++++++++++++++++++++++++++
 test/webcrypto/ec2_secp384r1.pkcs8 |   6 +
 test/webcrypto/ec2_secp384r1.spki  |   5 +
 test/webcrypto/ec2_secp521r1.pkcs8 |   8 ++
 test/webcrypto/ec2_secp521r1.spki  |   6 +
 test/webcrypto/ec_secp384r1.pkcs8  |   6 +
 test/webcrypto/ec_secp384r1.spki   |   5 +
 test/webcrypto/ec_secp521r1.pkcs8  |   8 ++
 test/webcrypto/ec_secp521r1.spki   |   6 +
 test/webcrypto/export.t.mjs        |  24 +++-
 13 files changed, 728 insertions(+), 34 deletions(-)

diff --git a/external/njs_webcrypto_module.c b/external/njs_webcrypto_module.c
index 8cc172cc..d9b05d09 100644
--- a/external/njs_webcrypto_module.c
+++ b/external/njs_webcrypto_module.c
@@ -28,7 +28,6 @@ typedef enum {
     NJS_KEY_USAGE_SIGN          = 1 << 6,
     NJS_KEY_USAGE_VERIFY        = 1 << 7,
     NJS_KEY_USAGE_WRAP_KEY      = 1 << 8,
-    NJS_KEY_USAGE_UNSUPPORTED   = 1 << 9,
     NJS_KEY_USAGE_UNWRAP_KEY    = 1 << 10,
 } njs_webcrypto_key_usage_t;
 
@@ -281,9 +280,11 @@ static njs_webcrypto_entry_t njs_webcrypto_alg[] = {
       njs_webcrypto_algorithm(NJS_ALGORITHM_ECDH,
                               NJS_KEY_USAGE_DERIVE_KEY |
                               NJS_KEY_USAGE_DERIVE_BITS |
-                              NJS_KEY_USAGE_GENERATE_KEY |
-                              NJS_KEY_USAGE_UNSUPPORTED,
-                              NJS_KEY_FORMAT_UNKNOWN,
+                              NJS_KEY_USAGE_GENERATE_KEY,
+                              NJS_KEY_FORMAT_PKCS8 |
+                              NJS_KEY_FORMAT_SPKI |
+                              NJS_KEY_FORMAT_RAW |
+                              NJS_KEY_FORMAT_JWK,
                               0)
     },
 
@@ -1441,6 +1442,188 @@ fail:
 }
 
 
+static njs_int_t
+njs_ext_derive_ecdh(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t derive_key, njs_webcrypto_key_t *key, njs_value_t *retval)
+{
+    u_char                     *k;
+    size_t                     olen;
+    int64_t                    length;
+    unsigned                   usage;
+    EVP_PKEY                   *priv_pkey, *pub_pkey;
+    njs_int_t                  ret;
+    njs_value_t                *value, *dobject;
+    EVP_PKEY_CTX               *pctx;
+    njs_opaque_value_t         lvalue;
+    njs_webcrypto_key_t        *dkey, *pkey;
+    njs_webcrypto_algorithm_t  *dalg;
+
+    static const njs_str_t  string_public = njs_str("public");
+
+    dobject = njs_arg(args, nargs, 3);
+
+    if (derive_key) {
+        dalg = njs_key_algorithm(vm, dobject);
+        if (njs_slow_path(dalg == NULL)) {
+            goto fail;
+        }
+
+        value = njs_vm_object_prop(vm, dobject, &string_length, &lvalue);
+        if (value == NULL) {
+            njs_vm_type_error(vm, "derivedKeyAlgorithm.length is not provided");
+            goto fail;
+        }
+
+    } else {
+        dalg = NULL;
+        value = dobject;
+    }
+
+    ret = njs_value_to_integer(vm, value, &length);
+    if (njs_slow_path(ret != NJS_OK)) {
+        goto fail;
+    }
+
+    dkey = NULL;
+    length /= 8;
+
+    if (derive_key) {
+        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 & ~dalg->usage)) {
+            njs_vm_type_error(vm, "unsupported key usage for \"ECDH\" key");
+            goto fail;
+        }
+
+        dkey = njs_mp_zalloc(njs_vm_memory_pool(vm),
+                             sizeof(njs_webcrypto_key_t));
+        if (njs_slow_path(dkey == NULL)) {
+            njs_vm_memory_error(vm);
+            goto fail;
+        }
+
+        dkey->alg = dalg;
+        dkey->usage = usage;
+    }
+
+    value = njs_vm_object_prop(vm, njs_arg(args, nargs, 1), &string_public,
+                               &lvalue);
+    if (value == NULL) {
+        njs_vm_type_error(vm, "ECDH algorithm.public is not provided");
+        goto fail;
+    }
+
+    pkey = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, value);
+    if (njs_slow_path(pkey == NULL)) {
+        njs_vm_type_error(vm, "algorithm.public is not a CryptoKey object");
+        goto fail;
+    }
+
+    if (njs_slow_path(pkey->alg->type != NJS_ALGORITHM_ECDH)) {
+        njs_vm_type_error(vm, "algorithm.public is not an ECDH key");
+        goto fail;
+    }
+
+    if (njs_slow_path(key->u.a.curve != pkey->u.a.curve)) {
+        njs_vm_type_error(vm, "ECDH keys must use the same curve");
+        goto fail;
+    }
+
+    if (!key->u.a.privat) {
+        njs_vm_type_error(vm, "baseKey must be a private key for ECDH");
+        goto fail;
+    }
+
+    if (pkey->u.a.privat) {
+        njs_vm_type_error(vm, "algorithm.public must be a public key");
+        goto fail;
+    }
+
+    priv_pkey = key->u.a.pkey;
+    pub_pkey = pkey->u.a.pkey;
+
+    pctx = EVP_PKEY_CTX_new(priv_pkey, NULL);
+    if (njs_slow_path(pctx == NULL)) {
+        njs_webcrypto_error(vm, "EVP_PKEY_CTX_new() failed");
+        goto fail;
+    }
+
+    if (EVP_PKEY_derive_init(pctx) != 1) {
+        njs_webcrypto_error(vm, "EVP_PKEY_derive_init() failed");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    if (EVP_PKEY_derive_set_peer(pctx, pub_pkey) != 1) {
+        njs_webcrypto_error(vm, "EVP_PKEY_derive_set_peer() failed");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    olen = (size_t) length;
+    if (EVP_PKEY_derive(pctx, NULL, &olen) != 1) {
+        njs_webcrypto_error(vm, "EVP_PKEY_derive() failed (size query)");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    if (njs_slow_path(olen < (size_t) length)) {
+        njs_vm_type_error(vm, "derived bit length is too small");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    k = njs_mp_alloc(njs_vm_memory_pool(vm), olen);
+    if (njs_slow_path(k == NULL)) {
+        njs_vm_memory_error(vm);
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    if (EVP_PKEY_derive(pctx, k, &olen) != 1) {
+        njs_webcrypto_error(vm, "EVP_PKEY_derive() failed");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    EVP_PKEY_CTX_free(pctx);
+
+    if (derive_key) {
+        if (dalg->type == NJS_ALGORITHM_HMAC) {
+            ret = njs_algorithm_hash(vm, dobject, &dkey->hash);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                goto fail;
+            }
+        }
+
+        dkey->extractable = njs_value_bool(njs_arg(args, nargs, 4));
+
+        dkey->u.s.raw.start = k;
+        dkey->u.s.raw.length = length;
+
+        ret = njs_vm_external_create(vm, njs_value_arg(&lvalue),
+                                     njs_webcrypto_crypto_key_proto_id,
+                                     dkey, 0);
+    } else {
+        ret = njs_vm_value_array_buffer_set(vm, njs_value_arg(&lvalue), k,
+                                            length);
+    }
+
+    if (njs_slow_path(ret != NJS_OK)) {
+        goto fail;
+    }
+
+    return njs_webcrypto_result(vm, &lvalue, NJS_OK, retval);
+
+fail:
+
+    return njs_webcrypto_result(vm, NULL, NJS_ERROR, retval);
+}
+
+
 static njs_int_t
 njs_ext_derive(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t derive_key, njs_value_t *retval)
@@ -1454,8 +1637,8 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_value_t                *value, *aobject, *dobject;
     const EVP_MD               *md;
     EVP_PKEY_CTX               *pctx;
-    njs_webcrypto_key_t        *key, *dkey;
     njs_opaque_value_t         lvalue;
+    njs_webcrypto_key_t        *key, *dkey;
     njs_webcrypto_hash_t       hash;
     njs_webcrypto_algorithm_t  *alg, *dalg;
 
@@ -1491,6 +1674,10 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         goto fail;
     }
 
+    if (alg->type == NJS_ALGORITHM_ECDH) {
+        return njs_ext_derive_ecdh(vm, args, nargs, derive_key, key, retval);
+    }
+
     dobject = njs_arg(args, nargs, 3);
 
     if (derive_key) {
@@ -1707,7 +1894,6 @@ free:
         (void) &info;
 #endif
 
-    case NJS_ALGORITHM_ECDH:
     default:
         njs_vm_internal_error(vm, "not implemented deriveKey "
                               "algorithm: \"%V\"", njs_algorithm_string(alg));
@@ -2270,6 +2456,7 @@ njs_ext_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         case NJS_ALGORITHM_RSA_PSS:
         case NJS_ALGORITHM_RSA_OAEP:
         case NJS_ALGORITHM_ECDSA:
+        case NJS_ALGORITHM_ECDH:
             ret = njs_export_jwk_asymmetric(vm, key, njs_value_arg(&value));
             if (njs_slow_path(ret != NJS_OK)) {
                 goto fail;
@@ -2373,7 +2560,9 @@ njs_ext_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     case NJS_KEY_FORMAT_RAW:
     default:
-        if (key->alg->type == NJS_ALGORITHM_ECDSA) {
+        if (key->alg->type == NJS_ALGORITHM_ECDSA
+            || key->alg->type == NJS_ALGORITHM_ECDH)
+        {
             ret = njs_export_raw_ec(vm, key, njs_value_arg(&value));
             if (njs_slow_path(ret != NJS_OK)) {
                 goto fail;
@@ -2540,6 +2729,7 @@ njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         break;
 
     case NJS_ALGORITHM_ECDSA:
+    case NJS_ALGORITHM_ECDH:
         nid = 0;
         ret = njs_algorithm_curve(vm, aobject, &nid);
         if (njs_slow_path(ret == NJS_ERROR)) {
@@ -2572,7 +2762,14 @@ njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         ctx = NULL;
 
         key->u.a.privat = 1;
-        key->usage = NJS_KEY_USAGE_SIGN;
+
+        if (alg->type == NJS_ALGORITHM_ECDSA) {
+            key->usage = NJS_KEY_USAGE_SIGN;
+
+        } else {
+            /* ECDH */
+            key->usage = NJS_KEY_USAGE_DERIVE_KEY | NJS_KEY_USAGE_DERIVE_BITS;
+        }
 
         keypub = njs_webcrypto_key_alloc(vm, alg, usage, extractable);
         if (njs_slow_path(keypub == NULL)) {
@@ -2586,7 +2783,15 @@ njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
         keypub->u.a.pkey = key->u.a.pkey;
         keypub->u.a.curve = key->u.a.curve;
-        keypub->usage = NJS_KEY_USAGE_VERIFY;
+
+        if (alg->type == NJS_ALGORITHM_ECDSA) {
+            keypub->usage = NJS_KEY_USAGE_VERIFY;
+
+        } else {
+            /* ECDH */
+            keypub->usage = NJS_KEY_USAGE_DERIVE_KEY
+                            | NJS_KEY_USAGE_DERIVE_BITS;
+        }
 
         ret = njs_vm_external_create(vm, njs_value_arg(&priv),
                                      njs_webcrypto_crypto_key_proto_id, key, 0);
@@ -3565,7 +3770,17 @@ njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
             goto fail;
         }
 
-        mask = key->u.a.privat ? ~NJS_KEY_USAGE_SIGN : ~NJS_KEY_USAGE_VERIFY;
+        if (alg->type == NJS_ALGORITHM_ECDSA) {
+            mask = key->u.a.privat ? ~NJS_KEY_USAGE_SIGN
+                                   : ~NJS_KEY_USAGE_VERIFY;
+        } else {
+            if (key->u.a.privat) {
+                mask = ~(NJS_KEY_USAGE_DERIVE_KEY | NJS_KEY_USAGE_DERIVE_BITS);
+
+            } else {
+                mask = 0;
+            }
+        }
 
         if (key->usage & mask) {
             njs_vm_type_error(vm, "key usage mismatch for \"%V\" key",
@@ -4598,10 +4813,6 @@ njs_key_algorithm(njs_vm_t *vm, njs_value_t *options)
     for (e = &njs_webcrypto_alg[0]; e->name.length != 0; e++) {
         if (njs_strstr_case_eq(&a, &e->name)) {
             alg = (njs_webcrypto_algorithm_t *) e->value;
-            if (alg->usage & NJS_KEY_USAGE_UNSUPPORTED) {
-                njs_vm_type_error(vm, "unsupported algorithm: \"%V\"", &a);
-                return NULL;
-            }
 
             return alg;
         }
diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c
index f26b6505..b9c645d9 100644
--- a/external/qjs_webcrypto_module.c
+++ b/external/qjs_webcrypto_module.c
@@ -115,6 +115,8 @@ static JSValue qjs_cipher_aes_ctr(JSContext *cx, njs_str_t *data,
     qjs_webcrypto_key_t *key, JSValue options, int encrypt);
 static JSValue qjs_cipher_aes_cbc(JSContext *cx, njs_str_t *data,
     qjs_webcrypto_key_t *key, JSValue options, int encrypt);
+static JSValue qjs_derive_ecdh(JSContext *cx, JSValueConst *argv, int argc,
+    int derive_key, qjs_webcrypto_key_t *key);
 static JSValue qjs_webcrypto_derive(JSContext *cx, JSValueConst this_val,
     int argc, JSValueConst *argv, int derive_key);
 static JSValue qjs_webcrypto_digest(JSContext *cx, JSValueConst this_val,
@@ -272,9 +274,11 @@ static qjs_webcrypto_entry_t qjs_webcrypto_alg[] = {
       qjs_webcrypto_algorithm(QJS_ALGORITHM_ECDH,
                               QJS_KEY_USAGE_DERIVE_KEY |
                               QJS_KEY_USAGE_DERIVE_BITS |
-                              QJS_KEY_USAGE_GENERATE_KEY |
-                              QJS_KEY_USAGE_UNSUPPORTED,
-                              QJS_KEY_FORMAT_UNKNOWN,
+                              QJS_KEY_USAGE_GENERATE_KEY,
+                              QJS_KEY_FORMAT_PKCS8 |
+                              QJS_KEY_FORMAT_SPKI |
+                              QJS_KEY_FORMAT_RAW |
+                              QJS_KEY_FORMAT_JWK,
                               0)
     },
 
@@ -430,7 +434,7 @@ static const JSCFunctionListEntry qjs_webcrypto_subtle[] = {
     JS_CFUNC_DEF("importKey", 5, qjs_webcrypto_import_key),
     JS_CFUNC_MAGIC_DEF("decrypt", 4, qjs_webcrypto_cipher, 0),
     JS_CFUNC_MAGIC_DEF("deriveBits", 4, qjs_webcrypto_derive, 0),
-    JS_CFUNC_MAGIC_DEF("deriveKey", 4, qjs_webcrypto_derive, 1),
+    JS_CFUNC_MAGIC_DEF("deriveKey", 5, qjs_webcrypto_derive, 1),
     JS_CFUNC_DEF("digest", 3, qjs_webcrypto_digest),
     JS_CFUNC_MAGIC_DEF("encrypt", 4, qjs_webcrypto_cipher, 1),
     JS_CFUNC_DEF("exportKey", 3, qjs_webcrypto_export_key),
@@ -1664,6 +1668,190 @@ qjs_export_raw_ec(JSContext *cx, qjs_webcrypto_key_t *key)
 }
 
 
+static JSValue
+qjs_derive_ecdh(JSContext *cx, JSValueConst *argv, int argc, int derive_key,
+    qjs_webcrypto_key_t *key)
+{
+    u_char                     *k;
+    size_t                     olen;
+    int64_t                    length;
+    JSValue                    ret, result, value, dobject;
+    unsigned                   usage;
+    EVP_PKEY                   *priv_pkey, *pub_pkey;
+    EVP_PKEY_CTX               *pctx;
+    qjs_webcrypto_key_t        *dkey, *pkey;
+    qjs_webcrypto_algorithm_t  *dalg;
+
+    result = JS_UNDEFINED;
+    dobject = argv[2];
+
+    if (derive_key) {
+        dalg = qjs_key_algorithm(cx, dobject);
+        if (dalg == NULL) {
+            goto fail;
+        }
+
+        value = JS_GetPropertyStr(cx, dobject, "length");
+        if (JS_IsException(value)) {
+            goto fail;
+        }
+
+        if (JS_IsUndefined(value)) {
+            JS_ThrowTypeError(cx, "derivedKeyAlgorithm.length is not provided");
+            JS_FreeValue(cx, value);
+            goto fail;
+        }
+
+    } else {
+        dalg = NULL;
+        value = JS_DupValue(cx, dobject);
+    }
+
+    if (JS_ToInt64(cx, &length, value) < 0) {
+        JS_FreeValue(cx, value);
+        goto fail;
+    }
+
+    JS_FreeValue(cx, value);
+
+    dkey = NULL;
+    length /= 8;
+
+    if (derive_key) {
+        ret = qjs_key_usage(cx, argv[4], &usage);
+        if (JS_IsException(ret)) {
+            goto fail;
+        }
+
+        if (usage & ~dalg->usage) {
+            JS_ThrowTypeError(cx, "unsupported key usage for \"ECDH\" key");
+            goto fail;
+        }
+
+        result = qjs_webcrypto_key_make(cx, dalg, usage, 0);
+        if (JS_IsException(result)) {
+            JS_ThrowOutOfMemory(cx);
+            goto fail;
+        }
+
+        dkey = JS_GetOpaque(result, QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
+    }
+
+    value = JS_GetPropertyStr(cx, argv[0], "public");
+    if (JS_IsException(value)) {
+        goto fail;
+    }
+
+    if (JS_IsUndefined(value)) {
+        JS_ThrowTypeError(cx, "ECDH algorithm.public is not provided");
+        JS_FreeValue(cx, value);
+        goto fail;
+    }
+
+    pkey = JS_GetOpaque(value, QJS_CORE_CLASS_ID_WEBCRYPTO_KEY);
+    JS_FreeValue(cx, value);
+    if (pkey == NULL) {
+        JS_ThrowTypeError(cx, "algorithm.public is not a CryptoKey object");
+        goto fail;
+    }
+
+    if (pkey->alg->type != QJS_ALGORITHM_ECDH) {
+        JS_ThrowTypeError(cx, "algorithm.public is not an ECDH key");
+        goto fail;
+    }
+
+    if (key->u.a.curve != pkey->u.a.curve) {
+        JS_ThrowTypeError(cx, "ECDH keys must use the same curve");
+        goto fail;
+    }
+
+    if (!key->u.a.privat) {
+        JS_ThrowTypeError(cx, "baseKey must be a private key for ECDH");
+        goto fail;
+    }
+
+    if (pkey->u.a.privat) {
+        JS_ThrowTypeError(cx, "algorithm.public must be a public key");
+        goto fail;
+    }
+
+    priv_pkey = key->u.a.pkey;
+    pub_pkey = pkey->u.a.pkey;
+
+    pctx = EVP_PKEY_CTX_new(priv_pkey, NULL);
+    if (pctx == NULL) {
+        qjs_webcrypto_error(cx, "EVP_PKEY_CTX_new() failed");
+        goto fail;
+    }
+
+    if (EVP_PKEY_derive_init(pctx) != 1) {
+        qjs_webcrypto_error(cx, "EVP_PKEY_derive_init() failed");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    if (EVP_PKEY_derive_set_peer(pctx, pub_pkey) != 1) {
+        qjs_webcrypto_error(cx, "EVP_PKEY_derive_set_peer() failed");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    olen = (size_t) length;
+    if (EVP_PKEY_derive(pctx, NULL, &olen) != 1) {
+        qjs_webcrypto_error(cx, "EVP_PKEY_derive() failed (size query)");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    if (olen < (size_t) length) {
+        JS_ThrowTypeError(cx, "derived bit length is too small");
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    k = js_malloc(cx, olen);
+    if (k == NULL) {
+        JS_ThrowOutOfMemory(cx);
+        EVP_PKEY_CTX_free(pctx);
+        goto fail;
+    }
+
+    if (EVP_PKEY_derive(pctx, k, &olen) != 1) {
+        qjs_webcrypto_error(cx, "EVP_PKEY_derive() failed");
+        EVP_PKEY_CTX_free(pctx);
+        js_free(cx, k);
+        goto fail;
+    }
+
+    EVP_PKEY_CTX_free(pctx);
+
+    if (derive_key) {
+        if (dalg->type == QJS_ALGORITHM_HMAC) {
+            ret = qjs_algorithm_hash(cx, dobject, &dkey->hash);
+            if (JS_IsException(ret)) {
+                js_free(cx, k);
+                goto fail;
+            }
+        }
+
+        dkey->extractable = JS_ToBool(cx, argv[3]);
+
+        dkey->u.s.raw.start = k;
+        dkey->u.s.raw.length = length;
+
+    } else {
+        result = qjs_new_array_buffer(cx, k, length);
+    }
+
+    return qjs_promise_result(cx, result);
+
+fail:
+    JS_FreeValue(cx, result);
+
+    return qjs_promise_result(cx, JS_EXCEPTION);
+}
+
+
 static JSValue
 qjs_webcrypto_derive(JSContext *cx, JSValueConst this_val, int argc,
     JSValueConst *argv, int derive_key)
@@ -1709,6 +1897,10 @@ qjs_webcrypto_derive(JSContext *cx, JSValueConst this_val, int argc,
         return JS_EXCEPTION;
     }
 
+    if (alg->type == QJS_ALGORITHM_ECDH) {
+        return qjs_derive_ecdh(cx, argv, argc, derive_key, key);
+    }
+
     dobject = argv[2];
 
     if (derive_key) {
@@ -1933,7 +2125,6 @@ free:
         (void) &info;
 #endif
 
-    case QJS_ALGORITHM_ECDH:
     default:
         JS_ThrowTypeError(cx, "not implemented deriveKey algorithm: \"%s\"",
                           qjs_algorithm_string(alg));
@@ -2051,6 +2242,7 @@ qjs_webcrypto_export_key(JSContext *cx, JSValueConst this_val, int argc,
         case QJS_ALGORITHM_RSA_PSS:
         case QJS_ALGORITHM_RSA_OAEP:
         case QJS_ALGORITHM_ECDSA:
+        case QJS_ALGORITHM_ECDH:
             ret = qjs_export_jwk_asymmetric(cx, key);
             if (JS_IsException(ret)) {
                 goto fail;
@@ -2156,7 +2348,9 @@ qjs_webcrypto_export_key(JSContext *cx, JSValueConst this_val, int argc,
 
     case QJS_KEY_FORMAT_RAW:
     default:
-        if (key->alg->type == QJS_ALGORITHM_ECDSA) {
+        if (key->alg->type == QJS_ALGORITHM_ECDSA
+            || key->alg->type == QJS_ALGORITHM_ECDH)
+        {
             ret = qjs_export_raw_ec(cx, key);
             if (JS_IsException(ret)) {
                 goto fail;
@@ -2311,6 +2505,7 @@ qjs_webcrypto_generate_key(JSContext *cx, JSValueConst this_val,
         break;
 
     case QJS_ALGORITHM_ECDSA:
+    case QJS_ALGORITHM_ECDH:
         ret = qjs_algorithm_curve(cx, options, &wkey->u.a.curve);
         if (JS_IsException(ret)) {
             goto fail;
@@ -2342,7 +2537,14 @@ qjs_webcrypto_generate_key(JSContext *cx, JSValueConst this_val,
         ctx = NULL;
 
         wkey->u.a.privat = 1;
-        wkey->usage = QJS_KEY_USAGE_SIGN;
+
+        if (alg->type == QJS_ALGORITHM_ECDSA) {
+            wkey->usage = QJS_KEY_USAGE_SIGN;
+
+        } else {
+            /* ECDH */
+            wkey->usage = QJS_KEY_USAGE_DERIVE_KEY | QJS_KEY_USAGE_DERIVE_BITS;
+        }
 
         keypub = qjs_webcrypto_key_make(cx, alg, usage, extractable);
         if (JS_IsException(keypub)) {
@@ -2358,7 +2560,15 @@ qjs_webcrypto_generate_key(JSContext *cx, JSValueConst this_val,
 
         wkeypub->u.a.pkey = wkey->u.a.pkey;
         wkeypub->u.a.curve = wkey->u.a.curve;
-        wkeypub->usage = QJS_KEY_USAGE_VERIFY;
+
+        if (alg->type == QJS_ALGORITHM_ECDSA) {
+            wkeypub->usage = QJS_KEY_USAGE_VERIFY;
+
+        } else {
+            /* ECDH */
+            wkeypub->usage = QJS_KEY_USAGE_DERIVE_KEY
+                             | QJS_KEY_USAGE_DERIVE_BITS;
+        }
 
         obj = JS_NewObject(cx);
         if (JS_IsException(obj)) {
@@ -3408,7 +3618,17 @@ qjs_webcrypto_import_key(JSContext *cx, JSValueConst this_val, int argc,
             goto fail;
         }
 
-        mask = wkey->u.a.privat ? ~QJS_KEY_USAGE_SIGN : ~QJS_KEY_USAGE_VERIFY;
+        if (alg->type == QJS_ALGORITHM_ECDSA) {
+            mask = wkey->u.a.privat ? ~QJS_KEY_USAGE_SIGN
+                                    : ~QJS_KEY_USAGE_VERIFY;
+        } else {
+            if (wkey->u.a.privat) {
+                mask = ~(QJS_KEY_USAGE_DERIVE_KEY | QJS_KEY_USAGE_DERIVE_BITS);
+
+            } else {
+                mask = 0;
+            }
+        }
 
         if (wkey->usage & mask) {
             JS_ThrowTypeError(cx, "key usage mismatch for \"%s\" key",
@@ -4369,13 +4589,6 @@ qjs_key_algorithm(JSContext *cx, JSValue options)
     for (e = &qjs_webcrypto_alg[0]; e->name.length != 0; e++) {
         if (njs_strstr_case_eq(&a, &e->name)) {
             alg = (qjs_webcrypto_algorithm_t *) e->value;
-            if (alg->usage & QJS_KEY_USAGE_UNSUPPORTED) {
-                JS_ThrowTypeError(cx, "unsupported algorithm: \"%.*s\"",
-                                  (int) a.length, a.start);
-                JS_FreeCString(cx, (char *) a.start);
-                return NULL;
-            }
-
             JS_FreeCString(cx, (char *) a.start);
             return alg;
         }
diff --git a/test/harness/webCryptoUtils.js b/test/harness/webCryptoUtils.js
index d403f39a..c37fb489 100644
--- a/test/harness/webCryptoUtils.js
+++ b/test/harness/webCryptoUtils.js
@@ -19,3 +19,19 @@ function load_jwk(data) {
 
     return data;
 }
+
+function compareUsage(a, b) {
+    a.sort();
+    b.sort();
+
+    if (b.length !== a.length) {
+        return false;
+    }
+
+    for (var i = 0; i < a.length; i++) {
+        if (b[i] !== a[i]) {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/test/webcrypto/derive_ecdh.t.mjs b/test/webcrypto/derive_ecdh.t.mjs
new file mode 100644
index 00000000..50c1925e
--- /dev/null
+++ b/test/webcrypto/derive_ecdh.t.mjs
@@ -0,0 +1,188 @@
+/*---
+includes: [compatFs.js, compatBuffer.js, compatWebcrypto.js, webCryptoUtils.js, runTsuite.js]
+flags: [async]
+---*/
+
+async function testDeriveBits(params) {
+    let aliceKeyPair = await load_key_pair(params.pair[0]);
+    let bobKeyPair = await load_key_pair(params.pair[1]);
+
+    let ecdhParams = { name: "ECDH", public: bobKeyPair.publicKey };
+
+    let result = await crypto.subtle.deriveBits(ecdhParams, aliceKeyPair.privateKey, params.length);
+    result = Buffer.from(result).toString('base64url');
+
+    if (result !== params.expected) {
+        throw Error(`ECDH deriveBits failed expected: "${params.expected}" vs "${result}"`);
+    }
+
+    let ecdhParamsReverse = { name: "ECDH", public: aliceKeyPair.publicKey };
+
+    let secondResult = await crypto.subtle.deriveBits(ecdhParamsReverse, bobKeyPair.privateKey, params.length);
+    secondResult = Buffer.from(secondResult).toString('base64url');
+
+    if (secondResult !== params.expected) {
+        throw Error(`ECDH reverse deriveBits failed expected: "${params.expected}" vs "${secondResult}"`);
+    }
+
+    return "SUCCESS";
+}
+
+function deriveCurveFromName(name) {
+    if (/secp384r1/.test(name)) {
+        return "P-384";
+    }
+
+    if (/secp521r1/.test(name)) {
+        return "P-521";
+    }
+
+    return "P-256";
+}
+
+async function load_key_pair(name) {
+    let pair = {};
+    let pem = fs.readFileSync(`test/webcrypto/${name}.pkcs8`);
+    let key = pem_to_der(pem, "private");
+
+    pair.privateKey = await crypto.subtle.importKey("pkcs8", key,
+                                                    { name: "ECDH", namedCurve: deriveCurveFromName(name) },
+                                                    true, ["deriveBits", "deriveKey"]);
+
+    pem = fs.readFileSync(`test/webcrypto/${name}.spki`);
+    key = pem_to_der(pem, "public");
+    pair.publicKey = await crypto.subtle.importKey("spki", key,
+                                                   { name: "ECDH", namedCurve: deriveCurveFromName(name) },
+                                                   true, []);
+
+    return pair;
+}
+
+let ecdh_bits_tsuite = {
+    name: "ECDH-DeriveBits",
+    skip: () => (!has_buffer() || !has_webcrypto()),
+    T: testDeriveBits,
+    opts: {
+        pair: ['ec', 'ec2'],
+        length: 256
+    },
+    tests: [
+        { expected: "mMAGhQ_1Wr3u6Y6VyzVuolCA7x8RM-15e73laLJMUok" },
+        { pair: ['ec_secp384r1', 'ec2_secp384r1'],
+          expected: "4OmeRzZZ53eCgn09zI2TumH4n4Zp-nfHsfZTOBEu8Hg" },
+        { pair: ['ec_secp384r1', 'ec2_secp384r1'],
+          length: 384,
+          expected: "4OmeRzZZ53eCgn09zI2TumH4n4Zp-nfHsfZTOBEu8HjB0GF2YrOw5dCUgavKZaNR" },
+        { pair: ['ec_secp521r1', 'ec2_secp521r1'],
+          length: 528,
+          expected: "ATBls20ukLQI7AJQ6LRnyD6wLDR_FDmBoAdVX5_DB_bMDe_uYMjN-jQqPTkGNIo6NOqmXMX9KNQ-AqL8aPjySMMm" },
+    ]
+};
+
+async function testDeriveKey(params) {
+    let aliceKeyPair = await load_key_pair(params.pair[0]);
+    let bobKeyPair = await load_key_pair(params.pair[1]);
+    let eveKeyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: deriveCurveFromName(params.pair[0]) },
+                                                     true, ["deriveKey", "deriveBits"]);
+
+    let ecdhParamsAlice = { name: "ECDH", public: bobKeyPair.publicKey };
+    let ecdhParamsBob = { name: "ECDH", public: aliceKeyPair.publicKey };
+    let ecdhParamsEve = { name: "ECDH", public: eveKeyPair.publicKey };
+
+    let derivedAlgorithm = { name: params.derivedAlgorithm.name };
+
+    derivedAlgorithm.length = params.derivedAlgorithm.length;
+    derivedAlgorithm.hash = params.derivedAlgorithm.hash;
+
+    let aliceDerivedKey = await crypto.subtle.deriveKey(ecdhParamsAlice, aliceKeyPair.privateKey,
+                                                        derivedAlgorithm, params.extractable, params.usage);
+
+    if (aliceDerivedKey.extractable !== params.extractable) {
+        throw Error(`ECDH extractable test failed: ${params.extractable} vs ${aliceDerivedKey.extractable}`);
+    }
+
+    if (compareUsage(aliceDerivedKey.usages, params.usage) !== true) {
+        throw Error(`ECDH usage test failed: ${params.usage} vs ${aliceDerivedKey.usages}`);
+    }
+
+    let bobDerivedKey = await crypto.subtle.deriveKey(ecdhParamsBob, bobKeyPair.privateKey,
+                                                      derivedAlgorithm, params.extractable, params.usage);
+
+    let eveDerivedKey = await crypto.subtle.deriveKey(ecdhParamsEve, eveKeyPair.privateKey,
+                                                      derivedAlgorithm, params.extractable, params.usage);
+
+    if (params.extractable &&
+        (params.derivedAlgorithm.name === "AES-GCM"
+         || params.derivedAlgorithm.name === "AES-CBC"
+         || params.derivedAlgorithm.name === "AES-CTR"
+         || params.derivedAlgorithm.name === "HMAC"))
+    {
+        const aliceRawKey = await crypto.subtle.exportKey("raw", aliceDerivedKey);
+        const bobRawKey = await crypto.subtle.exportKey("raw", bobDerivedKey);
+        const eveRawKey = await crypto.subtle.exportKey("raw", eveDerivedKey);
+
+        const aliceKeyData = Buffer.from(aliceRawKey).toString("base64url");
+        const bobKeyData = Buffer.from(bobRawKey).toString("base64url");
+        const eveKeyData = Buffer.from(eveRawKey).toString("base64url");
+
+        if (aliceKeyData !== bobKeyData) {
+            throw Error(`ECDH key symmetry test failed: keys are not equal`);
+        }
+
+        if (aliceKeyData !== params.expected) {
+            throw Error(`ECDH key symmetry test failed: expected: "${params.expected}" vs "${aliceKeyData}"`);
+        }
+
+        if (aliceKeyData === eveKeyData) {
+            throw Error(`ECDH key symmetry test failed: keys are equal`);
+        }
+    }
+
+    return "SUCCESS";
+}
+
+let ecdh_key_tsuite = {
+    name: "ECDH-DeriveKey",
+    skip: () => (!has_buffer() || !has_webcrypto()),
+    T: testDeriveKey,
+    opts: {
+        pair: ['ec', 'ec2'],
+        extractable: true,
+        derivedAlgorithm: {
+            name: "AES-GCM",
+            length: 256
+        },
+        expected: "mMAGhQ_1Wr3u6Y6VyzVuolCA7x8RM-15e73laLJMUok",
+        usage: ["encrypt", "decrypt"]
+    },
+    tests: [
+        { },
+        { extractable: false },
+        { derivedAlgorithm: { name: "AES-CBC", length: 256 } },
+        { derivedAlgorithm: { name: "AES-CTR", length: 256 } },
+        { derivedAlgorithm: { name: "AES-GCM", length: 256 } },
+        { derivedAlgorithm: { name: "HMAC", hash: "SHA-256", length: 256 },
+          usage: ["sign", "verify"] },
+
+        { pair: ['ec_secp384r1', 'ec2_secp384r1'],
+          expected: "4OmeRzZZ53eCgn09zI2TumH4n4Zp-nfHsfZTOBEu8Hg" },
+        { pair: ['ec_secp384r1', 'ec2_secp384r1'], extractable: false },
+
+        { pair: ['ec_secp521r1', 'ec_secp384r1'],
+          exception: "TypeError: ECDH keys must use the same curve" },
+
+        { pair: ['ec_secp521r1', 'ec2_secp521r1'],
+          derivedAlgorithm: { name: "AES-GCM", length: 128 },
+          expected: "ATBls20ukLQI7AJQ6LRnyA" },
+        { pair: ['ec_secp521r1', 'ec2_secp521r1'],
+          derivedAlgorithm: { name: "HMAC", hash: "SHA-384", length: 512 },
+          expected: "ATBls20ukLQI7AJQ6LRnyD6wLDR_FDmBoAdVX5_DB_bMDe_uYMjN-jQqPTkGNIo6NOqmXMX9KNQ-AqL8aPjySA",
+          usage: ["sign", "verify"] }
+    ]
+};
+
+run([
+    ecdh_bits_tsuite,
+    ecdh_key_tsuite,
+])
+.then($DONE, $DONE);
diff --git a/test/webcrypto/ec2_secp384r1.pkcs8 b/test/webcrypto/ec2_secp384r1.pkcs8
new file mode 100644
index 00000000..c94fdf9f
--- /dev/null
+++ b/test/webcrypto/ec2_secp384r1.pkcs8
@@ -0,0 +1,6 @@
+-----BEGIN PRIVATE KEY-----
+MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDM3LQ/iPLxtGh4I0IH
+tkE14NPOSBWWxa0C5Dt7KFA2T5Goh/C9hulx4waSJtJgpsmhZANiAAQr0yzNebjy
+oATeQbsSQGcBgC6Vm31MqarylkteLBxC+tWVgrCjxps/ZN9l+wOBo6kceuGrmoi6
+YJYkRAZk9QOCODFou+VyW741sQRtenfkCb904Iy83tLXw9CCOZ3M5tM=
+-----END PRIVATE KEY-----
diff --git a/test/webcrypto/ec2_secp384r1.spki b/test/webcrypto/ec2_secp384r1.spki
new file mode 100644
index 00000000..52379e77
--- /dev/null
+++ b/test/webcrypto/ec2_secp384r1.spki
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEK9MszXm48qAE3kG7EkBnAYAulZt9TKmq
+8pZLXiwcQvrVlYKwo8abP2TfZfsDgaOpHHrhq5qIumCWJEQGZPUDgjgxaLvlclu+
+NbEEbXp35Am/dOCMvN7S18PQgjmdzObT
+-----END PUBLIC KEY-----
diff --git a/test/webcrypto/ec2_secp521r1.pkcs8 b/test/webcrypto/ec2_secp521r1.pkcs8
new file mode 100644
index 00000000..8d6000cf
--- /dev/null
+++ b/test/webcrypto/ec2_secp521r1.pkcs8
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBK+Wq6/RhJ0n1s/+r
+qcwVBZYo6OFeOpwlmvFfrrsRwxWnptigR6kKXm1/w7AX7eHFuc+kyVI5KXu7hJUP
+S9sAwcmhgYkDgYYABAE5InvhsngiOkoFhRcSDgxmFMjWMZG6BAw57Cwz2ar9VoyY
+GYIJtw976kc8Yz+NPz6BNJpfo2wv6YnyrV6CEFqbtQAXI5DY7kk1qsaawgZcFoaH
+ngIII80o6Eo9OMwsVzTUmkkAmWGySwvqRge3eVMJTkPjY1AxoP5aOJr+qcDRbZLr
+0A==
+-----END PRIVATE KEY-----
diff --git a/test/webcrypto/ec2_secp521r1.spki b/test/webcrypto/ec2_secp521r1.spki
new file mode 100644
index 00000000..8834d890
--- /dev/null
+++ b/test/webcrypto/ec2_secp521r1.spki
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBOSJ74bJ4IjpKBYUXEg4MZhTI1jGR
+ugQMOewsM9mq/VaMmBmCCbcPe+pHPGM/jT8+gTSaX6NsL+mJ8q1eghBam7UAFyOQ
+2O5JNarGmsIGXBaGh54CCCPNKOhKPTjMLFc01JpJAJlhsksL6kYHt3lTCU5D42NQ
+MaD+Wjia/qnA0W2S69A=
+-----END PUBLIC KEY-----
diff --git a/test/webcrypto/ec_secp384r1.pkcs8 b/test/webcrypto/ec_secp384r1.pkcs8
new file mode 100644
index 00000000..17f8d319
--- /dev/null
+++ b/test/webcrypto/ec_secp384r1.pkcs8
@@ -0,0 +1,6 @@
+-----BEGIN PRIVATE KEY-----
+MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDA50z1r3E3NARroawH9
+eAXuoQPu1xVbcRDZ0bTNgdOHDh2E0uW5fybZnAYVjbbEPxuhZANiAATu1zCeNJ+n
+F65Wjdoltr1AnHDxn+k+9KdQeXd//JMWaBReirIcmU40qvSzLmQtPiDoMHFpMf11
+UCjSMLA8sVNtEwD0bdUmYfoGBNgwzk/4y5vTiyCNSozso3xx+4/WuGs=
+-----END PRIVATE KEY-----
diff --git a/test/webcrypto/ec_secp384r1.spki b/test/webcrypto/ec_secp384r1.spki
new file mode 100644
index 00000000..820adad8
--- /dev/null
+++ b/test/webcrypto/ec_secp384r1.spki
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7tcwnjSfpxeuVo3aJba9QJxw8Z/pPvSn
+UHl3f/yTFmgUXoqyHJlONKr0sy5kLT4g6DBxaTH9dVAo0jCwPLFTbRMA9G3VJmH6
+BgTYMM5P+Mub04sgjUqM7KN8cfuP1rhr
+-----END PUBLIC KEY-----
diff --git a/test/webcrypto/ec_secp521r1.pkcs8 b/test/webcrypto/ec_secp521r1.pkcs8
new file mode 100644
index 00000000..7a6fe728
--- /dev/null
+++ b/test/webcrypto/ec_secp521r1.pkcs8
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEGh8E2g1TbnN0xzm
+7nKGSvDbSVZHaA+XQEuTbhklfRcMJH8X8oqOJRzl4m9nIGmXy6TGPwIMlA6maRwB
+PSEGqsChgYkDgYYABADSOIb4Rpa/7WDON8vDH6DPTR9gOFcFkI2lOa68MEdE7pF1
+m57cuJ0X2qTlFS6YuurbpiF6h4cltB1pM3eGQXKNcgD6stL3WjMjpC8Phv9Q391Z
+2E0ezlX0nDtFPIXAwmxptIC2U7WxHRQqkQwgJyq6xklp3vkD/eFeOSi/j0qvKrlD
+SA==
+-----END PRIVATE KEY-----
diff --git a/test/webcrypto/ec_secp521r1.spki b/test/webcrypto/ec_secp521r1.spki
new file mode 100644
index 00000000..9bd02998
--- /dev/null
+++ b/test/webcrypto/ec_secp521r1.spki
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA0jiG+EaWv+1gzjfLwx+gz00fYDhX
+BZCNpTmuvDBHRO6RdZue3LidF9qk5RUumLrq26YheoeHJbQdaTN3hkFyjXIA+rLS
+91ozI6QvD4b/UN/dWdhNHs5V9Jw7RTyFwMJsabSAtlO1sR0UKpEMICcqusZJad75
+A/3hXjkov49Kryq5Q0g=
+-----END PUBLIC KEY-----
diff --git a/test/webcrypto/export.t.mjs b/test/webcrypto/export.t.mjs
index 81b549df..58fbf8bb 100644
--- a/test/webcrypto/export.t.mjs
+++ b/test/webcrypto/export.t.mjs
@@ -375,17 +375,18 @@ let ec_tsuite = {
         key: { fmt: "spki",
                key: "ec.spki",
                alg: { name: "ECDSA", namedCurve: "P-256" },
-               extractable: true,
-               usage: [ "verify" ] },
+               extractable: true },
         export: { fmt: "jwk" },
         expected: { ext: true, kty: "EC" },
     },
 
     tests: [
-      { expected: { key_ops: [ "verify" ],
+      { key: { usage: [ "verify" ] },
+        expected: { key_ops: [ "verify" ],
                     x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
                     y: "4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI",
                     crv: "P-256" } },
+      { key: { alg: { name: "ECDH", namedCurve: "P-256" }, usage: [ ] } },
       { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
         expected: { key_ops: [ "sign" ],
                     x: "cUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdw",
@@ -397,7 +398,22 @@ let ec_tsuite = {
         expected: "ArrayBuffer:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6ChRANCAARxRSxlEa5VhF4aJNCX0ypHuKvp1kiDD7ykz4XSmElZ3ODc5_-7jc9AAN1OH4aX1cUg-FOUHIhshKDOK94wu24y" },
       { export: { fmt: "pkcs8" },
         exception: "TypeError: public key of \"ECDSA\" cannot be exported as PKCS8" },
-      { export: { fmt: "raw" },
+      { key: { alg: { name: "ECDH", namedCurve: "P-256" },
+               fmt: "pkcs8", key: "ec.pkcs8",
+               usage: [ "deriveKey" ] },
+        export: { fmt: "pkcs8" },
+        expected: "ArrayBuffer:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE2sW0_4a3QXaSTJ0JKbSUbieKTD1UFtr7i_2CuetP6ChRANCAARxRSxlEa5VhF4aJNCX0ypHuKvp1kiDD7ykz4XSmElZ3ODc5_-7jc9AAN1OH4aX1cUg-FOUHIhshKDOK94wu24y" },
+      { key: { usage: [ "verify" ] },
+        export: { fmt: "spki" },
+        expected: "ArrayBuffer:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdzg3Of_u43PQADdTh-Gl9XFIPhTlByIbISgziveMLtuMg"},
+      { key: { alg: { name: "ECDH", namedCurve: "P-256" }, usage: [ ] },
+        export: { fmt: "spki" },
+        expected: "ArrayBuffer:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcUUsZRGuVYReGiTQl9MqR7ir6dZIgw-8pM-F0phJWdzg3Of_u43PQADdTh-Gl9XFIPhTlByIbISgziveMLtuMg"},
+      { key: { usage: [ "verify" ] },
+        export: { fmt: "raw" },
+        expected: "ArrayBuffer:BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" },
+      { key: { alg: { name: "ECDH", namedCurve: "P-256" }, usage: [ ] },
+        export: { fmt: "raw" },
         expected: "ArrayBuffer:BHFFLGURrlWEXhok0JfTKke4q-nWSIMPvKTPhdKYSVnc4Nzn_7uNz0AA3U4fhpfVxSD4U5QciGyEoM4r3jC7bjI" },
       { key: { fmt: "pkcs8", key: "ec.pkcs8", usage: [ "sign" ] },
         export: { fmt: "raw" },


More information about the nginx-devel mailing list