424 lines
12 KiB
C++
424 lines
12 KiB
C++
#pragma once
|
|
|
|
#include "bitstream.h"
|
|
#include "base32.h"
|
|
#include "base64.h"
|
|
|
|
#include <map>
|
|
|
|
#ifndef LICENSING_NO_NETWORKING
|
|
#ifdef _WIN32
|
|
#include <winhttp.h>
|
|
#include <tchar.h>
|
|
#else
|
|
#include <curl/curl.h>
|
|
#endif
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
class KeyGeneratorImpl {
|
|
public:
|
|
KeyGeneratorImpl():
|
|
m_signingServiceStatusCode(-1)
|
|
{
|
|
|
|
}
|
|
|
|
KeyGeneratorImpl(const LicenseTemplateImpl * templ):
|
|
KeyGeneratorImpl()
|
|
{
|
|
SetKeyTemplate(templ);
|
|
}
|
|
|
|
void SetKeyTemplate(const LicenseTemplateImpl * templ)
|
|
{
|
|
m_keyTemplate = templ;
|
|
|
|
m_keyData.Create(m_keyTemplate->m_numGroups * m_keyTemplate->m_charsPerGroup * m_keyTemplate->m_keyEncoding);
|
|
m_keyData.GetBitStream().Clear();
|
|
|
|
for (const auto& field : m_keyTemplate->m_dataFields)
|
|
m_keyData.AddField(field.first.c_str(), field.second.type, field.second.size, field.second.offset);
|
|
|
|
if (m_keyTemplate->m_validationDataSize)
|
|
{
|
|
m_validationData.Create(m_keyTemplate->m_validationDataSize);
|
|
m_validationData.GetBitStream().Clear();
|
|
|
|
for (const auto& field : m_keyTemplate->m_validationFields)
|
|
m_validationData.AddField(field.first.c_str(), field.second.type, field.second.size, field.second.offset);
|
|
}
|
|
}
|
|
|
|
void SetKeyData(const char * fieldName, const void * buf, int len)
|
|
{
|
|
m_keyData.Set(fieldName, buf, len);
|
|
}
|
|
|
|
void SetKeyData(const char * fieldName, const char * data)
|
|
{
|
|
m_keyData.Set(fieldName, data);
|
|
}
|
|
|
|
void SetKeyData(const char * fieldName, int data)
|
|
{
|
|
m_keyData.Set(fieldName, data);
|
|
}
|
|
|
|
void SetKeyData(const char * fieldName, int year, int month, int day)
|
|
{
|
|
m_keyData.Set(fieldName, year, month, day);
|
|
}
|
|
|
|
void SetValidationData(const char * fieldName, const void * buf, int len)
|
|
{
|
|
m_validationData.Set(fieldName, buf, len);
|
|
}
|
|
|
|
void SetValidationData(const char * fieldName, const char * data)
|
|
{
|
|
m_validationData.Set(fieldName, data);
|
|
}
|
|
|
|
void SetValidationData(const char * fieldName, int data)
|
|
{
|
|
m_validationData.Set(fieldName, data);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
static size_t
|
|
CurlWriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
|
{
|
|
KeyGeneratorImpl * pthis = (KeyGeneratorImpl *)userp;
|
|
size_t realsize = size * nmemb;
|
|
|
|
if (realsize > 1024 || pthis->m_keyBuffer.length() > 1024)
|
|
return realsize - 1; // signal error
|
|
|
|
if (realsize > 0)
|
|
pthis->m_keyBuffer.append((char *)contents, realsize);
|
|
|
|
return realsize;
|
|
}
|
|
#endif
|
|
|
|
const char * GenerateKey()
|
|
{
|
|
BitStream signedData;
|
|
BitStream signature;
|
|
ECC::Signer signer;
|
|
int sigLen, sigLenBits;
|
|
|
|
#ifndef LICENSING_NO_NETWORKING
|
|
if (m_keyTemplate->m_signingKey == NULL)
|
|
{
|
|
|
|
|
|
m_signingServiceStatusCode = -1;
|
|
|
|
string activationQuery("Activate.ashx?");
|
|
|
|
//if (activationQuery[m_keyTemplate->m_signingServiceUrl.length() - 1] != '/')
|
|
// activationQuery.append(1, '/');
|
|
//activationQuery.append("Activate.ashx?");
|
|
|
|
unsigned char * buf = (unsigned char *)_alloca(1 + ((m_validationData.GetSize() + 7) >> 3));
|
|
|
|
void * enumHandle = NULL;
|
|
const char * name;
|
|
BitStruct::FIELD_TYPE type;
|
|
int size, offset;
|
|
|
|
while (m_keyTemplate->EnumValidationFields(&enumHandle, &name, (int*)&type, &size, &offset))
|
|
{
|
|
m_validationData.GetBitStream().Seek(offset);
|
|
m_validationData.GetBitStream().Read(buf, size);
|
|
|
|
activationQuery.append(name);
|
|
activationQuery.append("=");
|
|
|
|
if ((int)type == FIELD_TYPE_STRING)
|
|
{
|
|
buf[(size + 7) >> 3] = '\0';
|
|
activationQuery.append((char *)buf);
|
|
} else
|
|
{
|
|
BASE64 base64;
|
|
|
|
string base64Value = base64.encode(buf, (size + 7) >> 3, true);
|
|
|
|
replace_all(base64Value, "+", "%2B");
|
|
replace_all(base64Value, "/", "%2F");
|
|
replace_all(base64Value, "=", "%3D");
|
|
|
|
activationQuery.append(base64Value.c_str());
|
|
}
|
|
|
|
activationQuery.append("&");
|
|
}
|
|
|
|
if (m_keyTemplate->m_licensingServiceParameters.length() > 0)
|
|
{
|
|
activationQuery.append(m_keyTemplate->m_licensingServiceParameters);
|
|
activationQuery.append("&");
|
|
}
|
|
|
|
activationQuery.erase(activationQuery.length() - 1, 1);
|
|
|
|
m_keyBuffer.clear();
|
|
|
|
#ifdef _WIN32
|
|
URL_COMPONENTS urlComponents;
|
|
|
|
ZeroMemory(&urlComponents, sizeof(urlComponents));
|
|
urlComponents.dwStructSize = sizeof(urlComponents);
|
|
|
|
urlComponents.dwHostNameLength = (DWORD)-1;
|
|
urlComponents.dwUrlPathLength = (DWORD)-1;
|
|
|
|
auto licensingServiceUrl = s2w(m_keyTemplate->m_licensingServiceUrl);
|
|
|
|
if (!WinHttpCrackUrl(licensingServiceUrl.c_str(), m_keyTemplate->m_licensingServiceUrl.length(), 0L, &urlComponents))
|
|
throw new LicensingException(STATUS_NET_ERROR, _T("networking error (1)"));
|
|
|
|
std::wstring hostName(urlComponents.lpszHostName, urlComponents.dwHostNameLength);
|
|
|
|
unique_ptr<VOID, decltype(&WinHttpCloseHandle)> internet_ptr(WinHttpOpen(L"ANSCENTER Licensing SDK 2.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0L), WinHttpCloseHandle);
|
|
|
|
if (!internet_ptr)
|
|
throw new LicensingException(STATUS_NET_ERROR, _T("networking error (2)"));
|
|
|
|
unique_ptr<VOID, decltype(&WinHttpCloseHandle)> connection_ptr(WinHttpConnect(internet_ptr.get(), hostName.c_str(), urlComponents.nPort, 0L), WinHttpCloseHandle);
|
|
if (!connection_ptr)
|
|
throw new LicensingException(STATUS_NET_ERROR, _T("networking error (3)"));
|
|
|
|
activationQuery.insert(0, w2s(urlComponents.lpszUrlPath, urlComponents.dwUrlPathLength));
|
|
|
|
DWORD requestFlags = WINHTTP_FLAG_BYPASS_PROXY_CACHE;
|
|
if (urlComponents.nScheme == INTERNET_SCHEME_HTTPS)
|
|
requestFlags |= WINHTTP_FLAG_SECURE;
|
|
|
|
unique_ptr<VOID, decltype(&WinHttpCloseHandle)> request_ptr(WinHttpOpenRequest(connection_ptr.get(), L"GET", s2w(activationQuery).c_str(), NULL, NULL, NULL, requestFlags), WinHttpCloseHandle);
|
|
if (!request_ptr)
|
|
throw new LicensingException(STATUS_NET_ERROR, _T("networking error (4)"));
|
|
|
|
if (!WinHttpSendRequest(request_ptr.get(), NULL, 0L, NULL, 0L, 0L, 0L))
|
|
{
|
|
DWORD result = GetLastError();
|
|
throw new LicensingException(STATUS_NET_ERROR, _T("networking error (5)"));
|
|
}
|
|
|
|
if (!WinHttpReceiveResponse(request_ptr.get(), NULL))
|
|
throw new LicensingException(STATUS_NET_ERROR, _T("networking error (6)"));
|
|
|
|
DWORD status;
|
|
DWORD statusSize = sizeof(DWORD);
|
|
|
|
if (!WinHttpQueryHeaders(request_ptr.get(), WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status, &statusSize, WINHTTP_NO_HEADER_INDEX))
|
|
throw new LicensingException(STATUS_NET_ERROR, _T("networking error (7)"));
|
|
|
|
m_signingServiceStatusCode = status;
|
|
|
|
if (status != HTTP_STATUS_OK && status != HTTP_STATUS_ACCEPTED && status != HTTP_STATUS_CREATED)
|
|
{
|
|
WCHAR buffer[1024];
|
|
DWORD bufferSize = 1024;
|
|
|
|
if (!WinHttpQueryHeaders(request_ptr.get(), WINHTTP_QUERY_STATUS_TEXT, WINHTTP_HEADER_NAME_BY_INDEX, buffer, &bufferSize, WINHTTP_NO_HEADER_INDEX))
|
|
throw new LicensingException(STATUS_NET_ERROR, "networking error (8)");
|
|
|
|
throw new LicensingException(STATUS_NET_ERROR, w2s(buffer).c_str());
|
|
}
|
|
|
|
DWORD dataSize, count;
|
|
char buffer[0x100];
|
|
|
|
do {
|
|
if (!WinHttpQueryDataAvailable(request_ptr.get(), &dataSize))
|
|
break;
|
|
|
|
if (size > 0x100)
|
|
size = 0x100;
|
|
|
|
if (WinHttpReadData(request_ptr.get(), buffer, dataSize, &count))
|
|
m_keyBuffer.append(buffer, count);
|
|
|
|
} while (dataSize > 0);
|
|
#else
|
|
CURL *curl;
|
|
CURLcode res;
|
|
|
|
if ((curl = curl_easy_init()) == NULL)
|
|
throw new LicensingException(STATUS_NET_ERROR, "cURL initalization failed");
|
|
|
|
if (*(m_keyTemplate->m_licensingServiceUrl.rbegin()) != '/')
|
|
activationQuery.insert(0, "/");
|
|
|
|
activationQuery.insert(0, m_keyTemplate->m_licensingServiceUrl);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, activationQuery.c_str());
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if(res != CURLE_OK)
|
|
throw new LicensingException(STATUS_NET_ERROR, curl_easy_strerror(res));
|
|
|
|
curl_easy_cleanup(curl);
|
|
#endif
|
|
|
|
m_key = m_keyBuffer.c_str();
|
|
|
|
return m_key.c_str();
|
|
}
|
|
#else
|
|
if (m_keyTemplate->m_signingKey == NULL)
|
|
{
|
|
throw new LicensingException(STATUS_NET_ERROR, "private key not provided");
|
|
}
|
|
#endif
|
|
|
|
// signed data is license key data followed by validation data
|
|
signedData.Create(m_keyTemplate->m_dataSize + m_keyTemplate->m_validationDataSize);
|
|
|
|
if (m_keyTemplate->m_dataSize)
|
|
signedData.Write(m_keyData.GetBuffer(), m_keyTemplate->m_dataSize);
|
|
|
|
if (m_keyTemplate->m_validationDataSize)
|
|
signedData.Write(m_validationData.GetBuffer(), m_keyTemplate->m_validationDataSize);
|
|
|
|
signedData.ZeroPadToNextByte();
|
|
|
|
// create a bit stream to hold the signature
|
|
signature.Create(m_keyTemplate->m_signatureSize);
|
|
|
|
// sign data
|
|
|
|
// we use a different algorithm than ECDSA when the signature size must be smaller than twice the key size
|
|
if (m_keyTemplate->m_signatureSize < (m_keyTemplate->m_signatureKeySize << 1))
|
|
signer.SetHashSize(m_keyTemplate->m_signatureSize - m_keyTemplate->m_signatureKeySize);
|
|
else
|
|
signer.SetHashSize(0);
|
|
|
|
signer.SetPrivateKey(m_keyTemplate->m_signingKey.get());
|
|
|
|
signer.Sign(signedData.GetBuffer(), (signedData.GetSize() + 7) >> 3,
|
|
signature.GetBuffer(), &sigLen, &sigLenBits);
|
|
|
|
signature.ReleaseBuffer(sigLenBits);
|
|
|
|
// write the signature at the end of key data
|
|
m_keyData.GetBitStream().Seek(m_keyTemplate->m_dataSize);
|
|
m_keyData.GetBitStream().Write(signature.GetBuffer(), m_keyTemplate->m_signatureSize);
|
|
|
|
// text-encode the license key
|
|
EncodeKey();
|
|
|
|
// success
|
|
return m_key.c_str();
|
|
}
|
|
|
|
int GetSigningServiceStatusCode()
|
|
{
|
|
return m_signingServiceStatusCode;
|
|
}
|
|
|
|
void EncodeKey()
|
|
{
|
|
|
|
string buf;
|
|
|
|
// set all bits to 0 until the next byte boundary
|
|
//m_rawKey.ZeroPadToNextByte();
|
|
|
|
// reverse last byte from the stream,
|
|
// so that we will not lose significant bits in the padless, truncated BAS32/64 encoding
|
|
unsigned char * lastBytePtr = (unsigned char *)m_keyData.GetBuffer() + ((m_keyData.GetSize() + 7) >> 3) - 1;
|
|
*lastBytePtr = (unsigned char)(((*lastBytePtr * 0x0802LU & 0x22110LU) | (*lastBytePtr * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16);
|
|
|
|
switch (m_keyTemplate->m_keyEncoding)
|
|
{
|
|
case ENCODING_BASE32X:
|
|
{
|
|
BASE32 base32;
|
|
buf = base32.encode((unsigned char *)m_keyData.GetBuffer(), (m_keyData.GetSize() + 7) >> 3, true);
|
|
}
|
|
break;
|
|
|
|
case ENCODING_BASE64X:
|
|
{
|
|
BASE64 base64;
|
|
buf = base64.encode((unsigned char *)m_keyData.GetBuffer(), (m_keyData.GetSize() + 7) >> 3, false);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new LicensingException(STATUS_INVALID_KEY_ENCODING);
|
|
}
|
|
|
|
if (buf.empty())
|
|
throw new LicensingException(STATUS_OUT_OF_MEMORY);
|
|
|
|
m_key = buf.c_str();
|
|
|
|
if ((int)m_key.length() > m_keyTemplate->m_numGroups * m_keyTemplate->m_charsPerGroup)
|
|
m_key.resize(m_keyTemplate->m_numGroups * m_keyTemplate->m_charsPerGroup);
|
|
|
|
int i;
|
|
int insertPos;
|
|
// separate character groups
|
|
for (i = 0, insertPos = m_keyTemplate->m_charsPerGroup; i < m_keyTemplate->m_numGroups - 1; i++)
|
|
{
|
|
m_key.insert(insertPos, m_keyTemplate->m_groupSeparator);
|
|
insertPos += (unsigned)( m_keyTemplate->m_groupSeparator.length() + m_keyTemplate->m_charsPerGroup );
|
|
}
|
|
|
|
// add header and footer (if any)
|
|
if (!m_keyTemplate->m_header.empty())
|
|
m_key.insert(0, m_keyTemplate->m_header + "\r\n");
|
|
|
|
if (!m_keyTemplate->m_footer.empty())
|
|
m_key.append("\r\n" + m_keyTemplate->m_footer);
|
|
}
|
|
|
|
std::size_t replace_all(std::string& inout, std::string_view what, std::string_view with)
|
|
{
|
|
std::size_t count{};
|
|
|
|
for (std::string::size_type pos{};
|
|
inout.npos != (pos = inout.find(what.data(), pos, what.length()));
|
|
pos += with.length(), ++count) {
|
|
inout.replace(pos, what.length(), with.data(), with.length());
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void StrReplaceA(std::string& str, const std::string& oldStr, const std::string& newStr)
|
|
{
|
|
size_t pos = 0;
|
|
while((pos = str.find(oldStr, pos)) != std::string::npos)
|
|
{
|
|
str.replace(pos, oldStr.length(), newStr);
|
|
pos += newStr.length();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const LicenseTemplateImpl* m_keyTemplate;
|
|
|
|
string m_keyBuffer;
|
|
string m_key;
|
|
|
|
BitStruct m_keyData;
|
|
BitStruct m_validationData;
|
|
|
|
int m_signingServiceStatusCode;
|
|
};
|