details: https://hg.nginx.org/njs/rev/0681bf662222 branches: changeset: 2020:0681bf662222 user: Dmitry Volyntsev <xei...@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