--- /dev/null
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <njs_main.h>
+#include "njs_webcrypto.h"
+
+#include <openssl/bn.h>
+#include <openssl/bio.h>
+#include <openssl/x509.h>
+#include <openssl/evp.h>
+#include <openssl/aes.h>
+#include <openssl/rsa.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/crypto.h>
+
+#if NJS_HAVE_OPENSSL_HKDF
+#include <openssl/kdf.h>
+#endif
+
+#if NJS_HAVE_OPENSSL_EVP_MD_CTX_NEW
+#define njs_evp_md_ctx_new() EVP_MD_CTX_new();
+#define njs_evp_md_ctx_free(_ctx) EVP_MD_CTX_free(_ctx);
+#else
+#define njs_evp_md_ctx_new() EVP_MD_CTX_create();
+#define njs_evp_md_ctx_free(_ctx) EVP_MD_CTX_destroy(_ctx);
+#endif
+
+
+typedef enum {
+ NJS_KEY_FORMAT_RAW = 1 << 1,
+ NJS_KEY_FORMAT_PKCS8 = 1 << 2,
+ NJS_KEY_FORMAT_SPKI = 1 << 3,
+ NJS_KEY_FORMAT_JWK = 1 << 4,
+ NJS_KEY_FORMAT_UNKNOWN = 1 << 5,
+} njs_webcrypto_key_format_t;
+
+
+typedef enum {
+ NJS_KEY_USAGE_DECRYPT = 1 << 1,
+ NJS_KEY_USAGE_DERIVE_BITS = 1 << 2,
+ NJS_KEY_USAGE_DERIVE_KEY = 1 << 3,
+ NJS_KEY_USAGE_ENCRYPT = 1 << 4,
+ NJS_KEY_USAGE_GENERATE_KEY = 1 << 5,
+ NJS_KEY_USAGE_SIGN = 1 << 6,
+ NJS_KEY_USAGE_VERIFY = 1 << 7,
+ NJS_KEY_USAGE_WRAP_KEY = 1 << 8,
+ NJS_KEY_USAGE_UNSUPPORTED = 1 << 9,
+ NJS_KEY_USAGE_UNWRAP_KEY = 1 << 10,
+} njs_webcrypto_key_usage_t;
+
+
+typedef enum {
+ NJS_ALGORITHM_RSA_OAEP,
+ 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_SHA1,
+ NJS_HASH_SHA256,
+ NJS_HASH_SHA384,
+ NJS_HASH_SHA512,
+} 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;
+} njs_webcrypto_entry_t;
+
+
+typedef struct {
+ njs_webcrypto_alg_t type;
+ unsigned usage;
+ unsigned fmt;
+} njs_webcrypto_algorithm_t;
+
+
+typedef struct {
+ njs_webcrypto_algorithm_t *alg;
+ unsigned usage;
+ njs_webcrypto_hash_t hash;
+ njs_webcrypto_curve_t curve;
+
+ EVP_PKEY *pkey;
+ njs_str_t raw;
+} njs_webcrypto_key_t;
+
+
+typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX *ctx);
+typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX *ctx, unsigned char *out,
+ size_t *outlen, const unsigned char *in, size_t inlen);
+
+
+static njs_int_t njs_ext_cipher(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_cipher_pkey(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_index_t encrypt);
+static njs_int_t njs_cipher_aes_gcm(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt);
+static njs_int_t njs_cipher_aes_ctr(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt);
+static njs_int_t njs_cipher_aes_cbc(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt);
+static njs_int_t njs_ext_derive(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t derive_key);
+static njs_int_t njs_ext_digest(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_export_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_import_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_sign(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t verify);
+static njs_int_t njs_ext_unwrap_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_wrap_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+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_format_t njs_key_format(njs_vm_t *vm,
+ njs_value_t *value, njs_str_t *format);
+static njs_int_t njs_key_usage(njs_vm_t *vm, njs_value_t *value,
+ 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);
+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);
+
+static njs_int_t njs_webcrypto_result(njs_vm_t *vm, njs_value_t *result,
+ njs_int_t rc);
+static void njs_webcrypto_error(njs_vm_t *vm, const char *fmt, ...);
+
+static njs_webcrypto_entry_t njs_webcrypto_alg[] = {
+
+#define njs_webcrypto_algorithm(type, usage_mask, fmt_mask) \
+ (uintptr_t) & (njs_webcrypto_algorithm_t) { type, usage_mask, fmt_mask }
+
+ {
+ njs_str("RSA-OAEP"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_RSA_OAEP,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("AES-GCM"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_AES_GCM,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("AES-CTR"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_AES_CTR,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("AES-CBC"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_AES_CBC,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("RSASSA-PKCS1-v1_5"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_RSASSA_PKCS1_v1_5,
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("RSA-PSS"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_RSA_PSS,
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("ECDSA"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_ECDSA,
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("ECDH"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_ECDH,
+ NJS_KEY_USAGE_DERIVE_KEY |
+ NJS_KEY_USAGE_DERIVE_BITS |
+ NJS_KEY_USAGE_GENERATE_KEY |
+ NJS_KEY_USAGE_UNSUPPORTED,
+ NJS_KEY_FORMAT_UNKNOWN)
+ },
+
+ {
+ njs_str("PBKDF2"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_PBKDF2,
+ NJS_KEY_USAGE_DERIVE_KEY |
+ NJS_KEY_USAGE_DERIVE_BITS,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("HKDF"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_HKDF,
+ NJS_KEY_USAGE_DERIVE_KEY |
+ NJS_KEY_USAGE_DERIVE_BITS,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("HMAC"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_HMAC,
+ NJS_KEY_USAGE_GENERATE_KEY |
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_null_str,
+ 0
+ }
+};
+
+
+static njs_webcrypto_entry_t njs_webcrypto_hash[] = {
+ { njs_str("SHA-256"), NJS_HASH_SHA256 },
+ { njs_str("SHA-384"), NJS_HASH_SHA384 },
+ { njs_str("SHA-512"), NJS_HASH_SHA512 },
+ { njs_str("SHA-1"), NJS_HASH_SHA1 },
+ { njs_null_str, 0 }
+};
+
+
+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_null_str, 0 }
+};
+
+
+static njs_webcrypto_entry_t njs_webcrypto_usage[] = {
+ { njs_str("decrypt"), NJS_KEY_USAGE_DECRYPT },
+ { njs_str("deriveBits"), NJS_KEY_USAGE_DERIVE_BITS },
+ { njs_str("deriveKey"), NJS_KEY_USAGE_DERIVE_KEY },
+ { njs_str("encrypt"), NJS_KEY_USAGE_ENCRYPT },
+ { njs_str("sign"), NJS_KEY_USAGE_SIGN },
+ { njs_str("unwrapKey"), NJS_KEY_USAGE_UNWRAP_KEY },
+ { njs_str("verify"), NJS_KEY_USAGE_VERIFY },
+ { njs_str("wrapKey"), NJS_KEY_USAGE_WRAP_KEY },
+ { njs_null_str, 0 }
+};
+
+
+static njs_external_t njs_ext_webcrypto_crypto_key[] = {
+
+ {
+ .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+ .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+ .u.property = {
+ .value = "CryptoKey",
+ }
+ },
+};
+
+
+static njs_external_t njs_ext_subtle_webcrypto[] = {
+
+ {
+ .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+ .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+ .u.property = {
+ .value = "SubtleCrypto",
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("decrypt"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_cipher,
+ .magic8 = 0,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("deriveBits"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_derive,
+ .magic8 = 0,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("deriveKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_derive,
+ .magic8 = 1,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("digest"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_digest,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("encrypt"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_cipher,
+ .magic8 = 1,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("exportKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_export_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("generateKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_generate_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("importKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_import_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("sign"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_sign,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("unwrapKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_unwrap_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("verify"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_sign,
+ .magic8 = 1,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("wrapKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_wrap_key,
+ }
+ },
+
+};
+
+static njs_external_t njs_ext_webcrypto[] = {
+
+ {
+ .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+ .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+ .u.property = {
+ .value = "Crypto",
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("getRandomValues"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_get_random_values,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_OBJECT,
+ .name.string = njs_str("subtle"),
+ .enumerable = 1,
+ .writable = 1,
+ .u.object = {
+ .enumerable = 1,
+ .properties = njs_ext_subtle_webcrypto,
+ .nproperties = njs_nitems(njs_ext_subtle_webcrypto),
+ }
+ },
+
+};
+
+
+static njs_int_t njs_webcrypto_crypto_key_proto_id;
+
+
+static njs_int_t
+njs_ext_cipher(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t encrypt)
+{
+ unsigned mask;
+ njs_int_t ret;
+ njs_str_t data;
+ njs_value_t *options;
+ njs_webcrypto_key_t *key;
+ njs_webcrypto_algorithm_t *alg;
+
+ options = njs_arg(args, nargs, 1);
+ alg = njs_key_algorithm(vm, options);
+ if (njs_slow_path(alg == NULL)) {
+ 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;
+ }
+
+ mask = encrypt ? NJS_KEY_USAGE_ENCRYPT : NJS_KEY_USAGE_DECRYPT;
+ if (njs_slow_path(!(key->usage & mask))) {
+ njs_type_error(vm, "provide key does not support %s operation",
+ encrypt ? "encrypt" : "decrypt");
+ goto fail;
+ }
+
+ if (njs_slow_path(key->alg != alg)) {
+ njs_type_error(vm, "cannot %s using \"%V\" with \"%V\" key",
+ encrypt ? "encrypt" : "decrypt",
+ njs_algorithm_string(key->alg),
+ njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 3));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ switch (alg->type) {
+ case NJS_ALGORITHM_RSA_OAEP:
+ ret = njs_cipher_pkey(vm, &data, key, encrypt);
+ break;
+
+ case NJS_ALGORITHM_AES_GCM:
+ ret = njs_cipher_aes_gcm(vm, &data, key, options, encrypt);
+ break;
+
+ case NJS_ALGORITHM_AES_CTR:
+ ret = njs_cipher_aes_ctr(vm, &data, key, options, encrypt);
+ break;
+
+ case NJS_ALGORITHM_AES_CBC:
+ default:
+ ret = njs_cipher_aes_cbc(vm, &data, key, options, encrypt);
+ }
+
+ return njs_webcrypto_result(vm, njs_vm_retval(vm), ret);
+
+fail:
+
+ return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR);
+}
+
+
+static njs_int_t
+njs_cipher_pkey(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key,
+ njs_index_t encrypt)
+{
+ u_char *dst;
+ size_t outlen;
+ njs_int_t ret;
+ const EVP_MD *md;
+ EVP_PKEY_CTX *ctx;
+ EVP_PKEY_cipher_t cipher;
+ EVP_PKEY_cipher_init_t init;
+
+ ctx = EVP_PKEY_CTX_new(key->pkey, NULL);
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ if (encrypt) {
+ init = EVP_PKEY_encrypt_init;
+ cipher = EVP_PKEY_encrypt;
+
+ } else {
+ init = EVP_PKEY_decrypt_init;
+ cipher = EVP_PKEY_decrypt;
+ }
+
+ ret = init(ctx);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_%scrypt_init() failed",
+ encrypt ? "en" : "de");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ md = njs_algorithm_hash_digest(key->hash);
+
+ EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
+ EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md);
+ EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md);
+
+ ret = cipher(ctx, NULL, &outlen, data->start, data->length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_%scrypt() failed",
+ encrypt ? "en" : "de");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm), outlen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = cipher(ctx, dst, &outlen, data->start, data->length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_%scrypt() failed",
+ encrypt ? "en" : "de");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, outlen);
+
+fail:
+
+ EVP_PKEY_CTX_free(ctx);
+
+ return ret;
+}
+
+
+static njs_int_t
+njs_cipher_aes_gcm(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key,
+ njs_value_t *options, njs_bool_t encrypt)
+{
+ int len, outlen, dstlen;
+ u_char *dst, *p;
+ int64_t taglen;
+ njs_str_t iv, aad;
+ njs_int_t ret;
+ njs_value_t value;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+
+ static const njs_value_t string_iv = njs_string("iv");
+ static const njs_value_t string_ad = njs_string("additionalData");
+ static const njs_value_t string_tl = njs_string("tagLength");
+
+ switch (key->raw.length) {
+ case 16:
+ cipher = EVP_aes_128_gcm();
+ break;
+
+ case 32:
+ cipher = EVP_aes_256_gcm();
+ break;
+
+ default:
+ njs_type_error(vm, "AES-GCM Invalid key length");
+ return NJS_ERROR;
+ }
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_iv), &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "AES-GCM algorithm.iv is not provided");
+ }
+
+ return NJS_ERROR;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &iv, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ taglen = 128;
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_tl), &value);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+
+ if (njs_is_defined(&value)) {
+ ret = njs_value_to_integer(vm, &value, &taglen);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ if (njs_slow_path(taglen != 32
+ && taglen != 64
+ && taglen != 96
+ && taglen != 104
+ && taglen != 112
+ && taglen != 120
+ && taglen != 128))
+ {
+ njs_type_error(vm, "AES-GCM Invalid tagLength");
+ return NJS_ERROR;
+ }
+
+ taglen /= 8;
+
+ if (njs_slow_path(!encrypt && (data->length < (size_t) taglen))) {
+ njs_type_error(vm, "AES-GCM data is too short");
+ return NJS_ERROR;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.length, NULL);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_ctrl() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CipherInit_ex(ctx, NULL, NULL, key->raw.start, iv.start,
+ encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ if (!encrypt) {
+ ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, taglen,
+ &data->start[data->length - taglen]);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_ctrl() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+ }
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_ad), &value);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+
+ aad.length = 0;
+
+ if (njs_is_defined(&value)) {
+ ret = njs_vm_value_to_bytes(vm, &aad, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ if (aad.length != 0) {
+ ret = EVP_CipherUpdate(ctx, NULL, &outlen, aad.start, aad.length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+ }
+
+ dstlen = data->length + EVP_CIPHER_CTX_block_size(ctx) + taglen;
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm), dstlen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ return NJS_ERROR;
+ }
+
+ ret = EVP_CipherUpdate(ctx, dst, &outlen, data->start,
+ data->length - (encrypt ? 0 : taglen));
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ p = &dst[outlen];
+ len = EVP_CIPHER_CTX_block_size(ctx);
+
+ ret = EVP_CipherFinal_ex(ctx, p, &len);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sFinal_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ outlen += len;
+ p += len;
+
+ if (encrypt) {
+ ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, taglen, p);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_ctrl() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ outlen += taglen;
+ }
+
+ ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, outlen);
+
+fail:
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ret;
+}
+
+
+static njs_int_t
+njs_cipher_aes_ctr128(njs_vm_t *vm, const EVP_CIPHER *cipher, u_char *key,
+ u_char *data, size_t dlen, u_char *counter, u_char *dst, int *olen,
+ njs_bool_t encrypt)
+{
+ int len, outlen;
+ njs_int_t ret;
+ EVP_CIPHER_CTX *ctx;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = EVP_CipherInit_ex(ctx, cipher, NULL, key, counter, encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CipherUpdate(ctx, dst, &outlen, data, dlen);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CipherFinal_ex(ctx, &dst[outlen], &len);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sFinal_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ outlen += len;
+ *olen = outlen;
+
+ ret = NJS_OK;
+
+fail:
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ret;
+}
+
+
+njs_inline njs_uint_t
+njs_ceil_div(njs_uint_t dend, njs_uint_t dsor)
+{
+ return (dsor == 0) ? 0 : 1 + (dend - 1) / dsor;
+}
+
+
+njs_inline BIGNUM *
+njs_bn_counter128(njs_str_t *ctr, njs_uint_t bits)
+{
+ njs_uint_t remainder, bytes;
+ uint8_t buf[16];
+
+ remainder = bits % 8;
+
+ if (remainder == 0) {
+ bytes = bits / 8;
+
+ return BN_bin2bn(&ctr->start[ctr->length - bytes], bytes, NULL);
+ }
+
+ bytes = njs_ceil_div(bits, 8);
+
+ memcpy(buf, &ctr->start[ctr->length - bytes], bytes);
+
+ buf[0] &= ~(0xFF << remainder);
+
+ return BN_bin2bn(buf, bytes, NULL);
+}
+
+
+njs_inline void
+njs_counter128_reset(u_char *src, u_char *dst, njs_uint_t bits)
+{
+ size_t index;
+ njs_uint_t remainder, bytes;
+
+ bytes = bits / 8;
+ remainder = bits % 8;
+
+ memcpy(dst, src, 16);
+
+ index = 16 - bytes;
+
+ memset(&dst[index], 0, bytes);
+
+ if (remainder) {
+ dst[index - 1] &= 0xff << remainder;
+ }
+}
+
+
+static njs_int_t
+njs_cipher_aes_ctr(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key,
+ njs_value_t *options, njs_bool_t encrypt)
+{
+ int len, len2;
+ u_char *dst;
+ int64_t length;
+ BIGNUM *total, *blocks, *left, *ctr;
+ njs_int_t ret;
+ njs_str_t iv;
+ njs_uint_t size1;
+ njs_value_t value;
+ const EVP_CIPHER *cipher;
+ u_char iv2[16];
+
+ static const njs_value_t string_counter = njs_string("counter");
+ static const njs_value_t string_length = njs_string("length");
+
+ switch (key->raw.length) {
+ case 16:
+ cipher = EVP_aes_128_ctr();
+ break;
+
+ case 32:
+ cipher = EVP_aes_256_ctr();
+ break;
+
+ default:
+ njs_type_error(vm, "AES-CTR Invalid key length");
+ return NJS_ERROR;
+ }
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_counter),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "AES-CTR algorithm.counter is not provided");
+ }
+
+ return NJS_ERROR;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &iv, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ if (njs_slow_path(iv.length != 16)) {
+ njs_type_error(vm, "AES-CTR algorithm.counter must be 16 bytes long");
+ return NJS_ERROR;
+ }
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_length),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "AES-CTR algorithm.length is not provided");
+ }
+
+ return NJS_ERROR;
+ }
+
+ ret = njs_value_to_integer(vm, &value, &length);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ if (njs_slow_path(length == 0 || length > 128)) {
+ njs_type_error(vm, "AES-CTR algorithm.length "
+ "must be between 1 and 128");
+ return NJS_ERROR;
+ }
+
+ ctr = NULL;
+ blocks = NULL;
+ left = NULL;
+
+ total = BN_new();
+ if (njs_slow_path(total == NULL)) {
+ njs_webcrypto_error(vm, "BN_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = BN_lshift(total, BN_value_one(), length);
+ if (njs_slow_path(ret != 1)) {
+ njs_webcrypto_error(vm, "BN_lshift() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ctr = njs_bn_counter128(&iv, length);
+ if (njs_slow_path(ctr == NULL)) {
+ njs_webcrypto_error(vm, "BN_bin2bn() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ blocks = BN_new();
+ if (njs_slow_path(blocks == NULL)) {
+ njs_webcrypto_error(vm, "BN_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = BN_set_word(blocks, njs_ceil_div(data->length, AES_BLOCK_SIZE));
+ if (njs_slow_path(ret != 1)) {
+ njs_webcrypto_error(vm, "BN_set_word() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = BN_cmp(blocks, total);
+ if (njs_slow_path(ret > 0)) {
+ njs_type_error(vm, "AES-CTR repeated counter");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ left = BN_new();
+ if (njs_slow_path(left == NULL)) {
+ njs_webcrypto_error(vm, "BN_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = BN_sub(left, total, ctr);
+ if (njs_slow_path(ret != 1)) {
+ njs_webcrypto_error(vm, "BN_sub() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm),
+ data->length + EVP_MAX_BLOCK_LENGTH);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ return NJS_ERROR;
+ }
+
+ ret = BN_cmp(left, blocks);
+ if (ret >= 0) {
+
+ /*
+ * Doing a single run if a counter is not wrapped-around
+ * during the ciphering.
+ * */
+
+ ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start,
+ data->start, data->length, iv.start, dst,
+ &len, encrypt);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ goto done;
+ }
+
+ /*
+ * Otherwise splitting ciphering into two parts:
+ * Until the wrapping moment
+ * After the resetting counter to zero.
+ */
+
+ size1 = BN_get_word(left) * AES_BLOCK_SIZE;
+
+ ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, data->start, size1,
+ iv.start, dst, &len, encrypt);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ njs_counter128_reset(iv.start, (u_char *) iv2, length);
+
+ ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, &data->start[size1],
+ data->length - size1, iv2, &dst[size1], &len2,
+ encrypt);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ len += len2;
+
+done:
+
+ ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, len);
+
+fail:
+
+ BN_free(total);
+
+ if (ctr != NULL) {
+ BN_free(ctr);
+ }
+
+ if (blocks != NULL) {
+ BN_free(blocks);
+ }
+
+ if (left != NULL) {
+ BN_free(left);
+ }
+
+ return ret;
+}
+
+
+static njs_int_t
+njs_cipher_aes_cbc(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key,
+ njs_value_t *options, njs_bool_t encrypt)
+{
+ int olen_max, olen, olen2;
+ u_char *dst;
+ unsigned remainder;
+ njs_str_t iv;
+ njs_int_t ret;
+ njs_value_t value;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+
+ static const njs_value_t string_iv = njs_string("iv");
+
+ switch (key->raw.length) {
+ case 16:
+ cipher = EVP_aes_128_cbc();
+ break;
+
+ case 32:
+ cipher = EVP_aes_256_cbc();
+ break;
+
+ default:
+ njs_type_error(vm, "AES-CBC Invalid key length");
+ return NJS_ERROR;
+ }
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_iv), &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "AES-CBC algorithm.iv is not provided");
+ }
+
+ return NJS_ERROR;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &iv, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ if (njs_slow_path(iv.length != 16)) {
+ njs_type_error(vm, "AES-CBC algorithm.iv must be 16 bytes long");
+ return NJS_ERROR;
+ }
+
+ olen_max = data->length + AES_BLOCK_SIZE - 1;
+ remainder = olen_max % AES_BLOCK_SIZE;
+
+ if (remainder != 0) {
+ olen_max += AES_BLOCK_SIZE - remainder;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = EVP_CipherInit_ex(ctx, cipher, NULL, key->raw.start, iv.start,
+ encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%SInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm), olen_max);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CipherUpdate(ctx, dst, &olen, data->start, data->length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%SUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CipherFinal_ex(ctx, &dst[olen], &olen2);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sFinal_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ olen += olen2;
+
+ ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, olen);
+
+fail:
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ret;
+}
+
+
+static njs_int_t
+njs_ext_derive(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t derive_key)
+{
+ u_char *k;
+ size_t olen;
+ int64_t iterations, length;
+ EVP_PKEY *pkey;
+ unsigned usage, mask;
+ njs_int_t ret;
+ njs_str_t salt, info;
+ njs_value_t value, *aobject, *dobject;
+ const EVP_MD *md;
+ EVP_PKEY_CTX *pctx;
+ njs_mp_cleanup_t *cln;
+ njs_webcrypto_key_t *key, *dkey;
+ njs_webcrypto_hash_t hash;
+ njs_webcrypto_algorithm_t *alg, *dalg;
+
+ static const njs_value_t string_info = njs_string("info");
+ static const njs_value_t string_salt = njs_string("salt");
+ static const njs_value_t string_length = njs_string("length");
+ static const njs_value_t string_iterations = njs_string("iterations");
+
+ aobject = njs_arg(args, nargs, 1);
+ alg = njs_key_algorithm(vm, aobject);
+ if (njs_slow_path(alg == NULL)) {
+ 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, "\"baseKey\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ mask = derive_key ? NJS_KEY_USAGE_DERIVE_KEY : NJS_KEY_USAGE_DERIVE_BITS;
+ if (njs_slow_path(!(key->usage & mask))) {
+ njs_type_error(vm, "provide key does not support \"%s\" operation",
+ derive_key ? "deriveKey" : "deriveBits");
+ goto fail;
+ }
+
+ if (njs_slow_path(key->alg != alg)) {
+ njs_type_error(vm, "cannot derive %s using \"%V\" with \"%V\" key",
+ derive_key ? "key" : "bits",
+ njs_algorithm_string(key->alg),
+ njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ dobject = njs_arg(args, nargs, 3);
+
+ if (derive_key) {
+ dalg = njs_key_algorithm(vm, dobject);
+ if (njs_slow_path(dalg == NULL)) {
+ goto fail;
+ }
+
+ ret = njs_value_property(vm, dobject, njs_value_arg(&string_length),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "derivedKeyAlgorithm.length "
+ "is not provided");
+ goto fail;
+ }
+ }
+
+ } else {
+ dalg = NULL;
+ njs_value_assign(&value, dobject);
+ }
+
+ ret = njs_value_to_integer(vm, &value, &length);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ dkey = NULL;
+ length /= 8;
+
+ if (derive_key) {
+ switch (dalg->type) {
+ case NJS_ALGORITHM_AES_GCM:
+ case NJS_ALGORITHM_AES_CTR:
+ case NJS_ALGORITHM_AES_CBC:
+
+ if (length != 16 && length != 32) {
+ njs_type_error(vm, "deriveKey \"%V\" length must be 128 or 256",
+ njs_algorithm_string(dalg));
+ goto fail;
+ }
+
+ break;
+
+ default:
+ njs_internal_error(vm, "not implemented deriveKey: \"%V\"",
+ njs_algorithm_string(dalg));
+ 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 & ~dalg->usage)) {
+ njs_type_error(vm, "unsupported key usage for \"%V\" key",
+ njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ dkey = njs_mp_zalloc(njs_vm_memory_pool(vm),
+ sizeof(njs_webcrypto_key_t));
+ if (njs_slow_path(dkey == NULL)) {
+ njs_memory_error(vm);
+ goto fail;
+ }
+
+ dkey->alg = dalg;
+ dkey->usage = usage;
+ }
+
+ k = njs_mp_zalloc(njs_vm_memory_pool(vm), length);
+ if (njs_slow_path(k == NULL)) {
+ njs_memory_error(vm);
+ goto fail;
+ }
+
+ switch (alg->type) {
+ case NJS_ALGORITHM_PBKDF2:
+ ret = njs_algorithm_hash(vm, aobject, &hash);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ ret = njs_value_property(vm, aobject, njs_value_arg(&string_salt),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "PBKDF2 algorithm.salt is not provided");
+ }
+
+ goto fail;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &salt, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ if (njs_slow_path(salt.length < 16)) {
+ njs_type_error(vm, "PBKDF2 algorithm.salt must be "
+ "at least 16 bytes long");
+ goto fail;
+ }
+
+ ret = njs_value_property(vm, aobject, njs_value_arg(&string_iterations),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "PBKDF2 algorithm.iterations "
+ "is not provided");
+ }
+
+ goto fail;
+ }
+
+ ret = njs_value_to_integer(vm, &value, &iterations);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ md = njs_algorithm_hash_digest(hash);
+
+ ret = PKCS5_PBKDF2_HMAC((char *) key->raw.start, key->raw.length,
+ salt.start, salt.length, iterations, md,
+ length, k);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "PKCS5_PBKDF2_HMAC() failed");
+ goto fail;
+ }
+ break;
+
+ case NJS_ALGORITHM_HKDF:
+#ifdef NJS_HAVE_OPENSSL_HKDF
+ ret = njs_algorithm_hash(vm, aobject, &hash);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ ret = njs_value_property(vm, aobject, njs_value_arg(&string_salt),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "HKDF algorithm.salt is not provided");
+ }
+
+ goto fail;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &salt, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ ret = njs_value_property(vm, aobject, njs_value_arg(&string_info),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "HKDF algorithm.info is not provided");
+ }
+
+ goto fail;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &info, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ if (njs_slow_path(pctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_new_id() failed");
+ goto fail;
+ }
+
+ ret = EVP_PKEY_derive_init(pctx);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_derive_init() failed");
+ goto free;
+ }
+
+ md = njs_algorithm_hash_digest(hash);
+
+ ret = EVP_PKEY_CTX_set_hkdf_md(pctx, md);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_hkdf_md() failed");
+ goto free;
+ }
+
+ ret = EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt.start, salt.length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_set1_hkdf_salt() failed");
+ goto free;
+ }
+
+ ret = EVP_PKEY_CTX_set1_hkdf_key(pctx, key->raw.start, key->raw.length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_set1_hkdf_key() failed");
+ goto free;
+ }
+
+ ret = EVP_PKEY_CTX_add1_hkdf_info(pctx, info.start, info.length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_add1_hkdf_info() failed");
+ goto free;
+ }
+
+ olen = (size_t) length;
+ ret = EVP_PKEY_derive(pctx, k, &olen);
+ if (njs_slow_path(ret <= 0 || olen != (size_t) length)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_derive() failed");
+ goto free;
+ }
+
+free:
+
+ EVP_PKEY_CTX_free(pctx);
+
+ if (njs_slow_path(ret <= 0)) {
+ goto fail;
+ }
+
+ break;
+#else
+ (void) pctx;
+ (void) olen;
+ (void) &string_info;
+ (void) &info;
+#endif
+
+ case NJS_ALGORITHM_ECDH:
+ default:
+ njs_internal_error(vm, "not implemented deriveKey "
+ "algorithm: \"%V\"", njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ if (derive_key) {
+ if (dalg->type == NJS_ALGORITHM_HMAC) {
+ ret = njs_algorithm_hash(vm, dobject, &dkey->hash);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, k, length);
+ if (njs_slow_path(pkey == NULL)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_new_mac_key() failed");
+ goto fail;
+ }
+
+ 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;
+
+ dkey->pkey = pkey;
+
+ } else {
+ dkey->raw.start = k;
+ dkey->raw.length = length;
+ }
+
+ ret = njs_vm_external_create(vm, &value,
+ njs_webcrypto_crypto_key_proto_id,
+ dkey, 0);
+ } else {
+ ret = njs_vm_value_array_buffer_set(vm, &value, k, length);
+ }
+
+ if (njs_slow_path(ret != NJS_OK)) {
+ 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_digest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t unused)
+{
+ unsigned olen;
+ u_char *dst;
+ njs_str_t data;
+ njs_int_t ret;
+ njs_value_t value;
+ const EVP_MD *md;
+ njs_webcrypto_hash_t hash;
+
+ ret = njs_algorithm_hash(vm, njs_arg(args, nargs, 1), &hash);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 2));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ md = njs_algorithm_hash_digest(hash);
+ olen = EVP_MD_size(md);
+
+ dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ goto fail;
+ }
+
+ ret = EVP_Digest(data.start, data.length, dst, &olen, md, NULL);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_Digest() failed");
+ goto fail;
+ }
+
+ ret = njs_vm_value_array_buffer_set(vm, &value, dst, olen);
+ if (njs_slow_path(ret != NJS_OK)) {
+ 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_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t unused)
+{
+ njs_internal_error(vm, "\"exportKey\" not implemented");
+ return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t unused)
+{
+ njs_internal_error(vm, "\"generateKey\" not implemented");
+ return NJS_ERROR;
+}
+
+
+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;
+ RSA *rsa;
+ EC_KEY *ec;
+ unsigned usage;
+ EVP_PKEY *pkey;
+ njs_int_t ret;
+ njs_str_t key_data, format;
+ njs_value_t value, *options;
+ const u_char *start;
+ const EC_GROUP *group;
+ 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;
+
+ static const int curves[] = {
+ NID_X9_62_prime256v1,
+ NID_secp384r1,
+ NID_secp521r1,
+ };
+
+ pkey = NULL;
+
+ fmt = njs_key_format(vm, njs_arg(args, nargs, 1), &format);
+ if (njs_slow_path(fmt == NJS_KEY_FORMAT_UNKNOWN)) {
+ njs_type_error(vm, "unknown key format: \"%V\"", &format);
+ 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 for \"%V\" key",
+ 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;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &key_data, njs_arg(args, nargs, 2));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ start = key_data.start;
+
+ switch (fmt) {
+ case NJS_KEY_FORMAT_PKCS8:
+ bio = 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);
+
+ 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_RAW:
+ break;
+
+ default:
+ njs_internal_error(vm, "not implemented key format: \"%V\"", &format);
+ 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);
+ goto fail;
+ }
+
+ key->alg = alg;
+ key->usage = usage;
+
+ switch (alg->type) {
+ case NJS_ALGORITHM_RSA_OAEP:
+ case NJS_ALGORITHM_RSA_PSS:
+ case NJS_ALGORITHM_RSASSA_PKCS1_v1_5:
+ 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);
+
+ ret = njs_algorithm_hash(vm, options, &key->hash);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ key->pkey = pkey;
+
+ break;
+
+ case NJS_ALGORITHM_ECDSA:
+ case NJS_ALGORITHM_ECDH:
+ 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);
+
+ ret = njs_algorithm_curve(vm, options, &key->curve);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ if (njs_slow_path(curves[key->curve] != nid)) {
+ njs_webcrypto_error(vm, "name curve mismatch");
+ goto fail;
+ }
+
+ key->pkey = pkey;
+
+ break;
+
+ case NJS_ALGORITHM_HMAC:
+ pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key_data.start,
+ key_data.length);
+ if (njs_slow_path(pkey == NULL)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_new_mac_key() failed");
+ goto fail;
+ }
+
+ ret = njs_algorithm_hash(vm, options, &key->hash);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ key->pkey = pkey;
+
+ break;
+
+ case NJS_ALGORITHM_AES_GCM:
+ case NJS_ALGORITHM_AES_CTR:
+ case NJS_ALGORITHM_AES_CBC:
+ case NJS_ALGORITHM_PBKDF2:
+ case NJS_ALGORITHM_HKDF:
+ key->raw = key_data;
+ default:
+ 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)) {
+ goto fail;
+ }
+
+ return njs_webcrypto_result(vm, &value, NJS_OK);
+
+fail:
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR);
+}
+
+
+static njs_int_t
+njs_set_rsa_padding(njs_vm_t *vm, njs_value_t *options, EVP_PKEY *pkey,
+ EVP_PKEY_CTX *ctx, njs_webcrypto_alg_t type)
+{
+ int padding;
+ int64_t salt_length;
+ njs_int_t ret;
+ njs_value_t value;
+
+ static const njs_value_t string_saltl = njs_string("saltLength");
+
+ if (type == NJS_ALGORITHM_ECDSA) {
+ return NJS_OK;
+ }
+
+ padding = (type == NJS_ALGORITHM_RSA_PSS) ? RSA_PKCS1_PSS_PADDING
+ : RSA_PKCS1_PADDING;
+ ret = EVP_PKEY_CTX_set_rsa_padding(ctx, padding);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_rsa_padding() failed");
+ return NJS_ERROR;
+ }
+
+ if (padding == RSA_PKCS1_PSS_PADDING) {
+ ret = njs_value_property(vm, options, njs_value_arg(&string_saltl),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "RSA-PSS algorithm.saltLength "
+ "is not provided");
+ }
+
+ return NJS_ERROR;
+ }
+
+ ret = njs_value_to_integer(vm, &value, &salt_length);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ ret = EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, salt_length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm,
+ "EVP_PKEY_CTX_set_rsa_pss_saltlen() failed");
+ return NJS_ERROR;
+ }
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
+njs_ext_sign(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t verify)
+{
+ u_char *dst;
+ size_t olen, outlen;
+ unsigned mask, m_len;
+ njs_int_t ret;
+ njs_str_t data, sig;
+ EVP_MD_CTX *mctx;
+ njs_value_t value, *options;
+ EVP_PKEY_CTX *pctx;
+ const EVP_MD *md;
+ njs_webcrypto_key_t *key;
+ njs_webcrypto_hash_t hash;
+ njs_webcrypto_algorithm_t *alg;
+ unsigned char m[EVP_MAX_MD_SIZE];
+
+ mctx = NULL;
+ pctx = NULL;
+
+ options = njs_arg(args, nargs, 1);
+ alg = njs_key_algorithm(vm, options);
+ if (njs_slow_path(alg == NULL)) {
+ 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;
+ }
+
+ mask = verify ? NJS_KEY_USAGE_VERIFY : NJS_KEY_USAGE_SIGN;
+ if (njs_slow_path(!(key->usage & mask))) {
+ njs_type_error(vm, "provide key does not support \"sign\" operation");
+ goto fail;
+ }
+
+ if (njs_slow_path(key->alg != alg)) {
+ njs_type_error(vm, "cannot %s using \"%V\" with \"%V\" key",
+ verify ? "verify" : "sign",
+ njs_algorithm_string(key->alg),
+ njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ if (verify) {
+ ret = njs_vm_value_to_bytes(vm, &sig, njs_arg(args, nargs, 3));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 4));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ } else {
+ ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 3));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+ }
+
+ mctx = njs_evp_md_ctx_new();
+ if (njs_slow_path(mctx == NULL)) {
+ njs_webcrypto_error(vm, "njs_evp_md_ctx_new() failed");
+ goto fail;
+ }
+
+ if (alg->type == NJS_ALGORITHM_ECDSA) {
+ ret = njs_algorithm_hash(vm, options, &hash);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ goto fail;
+ }
+
+ } else {
+ hash = key->hash;
+ }
+
+ md = njs_algorithm_hash_digest(hash);
+
+ ret = EVP_DigestSignInit(mctx, NULL, md, NULL, key->pkey);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_DigestSignInit() failed");
+ goto fail;
+ }
+
+ ret = EVP_DigestSignUpdate(mctx, data.start, data.length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_DigestSignUpdate() failed");
+ goto fail;
+ }
+
+ outlen = 0;
+
+ switch (alg->type) {
+ case NJS_ALGORITHM_HMAC:
+ olen = EVP_MD_size(md);
+
+ if (!verify) {
+ dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ goto fail;
+ }
+
+ } else {
+ dst = (u_char *) &m[0];
+ }
+
+ ret = EVP_DigestSignFinal(mctx, dst, &outlen);
+ if (njs_slow_path(ret <= 0 || olen != outlen)) {
+ njs_webcrypto_error(vm, "EVP_DigestSignFinal() failed");
+ goto fail;
+ }
+
+ if (verify) {
+ ret = (sig.length == outlen && memcmp(sig.start, dst, outlen) == 0);
+ }
+
+ break;
+
+ case NJS_ALGORITHM_RSASSA_PKCS1_v1_5:
+ case NJS_ALGORITHM_RSA_PSS:
+ case NJS_ALGORITHM_ECDSA:
+ default:
+ ret = EVP_DigestFinal_ex(mctx, m, &m_len);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_DigestFinal_ex() failed");
+ goto fail;
+ }
+
+ olen = EVP_PKEY_size(key->pkey);
+ dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ goto fail;
+ }
+
+ pctx = EVP_PKEY_CTX_new(key->pkey, NULL);
+ if (njs_slow_path(pctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_new() failed");
+ goto fail;
+ }
+
+ if (!verify) {
+ ret = EVP_PKEY_sign_init(pctx);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_sign_init() failed");
+ goto fail;
+ }
+
+ } else {
+ ret = EVP_PKEY_verify_init(pctx);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_verify_init() failed");
+ goto fail;
+ }
+ }
+
+ ret = njs_set_rsa_padding(vm, options, key->pkey, pctx, alg->type);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ ret = EVP_PKEY_CTX_set_signature_md(pctx, md);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_signature_md() failed");
+ goto fail;
+ }
+
+ if (!verify) {
+ outlen = olen;
+ ret = EVP_PKEY_sign(pctx, dst, &outlen, m, m_len);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_sign() failed");
+ goto fail;
+ }
+
+ } else {
+ ret = EVP_PKEY_verify(pctx, sig.start, sig.length, m, m_len);
+ if (njs_slow_path(ret < 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_verify() failed");
+ goto fail;
+ }
+ }
+
+ EVP_PKEY_CTX_free(pctx);
+
+ break;
+ }
+
+ if (!verify) {
+ ret = njs_vm_value_array_buffer_set(vm, &value, dst, outlen);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ } else {
+ njs_set_boolean(&value, ret != 0);
+ }
+
+ njs_evp_md_ctx_free(mctx);
+
+ return njs_webcrypto_result(vm, &value, NJS_OK);
+
+fail:
+
+ if (mctx != NULL) {
+ njs_evp_md_ctx_free(mctx);
+ }
+
+ if (pctx != NULL) {
+ EVP_PKEY_CTX_free(pctx);
+ }
+
+ return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR);
+}
+
+
+static njs_int_t
+njs_ext_unwrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t unused)
+{
+ njs_internal_error(vm, "\"unwrapKey\" not implemented");
+ return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_ext_wrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t unused)
+{
+ njs_internal_error(vm, "\"wrapKey\" not implemented");
+ return NJS_ERROR;
+}
+
+
+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)
+{
+ njs_int_t ret;
+ njs_str_t fill;
+
+ ret = njs_vm_value_to_bytes(vm, &fill, njs_arg(args, nargs, 1));
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ if (njs_slow_path(fill.length > 65536)) {
+ njs_type_error(vm, "requested length exceeds 65536 bytes");
+ return NJS_ERROR;
+ }
+
+ if (RAND_bytes(fill.start, fill.length) != 1) {
+ njs_webcrypto_error(vm, "RAND_bytes() failed");
+ return NJS_ERROR;
+ }
+
+ return NJS_OK;
+}
+
+
+static void
+njs_webcrypto_cleanup_pkey(void *data)
+{
+ njs_webcrypto_key_t *key = data;
+
+ if (key->pkey != NULL) {
+ EVP_PKEY_free(key->pkey);
+ }
+}
+
+
+static njs_webcrypto_key_format_t
+njs_key_format(njs_vm_t *vm, njs_value_t *value, njs_str_t *format)
+{
+ njs_int_t ret;
+ njs_uint_t fmt;
+
+ static const struct {
+ njs_str_t name;
+ njs_uint_t value;
+ } formats[] = {
+ { njs_str("raw"), NJS_KEY_FORMAT_RAW },
+ { njs_str("pkcs8"), NJS_KEY_FORMAT_PKCS8 },
+ { njs_str("spki"), NJS_KEY_FORMAT_SPKI },
+ { njs_str("jwk"), NJS_KEY_FORMAT_JWK },
+ };
+
+ ret = njs_value_to_string(vm, value, value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ njs_string_get(value, format);
+
+ fmt = 0;
+
+ while (fmt < sizeof(formats) / sizeof(formats[0])) {
+ if (njs_strstr_eq(format, &formats[fmt].name)) {
+ return formats[fmt].value;
+ }
+
+ fmt++;
+ }
+
+ return NJS_KEY_FORMAT_UNKNOWN;
+}
+
+
+static njs_int_t
+njs_key_usage_array_handler(njs_vm_t *vm, njs_iterator_args_t *args,
+ njs_value_t *value, int64_t index)
+{
+ unsigned *mask;
+ njs_str_t u;
+ njs_int_t ret;
+ njs_value_t usage;
+ njs_webcrypto_entry_t *e;
+
+ njs_value_assign(&usage, value);
+
+ ret = njs_value_to_string(vm, &usage, &usage);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ njs_string_get(&usage, &u);
+
+ for (e = &njs_webcrypto_usage[0]; e->name.length != 0; e++) {
+ if (njs_strstr_eq(&u, &e->name)) {
+ mask = args->data;
+ *mask |= e->value;
+ return NJS_OK;
+ }
+ }
+
+ njs_type_error(vm, "unknown key usage: \"%V\"", &u);
+
+ return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_key_usage(njs_vm_t *vm, njs_value_t *value, unsigned *mask)
+{
+ int64_t length;
+ njs_int_t ret;
+ njs_iterator_args_t args;
+
+ ret = njs_object_length(vm, value, &length);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ *mask = 0;
+
+ args.value = value;
+ args.from = 0;
+ args.to = length;
+ args.data = mask;
+
+ return njs_object_iterate(vm, &args, njs_key_usage_array_handler);
+}
+
+
+static njs_webcrypto_algorithm_t *
+njs_key_algorithm(njs_vm_t *vm, njs_value_t *options)
+{
+ njs_int_t ret;
+ njs_str_t a;
+ njs_value_t name;
+ njs_webcrypto_entry_t *e;
+ njs_webcrypto_algorithm_t *alg;
+
+ static const njs_value_t string_name = njs_string("name");
+
+ if (njs_is_object(options)) {
+ ret = njs_value_property(vm, options, njs_value_arg(&string_name),
+ &name);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "algorithm name is not provided");
+ }
+
+ return NULL;
+ }
+
+ } else {
+ njs_value_assign(&name, options);
+ }
+
+ ret = njs_value_to_string(vm, &name, &name);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NULL;
+ }
+
+ njs_string_get(&name, &a);
+
+ for (e = &njs_webcrypto_alg[0]; e->name.length != 0; e++) {
+ if (njs_strstr_case_eq(&a, &e->name)) {
+ alg = (njs_webcrypto_algorithm_t *) e->value;
+ if (alg->usage & NJS_KEY_USAGE_UNSUPPORTED) {
+ njs_type_error(vm, "unsupported algorithm: \"%V\"", &a);
+ return NULL;
+ }
+
+ return alg;
+ }
+ }
+
+ njs_type_error(vm, "unknown algorithm name: \"%V\"", &a);
+
+ return NULL;
+}
+
+
+static njs_str_t *
+njs_algorithm_string(njs_webcrypto_algorithm_t *algorithm)
+{
+ njs_webcrypto_entry_t *e;
+ njs_webcrypto_algorithm_t *alg;
+
+ for (e = &njs_webcrypto_alg[0]; e->name.length != 0; e++) {
+ alg = (njs_webcrypto_algorithm_t *) e->value;
+ if (alg->type == algorithm->type) {
+ break;
+ }
+ }
+
+ return &e->name;
+}
+
+
+static njs_int_t
+njs_algorithm_hash(njs_vm_t *vm, njs_value_t *options,
+ njs_webcrypto_hash_t *hash)
+{
+ njs_int_t ret;
+ njs_str_t name;
+ njs_value_t value;
+ njs_webcrypto_entry_t *e;
+
+ static const njs_value_t string_hash = njs_string("hash");
+
+ if (njs_is_object(options)) {
+ ret = njs_value_property(vm, options, njs_value_arg(&string_hash),
+ &value);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+
+ } else {
+ njs_value_assign(&value, options);
+ }
+
+ ret = njs_value_to_string(vm, &value, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ njs_string_get(&value, &name);
+
+ for (e = &njs_webcrypto_hash[0]; e->name.length != 0; e++) {
+ if (njs_strstr_eq(&name, &e->name)) {
+ *hash = e->value;
+ return NJS_OK;
+ }
+ }
+
+ njs_type_error(vm, "unknown hash name: \"%V\"", &name);
+
+ return NJS_ERROR;
+}
+
+
+static const EVP_MD *
+njs_algorithm_hash_digest(njs_webcrypto_hash_t hash)
+{
+ switch (hash) {
+ case NJS_HASH_SHA256:
+ return EVP_sha256();
+
+ case NJS_HASH_SHA384:
+ return EVP_sha384();
+
+ case NJS_HASH_SHA512:
+ return EVP_sha512();
+
+ case NJS_HASH_SHA1:
+ default:
+ break;
+ }
+
+ return EVP_sha1();
+}
+
+
+static njs_int_t
+njs_algorithm_curve(njs_vm_t *vm, njs_value_t *options,
+ njs_webcrypto_curve_t *curve)
+{
+ njs_int_t ret;
+ njs_str_t name;
+ njs_value_t value;
+ njs_webcrypto_entry_t *e;
+
+ static const njs_value_t string_curve = njs_string("namedCurve");
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_curve),
+ &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ ret = njs_value_to_string(vm, &value, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ 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;
+ return NJS_OK;
+ }
+ }
+
+ njs_type_error(vm, "unknown namedCurve: \"%V\"", &name);
+
+ return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_promise_trampoline(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused)
+{
+ njs_function_t *callback;
+
+ callback = njs_value_function(njs_argument(args, 1));
+
+ if (callback != NULL) {
+ return njs_vm_call(vm, callback, njs_argument(args, 2), 1);
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
+njs_webcrypto_result(njs_vm_t *vm, njs_value_t *result, njs_int_t rc)
+{
+ njs_int_t ret;
+ njs_value_t retval, arguments[2];
+ njs_function_t *callback;
+ njs_vm_event_t vm_event;
+
+ ret = njs_vm_promise_create(vm, &retval, njs_value_arg(&arguments));
+ if (ret != NJS_OK) {
+ goto error;
+ }
+
+ callback = njs_vm_function_alloc(vm, njs_promise_trampoline);
+ if (callback == NULL) {
+ goto error;
+ }
+
+ vm_event = njs_vm_add_event(vm, callback, 1, NULL, NULL);
+ if (vm_event == NULL) {
+ goto error;
+ }
+
+ njs_value_assign(&arguments[0], &arguments[(rc != NJS_OK)]);
+ njs_value_assign(&arguments[1], result);
+
+ ret = njs_vm_post_event(vm, vm_event, njs_value_arg(&arguments), 2);
+ if (ret == NJS_ERROR) {
+ goto error;
+ }
+
+ njs_vm_retval_set(vm, njs_value_arg(&retval));
+
+ return NJS_OK;
+
+error:
+
+ njs_vm_error(vm, "internal error");
+
+ return NJS_ERROR;
+}
+
+
+static u_char *
+njs_cpystrn(u_char *dst, u_char *src, size_t n)
+{
+ if (n == 0) {
+ return dst;
+ }
+
+ while (--n) {
+ *dst = *src;
+
+ if (*dst == '\0') {
+ return dst;
+ }
+
+ dst++;
+ src++;
+ }
+
+ *dst = '\0';
+
+ return dst;
+}
+
+
+static void
+njs_webcrypto_error(njs_vm_t *vm, const char *fmt, ...)
+{
+ int flags;
+ u_char *p, *last;
+ va_list args;
+ const char *data;
+ unsigned long n;
+ u_char errstr[NJS_MAX_ERROR_STR];
+
+ last = &errstr[NJS_MAX_ERROR_STR];
+
+ va_start(args, fmt);
+ p = njs_vsprintf(errstr, last - 1, fmt, args);
+ va_end(args);
+
+ if (ERR_peek_error()) {
+ p = njs_cpystrn(p, (u_char *) " (SSL:", last - p);
+
+ for ( ;; ) {
+
+ n = ERR_peek_error_line_data(NULL, NULL, &data, &flags);
+
+ if (n == 0) {
+ break;
+ }
+
+ /* ERR_error_string_n() requires at least one byte */
+
+ if (p >= last - 1) {
+ goto next;
+ }
+
+ *p++ = ' ';
+
+ ERR_error_string_n(n, (char *) p, last - p);
+
+ while (p < last && *p) {
+ p++;
+ }
+
+ if (p < last && *data && (flags & ERR_TXT_STRING)) {
+ *p++ = ':';
+ p = njs_cpystrn(p, (u_char *) data, last - p);
+ }
+
+ next:
+
+ (void) ERR_get_error();
+ }
+
+ if (p < last) {
+ *p++ = ')';
+ }
+ }
+
+ njs_vm_value_error_set(vm, njs_vm_retval(vm), "%*s", p - errstr, errstr);
+}
+
+
+njs_int_t
+njs_external_webcrypto_init(njs_vm_t *vm)
+{
+ njs_int_t ret, proto_id;
+ njs_str_t name;
+ njs_opaque_value_t value;
+
+ OpenSSL_add_all_algorithms();
+
+ njs_webcrypto_crypto_key_proto_id =
+ njs_vm_external_prototype(vm, njs_ext_webcrypto_crypto_key,
+ njs_nitems(njs_ext_webcrypto_crypto_key));
+ if (njs_slow_path(njs_webcrypto_crypto_key_proto_id < 0)) {
+ return NJS_ERROR;
+ }
+
+ proto_id = njs_vm_external_prototype(vm, njs_ext_webcrypto,
+ njs_nitems(njs_ext_webcrypto));
+ if (njs_slow_path(proto_id < 0)) {
+ return NJS_ERROR;
+ }
+
+ ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ name.length = njs_length("crypto");
+ name.start = (u_char *) "crypto";
+
+ ret = njs_vm_bind(vm, &name, njs_value_arg(&value), 1);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ return NJS_OK;
+}