|
@@ -24,6 +24,10 @@
|
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/hmac.h>
|
|
|
#include <openssl/md5.h>
|
|
#include <openssl/md5.h>
|
|
|
#include <openssl/rand.h>
|
|
#include <openssl/rand.h>
|
|
|
|
|
+#include <openssl/pem.h>
|
|
|
|
|
+#include <openssl/rsa.h>
|
|
|
|
|
+#include <openssl/bio.h>
|
|
|
|
|
+#include <openssl/err.h>
|
|
|
|
|
|
|
|
namespace smartbotic::runner::engine {
|
|
namespace smartbotic::runner::engine {
|
|
|
|
|
|
|
@@ -171,6 +175,596 @@ static std::string generateRandomBytes(int length) {
|
|
|
return ss.str();
|
|
return ss.str();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Convert hex string to bytes
|
|
|
|
|
+static std::vector<unsigned char> hexToBytes(const std::string& hex) {
|
|
|
|
|
+ std::vector<unsigned char> bytes;
|
|
|
|
|
+ bytes.reserve(hex.length() / 2);
|
|
|
|
|
+ for (size_t i = 0; i + 1 < hex.length(); i += 2) {
|
|
|
|
|
+ unsigned char byte = static_cast<unsigned char>(std::stoul(hex.substr(i, 2), nullptr, 16));
|
|
|
|
|
+ bytes.push_back(byte);
|
|
|
|
|
+ }
|
|
|
|
|
+ return bytes;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Convert bytes to hex string
|
|
|
|
|
+static std::string bytesToHex(const std::vector<unsigned char>& bytes) {
|
|
|
|
|
+ std::ostringstream ss;
|
|
|
|
|
+ for (const auto& byte : bytes) {
|
|
|
|
|
+ ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(byte);
|
|
|
|
|
+ }
|
|
|
|
|
+ return ss.str();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// AES encryption result structure
|
|
|
|
|
+struct AesEncryptResult {
|
|
|
|
|
+ std::string ciphertext; // base64 encoded
|
|
|
|
|
+ std::string iv; // base64 encoded
|
|
|
|
|
+ std::string authTag; // base64 encoded (only for GCM)
|
|
|
|
|
+ std::string error;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// AES decryption result structure
|
|
|
|
|
+struct AesDecryptResult {
|
|
|
|
|
+ std::string plaintext;
|
|
|
|
|
+ std::string error;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// Derive key from passphrase using PBKDF2
|
|
|
|
|
+static std::vector<unsigned char> deriveKeyFromPassphrase(const std::string& passphrase, const std::vector<unsigned char>& salt, int iterations = 100000) {
|
|
|
|
|
+ std::vector<unsigned char> key(32); // 256 bits for AES-256
|
|
|
|
|
+
|
|
|
|
|
+ if (PKCS5_PBKDF2_HMAC(
|
|
|
|
|
+ passphrase.c_str(),
|
|
|
|
|
+ static_cast<int>(passphrase.length()),
|
|
|
|
|
+ salt.data(),
|
|
|
|
|
+ static_cast<int>(salt.size()),
|
|
|
|
|
+ iterations,
|
|
|
|
|
+ EVP_sha256(),
|
|
|
|
|
+ static_cast<int>(key.size()),
|
|
|
|
|
+ key.data()) != 1) {
|
|
|
|
|
+ return {};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return key;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// AES-256-CBC encryption
|
|
|
|
|
+static AesEncryptResult aes256CbcEncrypt(const std::string& plaintext, const std::string& keyBase64, const std::string& ivBase64, bool keyIsPassphrase) {
|
|
|
|
|
+ AesEncryptResult result;
|
|
|
|
|
+
|
|
|
|
|
+ // Decode or generate IV
|
|
|
|
|
+ std::vector<unsigned char> iv;
|
|
|
|
|
+ if (ivBase64.empty()) {
|
|
|
|
|
+ iv.resize(16); // 128-bit IV for AES-CBC
|
|
|
|
|
+ if (RAND_bytes(iv.data(), static_cast<int>(iv.size())) != 1) {
|
|
|
|
|
+ result.error = "Failed to generate IV";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ std::string decoded = base64Decode(ivBase64);
|
|
|
|
|
+ if (decoded.empty() || decoded.size() != 16) {
|
|
|
|
|
+ result.error = "IV must be 16 bytes (128 bits) when base64 decoded";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ iv.assign(decoded.begin(), decoded.end());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get key
|
|
|
|
|
+ std::vector<unsigned char> key;
|
|
|
|
|
+ if (keyIsPassphrase) {
|
|
|
|
|
+ key = deriveKeyFromPassphrase(keyBase64, iv);
|
|
|
|
|
+ if (key.empty()) {
|
|
|
|
|
+ result.error = "Failed to derive key from passphrase";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ std::string decoded = base64Decode(keyBase64);
|
|
|
|
|
+ if (decoded.empty() || decoded.size() != 32) {
|
|
|
|
|
+ result.error = "Key must be 32 bytes (256 bits) when base64 decoded";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ key.assign(decoded.begin(), decoded.end());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create cipher context
|
|
|
|
|
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
|
|
|
|
+ if (!ctx) {
|
|
|
|
|
+ result.error = "Failed to create cipher context";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize encryption
|
|
|
|
|
+ if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to initialize encryption";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Encrypt
|
|
|
|
|
+ std::vector<unsigned char> ciphertext(plaintext.size() + EVP_MAX_BLOCK_LENGTH);
|
|
|
|
|
+ int out_len = 0;
|
|
|
|
|
+ int total_len = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (EVP_EncryptUpdate(ctx, ciphertext.data(), &out_len,
|
|
|
|
|
+ reinterpret_cast<const unsigned char*>(plaintext.data()),
|
|
|
|
|
+ static_cast<int>(plaintext.size())) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Encryption failed";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len = out_len;
|
|
|
|
|
+
|
|
|
|
|
+ if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + total_len, &out_len) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Encryption finalization failed";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len += out_len;
|
|
|
|
|
+ ciphertext.resize(total_len);
|
|
|
|
|
+
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ // Encode results
|
|
|
|
|
+ result.ciphertext = base64Encode(std::string(ciphertext.begin(), ciphertext.end()));
|
|
|
|
|
+ result.iv = base64Encode(std::string(iv.begin(), iv.end()));
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// AES-256-CBC decryption
|
|
|
|
|
+static AesDecryptResult aes256CbcDecrypt(const std::string& ciphertextBase64, const std::string& keyBase64, const std::string& ivBase64, bool keyIsPassphrase) {
|
|
|
|
|
+ AesDecryptResult result;
|
|
|
|
|
+
|
|
|
|
|
+ // Decode ciphertext
|
|
|
|
|
+ std::string ciphertextStr = base64Decode(ciphertextBase64);
|
|
|
|
|
+ if (ciphertextStr.empty() && !ciphertextBase64.empty()) {
|
|
|
|
|
+ result.error = "Invalid base64 encoded ciphertext";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::vector<unsigned char> ciphertext(ciphertextStr.begin(), ciphertextStr.end());
|
|
|
|
|
+
|
|
|
|
|
+ // Decode IV
|
|
|
|
|
+ std::string ivStr = base64Decode(ivBase64);
|
|
|
|
|
+ if (ivStr.empty() || ivStr.size() != 16) {
|
|
|
|
|
+ result.error = "IV must be 16 bytes (128 bits) when base64 decoded";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::vector<unsigned char> iv(ivStr.begin(), ivStr.end());
|
|
|
|
|
+
|
|
|
|
|
+ // Get key
|
|
|
|
|
+ std::vector<unsigned char> key;
|
|
|
|
|
+ if (keyIsPassphrase) {
|
|
|
|
|
+ key = deriveKeyFromPassphrase(keyBase64, iv);
|
|
|
|
|
+ if (key.empty()) {
|
|
|
|
|
+ result.error = "Failed to derive key from passphrase";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ std::string decoded = base64Decode(keyBase64);
|
|
|
|
|
+ if (decoded.empty() || decoded.size() != 32) {
|
|
|
|
|
+ result.error = "Key must be 32 bytes (256 bits) when base64 decoded";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ key.assign(decoded.begin(), decoded.end());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create cipher context
|
|
|
|
|
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
|
|
|
|
+ if (!ctx) {
|
|
|
|
|
+ result.error = "Failed to create cipher context";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize decryption
|
|
|
|
|
+ if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to initialize decryption";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Decrypt
|
|
|
|
|
+ std::vector<unsigned char> plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH);
|
|
|
|
|
+ int out_len = 0;
|
|
|
|
|
+ int total_len = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (EVP_DecryptUpdate(ctx, plaintext.data(), &out_len, ciphertext.data(), static_cast<int>(ciphertext.size())) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Decryption failed - data may be corrupted";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len = out_len;
|
|
|
|
|
+
|
|
|
|
|
+ if (EVP_DecryptFinal_ex(ctx, plaintext.data() + total_len, &out_len) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Decryption failed - invalid key or corrupted data";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len += out_len;
|
|
|
|
|
+ plaintext.resize(total_len);
|
|
|
|
|
+
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ result.plaintext = std::string(plaintext.begin(), plaintext.end());
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// AES-256-GCM encryption
|
|
|
|
|
+static AesEncryptResult aes256GcmEncrypt(const std::string& plaintext, const std::string& keyBase64, const std::string& ivBase64, bool keyIsPassphrase) {
|
|
|
|
|
+ AesEncryptResult result;
|
|
|
|
|
+
|
|
|
|
|
+ // Decode or generate IV (12 bytes for GCM)
|
|
|
|
|
+ std::vector<unsigned char> iv;
|
|
|
|
|
+ if (ivBase64.empty()) {
|
|
|
|
|
+ iv.resize(12); // 96-bit IV for AES-GCM (recommended)
|
|
|
|
|
+ if (RAND_bytes(iv.data(), static_cast<int>(iv.size())) != 1) {
|
|
|
|
|
+ result.error = "Failed to generate IV";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ std::string decoded = base64Decode(ivBase64);
|
|
|
|
|
+ if (decoded.empty() || decoded.size() != 12) {
|
|
|
|
|
+ result.error = "IV must be 12 bytes (96 bits) when base64 decoded for GCM mode";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ iv.assign(decoded.begin(), decoded.end());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get key
|
|
|
|
|
+ std::vector<unsigned char> key;
|
|
|
|
|
+ if (keyIsPassphrase) {
|
|
|
|
|
+ key = deriveKeyFromPassphrase(keyBase64, iv);
|
|
|
|
|
+ if (key.empty()) {
|
|
|
|
|
+ result.error = "Failed to derive key from passphrase";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ std::string decoded = base64Decode(keyBase64);
|
|
|
|
|
+ if (decoded.empty() || decoded.size() != 32) {
|
|
|
|
|
+ result.error = "Key must be 32 bytes (256 bits) when base64 decoded";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ key.assign(decoded.begin(), decoded.end());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create cipher context
|
|
|
|
|
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
|
|
|
|
+ if (!ctx) {
|
|
|
|
|
+ result.error = "Failed to create cipher context";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize encryption
|
|
|
|
|
+ if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to initialize encryption";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set IV length
|
|
|
|
|
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to set IV length";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set key and IV
|
|
|
|
|
+ if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to set key and IV";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Encrypt
|
|
|
|
|
+ std::vector<unsigned char> ciphertext(plaintext.size() + EVP_MAX_BLOCK_LENGTH);
|
|
|
|
|
+ int out_len = 0;
|
|
|
|
|
+ int total_len = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (EVP_EncryptUpdate(ctx, ciphertext.data(), &out_len,
|
|
|
|
|
+ reinterpret_cast<const unsigned char*>(plaintext.data()),
|
|
|
|
|
+ static_cast<int>(plaintext.size())) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Encryption failed";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len = out_len;
|
|
|
|
|
+
|
|
|
|
|
+ if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + total_len, &out_len) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Encryption finalization failed";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len += out_len;
|
|
|
|
|
+ ciphertext.resize(total_len);
|
|
|
|
|
+
|
|
|
|
|
+ // Get authentication tag
|
|
|
|
|
+ std::vector<unsigned char> authTag(16);
|
|
|
|
|
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, authTag.data()) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to get authentication tag";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ // Encode results
|
|
|
|
|
+ result.ciphertext = base64Encode(std::string(ciphertext.begin(), ciphertext.end()));
|
|
|
|
|
+ result.iv = base64Encode(std::string(iv.begin(), iv.end()));
|
|
|
|
|
+ result.authTag = base64Encode(std::string(authTag.begin(), authTag.end()));
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// AES-256-GCM decryption
|
|
|
|
|
+static AesDecryptResult aes256GcmDecrypt(const std::string& ciphertextBase64, const std::string& keyBase64, const std::string& ivBase64, const std::string& authTagBase64, bool keyIsPassphrase) {
|
|
|
|
|
+ AesDecryptResult result;
|
|
|
|
|
+
|
|
|
|
|
+ // Decode ciphertext
|
|
|
|
|
+ std::string ciphertextStr = base64Decode(ciphertextBase64);
|
|
|
|
|
+ if (ciphertextStr.empty() && !ciphertextBase64.empty()) {
|
|
|
|
|
+ result.error = "Invalid base64 encoded ciphertext";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::vector<unsigned char> ciphertext(ciphertextStr.begin(), ciphertextStr.end());
|
|
|
|
|
+
|
|
|
|
|
+ // Decode IV
|
|
|
|
|
+ std::string ivStr = base64Decode(ivBase64);
|
|
|
|
|
+ if (ivStr.empty() || ivStr.size() != 12) {
|
|
|
|
|
+ result.error = "IV must be 12 bytes (96 bits) when base64 decoded for GCM mode";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::vector<unsigned char> iv(ivStr.begin(), ivStr.end());
|
|
|
|
|
+
|
|
|
|
|
+ // Decode auth tag
|
|
|
|
|
+ std::string authTagStr = base64Decode(authTagBase64);
|
|
|
|
|
+ if (authTagStr.empty() || authTagStr.size() != 16) {
|
|
|
|
|
+ result.error = "Authentication tag must be 16 bytes (128 bits) when base64 decoded";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::vector<unsigned char> authTag(authTagStr.begin(), authTagStr.end());
|
|
|
|
|
+
|
|
|
|
|
+ // Get key
|
|
|
|
|
+ std::vector<unsigned char> key;
|
|
|
|
|
+ if (keyIsPassphrase) {
|
|
|
|
|
+ key = deriveKeyFromPassphrase(keyBase64, iv);
|
|
|
|
|
+ if (key.empty()) {
|
|
|
|
|
+ result.error = "Failed to derive key from passphrase";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ std::string decoded = base64Decode(keyBase64);
|
|
|
|
|
+ if (decoded.empty() || decoded.size() != 32) {
|
|
|
|
|
+ result.error = "Key must be 32 bytes (256 bits) when base64 decoded";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ key.assign(decoded.begin(), decoded.end());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create cipher context
|
|
|
|
|
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
|
|
|
|
+ if (!ctx) {
|
|
|
|
|
+ result.error = "Failed to create cipher context";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize decryption
|
|
|
|
|
+ if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to initialize decryption";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set IV length
|
|
|
|
|
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to set IV length";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set key and IV
|
|
|
|
|
+ if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to set key and IV";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Decrypt
|
|
|
|
|
+ std::vector<unsigned char> plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH);
|
|
|
|
|
+ int out_len = 0;
|
|
|
|
|
+ int total_len = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (EVP_DecryptUpdate(ctx, plaintext.data(), &out_len, ciphertext.data(), static_cast<int>(ciphertext.size())) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Decryption failed - data may be corrupted";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len = out_len;
|
|
|
|
|
+
|
|
|
|
|
+ // Set expected authentication tag
|
|
|
|
|
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, authTag.data()) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Failed to set authentication tag";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Finalize and verify tag
|
|
|
|
|
+ if (EVP_DecryptFinal_ex(ctx, plaintext.data() + total_len, &out_len) != 1) {
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+ result.error = "Authentication failed - data may be corrupted or tampered with";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ total_len += out_len;
|
|
|
|
|
+ plaintext.resize(total_len);
|
|
|
|
|
+
|
|
|
|
|
+ EVP_CIPHER_CTX_free(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ result.plaintext = std::string(plaintext.begin(), plaintext.end());
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// RSA encryption result structure
|
|
|
|
|
+struct RsaEncryptResult {
|
|
|
|
|
+ std::string ciphertext; // base64 encoded
|
|
|
|
|
+ std::string error;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// RSA decryption result structure
|
|
|
|
|
+struct RsaDecryptResult {
|
|
|
|
|
+ std::string plaintext;
|
|
|
|
|
+ std::string error;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// RSA encryption with public key (PEM format)
|
|
|
|
|
+static RsaEncryptResult rsaEncrypt(const std::string& plaintext, const std::string& publicKeyPem) {
|
|
|
|
|
+ RsaEncryptResult result;
|
|
|
|
|
+
|
|
|
|
|
+ // Create BIO from PEM string
|
|
|
|
|
+ BIO* bio = BIO_new_mem_buf(publicKeyPem.data(), static_cast<int>(publicKeyPem.size()));
|
|
|
|
|
+ if (!bio) {
|
|
|
|
|
+ result.error = "Failed to create BIO for public key";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Read public key
|
|
|
|
|
+ EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr);
|
|
|
|
|
+ BIO_free(bio);
|
|
|
|
|
+
|
|
|
|
|
+ if (!pkey) {
|
|
|
|
|
+ result.error = "Failed to parse public key - ensure it is in valid PEM format";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create encryption context
|
|
|
|
|
+ EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr);
|
|
|
|
|
+ if (!ctx) {
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to create encryption context";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize encryption
|
|
|
|
|
+ if (EVP_PKEY_encrypt_init(ctx) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to initialize RSA encryption";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set OAEP padding (more secure than PKCS1)
|
|
|
|
|
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to set RSA padding";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Determine output size
|
|
|
|
|
+ size_t outlen = 0;
|
|
|
|
|
+ if (EVP_PKEY_encrypt(ctx, nullptr, &outlen,
|
|
|
|
|
+ reinterpret_cast<const unsigned char*>(plaintext.data()),
|
|
|
|
|
+ plaintext.size()) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to determine encrypted data size";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Encrypt
|
|
|
|
|
+ std::vector<unsigned char> ciphertext(outlen);
|
|
|
|
|
+ if (EVP_PKEY_encrypt(ctx, ciphertext.data(), &outlen,
|
|
|
|
|
+ reinterpret_cast<const unsigned char*>(plaintext.data()),
|
|
|
|
|
+ plaintext.size()) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "RSA encryption failed - data may be too large for key size";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ ciphertext.resize(outlen);
|
|
|
|
|
+
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+
|
|
|
|
|
+ result.ciphertext = base64Encode(std::string(ciphertext.begin(), ciphertext.end()));
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// RSA decryption with private key (PEM format)
|
|
|
|
|
+static RsaDecryptResult rsaDecrypt(const std::string& ciphertextBase64, const std::string& privateKeyPem) {
|
|
|
|
|
+ RsaDecryptResult result;
|
|
|
|
|
+
|
|
|
|
|
+ // Decode ciphertext
|
|
|
|
|
+ std::string ciphertextStr = base64Decode(ciphertextBase64);
|
|
|
|
|
+ if (ciphertextStr.empty() && !ciphertextBase64.empty()) {
|
|
|
|
|
+ result.error = "Invalid base64 encoded ciphertext";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::vector<unsigned char> ciphertext(ciphertextStr.begin(), ciphertextStr.end());
|
|
|
|
|
+
|
|
|
|
|
+ // Create BIO from PEM string
|
|
|
|
|
+ BIO* bio = BIO_new_mem_buf(privateKeyPem.data(), static_cast<int>(privateKeyPem.size()));
|
|
|
|
|
+ if (!bio) {
|
|
|
|
|
+ result.error = "Failed to create BIO for private key";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Read private key
|
|
|
|
|
+ EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr);
|
|
|
|
|
+ BIO_free(bio);
|
|
|
|
|
+
|
|
|
|
|
+ if (!pkey) {
|
|
|
|
|
+ result.error = "Failed to parse private key - ensure it is in valid PEM format";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create decryption context
|
|
|
|
|
+ EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr);
|
|
|
|
|
+ if (!ctx) {
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to create decryption context";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize decryption
|
|
|
|
|
+ if (EVP_PKEY_decrypt_init(ctx) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to initialize RSA decryption";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set OAEP padding
|
|
|
|
|
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to set RSA padding";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Determine output size
|
|
|
|
|
+ size_t outlen = 0;
|
|
|
|
|
+ if (EVP_PKEY_decrypt(ctx, nullptr, &outlen, ciphertext.data(), ciphertext.size()) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "Failed to determine decrypted data size";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Decrypt
|
|
|
|
|
+ std::vector<unsigned char> plaintext(outlen);
|
|
|
|
|
+ if (EVP_PKEY_decrypt(ctx, plaintext.data(), &outlen, ciphertext.data(), ciphertext.size()) <= 0) {
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+ result.error = "RSA decryption failed - invalid private key or corrupted data";
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ plaintext.resize(outlen);
|
|
|
|
|
+
|
|
|
|
|
+ EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
+ EVP_PKEY_free(pkey);
|
|
|
|
|
+
|
|
|
|
|
+ result.plaintext = std::string(plaintext.begin(), plaintext.end());
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Helper structure for curl response
|
|
// Helper structure for curl response
|
|
|
struct CurlResponse {
|
|
struct CurlResponse {
|
|
|
std::string body;
|
|
std::string body;
|
|
@@ -1172,6 +1766,307 @@ void ScriptEngine::setupBuiltinAPIs() {
|
|
|
return JS_NewString(ctx, uuid.c_str());
|
|
return JS_NewString(ctx, uuid.c_str());
|
|
|
}, "randomUUID", 0));
|
|
}, "randomUUID", 0));
|
|
|
|
|
|
|
|
|
|
+ // crypto.aesEncrypt(options) - AES-256-CBC/GCM encryption
|
|
|
|
|
+ // options: { data, key, iv (optional), algorithm ('cbc' or 'gcm'), keyIsPassphrase (bool) }
|
|
|
|
|
+ // Returns: { ciphertext, iv, authTag (GCM only), error }
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "aesEncrypt", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ if (argc < 1 || !JS_IsObject(argv[0])) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "crypto.aesEncrypt requires an options object");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ JSValue options = argv[0];
|
|
|
|
|
+
|
|
|
|
|
+ // Get data
|
|
|
|
|
+ JSValue data_val = JS_GetPropertyStr(ctx, options, "data");
|
|
|
|
|
+ if (JS_IsUndefined(data_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "data is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* data_str = JS_ToCString(ctx, data_val);
|
|
|
|
|
+ JS_FreeValue(ctx, data_val);
|
|
|
|
|
+ if (!data_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "data must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string data(data_str);
|
|
|
|
|
+ JS_FreeCString(ctx, data_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Get key
|
|
|
|
|
+ JSValue key_val = JS_GetPropertyStr(ctx, options, "key");
|
|
|
|
|
+ if (JS_IsUndefined(key_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "key is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* key_str = JS_ToCString(ctx, key_val);
|
|
|
|
|
+ JS_FreeValue(ctx, key_val);
|
|
|
|
|
+ if (!key_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "key must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string key(key_str);
|
|
|
|
|
+ JS_FreeCString(ctx, key_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Get IV (optional)
|
|
|
|
|
+ std::string iv;
|
|
|
|
|
+ JSValue iv_val = JS_GetPropertyStr(ctx, options, "iv");
|
|
|
|
|
+ if (!JS_IsUndefined(iv_val) && !JS_IsNull(iv_val)) {
|
|
|
|
|
+ const char* iv_str = JS_ToCString(ctx, iv_val);
|
|
|
|
|
+ if (iv_str) {
|
|
|
|
|
+ iv = iv_str;
|
|
|
|
|
+ JS_FreeCString(ctx, iv_str);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ JS_FreeValue(ctx, iv_val);
|
|
|
|
|
+
|
|
|
|
|
+ // Get algorithm (default: gcm)
|
|
|
|
|
+ std::string algorithm = "gcm";
|
|
|
|
|
+ JSValue algo_val = JS_GetPropertyStr(ctx, options, "algorithm");
|
|
|
|
|
+ if (!JS_IsUndefined(algo_val) && !JS_IsNull(algo_val)) {
|
|
|
|
|
+ const char* algo_str = JS_ToCString(ctx, algo_val);
|
|
|
|
|
+ if (algo_str) {
|
|
|
|
|
+ algorithm = algo_str;
|
|
|
|
|
+ JS_FreeCString(ctx, algo_str);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ JS_FreeValue(ctx, algo_val);
|
|
|
|
|
+
|
|
|
|
|
+ // Get keyIsPassphrase (default: false)
|
|
|
|
|
+ bool keyIsPassphrase = false;
|
|
|
|
|
+ JSValue passphrase_val = JS_GetPropertyStr(ctx, options, "keyIsPassphrase");
|
|
|
|
|
+ if (!JS_IsUndefined(passphrase_val)) {
|
|
|
|
|
+ keyIsPassphrase = JS_ToBool(ctx, passphrase_val) == 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ JS_FreeValue(ctx, passphrase_val);
|
|
|
|
|
+
|
|
|
|
|
+ // Create result object
|
|
|
|
|
+ JSValue result = JS_NewObject(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ if (algorithm == "cbc") {
|
|
|
|
|
+ AesEncryptResult encResult = aes256CbcEncrypt(data, key, iv, keyIsPassphrase);
|
|
|
|
|
+ if (!encResult.error.empty()) {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, encResult.error.c_str()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "ciphertext", JS_NewString(ctx, encResult.ciphertext.c_str()));
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "iv", JS_NewString(ctx, encResult.iv.c_str()));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (algorithm == "gcm") {
|
|
|
|
|
+ AesEncryptResult encResult = aes256GcmEncrypt(data, key, iv, keyIsPassphrase);
|
|
|
|
|
+ if (!encResult.error.empty()) {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, encResult.error.c_str()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "ciphertext", JS_NewString(ctx, encResult.ciphertext.c_str()));
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "iv", JS_NewString(ctx, encResult.iv.c_str()));
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "authTag", JS_NewString(ctx, encResult.authTag.c_str()));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, "Unsupported algorithm. Use 'cbc' or 'gcm'"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }, "aesEncrypt", 1));
|
|
|
|
|
+
|
|
|
|
|
+ // crypto.aesDecrypt(options) - AES-256-CBC/GCM decryption
|
|
|
|
|
+ // options: { ciphertext, key, iv, authTag (GCM only), algorithm ('cbc' or 'gcm'), keyIsPassphrase (bool) }
|
|
|
|
|
+ // Returns: { plaintext, error }
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "aesDecrypt", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ if (argc < 1 || !JS_IsObject(argv[0])) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "crypto.aesDecrypt requires an options object");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ JSValue options = argv[0];
|
|
|
|
|
+
|
|
|
|
|
+ // Get ciphertext
|
|
|
|
|
+ JSValue ct_val = JS_GetPropertyStr(ctx, options, "ciphertext");
|
|
|
|
|
+ if (JS_IsUndefined(ct_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "ciphertext is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* ct_str = JS_ToCString(ctx, ct_val);
|
|
|
|
|
+ JS_FreeValue(ctx, ct_val);
|
|
|
|
|
+ if (!ct_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "ciphertext must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string ciphertext(ct_str);
|
|
|
|
|
+ JS_FreeCString(ctx, ct_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Get key
|
|
|
|
|
+ JSValue key_val = JS_GetPropertyStr(ctx, options, "key");
|
|
|
|
|
+ if (JS_IsUndefined(key_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "key is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* key_str = JS_ToCString(ctx, key_val);
|
|
|
|
|
+ JS_FreeValue(ctx, key_val);
|
|
|
|
|
+ if (!key_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "key must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string key(key_str);
|
|
|
|
|
+ JS_FreeCString(ctx, key_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Get IV
|
|
|
|
|
+ JSValue iv_val = JS_GetPropertyStr(ctx, options, "iv");
|
|
|
|
|
+ if (JS_IsUndefined(iv_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "iv is required for decryption");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* iv_str = JS_ToCString(ctx, iv_val);
|
|
|
|
|
+ JS_FreeValue(ctx, iv_val);
|
|
|
|
|
+ if (!iv_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "iv must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string iv(iv_str);
|
|
|
|
|
+ JS_FreeCString(ctx, iv_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Get algorithm (default: gcm)
|
|
|
|
|
+ std::string algorithm = "gcm";
|
|
|
|
|
+ JSValue algo_val = JS_GetPropertyStr(ctx, options, "algorithm");
|
|
|
|
|
+ if (!JS_IsUndefined(algo_val) && !JS_IsNull(algo_val)) {
|
|
|
|
|
+ const char* algo_str = JS_ToCString(ctx, algo_val);
|
|
|
|
|
+ if (algo_str) {
|
|
|
|
|
+ algorithm = algo_str;
|
|
|
|
|
+ JS_FreeCString(ctx, algo_str);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ JS_FreeValue(ctx, algo_val);
|
|
|
|
|
+
|
|
|
|
|
+ // Get keyIsPassphrase (default: false)
|
|
|
|
|
+ bool keyIsPassphrase = false;
|
|
|
|
|
+ JSValue passphrase_val = JS_GetPropertyStr(ctx, options, "keyIsPassphrase");
|
|
|
|
|
+ if (!JS_IsUndefined(passphrase_val)) {
|
|
|
|
|
+ keyIsPassphrase = JS_ToBool(ctx, passphrase_val) == 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ JS_FreeValue(ctx, passphrase_val);
|
|
|
|
|
+
|
|
|
|
|
+ // Create result object
|
|
|
|
|
+ JSValue result = JS_NewObject(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ if (algorithm == "cbc") {
|
|
|
|
|
+ AesDecryptResult decResult = aes256CbcDecrypt(ciphertext, key, iv, keyIsPassphrase);
|
|
|
|
|
+ if (!decResult.error.empty()) {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, decResult.error.c_str()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "plaintext", JS_NewString(ctx, decResult.plaintext.c_str()));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (algorithm == "gcm") {
|
|
|
|
|
+ // Get auth tag (required for GCM)
|
|
|
|
|
+ JSValue tag_val = JS_GetPropertyStr(ctx, options, "authTag");
|
|
|
|
|
+ if (JS_IsUndefined(tag_val)) {
|
|
|
|
|
+ JS_FreeValue(ctx, result);
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "authTag is required for GCM decryption");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* tag_str = JS_ToCString(ctx, tag_val);
|
|
|
|
|
+ JS_FreeValue(ctx, tag_val);
|
|
|
|
|
+ if (!tag_str) {
|
|
|
|
|
+ JS_FreeValue(ctx, result);
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "authTag must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string authTag(tag_str);
|
|
|
|
|
+ JS_FreeCString(ctx, tag_str);
|
|
|
|
|
+
|
|
|
|
|
+ AesDecryptResult decResult = aes256GcmDecrypt(ciphertext, key, iv, authTag, keyIsPassphrase);
|
|
|
|
|
+ if (!decResult.error.empty()) {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, decResult.error.c_str()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "plaintext", JS_NewString(ctx, decResult.plaintext.c_str()));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, "Unsupported algorithm. Use 'cbc' or 'gcm'"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }, "aesDecrypt", 1));
|
|
|
|
|
+
|
|
|
|
|
+ // crypto.rsaEncrypt(options) - RSA encryption with public key
|
|
|
|
|
+ // options: { data, publicKey (PEM format) }
|
|
|
|
|
+ // Returns: { ciphertext, error }
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "rsaEncrypt", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ if (argc < 1 || !JS_IsObject(argv[0])) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "crypto.rsaEncrypt requires an options object");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ JSValue options = argv[0];
|
|
|
|
|
+
|
|
|
|
|
+ // Get data
|
|
|
|
|
+ JSValue data_val = JS_GetPropertyStr(ctx, options, "data");
|
|
|
|
|
+ if (JS_IsUndefined(data_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "data is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* data_str = JS_ToCString(ctx, data_val);
|
|
|
|
|
+ JS_FreeValue(ctx, data_val);
|
|
|
|
|
+ if (!data_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "data must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string data(data_str);
|
|
|
|
|
+ JS_FreeCString(ctx, data_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Get public key
|
|
|
|
|
+ JSValue key_val = JS_GetPropertyStr(ctx, options, "publicKey");
|
|
|
|
|
+ if (JS_IsUndefined(key_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "publicKey is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* key_str = JS_ToCString(ctx, key_val);
|
|
|
|
|
+ JS_FreeValue(ctx, key_val);
|
|
|
|
|
+ if (!key_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "publicKey must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string publicKey(key_str);
|
|
|
|
|
+ JS_FreeCString(ctx, key_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Create result object
|
|
|
|
|
+ JSValue result = JS_NewObject(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ RsaEncryptResult encResult = rsaEncrypt(data, publicKey);
|
|
|
|
|
+ if (!encResult.error.empty()) {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, encResult.error.c_str()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "ciphertext", JS_NewString(ctx, encResult.ciphertext.c_str()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }, "rsaEncrypt", 1));
|
|
|
|
|
+
|
|
|
|
|
+ // crypto.rsaDecrypt(options) - RSA decryption with private key
|
|
|
|
|
+ // options: { ciphertext, privateKey (PEM format) }
|
|
|
|
|
+ // Returns: { plaintext, error }
|
|
|
|
|
+ JS_SetPropertyStr(ctx, crypto, "rsaDecrypt", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
|
|
|
|
|
+ if (argc < 1 || !JS_IsObject(argv[0])) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "crypto.rsaDecrypt requires an options object");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ JSValue options = argv[0];
|
|
|
|
|
+
|
|
|
|
|
+ // Get ciphertext
|
|
|
|
|
+ JSValue ct_val = JS_GetPropertyStr(ctx, options, "ciphertext");
|
|
|
|
|
+ if (JS_IsUndefined(ct_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "ciphertext is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* ct_str = JS_ToCString(ctx, ct_val);
|
|
|
|
|
+ JS_FreeValue(ctx, ct_val);
|
|
|
|
|
+ if (!ct_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "ciphertext must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string ciphertext(ct_str);
|
|
|
|
|
+ JS_FreeCString(ctx, ct_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Get private key
|
|
|
|
|
+ JSValue key_val = JS_GetPropertyStr(ctx, options, "privateKey");
|
|
|
|
|
+ if (JS_IsUndefined(key_val)) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "privateKey is required");
|
|
|
|
|
+ }
|
|
|
|
|
+ const char* key_str = JS_ToCString(ctx, key_val);
|
|
|
|
|
+ JS_FreeValue(ctx, key_val);
|
|
|
|
|
+ if (!key_str) {
|
|
|
|
|
+ return JS_ThrowTypeError(ctx, "privateKey must be a string");
|
|
|
|
|
+ }
|
|
|
|
|
+ std::string privateKey(key_str);
|
|
|
|
|
+ JS_FreeCString(ctx, key_str);
|
|
|
|
|
+
|
|
|
|
|
+ // Create result object
|
|
|
|
|
+ JSValue result = JS_NewObject(ctx);
|
|
|
|
|
+
|
|
|
|
|
+ RsaDecryptResult decResult = rsaDecrypt(ciphertext, privateKey);
|
|
|
|
|
+ if (!decResult.error.empty()) {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, decResult.error.c_str()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ JS_SetPropertyStr(ctx, result, "plaintext", JS_NewString(ctx, decResult.plaintext.c_str()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }, "rsaDecrypt", 1));
|
|
|
|
|
+
|
|
|
JS_SetPropertyStr(ctx, smartbotic, "crypto", crypto);
|
|
JS_SetPropertyStr(ctx, smartbotic, "crypto", crypto);
|
|
|
|
|
|
|
|
// smartbotic.http
|
|
// smartbotic.http
|