#include "LicenseClient.h" #include "CryptoUtils.h" #include "HashUtils.h" #include "JSONSerialization.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace pt = boost::property_tree; namespace fs = boost::filesystem; using CryptoPP::AES; using CryptoPP::GCM; using CryptoPP::byte; using CryptoPP::SecByteBlock; using CryptoPP::AutoSeededRandomPool; using CryptoPP::AuthenticatedDecryptionFilter; using CryptoPP::StringSink; using CryptoPP::StringSource; using CryptoPP::Redirector; using CryptoPP::ECDSA; using CryptoPP::EC2N; using CryptoPP::SHA256; using CryptoPP::HexDecoder; namespace ASN1 = CryptoPP::ASN1; namespace { static const std::string productId = "coc"; static const std::string serverPath = "https://license.apis.sk/api/v1"; static const uint32_t initializationVectorSize = AES::BLOCKSIZE; static const uint32_t macTagSize = 16; static const uint32_t ecdsaSignatureSize = 72; template< typename T > std::string intToHex(T i) { std::stringstream stream; stream << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << i; return stream.str(); } typedef std::string ParamHash; ParamHash fletcher64(const std::string& input) { std::vector buf((input.size() + sizeof(uint32_t) - 1) / sizeof(uint32_t), 0); std::copy_n(std::begin(input), input.size(), stdext::make_checked_array_iterator((std::string::pointer)buf.data(), buf.size() * sizeof(uint32_t))); auto resInt = ::fletcher64(buf.data(), buf.size()); return intToHex(resInt); } SystemParams skipEmptyParams(const SystemParams& systemParams) { SystemParams result; for (const auto& entry : systemParams) { if (!entry.second.empty()) { result[entry.first] = entry.second; } } return result; } SystemParams hashParams(const SystemParams& systemParams) { SystemParams result; for (const auto& entry : systemParams) { result[entry.first] = fletcher64(entry.second); } return result; } typedef std::string Signature; struct SignedData { std::string data; std::string signature; }; std::string readBinaryFile(const std::string& filename) { std::ifstream t(filename, std::istream::binary); std::string str; t.seekg(0, std::ios::end); str.reserve(static_cast(t.tellg())); t.seekg(0, std::ios::beg); str.assign((std::istreambuf_iterator(t)), std::istreambuf_iterator()); return str; } struct PreactivationRequest { std::string productId = productId; SystemParams systemParams; }; struct ActivationRequest { std::string productId = productId; SystemParams systemParams; std::string licenseNumber; }; struct ActivationResponse { bool success; std::optional licenseFile; }; struct CheckUpdatesRequest { SystemParams systemParams; std::string activationId; std::string productId = productId; std::map moduleVersions; }; struct CheckUpdatesResponse { bool success; std::optional licenseFile; std::vector moduleUpdates; }; } // anonymous namespace void serialize(std::ostream& os, const ActivationData& a) { os << "{"; os << "\"activationId\":"; serialize(os, a.activationId); os << ",\"productId\":"; serialize(os, a.productId); os << ",\"systemParams\":"; serialize(os, a.systemParams); os << ",\"licensedModules\":"; serialize(os, a.licensedModules); os << "}"; } void deserialize(const pt::ptree& tree, ActivationData& a) { deserialize(tree.get_child("activationId"), a.activationId); deserialize(tree.get_child("productId"), a.productId); deserialize(tree.get_child("systemParams"), a.systemParams); deserialize(tree.get_child("licensedModules"), a.licensedModules); } void serialize(std::ostream& os, const SignedData& d) { os << "{"; os << "\"data\":"; serialize(os, d.data); os << ",\"signature\":"; serialize(os, d.signature); os << "}"; } void deserialize(const pt::ptree& tree, SignedData& d) { deserialize(tree.get_child("data"), d.data); deserialize(tree.get_child("signature"), d.signature); } void serialize(std::ostream& os, const PreactivationRequest& a) { os << "{"; os << "\"productId\":"; ::serialize(os, a.productId); os << ",\"systemParams\":"; ::serialize(os, a.systemParams); os << "}"; } void serialize(std::ostream& os, const ActivationRequest& a) { os << "{"; os << "\"productId\":"; ::serialize(os, a.productId); os << ",\"systemParams\":"; ::serialize(os, a.systemParams); os << ",\"licenseNumber\":"; ::serialize(os, a.licenseNumber); os << "}"; } void deserialize(const pt::ptree& tree, ActivationResponse& a) { deserialize(tree.get_child("success"), a.success); auto licenseFileOpt = tree.get_child_optional("licenseFile"); if (licenseFileOpt) { std::string res; deserialize(licenseFileOpt.value(), res); a.licenseFile = base64Decode(res); } else { a.licenseFile = {}; } } void serialize(std::ostream& os, const CheckUpdatesRequest& r) { os << "{"; os << "\"systemParams\":"; ::serialize(os, r.systemParams); os << ",\"activationId\":"; ::serialize(os, r.activationId); os << ",\"productId\":"; serialize(os, r.productId); os << ",\"moduleVersions\":"; ::serialize(os, r.moduleVersions); os << "}"; } void deserialize(const pt::ptree& tree, CheckUpdatesResponse& r) { deserialize(tree.get_child("success"), r.success); auto licenseFileOpt = tree.get_child_optional("licenseFile"); if (licenseFileOpt) { std::string res; deserialize(licenseFileOpt.value(), res); r.licenseFile = base64Decode(res); } else { r.licenseFile = {}; } if (r.success) { deserialize(tree.get_child("moduleUpdates"), r.moduleUpdates); } } std::optional validateLicenseKey(const std::string& licenseKey) { auto result = base32Decode(licenseKey); if (result.size() != 15) return {}; return base32Encode(result); } LicenseClient::LicenseClient(SystemParamsProvider& systemParamsProvider, const std::string& licenseFile) : m_systemParamsProvider(systemParamsProvider) , m_licenseFile(licenseFile) { } LicenseClient::~LicenseClient() { } void LicenseClient::init() { m_systemParams = hashParams(skipEmptyParams(m_systemParamsProvider.retrieveSystemParams())); loadActivationData(); } bool LicenseClient::tryPreactivate(HTTPClient &httpClient) { PreactivationRequest req{ productId, m_systemParams }; std::string jsonReq; { std::ostringstream ss1; serialize(ss1, req); jsonReq = ss1.str(); } std::string jsonRes; { std::ostringstream ss2; httpClient.postJson(serverPath + "/activate0", jsonReq, ss2); jsonRes = ss2.str(); } ActivationResponse activationResponse; pt::ptree root; std::istringstream ss2(jsonRes); pt::read_json(ss2, root); deserialize(root, activationResponse); if (activationResponse.success) { { const auto& licenseData = activationResponse.licenseFile.value(); std::ofstream os(m_licenseFile, std::ofstream::binary); os.write(licenseData.data(), licenseData.size()); } return loadActivationData(); } return false; } bool LicenseClient::activate(HTTPClient &httpClient, const std::string & licenseNumber) { std::string jsonReq; std::ostringstream ss1; ActivationRequest req{ productId, m_systemParams, licenseNumber }; serialize(ss1, req); std::stringstream ss2; httpClient.postJson(serverPath + "/activate", ss1.str() , ss2); ActivationResponse activationResponse; pt::ptree root; pt::read_json(ss2, root); deserialize(root, activationResponse); if (activationResponse.success) { { const auto& licenseData = activationResponse.licenseFile.value(); std::ofstream os(m_licenseFile, std::ofstream::binary); os.write(licenseData.data(), licenseData.size()); } return loadActivationData(); } return false; } std::vector LicenseClient::checkForUpdates(HTTPClient & httpClient, const std::map& currentVersions) { if (!isActivated()) { throw std::runtime_error("Not active"); } CheckUpdatesRequest req{ m_systemParams, m_activationData->activationId, productId, currentVersions }; std::ostringstream ss1; serialize(ss1, req); std::stringstream ss2; httpClient.postJson(serverPath + "/check", ss1.str(), ss2); CheckUpdatesResponse checkUpdatesResponse; pt::ptree root; pt::read_json(ss2, root); deserialize(root, checkUpdatesResponse); if (!checkUpdatesResponse.success) { throw std::runtime_error("Could not check for updates"); } if (checkUpdatesResponse.licenseFile) { // Something changes - Server sent us updated licenseFile { const auto& licenseData = checkUpdatesResponse.licenseFile.value(); std::ofstream os(m_licenseFile, std::ofstream::binary); os.write(licenseData.data(), licenseData.size()); } loadActivationData(); } return checkUpdatesResponse.moduleUpdates; } bool LicenseClient::loadActivationData() { if (fs::is_regular_file(m_licenseFile)) { // Encrypt in nodejs /* let activationData = { activationId: '1234567890', systemParams: { biosSerialNum: '1234567797980980', diskSerialNum: '1234567857845764' }, licensedModules: ['ccengine', 'cc-data-usd', 'cc-data-rub'] }; let algorithm = 'aes-256-gcm'; let password = Buffer.from('e73db572349005f1c41979baf8166a0900745119fa096b9c3efbcee11ddd8b88', 'hex'); let privateKey = '-----BEGIN EC PRIVATE KEY-----' + "\n" + 'MIGAAgEBBCQBPIZnOt/mEsgtH3S9XZMGRuHkB5hYbMJ/BxcGmAc/pZLdxDWgBwYF' + "\n" + 'K4EEABGhTANKAAQHyyrnJFywb+B0pcaVRHIOcEao3OtSMSJJZiluIMme1aE+20UA' + "\n" + '0c0+2u+M6bMi072XrXLf8KudcAxihG/aqCqbVVZS6i10SSM=' + "\n" + '-----END EC PRIVATE KEY-----'; crypto.randomBytes(16, (err, nonce) => { activationData.nonce = nonce.toString('base64'); let data = JSON.stringify(activationData); let sign = crypto.createSign('SHA256'); sign.write(data); sign.end(); let signature = sign.sign(privateKey, 'hex'); data = JSON.stringify({ data, signature }); crypto.randomBytes(16, (err, iv) => { zlib.deflateRaw(data, (err, compressed) => { let cipher = crypto.createCipheriv(algorithm, password, iv); let encrypted = cipher.update(compressed); encrypted = Buffer.concat([encrypted, cipher.final()]); let tag = cipher.getAuthTag(); let output = Buffer.concat([iv, tag, encrypted]); console.log(output.toString('hex')); }); }); }); */ /*unsigned char publicKey[] = { 0x04, 0x07, 0xcb, 0x2a, 0xe7, 0x24, 0x5c, 0xb0, 0x6f, 0xe0, 0x74, 0xa5, 0xc6, 0x95, 0x44, 0x72, 0x0e, 0x70, 0x46, 0xa8, 0xdc, 0xeb, 0x52, 0x31, 0x22, 0x49, 0x66, 0x29, 0x6e, 0x20, 0xc9, 0x9e, 0xd5, 0xa1, 0x3e, 0xdb, 0x45, 0x00, 0xd1, 0xcd, 0x3e, 0xda, 0xef, 0x8c, 0xe9, 0xb3, 0x22, 0xd3, 0xbd, 0x97, 0xad, 0x72, 0xdf, 0xf0, 0xab, 0x9d, 0x70, 0x0c, 0x62, 0x84, 0x6f, 0xda, 0xa8, 0x2a, 0x9b, 0x55, 0x56, 0x52, 0xea, 0x2d, 0x74, 0x49, 0x23 }; */ unsigned char publicKey[] = { 0x30, 0x5e, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x11, 0x03, 0x4a, 0x00, 0x04, 0x07, 0xcb, 0x2a, 0xe7, 0x24, 0x5c, 0xb0, 0x6f, 0xe0, 0x74, 0xa5, 0xc6, 0x95, 0x44, 0x72, 0x0e, 0x70, 0x46, 0xa8, 0xdc, 0xeb, 0x52, 0x31, 0x22, 0x49, 0x66, 0x29, 0x6e, 0x20, 0xc9, 0x9e, 0xd5, 0xa1, 0x3e, 0xdb, 0x45, 0x00, 0xd1, 0xcd, 0x3e, 0xda, 0xef, 0x8c, 0xe9, 0xb3, 0x22, 0xd3, 0xbd, 0x97, 0xad, 0x72, 0xdf, 0xf0, 0xab, 0x9d, 0x70, 0x0c, 0x62, 0x84, 0x6f, 0xda, 0xa8, 0x2a, 0x9b, 0x55, 0x56, 0x52, 0xea, 0x2d, 0x74, 0x49, 0x23 }; unsigned char password[] = { 0xe7, 0x3d, 0xb5, 0x72, 0x34, 0x90, 0x05, 0xf1, 0xc4, 0x19, 0x79, 0xba, 0xf8, 0x16, 0x6a, 0x09, 0x00, 0x74, 0x51, 0x19, 0xfa, 0x09, 0x6b, 0x9c, 0x3e, 0xfb, 0xce, 0xe1, 0x1d, 0xdd, 0x8b, 0x88 }; /* unsigned char encrypted[] = { 0x5d, 0xc1, 0x4e, 0xaf, 0x95, 0xf0, 0x1d, 0x84, 0x09, 0x71, 0x66, 0x0f, 0x87, 0x19, 0x7a, 0xa1, 0x6a, 0x77, 0x39, 0x1e, 0x0a, 0xde, 0x93, 0x0c, 0xda, 0xa8, 0x62, 0x76, 0x53, 0xcb, 0xa7, 0x9f, 0x8d, 0x36, 0x2a, 0x74, 0xcd, 0x5d, 0x78, 0x6e, 0x83, 0x14, 0xa4, 0x21, 0x3c }; */ /* { unsigned char encrypted[] = { 0x88, 0x5d, 0x38, 0xfe, 0xfc, 0x51, 0x7d, 0x3c, 0xb5, 0x95, 0x49, 0xae, 0xa4, 0x6a, 0xa4, 0x7e, 0xda, 0x5d, 0x29, 0x84, 0xc2, 0x85, 0xb6, 0x18, 0x6b, 0xd6, 0x40, 0x77, 0x28, 0xc3, 0xa4, 0x0c, 0xd1, 0x47, 0x78, 0xf9, 0xce, 0xe4, 0x22, 0xec, 0x68, 0x3f, 0x34, 0xe3, 0xa0, 0x23, 0x42, 0xcc, 0x35, 0x50, 0x2a, 0x34, 0xa5, 0xc3, 0x0b, 0x77, 0xa6, 0xb1, 0x00, 0x53, 0xf7, 0x86, 0x08, 0x94, 0x72, 0x99, 0x88, 0xc6, 0x07, 0x18, 0x2b, 0xb0, 0xd9, 0xd2, 0x1d, 0xea, 0x5c, 0x96, 0x14, 0x25, 0x70, 0xd8, 0x02, 0xb6, 0xc7, 0xa2, 0xae, 0x9e, 0x89, 0x87, 0xb9, 0x9f, 0xad, 0xd6, 0xc6, 0x8a, 0xb2, 0x53, 0x8f, 0xfb, 0x3d, 0x4b, 0x21, 0xd3, 0xa1, 0x43, 0x88, 0xef, 0x16, 0x20, 0x19, 0xa2, 0x6c, 0x36, 0xc4, 0xfd, 0x17, 0x0c, 0xad, 0x30, 0xef, 0xfc, 0x6c, 0xe8, 0x2c, 0x3a, 0x55, 0x18, 0x00, 0x8a, 0x15, 0x46, 0xd6, 0x36, 0x03, 0xb6, 0x8f, 0xb9, 0x86, 0x29, 0x1f, 0x9e, 0xc2, 0x89, 0xa2, 0x71, 0x49, 0x64, 0xc7, 0xa6, 0x70, 0x80, 0x00, 0x4c, 0x5d, 0x7c, 0x22, 0x6b, 0xdd, 0x0e, 0x2d, 0x17, 0xab, 0xe6, 0xf8, 0x75, 0x8b, 0xd2, 0x5d, 0x2d, 0x40, 0xd6, 0xea, 0x1b, 0x4f, 0xca, 0x02, 0x2e, 0x98, 0x16, 0x99, 0xdb, 0x14, 0x67, 0x90, 0xd6, 0x8f, 0xbf, 0xc6, 0x4d, 0xd2, 0x92, 0xd2, 0x7b, 0x37, 0x5c, 0x60, 0x7b, 0x78, 0x90, 0x47, 0x73, 0x0a, 0xda, 0x4d, 0xa5, 0x31, 0x51, 0x0c, 0xb6, 0x88, 0x93, 0x37, 0x4e, 0x39, 0x5c, 0x06, 0x90, 0x49, 0xd7, 0x48, 0x67, 0x60, 0xfc, 0x9f, 0x40, 0xaf, 0x50, 0x67, 0xc0, 0xf5, 0xb4, 0xab, 0xac, 0xa1, 0x1c, 0x95, 0xd8, 0x57, 0x15, 0x7d, 0xe8, 0xa7, 0x7f, 0x1a, 0xad, 0x64, 0x7d, 0xa9, 0x3d, 0x38, 0xa6, 0x06, 0xc2, 0x5a, 0x46, 0xae, 0x07, 0x53, 0x97, 0x68, 0x6c, 0xc5, 0xf8, 0x2a, 0xb4, 0x86, 0x8e, 0x9a, 0x7b, 0x48, 0x51, 0xb4, 0x76, 0x8d, 0x9e, 0x6d, 0x47, 0xa8, 0x55, 0x39, 0x73, 0x1d, 0x35, 0x7c, 0xd2, 0xc1, 0x6a, 0x22, 0x91, 0x59, 0x4d, 0xaa, 0x69, 0x11, 0xdf, 0xf3, 0x4f, 0x41, 0x04, 0xff, 0xb4, 0x5d, 0x42, 0x42, 0x73, 0x07, 0xc4, 0xfc, 0xac, 0xa4, 0x98, 0x40, 0x24, 0x1f, 0x0a, 0x86, 0xda, 0x06 }; std::ofstream ofs("license.dat", std::ofstream::binary); ofs.write((const char*)encrypted, sizeof(encrypted)); } */ SecByteBlock key(sizeof(password)); key.Assign(password, sizeof(password)); std::string encrypted = readBinaryFile(m_licenseFile); if (std::size(encrypted) < initializationVectorSize) { throw std::runtime_error("Invalid license file"); } std::string recoveredData; GCM::Decryption d; d.SetKeyWithIV(key, key.size(), (const CryptoPP::byte*)encrypted.data(), initializationVectorSize); AuthenticatedDecryptionFilter df(d, new StringSink(recoveredData), AuthenticatedDecryptionFilter::MAC_AT_BEGIN | AuthenticatedDecryptionFilter::THROW_EXCEPTION, macTagSize ); // AuthenticatedDecryptionFilter // The StringSource dtor will be called immediately // after construction below. This will cause the // destruction of objects it owns. To stop the // behavior so we can get the decoding result from // the DecryptionFilter, we must use a redirector // or manually Put(...) into the filter without // using a StringSource. StringSource ss2((const CryptoPP::byte*)encrypted.data() + initializationVectorSize, encrypted.size() - initializationVectorSize, true, new Redirector(df /*, PASS_EVERYTHING */) ); // StringSource if (!df.GetLastResult()) { throw std::runtime_error("Unable to decrypt data"); } boost::iostreams::array_source src{ recoveredData.data(), recoveredData.size() }; boost::iostreams::filtering_istream is; boost::iostreams::zlib_params zlibParams; zlibParams.noheader = true; is.push(boost::iostreams::zlib_decompressor{zlibParams}); is.push(src); SignedData signedData; { pt::ptree root; pt::read_json(is, root); deserialize(root, signedData); } ECDSA::PublicKey pubKey; //CryptoPP::FileSource fs("c:\\work\\ccengine\\openssl\\bin\\ccengine-pub.der", true /*binary*/); CryptoPP::ArraySource arraySource(static_cast(publicKey), sizeof(publicKey), true); pubKey.Load(arraySource); ECDSA::Verifier verifier(pubKey); std::string signatureDer; StringSource ss(signedData.signature, true, new HexDecoder( new StringSink(signatureDer) ) // HexDecoder ); // StringSource byte signature[ecdsaSignatureSize] = { 0 }; size_t signLength = CryptoPP::DSAConvertSignatureFormat(signature, sizeof(signature), CryptoPP::DSA_P1363, (const CryptoPP::byte*)signatureDer.data(), signatureDer.size(), CryptoPP::DSA_DER); bool result = verifier.VerifyMessage((const byte*)signedData.data.data(), signedData.data.size(), signature, signLength); if (!result) { throw std::runtime_error("Signature could not be verified"); } ActivationData activationData; std::istringstream iss(signedData.data); { pt::ptree root; pt::read_json(iss, root); deserialize(root, activationData); } if (!validateActivationData(activationData)) { throw std::runtime_error("You system is not genuine. Please contact support!"); } m_activationData = std::move(activationData); return true; } return false; } bool LicenseClient::validateActivationData(const ActivationData & activationData) { if (activationData.systemParams.empty()) { return false; } if (activationData.productId != productId) { return false; } // activation parameters must match system parameters for (const auto& entry : activationData.systemParams) { auto it = m_systemParams.find(entry.first); if (it == m_systemParams.end() || it->second != entry.second) { return false; } } return true; }