From 5d858dd1ccc9e9e276d6bf1c048ee87bbff0d7e9 Mon Sep 17 00:00:00 2001 From: Donat Zenichev Date: Thu, 20 Mar 2025 11:58:05 +0100 Subject: [PATCH] MT#56025 DSM Utils: new function for voucher code enc/decryption New functions introduced for DSM API (mod_utils): - encryptCodeAes128CBC() - decryptCodeAes128CBC() Will be used by the voucher DSM application to encrypt/decrypt the code given by a subscriber when filling up the balance. OpenSSL AES128 CBC is used as an algorithm. (with default AES key size 16 bytes) Required things to let it be working: - base64 encoding/decoding (of binary raw data) - padding based on default AES key size (16) Change-Id: I4b47f49ebf4b61157c3a4631cd8302565c660bc3 --- apps/dsm/DSMCoreModule.h | 3 +- apps/dsm/mods/mod_utils/ModUtils.cpp | 222 +++++++++++++++++++++++++++ apps/dsm/mods/mod_utils/ModUtils.h | 5 + 3 files changed, 229 insertions(+), 1 deletion(-) diff --git a/apps/dsm/DSMCoreModule.h b/apps/dsm/DSMCoreModule.h index 78e88e87..2a21b074 100644 --- a/apps/dsm/DSMCoreModule.h +++ b/apps/dsm/DSMCoreModule.h @@ -30,8 +30,9 @@ #include "DSMStateEngine.h" #include -using std::string; #include + +using std::string; using std::map; class AmSession; diff --git a/apps/dsm/mods/mod_utils/ModUtils.cpp b/apps/dsm/mods/mod_utils/ModUtils.cpp index 38a49ebc..cca73586 100644 --- a/apps/dsm/mods/mod_utils/ModUtils.cpp +++ b/apps/dsm/mods/mod_utils/ModUtils.cpp @@ -35,6 +35,11 @@ #include "AmSession.h" #include "AmPlaylist.h" +/* voucher */ +#include +#include +#include + SC_EXPORT(MOD_CLS_NAME); MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) { @@ -58,6 +63,9 @@ MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) { DEF_CMD("utils.unescapeCRLF", SCUUnescapeCRLFAction); DEF_CMD("utils.playRingTone", SCUPlayRingToneAction); + DEF_CMD("utils.encryptCodeAes128CBC", SCEncryptCodeAes128CBC); + DEF_CMD("utils.decryptCodeAes128CBC", SCDecryptCodeAes128CBC); + } MOD_ACTIONEXPORT_END; MOD_CONDITIONEXPORT_BEGIN(MOD_CLS_NAME) { @@ -67,6 +75,10 @@ MOD_CONDITIONEXPORT_BEGIN(MOD_CLS_NAME) { } MOD_CONDITIONEXPORT_END; +/** + * Helper functions + */ + vector utils_get_count_files(DSMSession* sc_sess, unsigned int cnt, const string& basedir, const string& suffix, bool right) { @@ -158,6 +170,94 @@ bool utils_play_count(DSMSession* sc_sess, unsigned int cnt, return false; } +static int encryptDataAES128CBC(unsigned char *plaintext, int plaintextLen, unsigned char *key, + unsigned char *iv, unsigned char *ciphertext) +{ + EVP_CIPHER_CTX *ctx; + int len; + int ciphertextLen; + + /* Create and initialise the context */ + if(!(ctx = EVP_CIPHER_CTX_new())) + return -1; + + /* Initialise the encryption operation. IMPORTANT - ensure you use a key + * and IV size appropriate for your cipher + * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * IV size for *most* modes is the same as the block size. For AES this + * is 128 bits + */ + if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) + return -1; + + /* Provide the message to be encrypted, and obtain the encrypted output. + * EVP_EncryptUpdate can be called multiple times if necessary + */ + if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintextLen)) + return -1; + ciphertextLen = len; + + /* Finalise the encryption. Further ciphertext bytes may be written at + * this stage. + */ + if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) + return -1; + ciphertextLen += len; + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + return ciphertextLen; +} + +static int decryptDataAES128CBC(unsigned char *ciphertext, int ciphertextLen, unsigned char *key, + unsigned char *iv, unsigned char *plaintext) +{ + EVP_CIPHER_CTX *ctx; + int len; + int ciplaintextLen; + + /* Create and initialise the context */ + if(!(ctx = EVP_CIPHER_CTX_new())) + return -1; + + /* Initialise the decryption operation. IMPORTANT - ensure you use a key + * and IV size appropriate for your cipher + * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * IV size for *most* modes is the same as the block size. For AES this + * is 128 bits + */ + if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) + return -1; + + /* Provide the message to be decrypted, and obtain the plaintext output. + * EVP_DecryptUpdate can be called multiple times if necessary. + */ + if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertextLen)) + return -1; + ciplaintextLen = len; + + /* Finalise the decryption. Further plaintext bytes may be written at + * this stage. + */ + if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) + return -1; + + ciplaintextLen += len; + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + return ciplaintextLen; +} + +static void removeFromString(string& value, const string & pattern) { + size_t pos = std::string::npos; + + while ((pos = value.find(pattern)) != std::string::npos) + value.erase(pos, pattern.length()); +} + /** * base64 */ @@ -645,3 +745,125 @@ MATCH_CONDITION_START(IsInListCondition) { return res; } } MATCH_CONDITION_END; + +CONST_ACTION_2P(SCEncryptCodeAes128CBC, ',', true); +EXEC_ACTION_START(SCEncryptCodeAes128CBC) { + string source_name = (par1.length() && par1[0] == '$') ? par1.substr(1) : par1; + string source_value = resolveVars(par1, sess, sc_sess, event_params); + + /* voucher_iv and voucher_key */ + vector params = explode(par2, ","); + + if (params.size() < 2) { + throw DSMException("encrypt", "cause", "Lack of arguments!\n"); + } + + if (params[0].empty() || params[1].empty()) { + throw DSMException("encrypt", "cause", "Empty arguments: iv or key!\n"); + } + + if (source_value.empty()) { + throw DSMException("encrypt", "cause", "Empty arguments: source value!\n"); + } + + /* multi-arguments list is passed as one string, make sure to remove trash */ + for (auto it = params.begin(); it != params.end(); ++it) + { + removeFromString(*it, " "); + removeFromString(*it, "\""); + } + + /* vector and key, cast right away for encryption needs */ + string rawIv = resolveVars(params[0], sess, sc_sess, event_params); + string rawKey = resolveVars(params[1], sess, sc_sess, event_params); + unsigned char * voucherIv = (unsigned char *)rawIv.c_str(); + unsigned char * voucherKey = (unsigned char *)rawKey.c_str(); + + /* prepare plain code for encryption */ + unsigned char * plainCode = (unsigned char *)source_value.c_str(); + + /* padding with default AES key length */ + int paddedLen = source_value.length() + AES_KEY_SIZE - (source_value.length() % AES_KEY_SIZE); + + /* a buffer for an encrypted data */ + unsigned char encryptedCode[paddedLen]; + + /* base64() related things */ + int flen = 0; + char * encryptedCodeBase64; + int encryptedLen = encryptDataAES128CBC(plainCode, strlen((char *)plainCode), + voucherKey, voucherIv, encryptedCode); + + if (encryptedLen == -1) { + throw DSMException("encrypt", "cause", "Encrypt failure!\n"); + /* format into base64 */ + } else { + encryptedCodeBase64 = base64(encryptedCode, paddedLen, &flen ); + DBG("Successfully encrypted the value: <%s>\n", encryptedCodeBase64); + sc_sess->var[source_name] = encryptedCodeBase64; + } + + /* base64() uses malloc, don't forget to free() */ + free(encryptedCodeBase64); +} EXEC_ACTION_END; + +CONST_ACTION_2P(SCDecryptCodeAes128CBC, ',', true); +EXEC_ACTION_START(SCDecryptCodeAes128CBC) { + /* encrypted code must be given in base64 */ + string source_name = (par1.length() && par1[0] == '$') ? par1.substr(1) : par1; + string source_value = resolveVars(par1, sess, sc_sess, event_params); + + /* voucher_iv and voucher_key */ + vector params = explode(par2, ","); + + if (params.size() < 2) { + WARN("Lack of arguments!\n"); + throw DSMException("encrypt", "cause", "Lack of arguments!\n"); + } + + if (params[0].empty() || params[1].empty()) { + WARN("Empty arguments: iv or key!\n"); + throw DSMException("encrypt", "cause", "Empty arguments: iv or key!\n"); + } + + if (source_value.empty()) { + WARN("Empty arguments: source value!\n"); + throw DSMException("encrypt", "cause", "Empty arguments: source value!\n"); + } + + /* multi-arguments list is passed as one string, make sure to remove trash */ + for (auto it = params.begin(); it != params.end(); ++it) + { + removeFromString(*it, " "); + removeFromString(*it, "\""); + } + + /* vector and key, cast right away for encryption needs */ + string rawIv = resolveVars(params[0], sess, sc_sess, event_params); + string rawKey = resolveVars(params[1], sess, sc_sess, event_params); + + unsigned char * voucherIv = (unsigned char *)rawIv.c_str(); + unsigned char * voucherKey = (unsigned char *)rawKey.c_str(); + + /* base64() related things */ + int flen = 0; + + /* buffer for decryption, must be big enough */ + unsigned char decryptedCode[DEC_BUF_LEN]; + + /* convert the encrypted code (in base64) into the raw binary data */ + unsigned char * encryptedCodeRaw = unbase64(source_value.c_str(), source_value.length(), &flen); + + int decrypteLen = decryptDataAES128CBC(encryptedCodeRaw, strlen((char *)encryptedCodeRaw), + voucherKey, voucherIv, decryptedCode); + if (decrypteLen == -1) { + throw DSMException("decrypt", "cause", "Decrypt failure!\n"); + } else { + DBG("Successfully decrypted the value: <%s>\n", decryptedCode); + sc_sess->var[source_name] = (char *)decryptedCode; + } + + /* unbase64() uses malloc, don't forget to free() */ + free(encryptedCodeRaw); + +} EXEC_ACTION_END; \ No newline at end of file diff --git a/apps/dsm/mods/mod_utils/ModUtils.h b/apps/dsm/mods/mod_utils/ModUtils.h index 2b864d90..e5a2c55d 100644 --- a/apps/dsm/mods/mod_utils/ModUtils.h +++ b/apps/dsm/mods/mod_utils/ModUtils.h @@ -35,6 +35,9 @@ #define MOD_CLS_NAME SCUtilsModule +#define DEC_BUF_LEN 512 +#define AES_KEY_SIZE 16 + DECLARE_MODULE(MOD_CLS_NAME); DEF_CONDITION_2P(IsInListCondition); @@ -60,6 +63,8 @@ DEF_ACTION_1P(SCUUnescapeCRLFAction); DEF_ACTION_2P(SCUPlayRingToneAction); +DEF_ACTION_2P(SCEncryptCodeAes128CBC); +DEF_ACTION_2P(SCDecryptCodeAes128CBC); class DSMRingTone : public AmRingTone,