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,
} 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;
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;
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);
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);
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)
},
{
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)
},
{
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)
},
{
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)
},
{
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 }
};
};
+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[] = {
{
};
+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;
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:
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)) {
}
-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)
{
}
-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)
}
-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)
}
+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)
{
}
+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)
{
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;
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)) {
}
+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)
{
--- /dev/null
+/*---
+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);
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 */
}
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) {
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}"`);
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}"`);
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");
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",
{ 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 = {
{ 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 = {
{ 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 = {
{ 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([