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