Dmitry Volyntsev
January 04, 2023 11:44PM
details: https://hg.nginx.org/njs/rev/0681bf662222
branches:
changeset: 2020:0681bf662222
user: Dmitry Volyntsev <xeioex@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");
+
_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
https://mailman.nginx.org/mailman/listinfo/nginx-devel
Subject Author Views Posted

[njs] WebCrypto: extended support for asymmetric keys.

Dmitry Volyntsev 444 January 04, 2023 11:44PM



Sorry, you do not have permission to post/reply in this forum.

Online Users

Guests: 298
Record Number of Users: 8 on April 13, 2023
Record Number of Guests: 421 on December 02, 2018
Powered by nginx      Powered by FreeBSD      PHP Powered      Powered by MariaDB      ipv6 ready