WindowsXPKg/src/libumskt/confid/confid.cpp

582 lines
16 KiB
C++

/**
* This file is a part of the UMSKT Project
*
* Copyleft (C) 2019-2024 UMSKT Contributors (et.al.)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @FileCreated by WitherOrNot on 06/02/2023
* @Maintainer WitherOrNot
*
* @History {
* This algorithm was provided to the UMSKT project by diamondggg
* the history provided by diamondggg is that they are the originator of the code
* and was created in tandem with an acquaintance who knows number theory.
* The file dates suggest this code was written sometime in 2017/2018
*
* The algorithm was refactored by Neo in 2023
* }
*/
#include "confid.h"
/**
*
* @param x0
* @param x1
* @param x2
* @param x3
* @param x4
* @param x5
* @param priv
* @param modulus
* @param nonresidue
* @param isOffice
* @param isXPBrand
* @param flagVersion
* @return
*/
BOOL ConfirmationID::LoadHyperellipticCurve(QWORD x0, QWORD x1, QWORD x2, QWORD x3, QWORD x4, QWORD x5, Q_OWORD priv,
QWORD modulus, QWORD nonresidue, DWORD32 iidkey, BOOL isOffice,
BOOL isXPBrand, BYTE flagVersion)
{
QWORD fvals[6] = {x0, x1, x2, x3, x4, x5};
return LoadHyperellipticCurve(fvals, priv, modulus, nonresidue, iidkey, isOffice, isXPBrand, flagVersion);
}
/**
*
* @param f
* @param priv
* @param modulus
* @param nonresidue
* @param isOffice
* @param isXPBrand
* @param flagVersion
* @return
*/
BOOL ConfirmationID::LoadHyperellipticCurve(QWORD *f, Q_OWORD priv, QWORD modulus, QWORD nonresidue, DWORD32 iidkey,
BOOL isOffice, BOOL isXPBrand, BYTE flagVersion)
{
memcpy(&curve, f, sizeof(curve));
memcpy(&privateKey, &priv, sizeof(Q_OWORD));
MOD = modulus;
NON_RESIDUE = nonresidue;
this->isOffice = isOffice;
this->isXPBrand = isXPBrand;
this->flagVersion = flagVersion;
return true;
}
BOOL ConfirmationID::LoadHyperellipticCurve(const std::string &x0, const std::string &x1, const std::string &x2,
const std::string &x3, const std::string &x4, const std::string &x5,
const std::string &priv, const std::string &modulus,
const std::string &nonresidue, const std::string &iidkey, BOOL isOffice,
BOOL isXPBrand, BYTE flagVersion)
{
std::string f[6];
f[0] = x0;
f[1] = x1;
f[2] = x2;
f[3] = x3;
f[4] = x4;
f[5] = x5;
return LoadHyperellipticCurve(f, priv, modulus, nonresidue, iidkey, isOffice, isXPBrand, flagVersion);
}
BOOL ConfirmationID::LoadHyperellipticCurve(const std::string *f, const std::string &priv, const std::string &modulus,
const std::string &nonresidue, const std::string &iidkey, BOOL isOffice,
BOOL isXPBrand, BYTE flagVersion)
{
for (int i = 0; i < 6; i++)
{
EncodeN(IntegerS(f[i]), curve[i]);
}
EncodeN(IntegerS(priv), privateKey);
EncodeN(IntegerS(modulus), MOD);
EncodeN(IntegerS(nonresidue), NON_RESIDUE);
this->isOffice = isOffice;
this->isXPBrand = isXPBrand;
this->flagVersion = flagVersion;
return true;
}
/**
*
* @param pid
* @return
*/
DWORD32 ConfirmationID::calculateCheckDigit(DWORD32 pid)
{
DWORD32 i = 0, j = 0, k = 0;
for (j = pid; j; i += k)
{
k = j % 10;
j /= 10;
}
return ((10 * pid) - (i % 7)) + 7;
}
/**
*
* @param iid
* @param hwid
* @param version
*/
void ConfirmationID::decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD32 *version)
{
QWORD buffer[5];
for (BYTE i = 0; i < 5; i++)
{
memcpy(&buffer[i], (iid + (4 * i)), 4);
}
DWORD32 v1 = (buffer[3] & 0xFFFFFFF8) | 2;
DWORD32 v2 = ((buffer[3] & 7) << 29) | (buffer[2] >> 3);
QWORD hardwareIDVal = ((QWORD)v1 << 32) | v2;
for (BYTE i = 0; i < 8; ++i)
{
hwid[i] = (hardwareIDVal >> (8 * i)) & 0xFF;
}
*version = buffer[0] & 7;
}
/**
*
* @param buffer
* @param bufSize
* @param key
* @param keySize
*/
void ConfirmationID::Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize)
{
BYTE sha1_input[64], sha1_result[SHA1::DIGESTSIZE];
BYTE half = bufSize / 2;
auto digest = SHA1();
// assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
for (BYTE external_counter = 0; external_counter < 4; external_counter++)
{
memset(sha1_input, 0, sizeof(sha1_input));
if (isXPBrand)
{
memcpy(sha1_input, buffer + half, half);
memcpy(sha1_input + half, key, keySize);
sha1_input[half + keySize] = 0x80;
sha1_input[sizeof(sha1_input) - 1] = (half + keySize) * 8;
sha1_input[sizeof(sha1_input) - 2] = (half + keySize) * 8 / 0x100;
}
else if (isOffice)
{
sha1_input[0] = 0x79;
memcpy(sha1_input + 1, buffer + half, half);
memcpy(sha1_input + 1 + half, key, keySize);
sha1_input[1 + half + keySize] = 0x80;
sha1_input[sizeof(sha1_input) - 1] = (1 + half + keySize) * 8;
sha1_input[sizeof(sha1_input) - 2] = (1 + half + keySize) * 8 / 0x100;
}
digest.Update(sha1_input, sizeof(sha1_input));
digest.Final(sha1_result);
for (BYTE i = half & ~3; i < half; i++)
{
sha1_result[i] = sha1_result[i + 4 - (half & 3)];
}
for (BYTE i = 0; i < half; i++)
{
unsigned char tmp = buffer[i + half];
buffer[i + half] = buffer[i] ^ sha1_result[i];
buffer[i] = tmp;
}
}
}
/**
*
* @param buffer
* @param bufSize
* @param key
* @param keySize
*/
void ConfirmationID::Unmix(BYTE *buffer, BYTE bufSize, const BYTE key[4], BYTE keySize)
{
BYTE sha1_input[64], sha1_result[SHA1::DIGESTSIZE];
BYTE half = bufSize / 2;
auto digest = SHA1();
// assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
for (BYTE external_counter = 0; external_counter < 4; external_counter++)
{
memset(sha1_input, 0, sizeof(sha1_input));
if (isXPBrand)
{
memcpy(sha1_input, buffer, half);
memcpy(sha1_input + half, key, keySize);
sha1_input[half + keySize] = 0x80;
sha1_input[sizeof(sha1_input) - 1] = (half + keySize) * 8;
sha1_input[sizeof(sha1_input) - 2] = (half + keySize) * 8 / 0x100;
}
else if (isOffice)
{
sha1_input[0] = 0x79;
memcpy(sha1_input + 1, buffer, half);
memcpy(sha1_input + 1 + half, key, keySize);
sha1_input[1 + half + keySize] = 0x80;
sha1_input[sizeof(sha1_input) - 1] = (1 + half + keySize) * 8;
sha1_input[sizeof(sha1_input) - 2] = (1 + half + keySize) * 8 / 0x100;
}
digest.Update(sha1_input, sizeof(sha1_input));
digest.Final(sha1_result);
for (BYTE i = half & ~3; i < half; i++)
{
sha1_result[i] = sha1_result[i + 4 - (half & 3)];
}
for (BYTE i = 0; i < half; i++)
{
unsigned char tmp = buffer[i];
buffer[i] = buffer[i + half] ^ sha1_result[i];
buffer[i + half] = tmp;
}
}
}
/**
*
* @param installationIDIn
* @param confirmationIDOut
* @param productIDIn
* @return
*/
CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationIDIn, std::string &confirmationIDOut,
std::string &productIDIn)
{
DWORD32 version;
BYTE hardwareID[8];
BYTE installation_id[19]; // 10**45 < 256**19
BYTE productID[4];
BYTE installation_id_len = 0;
auto pid = &installationIDIn[0];
BYTE count = 0, totalCount = 0;
unsigned check = 0;
BYTE i;
Q_OWORD mod_inv;
memcpy(&mod_inv, &privateKey, sizeof(mod_inv));
for (; *pid; pid++)
{
if (*pid == ' ' || *pid == '-')
{
continue;
}
int d = *pid - '0';
if (d < 0 || d > 9)
{
return ERR_INVALID_CHARACTER;
}
if (count == 5 || mod_inv.qword[0] == 0)
{
if (!count)
{
return (totalCount == 45) ? ERR_TOO_LARGE : ERR_TOO_SHORT;
}
if (d != check % 7)
{
return (count < 5) ? ERR_TOO_SHORT : ERR_INVALID_CHECK_DIGIT;
}
check = 0;
count = 0;
continue;
}
check += (count % 2 ? d * 2 : d);
count++;
totalCount++;
if (totalCount > 45)
{
return ERR_TOO_LARGE;
}
unsigned char carry = d;
for (i = 0; i < installation_id_len; i++)
{
unsigned x = installation_id[i] * 10 + carry;
installation_id[i] = x & 0xFF;
carry = x >> 8;
}
if (carry)
{
assert(installation_id_len < sizeof(installation_id));
installation_id[installation_id_len++] = carry;
}
}
if (totalCount != 41 && totalCount < 45)
{
return ERR_TOO_SHORT;
}
for (; installation_id_len < sizeof(installation_id); installation_id_len++)
{
installation_id[installation_id_len] = 0;
}
Unmix(installation_id, totalCount == 41 ? 17 : 19, iid_key, 4);
if (installation_id[18] >= 0x10)
{
return ERR_UNKNOWN_VERSION;
}
#pragma pack(push, 1)
struct
{
QWORD HardwareID;
QWORD ProductIDLow;
BYTE ProductIDHigh;
WORD KeySHA1;
} parsed;
#pragma pack(pop)
if (isXPBrand)
{
memcpy(&parsed, installation_id, sizeof(parsed));
productID[0] = parsed.ProductIDLow & ((1 << 17) - 1);
productID[1] = (parsed.ProductIDLow >> 17) & ((1 << 10) - 1);
productID[2] = (parsed.ProductIDLow >> 27) & ((1 << 24) - 1);
version = (parsed.ProductIDLow >> 51) & 15;
productID[3] = (parsed.ProductIDLow >> 55) | (parsed.ProductIDHigh << 9);
if (flagVersion == 0)
{
if (version != (totalCount == 41 ? 9 : 10))
{
return ERR_UNKNOWN_VERSION;
}
}
else if (flagVersion != version)
{
return ERR_UNKNOWN_VERSION;
}
}
else if (isOffice)
{
decode_iid_new_version(installation_id, hardwareID, &version);
if (flagVersion != version)
{
return ERR_UNKNOWN_VERSION;
}
memcpy(&parsed, hardwareID, 8);
productID[0] = stoi(productIDIn.substr(0, 5));
auto channelid = productIDIn.substr(6, 3);
char *p = &channelid[0];
for (; *p; p++)
{
*p = toupper((unsigned char)*p);
}
if (strcmp(&channelid[0], "OEM") == 0)
{
productID[1] = stoi(productIDIn.substr(12, 3));
productID[2] = (stoi(productIDIn.substr(15, 1)) * 100000) + stoi(productIDIn.substr(18, 5));
productID[2] = calculateCheckDigit(productID[2]);
productID[3] = ((stoi(productIDIn.substr(10, 2))) * 1000) + productID[3];
}
else
{
productID[1] = stoi(productIDIn.substr(6, 3));
productID[2] = stoi(productIDIn.substr(10, 7));
productID[3] = stoi(productIDIn.substr(18, 5));
}
// fmt::print("ProductID: {}-{}-{}-{} \n", productID[0], productID[1], productID[2], productID[3]);
}
BYTE keybuf[16];
memcpy(keybuf, &parsed.HardwareID, 8);
QWORD productIdMixed =
(QWORD)productID[0] << 41 | (QWORD)productID[1] << 58 | (QWORD)productID[2] << 17 | productID[3];
memcpy(keybuf + 8, &productIdMixed, 8);
TDivisor d;
BYTE attempt;
Q_OWORD ulowhi;
memset(&ulowhi, 0, sizeof(ulowhi));
for (attempt = 0; attempt <= 0x80; attempt++)
{
if (isXPBrand)
{
ulowhi.byte[7] = attempt;
}
else if (isOffice)
{
ulowhi.byte[6] = attempt;
}
Mix(ulowhi.byte, 14, keybuf, 16);
QWORD x2 = residue->ui128_quotient_mod(ulowhi.qword[0], ulowhi.qword[1]);
QWORD x1 = ulowhi.qword[0] - x2 * MOD;
x2++;
d.u.qword[0] = residue->sub(residue->mul(x1, x1), residue->mul(NON_RESIDUE, residue->mul(x2, x2)));
d.u.qword[1] = residue->add(x1, x1);
if (divisor->find_divisor_v(&d))
{
break;
}
}
if (attempt > 0x80)
{
return ERR_UNLUCKY;
}
Q_OWORD priv;
memcpy(&priv, &privateKey, sizeof(priv));
divisor->mul128(&d, priv.qword[0], priv.qword[1], &d);
Q_OWORD e;
memset(&e, 0, sizeof(e));
if (d.u.qword[0] == BAD)
{
// we can not get the zero divisor, actually...
e.qword[0] = residue->__umul128(MOD + 2, MOD, &e.qword[1]);
}
else if (d.u.qword[1] == BAD)
{
// O(1/MOD) chance
// encoded = (unsigned __int128)(MOD + 1) * d.u[0] + MOD; // * MOD + d.u[0] is fine too
e.qword[0] = residue->__umul128(MOD + 1, d.u.qword[0], &e.qword[1]);
e.qword[0] += MOD;
e.qword[1] += (e.qword[0] < MOD);
}
else
{
QWORD x1 = (d.u.qword[1] % 2 ? d.u.qword[1] + MOD : d.u.qword[1]) / 2;
QWORD x2sqr = residue->sub(residue->mul(x1, x1), d.u.qword[0]);
QWORD x2 = residue->sqrt(x2sqr);
if (x2 == BAD)
{
x2 = residue->sqrt(residue->mul(x2sqr, residue->inv(NON_RESIDUE)));
assert(x2 != BAD);
e.qword[0] = residue->__umul128(MOD + 1, MOD + x2, &e.qword[1]);
e.qword[0] += x1;
e.qword[1] += (e.qword[0] < x1);
}
else
{
// points (-x1+x2, v(-x1+x2)) and (-x1-x2, v(-x1-x2))
QWORD x1a = residue->sub(x1, x2);
QWORD y1 = residue->sub(d.v.qword[0], residue->mul(d.v.qword[1], x1a));
QWORD x2a = residue->add(x1, x2);
QWORD y2 = residue->sub(d.v.qword[0], residue->mul(d.v.qword[1], x2a));
if (x1a > x2a)
{
QWORD tmp = x1a;
x1a = x2a;
x2a = tmp;
}
if ((y1 ^ y2) & 1)
{
QWORD tmp = x1a;
x1a = x2a;
x2a = tmp;
}
e.qword[0] = residue->__umul128(MOD + 1, x1a, &e.qword[1]);
e.qword[0] += x2a;
e.qword[1] += (e.qword[0] < x2a);
}
}
BYTE decimal[35];
for (i = 0; i < 35; i++)
{
unsigned c = e.byte[3] % 10;
e.byte[3] /= 10;
unsigned c2 = ((QWORD)c << 32 | e.byte[2]) % 10;
e.byte[2] = ((QWORD)c << 32 | e.byte[2]) / 10;
unsigned c3 = ((QWORD)c2 << 32 | e.byte[1]) % 10;
e.byte[1] = ((QWORD)c2 << 32 | e.byte[1]) / 10;
unsigned c4 = ((QWORD)c3 << 32 | e.byte[0]) % 10;
e.byte[0] = ((QWORD)c3 << 32 | e.byte[0]) / 10;
decimal[34 - i] = c4;
}
assert(e.byte[0] == 0 && e.byte[1] == 0 && e.byte[2] == 0 && e.byte[3] == 0);
char *q = &confirmationIDOut[0];
for (i = 0; i < 7; i++)
{
if (i)
{
*q++ = '-';
}
unsigned char *p = decimal + i * 5;
q[0] = p[0] + '0';
q[1] = p[1] + '0';
q[2] = p[2] + '0';
q[3] = p[3] + '0';
q[4] = p[4] + '0';
q[5] = ((p[0] + p[1] * 2 + p[2] + p[3] * 2 + p[4]) % 7) + '0';
q += 6;
}
return SUCCESS;
}