openclonk/src/config/C4ConfigShareware.cpp

508 lines
14 KiB
C++
Raw Blame History

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2003-2004, 2007-2008 Matthes Bender
* Copyright (c) 2004-2005, 2007 Günther Brammer
* Copyright (c) 2007 Julian Raschke
* Copyright (c) 2007 Peter Wortmann
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#include <C4Include.h>
#include <C4ConfigShareware.h>
#include <C4SecurityCertificates.h>
#include <C4Log.h>
#include <C4Gui.h>
#include <StdFile.h>
#ifdef HAVE_IO_H
#include <io.h>
#endif
#ifdef _DEBUG
// this disables registration checking!
#ifdef _WIN32
//#define C4CHECKMEMLEAKS 1
#else
// Do not disable registration checking
#endif
#endif
#include <openssl/md5.h>
#ifndef C4CHECKMEMLEAKS
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/objects.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#include <openssl/pem.h>
// OpenSSL / Key file handling helpers
// Loads a public key from memory. The parameter provided must point to a certificate in memory.
// If generated by certscramble.pl, the certificate needs to be de-base64ed and de-XORed first.
// The loaded public key has to be cleared using clearPublicKey after use.
EVP_PKEY* loadPublicKey(const char *memKey, bool deBase64 = false, bool deXOR = false, const char *strXOR = 0)
{
const int maxKeyDataLen = 2048;
unsigned char keyData[maxKeyDataLen + 1];
unsigned int keyDataLen;
memset(keyData, 0, maxKeyDataLen + 1);
// De-base64 certificate
if (deBase64)
{
// The man page says that the data memKey points to will not be modified by this
BIO* memBio = BIO_new_mem_buf(const_cast<char *>(memKey), strlen(memKey));
BIO* b64Bio = BIO_new(BIO_f_base64());
BIO* bio = BIO_push(b64Bio, memBio);
BIO_flush(bio);
keyDataLen = BIO_read(bio, keyData, maxKeyDataLen * 2);
BIO_free_all(bio);
}
else
{
keyDataLen = strlen(memKey);
memcpy(keyData, memKey, keyDataLen);
}
// De-XOR certificate
if (deXOR)
{
int xorStrLen = strlen(strXOR);
for (unsigned int i = 0; i < keyDataLen; i++)
keyData[i] = keyData[i] ^ strXOR[i % xorStrLen];
}
// Load public key from certificate
BIO* mbio = BIO_new_mem_buf(keyData, -1);
X509* cert = PEM_read_bio_X509(mbio, NULL, NULL, NULL);
if (!cert) return 0;
BIO_free(mbio);
EVP_PKEY* pubKey = X509_get_pubkey(cert);
X509_free(cert);
// Return public key
return pubKey;
}
void clearPublicKey(EVP_PKEY* pubKey)
{
EVP_PKEY_free(pubKey);
}
// Verifies the specified block of data using the public key provided.
// The block of data is assumed to include a base64-ed signature separated
// from data by a double-linefeed delimier.
int verifyData(char *data, unsigned int dataLen, EVP_PKEY* pubKey)
{
// Invalid parameters
if (!data || !dataLen || !pubKey)
return -1;
// Find delimiter in data
char* pDelimiter = 0;
for (char* pBack = data + dataLen - 4; pBack >= data; pBack--)
if (memcmp(pBack, "\r\n\r\n", 4) == 0)
{
pDelimiter = pBack;
break;
}
if (!pDelimiter) return -1; //return error("No delimiter found in data to verify.");
unsigned int dataOnlyLen = pDelimiter - data;
// De-base64 signature on registration key
unsigned char* b64Sig = (unsigned char*) pDelimiter + 4;
unsigned int b64SigLen = dataLen - dataOnlyLen - 4;
BIO* memBio = BIO_new_mem_buf(b64Sig, b64SigLen);
BIO* b64Bio = BIO_new(BIO_f_base64());
BIO* bio = BIO_push(b64Bio, memBio);
BIO_flush(bio);
const int MAXSIGLEN = 1024;
unsigned char sig[MAXSIGLEN] = "";
unsigned int sigLen = BIO_read(bio, sig, MAXSIGLEN * 2);
BIO_free_all(bio);
// Verify signature
EVP_MD_CTX digest;
EVP_VerifyInit(&digest, EVP_sha1());
EVP_VerifyUpdate(&digest, data, dataOnlyLen);
int err = EVP_VerifyFinal(&digest, sig, sigLen, pubKey);
if (err != 1)
return -1;
EVP_MD_CTX_cleanup(&digest);
// Data is valid
return 0;
}
// Windows clipboard handling
bool getClipboardText(char *pBuffer, int iBufferSize)
{
#ifdef _WIN32
if (!IsClipboardFormatAvailable(CF_TEXT)) return false;
if (!OpenClipboard(NULL)) return false;
HANDLE hData = GetClipboardData(CF_TEXT);
LPVOID pData = GlobalLock(hData);
strncpy(pBuffer, (const char*) pData, iBufferSize); pBuffer[iBufferSize] = 0;
GlobalUnlock(pData);
CloseClipboard();
return true;
#else
return false;
#endif
}
#endif // C4CHECKMEMLEAKS
// Old-style string scrambling
#define ScrambleDefaultKey "_D.lp/8f3_ 3 ] =h16%2"
const char ScrambleMark = '<EFBFBD>',
ScrambleRangeLow = '0',
ScrambleRangeHi = 'z';
void ScrambleString(char *szString, int iLen, const char *szKey)
{
if (!szString || !szKey) return;
char *cpString = szString;
const char *cpKey = szKey;
int iChar,iEscSequence=0;
if (iLen<0) iLen=SLen(szString);
while (iLen>0)
{
// Detect character escape sequences
if (*cpString=='\\') iEscSequence=2;
// Process character
if (!iEscSequence)
{
// Scramble range
if (Inside(*cpString,ScrambleRangeLow,ScrambleRangeHi))
{
// Shift character
iChar= ((int) *cpString) + (BoundBy(*cpKey,ScrambleRangeLow,ScrambleRangeHi)-ScrambleRangeLow);
if (iChar>ScrambleRangeHi) iChar-=(ScrambleRangeHi-ScrambleRangeLow+1);
*cpString = (char) iChar;
// Advance key
cpKey++; if (!(*cpKey)) cpKey=szKey;
}
// Postconvert backslash
if (*cpString=='\\') *cpString='<EFBFBD>';
}
// Advance
cpString++; iLen--; if (iEscSequence) iEscSequence--;
}
}
void UnscrambleString(char *szString/*, int iLen, const char *szKey*/)
{
int iLen=-1;
const char *szKey = ScrambleDefaultKey;
if (!szString || !szKey) return;
char *cpString = szString;
const char *cpKey = szKey;
int iChar,iEscSequence=0;
if (iLen<0) iLen=SLen(szString);
while (iLen>0)
{
// Detect character escape sequences
if (*cpString=='\\') iEscSequence=2;
// Process character
if (!iEscSequence)
{
// Preconvert backslash
if (*cpString=='<EFBFBD>') *cpString='\\';
// Scramble range
if (Inside(*cpString,ScrambleRangeLow,ScrambleRangeHi))
{
// Unshift character
iChar= ((int) *cpString) - (BoundBy(*cpKey,ScrambleRangeLow,ScrambleRangeHi)-ScrambleRangeLow);
if (iChar<ScrambleRangeLow) iChar+=(ScrambleRangeHi-ScrambleRangeLow+1);
*cpString = (char) iChar;
// Advance key
cpKey++; if (!(*cpKey)) cpKey=szKey;
}
}
cpString++; iLen--; if (iEscSequence) iEscSequence--;
}
}
#define IDS_SEC_FREEFOLDERMAKER "<22>6ydHdln zhWijn"
#define IDS_SEC_FREEFOLDERS "<22>8>t`giiW.fcf>ECs;.P5l;OS>sV.X4nqNIyeoCrq.P5l;YS;lUh.c<Q>6afeU.cAS<SenIys.TtfC8dTs.f4H;QfogmkG.w4W0Mq^vMoqs.E4s"
const char *LoadSecStr(const char *szString)
{
static char szBuf[1024+1];
SCopy(szString,szBuf);
UnscrambleString(szBuf);
return szBuf+1;
}
C4ConfigShareware::C4ConfigShareware()
{
KeyFile[0] = 0;
InvalidKeyFile[0] = 0;
}
C4ConfigShareware::~C4ConfigShareware()
{
}
void C4ConfigShareware::Default()
{
ZeroMem(this, sizeof (C4ConfigShareware));
C4Config::Default();
}
BOOL C4ConfigShareware::Load(BOOL forceWorkingDirectory, const char *szCustomFile)
{
// Load standard config
if (!C4Config::Load(forceWorkingDirectory, szCustomFile)) return FALSE;
// Load registration
LoadRegistration();
// Done
return TRUE;
}
BOOL C4ConfigShareware::Save()
{
// Save standard config
if (!C4Config::Save()) return FALSE;
// Done
return TRUE;
}
BOOL C4ConfigShareware::Registered()
{
return RegistrationValid;
}
bool C4ConfigShareware::LoadRegistration()
{
// Reset error message(s)
RegistrationError.Clear();
// First look in configured KeyPath
if (Security.KeyPath[0])
{
char searchPath[_MAX_PATH] = "";
SCopy(GetKeyPath(), searchPath);
for (DirectoryIterator i(searchPath); *i; ++i)
if (WildcardMatch("*.c4k", *i))
if (LoadRegistration(*i))
return true;
else
SCopy(*i, InvalidKeyFile, CFG_MaxString);
}
// Then look in ExePath
for (DirectoryIterator i(General.ExePath); *i; ++i)
if (WildcardMatch("*.c4k", *i))
if (LoadRegistration(*i))
return true;
else
SCopy(*i, InvalidKeyFile, CFG_MaxString);
// No key file found
return HandleError("No valid key file found.");
}
bool C4ConfigShareware::LoadRegistration(const char *keyFile)
{
#ifdef C4CHECKMEMLEAKS
// Set registered flag
RegistrationValid = true;
SCopy("Debug User", General.Name);
return true;
#else
// Clear registered flag, general name, and key file names
RegistrationValid = false;
General.Name[0] = 0;
KeyFile[0] = 0;
InvalidKeyFile[0] = 0;
// Load registration key
char* delim = 0;
int regKeyLen = 0;
int dataLen = 0;
FILE* fh = fopen(keyFile, "rb");
if (!fh)
return HandleError("Cannot open key file.");
regKeyLen = fread(RegData, 1, MaxRegDataLen, fh);
fclose(fh);
delim = strstr(RegData, "\r\n\r\n");
if (!delim)
return HandleError("Invalid key data (no delimiter).");
dataLen = delim - RegData;
// Load public key from memory
EVP_PKEY* pubKey = loadPublicKey(Cert_Reg_XOR_Base64, true, true, XOR_Cert_Reg);
if (!pubKey)
return HandleError("Internal error on public key.");
// Verify registration data
if (verifyData(RegData, regKeyLen, pubKey) != 0)
{
clearPublicKey(pubKey);
return HandleError("Registration key not valid.");
}
// Clear public key
clearPublicKey(pubKey);
// Cuid blacklist (this will ban a certain player-id forever)
int i;
const char *BlackCuid[] = { "16206436", "36689615", "15315029", "13109693", "17584580", "14718309",
"10851931", "13295768", "15786864", "13769103",
NULL };
for (i = 0; BlackCuid[i]; i++)
if (SEqual2(SSearch(RegData, "Cuid="), BlackCuid[i]))
return HandleError("Invalid User-Id.");
// Cuid-Webcode blacklist (this will only ban a key with a given cuid-webcode combination, allowing to create a new key without assigning a new cuid)
const char *BlackCuidWebCode[] = { "16826502", "398ED5F7",
NULL, NULL };
for (i = 0; BlackCuidWebCode[i]; i += 2)
if (SEqual2(SSearch(RegData, "Cuid="), BlackCuidWebCode[i]) && SEqual2(SSearch(RegData, "WebCode="), BlackCuidWebCode[i + 1]))
return HandleError("Invalid User-Id/WebCode.");
// Now truncate the signature from the registration key
*delim = 0;
// Date check: Clonk Rage allows valid c4k keys which are newer than 2006-04-01
int iYear, iMonth, iDay;
sscanf(GetRegistrationData("Date"), "%d-%d-%d", &iYear, &iMonth, &iDay);
if ((iYear < 2006) || ((iYear == 2006) && (iMonth < 4)))
return HandleError("Registration key is too old for this version.");
// Set registered flag
RegistrationValid = true;
// Store valid keyfile name
SCopy(keyFile, KeyFile);
// Overwrite general config name (will be empty if not registered)
SAppend(GetRegistrationData("FirstName"), General.Name, CFG_MaxString);
SAppend(" ", General.Name, CFG_MaxString);
SAppend(GetRegistrationData("LastName"), General.Name, CFG_MaxString);
// Success
return true;
#endif
}
bool C4ConfigShareware::HandleError(const char *strMessage)
{
RegData[0] = 0;
Log(strMessage);
if (RegistrationError.getLength()) RegistrationError.Append(" ");
RegistrationError.Append(strMessage);
return false;
}
const char* C4ConfigShareware::GetRegistrationError()
{
return RegistrationError.getData();
}
const char* C4ConfigShareware::GetKeyFilename()
{
return KeyFile;
}
const char* C4ConfigShareware::GetInvalidKeyFilename()
{
return InvalidKeyFile;
}
const char* C4ConfigShareware::GetKeyPath()
{
// Returns the configured key path with environment variables expanded and terminating slash
static char strKeyPath[_MAX_PATH + 1];
if (!Security.KeyPath[0]) return "";
SCopy(Security.KeyPath, strKeyPath);
ExpandEnvironmentVariables(strKeyPath, _MAX_PATH);
AppendBackslash(strKeyPath);
return strKeyPath;
}
StdStrBuf C4ConfigShareware::GetKeyMD5()
{
if(!Registered()) return StdStrBuf();
// Calculate MD5 of full key data
BYTE Digest[MD5_DIGEST_LENGTH];
MD5(reinterpret_cast<BYTE *>(RegData), SLen(RegData), Digest);
// Convert to hex
StdStrBuf HexDigest;
for(int i = 0; i < MD5_DIGEST_LENGTH; i++)
HexDigest.AppendFormat("%02x", Digest[i]);
// Done
return HexDigest;
}
const char* C4ConfigShareware::GetRegistrationData(const char *strField)
{
// Notice: GetRegistrationData() is used by LoadRegistration even if the registered flag is not yet set.
static char strReturnValue[CFG_MaxString + 1];
// Find field in registration key
char strFieldMask[128 + 1 + 1];
strncpy(strFieldMask, strField, 128); strFieldMask[128] = 0;
strcat(strFieldMask, "=");
const char *pKeyField = strstr(RegData, strFieldMask);
// Field not found, key might be empty
if (!pKeyField) return "";
// Advance to value
pKeyField += strlen(strFieldMask);
// Get field value
int iValueLen = 256;
const char *pFieldEnd = strstr(pKeyField, "\x0d");
if (pFieldEnd) iValueLen = pFieldEnd - pKeyField;
iValueLen = Min(iValueLen, CFG_MaxString);
strncpy(strReturnValue, pKeyField, iValueLen); strReturnValue[iValueLen] = 0;
return strReturnValue;
}
void C4ConfigShareware::ClearRegistrationError()
{
// Reset error message(s)
RegistrationError.Clear();
}
bool C4ConfigShareware::IsConfidentialData(const char *szInput)
{
// safety
if (!szInput) return false;
// unreg users don't have confidential data
if (!Config.Registered()) return false;
// shouldn't send the webcode!
const char *szWebCode = GetRegistrationData("WebCode");
if (szWebCode && *szWebCode) if (SSearchNoCase(szInput, szWebCode))
{
/* if (fShowWarningMessage && ::pGUI)
::pGUI->ShowErrorMessage(LoadResStr("IDS_ERR_WARNINGYOUWERETRYINGTOSEN"));*/
return true;
}
// all OK
return false;
}
C4ConfigShareware Config;