forked from rosh/MQL5Book
223 lines
7.5 KiB
MQL5
223 lines
7.5 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| EnvSignature.mq5 |
| |
//| Copyright 2021, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//+------------------------------------------------------------------+
| |
#property service
| |
| |
#include "..\..\Include\PRTF.mqh"
| |
| |
//+------------------------------------------------------------------+
| |
//| Required secret stuff |
| |
//+------------------------------------------------------------------+
| |
// WARNING: change this macro for your own hard to break set of bytes
| |
#define PROGRAM_SPECIFIC_SECRET "<PROGRAM-SPECIFIC-SECRET>"
| |
// WARNING: choose your own string as a glue in the name'='value pairs
| |
#define INSTANCE_SPECIFIC_PEPPER "=" // it's obvious just for the demo
| |
// WARNING: disable the next macro in actual product,
| |
// leave it in your own subscription utility only
| |
#define I_AM_DEVELOPER
| |
| |
//+------------------------------------------------------------------+
| |
//| Public inputs for user |
| |
//+------------------------------------------------------------------+
| |
input string Validation = "";
| |
| |
#ifdef I_AM_DEVELOPER
| |
#define INPUT input
| |
#else
| |
#define INPUT const
| |
#endif
| |
| |
//+------------------------------------------------------------------+
| |
//| Private inputs for developer |
| |
//+------------------------------------------------------------------+
| |
INPUT string Signature = "";
| |
INPUT string Secret = PROGRAM_SPECIFIC_SECRET;
| |
INPUT string Pepper = INSTANCE_SPECIFIC_PEPPER;
| |
| |
//+------------------------------------------------------------------+
| |
//| Collect environment properties and build a hash array for them |
| |
//+------------------------------------------------------------------+
| |
class EnvSignature
| |
{
| |
private:
| |
string data;
| |
| |
protected:
| |
virtual string secret() const = 0;
| |
virtual string pepper() const = 0;
| |
| |
public:
| |
bool append(const ENUM_TERMINAL_INFO_STRING e)
| |
{
| |
return append(EnumToString(e) + pepper() + TerminalInfoString(e));
| |
}
| |
| |
bool append(const ENUM_MQL_INFO_STRING e)
| |
{
| |
return append(EnumToString(e) + pepper() + MQLInfoString(e));
| |
}
| |
| |
bool append(const ENUM_TERMINAL_INFO_INTEGER e)
| |
{
| |
return append(EnumToString(e) + pepper() + StringFormat("%d", TerminalInfoInteger(e)));
| |
}
| |
| |
bool append(const ENUM_MQL_INFO_INTEGER e)
| |
{
| |
return append(EnumToString(e) + pepper() + StringFormat("%d", MQLInfoInteger(e)));
| |
}
| |
| |
bool append(const string s)
| |
{
| |
data += s;
| |
return true;
| |
}
| |
| |
string emit() const
| |
{
| |
uchar pack[];
| |
if(StringToCharArray(data + secret(), pack, 0, StringLen(data) + StringLen(secret()), CP_UTF8) <= 0) return NULL;
| |
| |
uchar key[], result[];
| |
if(CryptEncode(CRYPT_HASH_SHA256, pack, key, result) <= 0) return NULL;
| |
| |
Print("Hash bytes:");
| |
ArrayPrint(result);
| |
| |
uchar text[];
| |
CryptEncode(CRYPT_BASE64, result, key, text);
| |
return CharArrayToString(text);
| |
}
| |
| |
bool check(const string sig, string &validation)
| |
{
| |
uchar bytes[];
| |
const int n = StringToCharArray(sig + secret(), bytes, 0, StringLen(sig) + StringLen(secret()), CP_UTF8);
| |
if(n <= 0) return false;
| |
| |
uchar key[], result1[], result2[];
| |
if(CryptEncode(CRYPT_HASH_SHA256, bytes, key, result1) <= 0) return false;
| |
| |
/*
| |
WARNING
| |
The next code should be available only in an utility running by developer.
| |
The program delivered to a user should be compiled without this branch.
| |
*/
| |
#ifdef I_AM_DEVELOPER
| |
if(StringLen(validation) == 0)
| |
{
| |
if(CryptEncode(CRYPT_BASE64, result1, key, result2) <= 0) return false;
| |
validation = CharArrayToString(result2);
| |
return true;
| |
}
| |
#endif
| |
uchar values[];
| |
// the exact length is needed to not append terminating '0'
| |
if(StringToCharArray(validation, values, 0, StringLen(validation)) <= 0) return false;
| |
if(CryptDecode(CRYPT_BASE64, values, key, result2) <= 0) return false;
| |
| |
return ArrayCompare(result1, result2) == 0;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Provide custom secrets for specific product and/or user |
| |
//+------------------------------------------------------------------+
| |
class MyEnvSignature : public EnvSignature
| |
{
| |
protected:
| |
virtual string secret() const override
| |
{
| |
return Secret;
| |
}
| |
virtual string pepper() const override
| |
{
| |
return Pepper;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Collect all required properties |
| |
//+------------------------------------------------------------------+
| |
void FillEnvironment(EnvSignature &env)
| |
{
| |
// the order is unimportant, we can shuffle it
| |
env.append(TERMINAL_LANGUAGE);
| |
env.append(TERMINAL_COMMONDATA_PATH);
| |
env.append(TERMINAL_CPU_CORES);
| |
env.append(TERMINAL_MEMORY_PHYSICAL);
| |
env.append(TERMINAL_SCREEN_DPI);
| |
env.append(TERMINAL_SCREEN_WIDTH);
| |
env.append(TERMINAL_SCREEN_HEIGHT);
| |
env.append(TERMINAL_VPS);
| |
env.append(MQL_PROGRAM_TYPE);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Script program start function |
| |
//+------------------------------------------------------------------+
| |
void OnStart()
| |
{
| |
MyEnvSignature env;
| |
string signature;
| |
| |
if(StringLen(Signature) > 0)
| |
{
| |
#ifdef I_AM_DEVELOPER
| |
if(StringLen(Validation) == 0)
| |
{
| |
string validation;
| |
if(env.check(Signature, validation))
| |
Print("Validation:", validation);
| |
// Validation:mT3a6Uuzopjcs2QkbC1zW10sYI8KVLgchq/i7gBY4Bg=
| |
return;
| |
}
| |
signature = Signature;
| |
#endif
| |
}
| |
else
| |
{
| |
// check actual environment to detect possible changes against the signature
| |
FillEnvironment(env);
| |
// fake change in environment could be a timezone
| |
// env.append("Dummy" + (string)(TimeGMTOffset() - TimeDaylightSavings()));
| |
signature = env.emit();
| |
}
| |
| |
if(StringLen(Validation) == 0)
| |
{
| |
Print("Validation string from developer is required to run this script");
| |
Print("Environment Signature is generated for current state...");
| |
Print("Signature:", signature);
| |
// Signature:+96RUrYFVpT9NAhLZTXjkmW9dWeqwo5FjXc2PoQqfl7LdfzJWYjJ+Unf6YU=
| |
return;
| |
}
| |
else
| |
{
| |
string validation = Validation; // non-const variable is required as argument
| |
const bool accessGranted = env.check(signature, validation);
| |
if(!accessGranted)
| |
{
| |
Print("Wrong validation string, terminating");
| |
return;
| |
}
| |
}
| |
| |
Print("The script is validated and running normally");
| |
// ... actual work code
| |
}
| |
//+------------------------------------------------------------------+
| |
/*
| |
| |
Hash bytes:
| |
93 92 5 229 65 218 193 49 193 99 184 152 124 64 51 247 178 51 248 124 86 169 216 198 39 146 166 64 81 18 147 174
| |
Validation string from developer is required to run this script
| |
Environment Signature is generated for current state...
| |
Signature:XVwF5UHawTHBY7iYfEAz97Iz+HxWqdjGJ5KmQFESk64=
| |
| |
Validation:ean4ZuRLZespy9D/oPtnNrq3oaokKeXdlQGy47Sv5zo=
| |
| |
*/
| |
//+------------------------------------------------------------------+
|