| |
| /* |
| * Copyright (C) Dmitry Volyntsev |
| * Copyright (C) NGINX, Inc. |
| */ |
| |
| |
| #include <njs_main.h> |
| #include "njs_webcrypto.h" |
| #include "njs_openssl.h" |
| |
| 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 EVP_PKEY_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_data(&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; |
| } |