|
@@ -21,6 +21,9 @@
|
|
|
#include <openssl/sha.h>
|
|
#include <openssl/sha.h>
|
|
|
#include <openssl/evp.h>
|
|
#include <openssl/evp.h>
|
|
|
#include <openssl/buffer.h>
|
|
#include <openssl/buffer.h>
|
|
|
|
|
+#include <openssl/hmac.h>
|
|
|
|
|
+#include <openssl/md5.h>
|
|
|
|
|
+#include <openssl/rand.h>
|
|
|
|
|
|
|
|
namespace smartbotic::runner::engine {
|
|
namespace smartbotic::runner::engine {
|
|
|
|
|
|
|
@@ -85,6 +88,89 @@ static std::string sha256Hash(const std::string& input) {
|
|
|
return ss.str();
|
|
return ss.str();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Generic hash function using EVP interface
|
|
|
|
|
+static std::string hashData(const std::string& input, const std::string& algorithm) {
|
|
|
|
|
+ const EVP_MD* md = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ if (algorithm == "md5") {
|
|
|
|
|
+ md = EVP_md5();
|
|
|
|
|
+ } else if (algorithm == "sha1") {
|
|
|
|
|
+ md = EVP_sha1();
|
|
|
|
|
+ } else if (algorithm == "sha256") {
|
|
|
|
|
+ md = EVP_sha256();
|
|
|
|
|
+ } else if (algorithm == "sha384") {
|
|
|
|
|
+ md = EVP_sha384();
|
|
|
|
|
+ } else if (algorithm == "sha512") {
|
|
|
|
|
+ md = EVP_sha512();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ unsigned char hash[EVP_MAX_MD_SIZE];
|
|
|
|
|
+ unsigned int hash_len = 0;
|
|
|
|
|
+
|
|
|
|
|
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
|
|
|
|
+ EVP_DigestInit_ex(ctx, md, nullptr);
|
|
|
|
|
+ EVP_DigestUpdate(ctx, input.data(), input.size());
|
|
|
|
|
+ EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
|
|
|
|
+ EVP_MD_CTX_free(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ std::ostringstream ss;
|
|
|
|
|
+ for (unsigned int i = 0; i < hash_len; ++i) {
|
|
|
|
|
+ ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(hash[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ss.str();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// HMAC function using EVP interface
|
|
|
|
|
+static std::string hmacData(const std::string& input, const std::string& key, const std::string& algorithm) {
|
|
|
|
|
+ const EVP_MD* md = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ if (algorithm == "md5") {
|
|
|
|
|
+ md = EVP_md5();
|
|
|
|
|
+ } else if (algorithm == "sha1") {
|
|
|
|
|
+ md = EVP_sha1();
|
|
|
|
|
+ } else if (algorithm == "sha256") {
|
|
|
|
|
+ md = EVP_sha256();
|
|
|
|
|
+ } else if (algorithm == "sha384") {
|
|
|
|
|
+ md = EVP_sha384();
|
|
|
|
|
+ } else if (algorithm == "sha512") {
|
|
|
|
|
+ md = EVP_sha512();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ unsigned char hmac_result[EVP_MAX_MD_SIZE];
|
|
|
|
|
+ unsigned int hmac_len = 0;
|
|
|
|
|
+
|
|
|
|
|
+ HMAC(md, key.data(), static_cast<int>(key.size()),
|
|
|
|
|
+ reinterpret_cast<const unsigned char*>(input.data()), input.size(),
|
|
|
|
|
+ hmac_result, &hmac_len);
|
|
|
|
|
+
|
|
|
|
|
+ std::ostringstream ss;
|
|
|
|
|
+ for (unsigned int i = 0; i < hmac_len; ++i) {
|
|
|
|
|
+ ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(hmac_result[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ss.str();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Generate random bytes
|
|
|
|
|
+static std::string generateRandomBytes(int length) {
|
|
|
|
|
+ std::vector<unsigned char> buffer(length);
|
|
|
|
|
+ if (RAND_bytes(buffer.data(), length) != 1) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::ostringstream ss;
|
|
|
|
|
+ for (int i = 0; i < length; ++i) {
|
|
|
|
|
+ ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(buffer[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ss.str();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Helper structure for curl response
|
|
// Helper structure for curl response
|
|
|
struct CurlResponse {
|
|
struct CurlResponse {
|
|
|
std::string body;
|
|
std::string body;
|
|
@@ -944,6 +1030,150 @@ void ScriptEngine::setupBuiltinAPIs() {
|
|
|
|
|
|
|
|
JS_SetPropertyStr(ctx, smartbotic, "utils", utils);
|
|
JS_SetPropertyStr(ctx, smartbotic, "utils", utils);
|
|
|
|
|
|
|
|
|
|
+ // smartbotic.crypto - Cryptographic operations
|
|
|
|
|
+ JSValue crypto = JS_NewObject(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ // crypto.hash(data, algorithm, outputFormat) - Hash data using specified algorithm
|
|
|
|
|
+ // algorithm: 'md5', 'sha1', 'sha256', 'sha384', 'sha512'
|
|
|
|
|
+ // outputFormat: 'hex' (default) or 'base64'
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "hash", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ if (argc < 2) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "crypto.hash requires data and algorithm arguments");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const char* data_str = JS_ToCString(ctx, argv[0]);
|
|
|
|
|
+ if (!data_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "data must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string data(data_str);
|
|
|
|
|
+ JS_FreeCString(ctx, data_str);
|
|
|
|
|
+
|
|
|
|
|
+ const char* algo_str = JS_ToCString(ctx, argv[1]);
|
|
|
|
|
+ if (!algo_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "algorithm must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string algorithm(algo_str);
|
|
|
|
|
+ JS_FreeCString(ctx, algo_str);
|
|
|
|
|
+
|
|
|
|
|
+ std::string outputFormat = "hex";
|
|
|
|
|
+ if (argc > 2 && !JS_IsUndefined(argv[2])) {
|
|
|
|
|
+ const char* format_str = JS_ToCString(ctx, argv[2]);
|
|
|
|
|
+ if (format_str) {
|
|
|
|
|
+ outputFormat = format_str;
|
|
|
|
|
+ JS_FreeCString(ctx, format_str);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string hash_result = hashData(data, algorithm);
|
|
|
|
|
+ if (hash_result.empty()) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "Unsupported hash algorithm");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (outputFormat == "base64") {
|
|
|
|
|
+ // Convert hex to binary, then to base64
|
|
|
|
|
+ std::string binary;
|
|
|
|
|
+ binary.reserve(hash_result.length() / 2);
|
|
|
|
|
+ for (size_t i = 0; i < hash_result.length(); i += 2) {
|
|
|
|
|
+ unsigned char byte = static_cast<unsigned char>(std::stoul(hash_result.substr(i, 2), nullptr, 16));
|
|
|
|
|
+ binary.push_back(static_cast<char>(byte));
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string base64_result = base64Encode(binary);
|
|
|
|
|
+ return JS_NewString(ctx, base64_result.c_str());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return JS_NewString(ctx, hash_result.c_str());
|
|
|
|
|
+ }, "hash", 3));
|
|
|
|
|
+
|
|
|
|
|
+ // crypto.hmac(data, key, algorithm, outputFormat) - Generate HMAC
|
|
|
|
|
+ // algorithm: 'md5', 'sha1', 'sha256', 'sha384', 'sha512'
|
|
|
|
|
+ // outputFormat: 'hex' (default) or 'base64'
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "hmac", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ if (argc < 3) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "crypto.hmac requires data, key, and algorithm arguments");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const char* data_str = JS_ToCString(ctx, argv[0]);
|
|
|
|
|
+ if (!data_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "data must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string data(data_str);
|
|
|
|
|
+ JS_FreeCString(ctx, data_str);
|
|
|
|
|
+
|
|
|
|
|
+ const char* key_str = JS_ToCString(ctx, argv[1]);
|
|
|
|
|
+ if (!key_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "key must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string key(key_str);
|
|
|
|
|
+ JS_FreeCString(ctx, key_str);
|
|
|
|
|
+
|
|
|
|
|
+ const char* algo_str = JS_ToCString(ctx, argv[2]);
|
|
|
|
|
+ if (!algo_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "algorithm must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string algorithm(algo_str);
|
|
|
|
|
+ JS_FreeCString(ctx, algo_str);
|
|
|
|
|
+
|
|
|
|
|
+ std::string outputFormat = "hex";
|
|
|
|
|
+ if (argc > 3 && !JS_IsUndefined(argv[3])) {
|
|
|
|
|
+ const char* format_str = JS_ToCString(ctx, argv[3]);
|
|
|
|
|
+ if (format_str) {
|
|
|
|
|
+ outputFormat = format_str;
|
|
|
|
|
+ JS_FreeCString(ctx, format_str);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string hmac_result = hmacData(data, key, algorithm);
|
|
|
|
|
+ if (hmac_result.empty()) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "Unsupported HMAC algorithm");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (outputFormat == "base64") {
|
|
|
|
|
+ // Convert hex to binary, then to base64
|
|
|
|
|
+ std::string binary;
|
|
|
|
|
+ binary.reserve(hmac_result.length() / 2);
|
|
|
|
|
+ for (size_t i = 0; i < hmac_result.length(); i += 2) {
|
|
|
|
|
+ unsigned char byte = static_cast<unsigned char>(std::stoul(hmac_result.substr(i, 2), nullptr, 16));
|
|
|
|
|
+ binary.push_back(static_cast<char>(byte));
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string base64_result = base64Encode(binary);
|
|
|
|
|
+ return JS_NewString(ctx, base64_result.c_str());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return JS_NewString(ctx, hmac_result.c_str());
|
|
|
|
|
+ }, "hmac", 4));
|
|
|
|
|
+
|
|
|
|
|
+ // crypto.randomBytes(length) - Generate cryptographically secure random bytes
|
|
|
|
|
+ // Returns hex string of random bytes
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "randomBytes", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ if (argc < 1) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "crypto.randomBytes requires a length argument");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int32_t length;
|
|
|
|
|
+ if (JS_ToInt32(ctx, &length, argv[0]) != 0) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "length must be a number");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (length <= 0 || length > 1024) {
|
|
|
|
|
+ return JS_ThrowRangeError(ctx, "length must be between 1 and 1024");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string result = generateRandomBytes(length);
|
|
|
|
|
+ if (result.empty()) {
|
|
|
|
|
+ return JS_ThrowInternalError(ctx, "Failed to generate random bytes");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return JS_NewString(ctx, result.c_str());
|
|
|
|
|
+ }, "randomBytes", 1));
|
|
|
|
|
+
|
|
|
|
|
+ // crypto.randomUUID() - Generate a random UUID v4
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "randomUUID", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ std::string uuid = common::UUID::generate();
|
|
|
|
|
+ return JS_NewString(ctx, uuid.c_str());
|
|
|
|
|
+ }, "randomUUID", 0));
|
|
|
|
|
+
|
|
|
|
|
+ JS_SetPropertyStr(ctx, smartbotic, "crypto", crypto);
|
|
|
|
|
+
|
|
|
// smartbotic.http
|
|
// smartbotic.http
|
|
|
JSValue http = JS_NewObject(ctx);
|
|
JSValue http = JS_NewObject(ctx);
|
|
|
JS_SetPropertyStr(ctx, http, "request", JS_NewCFunction(ctx, js_http_request, "request", 1));
|
|
JS_SetPropertyStr(ctx, http, "request", JS_NewCFunction(ctx, js_http_request, "request", 1));
|