[njs] WebCrypto: extended support for asymmetric keys.

Dmitry Volyntsev xeioex at nginx.com
Thu Jan 5 04:42:44 UTC 2023


details:   https://hg.nginx.org/njs/rev/0681bf662222
branches:  
changeset: 2020:0681bf662222
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Wed Jan 04 17:49:22 2023 -0800
description:
WebCrypto: extended support for asymmetric keys.

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().

diffstat:

 external/njs_openssl.h          |   265 ++++++
 external/njs_webcrypto_module.c |  1606 +++++++++++++++++++++++++++++++++++---
 test/harness/compareObjects.js  |    17 +
 test/harness/runTsuite.js       |     4 +-
 test/harness/webCryptoUtils.js  |     8 +
 test/ts/test.ts                 |    12 +
 test/webcrypto/ec.jwk           |     1 +
 test/webcrypto/ec.pub.jwk       |     1 +
 test/webcrypto/export.t.js      |   298 +++++++
 test/webcrypto/rsa.dec.jwk      |     1 +
 test/webcrypto/rsa.enc.pub.jwk  |     1 +
 test/webcrypto/rsa.jwk          |     1 +
 test/webcrypto/rsa.pub.jwk      |     1 +
 test/webcrypto/rsa.t.js         |   122 ++-
 test/webcrypto/sign.t.js        |   266 ++++++-
 ts/njs_webcrypto.d.ts           |    59 +-
 16 files changed, 2471 insertions(+), 192 deletions(-)

diffs (truncated from 3178 to 1000 lines):

diff -r 3e7e2eb6b9aa -r 0681bf662222 external/njs_openssl.h
--- a/external/njs_openssl.h	Fri Dec 30 18:22:02 2022 -0800
+++ b/external/njs_openssl.h	Wed Jan 04 17:49:22 2023 -0800
@@ -56,4 +56,269 @@
 #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_ */
diff -r 3e7e2eb6b9aa -r 0681bf662222 external/njs_webcrypto_module.c
--- a/external/njs_webcrypto_module.c	Fri Dec 30 18:22:02 2022 -0800
+++ b/external/njs_webcrypto_module.c	Wed Jan 04 17:49:22 2023 -0800
@@ -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
 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_
     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_webcryp
                               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_webcryp
                               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_webcryp
                               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_webcryp
                               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_webcryp
 
 
 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_webcryp
 };
 
 
+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,11 +1707,484 @@ fail:
 
 
 static njs_int_t
+njs_export_base64url_bignum(njs_vm_t *vm, njs_value_t *retval, const BIGNUM *v,
+    size_t size)
+{
+    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_base64url_bignum_set(njs_vm_t *vm, njs_value_t *jwk, njs_value_t *key,
+    const BIGNUM *v, size_t size)
+{
+    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_export_jwk_rsa(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval)
+{
+    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 njs_value_t  rsa_str = njs_string("RSA");
+
+    rsa = njs_pkey_get_rsa_key(key->pkey);
+
+    njs_rsa_get0_key(rsa, &n_bn, &e_bn, &d_bn);
+
+    ret = njs_export_base64url_bignum(vm, &nvalue, n_bn, 0);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_export_base64url_bignum(vm, &evalue, e_bn, 0);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    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 (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_base64url_bignum_set(vm, retval, njs_value_arg(&string_d),
+                                       d_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_p),
+                                       p_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_q),
+                                       q_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_dp),
+                                       dp_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_dq),
+                                       dq_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_qi),
+                                       qi_bn, 0);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+    }
+
+    nm = &njs_webcrypto_alg_name[key->alg->type][key->hash];
+
+    (void) njs_vm_value_string_set(vm, &alg, nm->start, nm->length);
+
+    return njs_value_property_set(vm, retval, njs_value_arg(&string_alg), &alg);
+}
+
+
+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;
+    }
+
+    y_bn = BN_new();
+    if (y_bn == NULL) {
+        goto fail;
+    }
+
+    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;
+    }
+
+    ret = njs_export_base64url_bignum(vm, &xvalue, x_bn, group_bytes);
+    if (ret != NJS_OK) {
+        goto fail;
+    }
+
+    BN_free(x_bn);
+    x_bn = NULL;
+
+    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;
+        }
+    }
+
+    if (e->name.length == 0) {
+        njs_type_error(vm, "Unsupported JWK EC curve: %s", OBJ_nid2sn(nid));
+        goto fail;
+    }
+
+    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;
+        }
+
+        ret = njs_value_property_set(vm, retval, njs_value_arg(&string_d),
+                                     &dvalue);
+        if (ret != NJS_OK) {
+            goto fail;
+        }
+    }
+
+    return NJS_OK;
+
+fail:
+
+    if (x_bn != NULL) {
+        BN_free(x_bn);
+    }
+
+    if (y_bn != NULL) {
+        BN_free(y_bn);
+    }
+
+    return NJS_ERROR;
+}
+
+
+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;
+    }
+
+    ec = njs_pkey_get_ec_key(key->pkey);
+
+    group = EC_KEY_get0_group(ec);
+    point = EC_KEY_get0_public_key(ec);
+    form = POINT_CONVERSION_UNCOMPRESSED;
+
+    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;
+    }
+
+    dst = njs_mp_alloc(njs_vm_memory_pool(vm), size);
+    if (njs_slow_path(dst == NULL)) {
+        return NJS_ERROR;
+    }
+
+    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;
+    }
+
+    return njs_vm_value_array_buffer_set(vm, retval, dst, size);
+}
+
+
+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;
+
+    njs_assert(key->pkey != NULL);
+
+    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)
 {
-    njs_internal_error(vm, "\"exportKey\" not implemented");
-    return NJS_ERROR;
+    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);
 }
 
 
@@ -1651,8 +2192,711 @@ 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_internal_error(vm, "\"generateKey\" not implemented");
-    return NJS_ERROR;
+    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");
+


More information about the nginx-devel mailing list