/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the envied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include "global.h" #include "embed.h" #include "duktape.h" #include "base64.h" #include "zbxcrypto.h" #include "zbxjson.h" /****************************************************************************** * * * Purpose: export duktape data at the index into buffer * * * * Return value: allocated buffer with exported data or NULL on error * * * * Comments: Throws an error: * * - if the top value at ctx value stack is non buffer object * * - if the value stack is empty * * The returned buffer must be freed by the caller. * * * ******************************************************************************/ char *es_get_buffer_dyn(duk_context *ctx, int index, duk_size_t *len) { duk_int_t type; const char *ptr; char *buf = NULL; *len = 0; type = duk_get_type(ctx, index); switch (type) { case DUK_TYPE_BUFFER: case DUK_TYPE_OBJECT: ptr = duk_require_buffer_data(ctx, index, len); buf = zbx_malloc(NULL, *len); memcpy(buf, ptr, *len); break; case DUK_TYPE_UNDEFINED: case DUK_TYPE_NONE: case DUK_TYPE_NULL: break; default: if (SUCCEED == es_duktape_string_decode(duk_to_string(ctx, index), &buf)) *len = strlen(buf); break; } return buf; } /****************************************************************************** * * * Purpose: encodes parameter to base64 string * * * * Parameters: ctx - [IN] pointer to duk_context * * * * Comments: Throws an error: * * - if the top value at ctx value stack is not a string * * - if the value stack is empty * * * ******************************************************************************/ static duk_ret_t es_btoa(duk_context *ctx) { char *str, *b64str = NULL; duk_size_t len; if (NULL == (str = es_get_buffer_dyn(ctx, 0, &len))) return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot obtain parameter"); str_base64_encode_dyn(str, &b64str, (int)len); duk_push_string(ctx, b64str); zbx_free(str); zbx_free(b64str); return 1; } /****************************************************************************** * * * Purpose: decodes base64 string * * * * Parameters: ctx - [IN] pointer to duk_context * * * * Comments: Throws an error: * * - if the top value at ctx value stack is not a string * * - if the value stack is empty * * * ******************************************************************************/ static duk_ret_t es_atob(duk_context *ctx) { char *buffer = NULL, *str = NULL; int out_size, buffer_size; if (SUCCEED != es_duktape_string_decode(duk_require_string(ctx, 0), &str)) return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot convert value to utf8"); buffer_size = (int)strlen(str) * 3 / 4 + 1; buffer = zbx_malloc(buffer, (size_t)buffer_size); str_base64_decode(str, buffer, buffer_size, &out_size); duk_push_lstring(ctx, buffer, (duk_size_t)out_size); zbx_free(str); zbx_free(buffer); return 1; } /****************************************************************************** * * * Purpose: convert binary data to hex string * * * * Parameters: bin - [IN] the data to convert * * len - [IN] the number of bytes to convert * * out - [OUT] the output buffer (must be 2x + 1 of input len) * * * ******************************************************************************/ static void es_bin_to_hex(const unsigned char *bin, size_t len, char *out) { const char *hex = "0123456789abcdef"; size_t i; for (i = 0; i < len; i++) { *out++ = hex[bin[i] >> 4]; *out++ = hex[bin[i] & 15]; } *out = '\0'; } /****************************************************************************** * * * Purpose: compute a md5 checksum * * * * Parameters: ctx - [IN] pointer to duk_context * * * * Comments: Throws an error: * * - if the top value at ctx value stack is not a string * * - if the value stack is empty * * * ******************************************************************************/ static duk_ret_t es_md5(duk_context *ctx) { char *str; md5_state_t state; md5_byte_t hash[ZBX_MD5_DIGEST_SIZE]; char *md5sum; duk_size_t len; if (NULL == (str = es_get_buffer_dyn(ctx, 0, &len))) return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot obtain parameter"); md5sum = (char *)zbx_malloc(NULL, ZBX_MD5_DIGEST_SIZE * 2 + 1); zbx_md5_init(&state); zbx_md5_append(&state, (const md5_byte_t *)str, (int)len); zbx_md5_finish(&state, hash); es_bin_to_hex(hash, ZBX_MD5_DIGEST_SIZE, md5sum); duk_push_string(ctx, md5sum); zbx_free(md5sum); zbx_free(str); return 1; } /****************************************************************************** * * * Purpose: compute a sha256 checksum * * * * Parameters: ctx - [IN] pointer to duk_context * * * * Comments: Throws an error: * * - if the top value at ctx value stack is not a string * * - if the value stack is empty * * * ******************************************************************************/ static duk_ret_t es_sha256(duk_context *ctx) { char *str; char hash_res[ZBX_SHA256_DIGEST_SIZE], hash_res_stringhexes[ZBX_SHA256_DIGEST_SIZE * 2 + 1]; duk_size_t len; if (NULL == (str = es_get_buffer_dyn(ctx, 0, &len))) return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot obtain parameter"); zbx_sha256_hash_len(str, len, hash_res); es_bin_to_hex((const unsigned char *)hash_res, ZBX_SHA256_DIGEST_SIZE, hash_res_stringhexes); duk_push_string(ctx, hash_res_stringhexes); zbx_free(str); return 1; } /****************************************************************************** * * * Purpose: compute hmac using specified hash type * * * * Parameters: ctx - [IN] pointer to duk_context * * * * Comments: Throws an error: * * - if the top value at ctx value stack is not a string * * - if the value stack is empty * * * ******************************************************************************/ static duk_ret_t es_hmac(duk_context *ctx) { char *out = NULL, *key, *text; const char *type; duk_size_t key_len, text_len; zbx_crypto_hash_t hash_type; int ret; type = duk_require_string(ctx, 0); if (0 == strcmp(type, "md5")) hash_type = ZBX_HASH_MD5; else if (0 == strcmp(type, "sha256")) hash_type = ZBX_HASH_SHA256; else return duk_error(ctx, DUK_RET_TYPE_ERROR, "unsupported hash function"); if (NULL == (key = es_get_buffer_dyn(ctx, 1, &key_len))) return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot obtain second parameter"); if (NULL == (text = es_get_buffer_dyn(ctx, 2, &text_len))) { zbx_free(key); return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot obtain third parameter"); } ret = zbx_hmac(hash_type, key, key_len, text, text_len, &out); zbx_free(text); zbx_free(key); if (SUCCEED != ret) return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot calculate HMAC"); duk_push_string(ctx, out); zbx_free(out); return 1; } #if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) static void unescape_newlines(const char *in, size_t *len, char *out) { const char *end = in + *len; for (; in < end; in++, out++) { if ('\\' == *in) { if ('n' == *(++in)) { *out = '\n'; *len = *len - 1; } else *out = *(--in); } else *out = *in; } *out = '\0'; } #endif /****************************************************************************** * * * Purpose: sign data using RSA with SHA-256 * * * * Parameters: ctx - [IN] pointer to duk_context * * * * Comments: Throws an error: * * - if the top value at ctx value stack is not a string * * - if the value stack is empty * * * ******************************************************************************/ static duk_ret_t es_rsa_sign(duk_context *ctx) { #if !defined(HAVE_OPENSSL) && !defined(HAVE_GNUTLS) ZBX_UNUSED(ctx); return duk_error(ctx, DUK_RET_TYPE_ERROR, "encryption support was not compiled in"); #else char *key_unesc = NULL, *data = NULL, *error = NULL; unsigned char *raw_sig = NULL; const char *key_ptr; duk_size_t key_len, data_len; duk_int_t arg_type; size_t raw_sig_len, key_unesc_alloc = 0; int err_index = -1; if (0 != strcmp(duk_require_string(ctx, 0), "sha256")) return duk_error(ctx, DUK_RET_TYPE_ERROR, "unsupported hash function, only 'sha256' is supported"); if (DUK_TYPE_UNDEFINED == (arg_type = duk_get_type(ctx, 1))) { err_index = duk_push_error_object(ctx, DUK_RET_TYPE_ERROR, "parameter 'key' is missing or is undefined"); goto out; } else { if (DUK_TYPE_BUFFER == arg_type || DUK_TYPE_OBJECT == arg_type) { key_ptr = duk_buffer_to_string(ctx, 1); key_len = strlen(key_ptr); } else key_ptr = duk_require_lstring(ctx, 1, &key_len); if ('\0' == key_ptr[0]) { err_index = duk_push_error_object(ctx, DUK_RET_EVAL_ERROR, "private key cannot be empty"); goto out; } } data = es_get_buffer_dyn(ctx, 2, &data_len); if (0 == data_len) { err_index = duk_push_error_object(ctx, DUK_RET_EVAL_ERROR, "data cannot be empty"); goto out; } if (NULL != zbx_json_decodevalue_dyn(key_ptr, &key_unesc, &key_unesc_alloc, NULL)) { key_len = strlen(key_unesc); } else if (NULL != strstr(key_ptr, "\\n")) { key_unesc = zbx_calloc(NULL, key_len + 1, sizeof(char)); unescape_newlines(key_ptr, &key_len, key_unesc); } else { key_unesc = zbx_strdup(NULL, key_ptr); zbx_normalize_pem(&key_unesc, &key_len); } if (SUCCEED == zbx_rs256_sign(key_unesc, key_len, data, data_len, &raw_sig, &raw_sig_len, &error)) { size_t hex_sig_len; char *out = NULL; hex_sig_len = raw_sig_len * 2 + 1; out = (char *)zbx_malloc(NULL, hex_sig_len); zbx_bin2hex(raw_sig, raw_sig_len, out, hex_sig_len); zbx_free(raw_sig); duk_push_string(ctx, out); zbx_free(out); } else err_index = duk_push_error_object(ctx, DUK_RET_EVAL_ERROR, error); out: zbx_free(error); zbx_free(data); zbx_free(key_unesc); if (-1 != err_index) return duk_throw(ctx); return 1; #endif } /****************************************************************************** * * * Purpose: initializes additional global functions * * * * Parameters: es - [IN] the embedded scripting engine * * * ******************************************************************************/ void es_init_global_functions(zbx_es_t *es) { duk_push_c_function(es->env->ctx, es_atob, 1); duk_put_global_string(es->env->ctx, "atob"); duk_push_c_function(es->env->ctx, es_btoa, 1); duk_put_global_string(es->env->ctx, "btoa"); duk_push_c_function(es->env->ctx, es_md5, 1); duk_put_global_string(es->env->ctx, "md5"); duk_push_c_function(es->env->ctx, es_sha256, 1); duk_put_global_string(es->env->ctx, "sha256"); duk_push_c_function(es->env->ctx, es_hmac, 3); duk_put_global_string(es->env->ctx, "hmac"); duk_push_c_function(es->env->ctx, es_rsa_sign, 3); duk_put_global_string(es->env->ctx, "sign"); }