blob: 00f9e63fc47af457058ae04291fb908df93d4096 [file] [log] [blame]
/*
* 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;
}