diff --git a/src/BINK1998.cpp b/src/BINK1998.cpp index c19f5cb..cda5a9d 100644 --- a/src/BINK1998.cpp +++ b/src/BINK1998.cpp @@ -94,6 +94,15 @@ bool BINK1998::Verify( // Extract RPK, hash and signature from bytecode. Unpack(pRaw, pUpgrade, pSerial, pHash, pSignature); + if (options.verbose) { + fmt::print("Validation results:\n"); + fmt::print(" Upgrade: 0x{:08x}\n", pUpgrade); + fmt::print(" Serial: 0x{:08x}\n", pSerial); + fmt::print(" Hash: 0x{:08x}\n", pHash); + fmt::print(" Signature: 0x{:08x}\n", pSignature); + fmt::print("\n"); + } + pData = pSerial << 1 | pUpgrade; /* diff --git a/src/cli.cpp b/src/cli.cpp index 1ece5a6..edefffa 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -53,6 +53,7 @@ void CLI::showHelp(char *argv[]) { fmt::print("\t-b --binkid\tspecify which BINK identifier to load (defaults to 2E)\n"); fmt::print("\t-l --list\tshow which products/binks can be loaded\n"); fmt::print("\t-c --channelid\tspecify which Channel Identifier to use (defaults to 640)\n"); + fmt::print("\t-V --validate\tproduct key to validate signature\n"); fmt::print("\n\n"); } @@ -61,13 +62,14 @@ int CLI::parseCommandLine(int argc, char* argv[], Options* options) { "2E", "keys.json", "", + "", 640, 1, false, false, false, false, - MODE_BINK1998 + MODE_BINK1998_GENERATE }; // set default options @@ -131,6 +133,15 @@ int CLI::parseCommandLine(int argc, char* argv[], Options* options) { options->instid = argv[i+1]; options->applicationMode = MODE_CONFIRMATION_ID; i++; + } else if (arg == "-V" || arg == "--validate") { + if (i == argc - 1) { + options->error = true; + break; + } + + options->keyToCheck = argv[i+1]; + options->applicationMode = MODE_BINK1998_VALIDATE; + i++; } else { options->error = true; } @@ -177,7 +188,8 @@ int CLI::validateCommandLine(Options* options, char *argv[], json *keys) { sscanf(options->binkid.c_str(), "%x", &intBinkID); if (intBinkID >= 0x40) { - options->applicationMode = MODE_BINK2002; + // set bink2002 validate mode if in bink1998 validate mode, else set bink2002 generate mode + options->applicationMode = (options->applicationMode == MODE_BINK1998_VALIDATE) ? MODE_BINK2002_VALIDATE : MODE_BINK2002_GENERATE; } if (options->channelID > 999) { @@ -219,7 +231,7 @@ void CLI::printID(DWORD *pid) } void CLI::printKey(char *pk) { - assert(strlen(pk) == 25); + assert(strlen(pk) >= PK_LENGTH); std::string spk = pk; fmt::print("{}-{}-{}-{}-{}", @@ -230,6 +242,29 @@ void CLI::printKey(char *pk) { spk.substr(20,5)); } +bool CLI::stripKey(const char *in_key, char out_key[PK_LENGTH]) { + // copy out the product key stripping out extraneous characters + const char *p = in_key; + size_t i = 0; + for (; *p; p++) { + // strip out space or dash + if (*p == ' ' || *p == '-') + continue; + // check if we've passed the product key length to avoid overflow + if (i >= PK_LENGTH) + return false; + // convert to uppercase - if character allowed, copy into array + for (int j = 0; j < strlen(pKeyCharset); j++) { + if (toupper(*p) == pKeyCharset[j]) { + out_key[i++] = toupper(*p); + continue; + } + } + } + // only return true if we've handled exactly PK_LENGTH chars + return (i == PK_LENGTH); +} + CLI::CLI(Options options, json keys) { this->options = options; this->keys = keys; @@ -280,7 +315,7 @@ CLI::CLI(Options options, json keys) { this->total = this->options.numKeys; } -int CLI::BINK1998() { +int CLI::BINK1998Generate() { DWORD nRaw = this->options.channelID * 1'000'000 ; /* <- change */ BIGNUM *bnrand = BN_new(); @@ -335,7 +370,7 @@ int CLI::BINK1998() { return 0; } -int CLI::BINK2002() { +int CLI::BINK2002Generate() { DWORD pChannelID = this->options.channelID; if (this->options.verbose) { @@ -353,6 +388,8 @@ int CLI::BINK2002() { } BINK2002::Generate(this->eCurve, this->genPoint, this->genOrder, this->privateKey, pChannelID, pAuthInfo, false, this->pKey); + CLI::printKey(this->pKey); + fmt::print("\n"); bool isValid = BINK2002::Verify(this->eCurve, this->genPoint, this->pubPoint, this->pKey); if (isValid) { @@ -384,6 +421,44 @@ int CLI::BINK2002() { return 0; } +int CLI::BINK1998Validate() { + char product_key[PK_LENGTH]{}; + + if (!CLI::stripKey(this->options.keyToCheck.c_str(), product_key)) { + fmt::print("ERROR: Product key is in an incorrect format!\n"); + return 1; + } + + CLI::printKey(product_key); + fmt::print("\n"); + if (!BINK1998::Verify(this->eCurve, this->genPoint, this->pubPoint, product_key)) { + fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n"); + return 1; + } + + fmt::print("Key validated successfully!\n"); + return 0; +} + +int CLI::BINK2002Validate() { + char product_key[PK_LENGTH]{}; + + if (!CLI::stripKey(this->options.keyToCheck.c_str(), product_key)) { + fmt::print("ERROR: Product key is in an incorrect format!\n"); + return 1; + } + + CLI::printKey(product_key); + fmt::print("\n"); + if (!BINK2002::Verify(this->eCurve, this->genPoint, this->pubPoint, product_key)) { + fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n"); + return 1; + } + + fmt::print("Key validated successfully!\n"); + return 0; +} + int CLI::ConfirmationID() { char confirmation_id[49]; int err = ConfirmationID::Generate(this->options.instid.c_str(), confirmation_id); diff --git a/src/cli.h b/src/cli.h index d5d4ed4..2a14ee9 100644 --- a/src/cli.h +++ b/src/cli.h @@ -44,9 +44,12 @@ public: static int validateCommandLine(Options* options, char *argv[], json *keys); static void printID(DWORD *pid); static void printKey(char *pk); + static bool stripKey(const char *in_key, char out_key[PK_LENGTH]); - int BINK1998(); - int BINK2002(); + int BINK1998Generate(); + int BINK2002Generate(); + int BINK1998Validate(); + int BINK2002Validate(); int ConfirmationID(); }; diff --git a/src/header.h b/src/header.h index 1ec94cc..bbd334e 100644 --- a/src/header.h +++ b/src/header.h @@ -76,15 +76,18 @@ using json = nlohmann::json; namespace fs = std::filesystem; enum MODE { - MODE_BINK1998 = 0, - MODE_BINK2002 = 1, + MODE_BINK1998_GENERATE = 0, + MODE_BINK2002_GENERATE = 1, MODE_CONFIRMATION_ID = 2, + MODE_BINK1998_VALIDATE = 3, + MODE_BINK2002_VALIDATE = 4, }; struct Options { std::string binkid; std::string keysFilename; std::string instid; + std::string keyToCheck; int channelID; int numKeys; bool verbose; @@ -126,6 +129,7 @@ EC_GROUP *initializeEllipticCurve( ); // key.cpp +extern char pKeyCharset[]; void unbase24(BYTE *byteSeq, const char *cdKey); void base24(char *cdKey, BYTE *byteSeq); diff --git a/src/key.cpp b/src/key.cpp index d95acf4..247d6ea 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -22,7 +22,8 @@ #include "header.h" -char pCharset[] = "BCDFGHJKMPQRTVWXY2346789"; +/* The allowed character set in a product key. */ +char pKeyCharset[] = "BCDFGHJKMPQRTVWXY2346789"; /* Converts from CD-key to a byte sequence. */ void unbase24(BYTE *byteSeq, const char *cdKey) { @@ -34,7 +35,7 @@ void unbase24(BYTE *byteSeq, const char *cdKey) { // Remove dashes from the CD-key and put it into a Base24 byte array. for (int i = 0, k = 0; i < strlen(cdKey) && k < PK_LENGTH; i++) { for (int j = 0; j < 24; j++) { - if (cdKey[i] != '-' && cdKey[i] == pCharset[j]) { + if (cdKey[i] != '-' && cdKey[i] == pKeyCharset[j]) { pDecodedKey[k++] = j; break; } @@ -82,7 +83,7 @@ void base24(char *cdKey, BYTE *byteSeq) { cdKey[25] = 0; for (int i = 24; i >= 0; i--) - cdKey[i] = pCharset[BN_div_word(z, 24)]; + cdKey[i] = pKeyCharset[BN_div_word(z, 24)]; BN_free(z); } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 86dce15..558a5b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,11 +42,17 @@ int main(int argc, char *argv[]) { CLI* run = new CLI(options, keys); switch(options.applicationMode) { - case MODE_BINK1998: - return run->BINK1998(); + case MODE_BINK1998_GENERATE: + return run->BINK1998Generate(); - case MODE_BINK2002: - return run->BINK2002(); + case MODE_BINK2002_GENERATE: + return run->BINK2002Generate(); + + case MODE_BINK1998_VALIDATE: + return run->BINK1998Validate(); + + case MODE_BINK2002_VALIDATE: + return run->BINK2002Validate(); case MODE_CONFIRMATION_ID: return run->ConfirmationID();