mirror of
https://github.com/Neo-Desktop/WindowsXPKg
synced 2025-12-02 08:15:13 +02:00
Refactor/Overhaul (#40)
* major refactor/overhaul
move generation implementation to libumskt/*
decouple CLI/Options (and JSON) from generation implementation
set groundwork for future shared library
use standardized PIDGEN2/PIDGEN3 naming convention
create a Windows Docker file for quick compilation
add Windows resouce file/header so we have an application icon on windows
use icon from @Endermanch (used with permission)
add support for fully-static linux/muslc-based compilation
add support for a dos/windows (i486+) binary using djgpp
add Dockerfile to compile gcc/djgpp/watt32/openssl to provide DOS (DPMI) binaries
add @Endermanch 's Vista+ documentation
update Readme for recent credits
* begin work on C linkage and emscripten buildpath
* Update CMake to include and build Crypto++
* move dllmain.cpp to the correct directory
* add rust port info to README.md
* re-add dropped changes from rebase
* update build config, specify windows XP version number for crypto++
* update dos-djgpp action to use new cmake builder and options
* update dos-djgpp to use UMSKT hosted forks
* update other workflows to include standard header
* remove crypto++ from build config for now
* use the new `shell` parameter in `threeal/cmake-action`
TODO: move to a stable version (v1.3.0) when ready
* use full commit hash because a shortened hash is unsupported
* add the required {0} parameter?
* add openssl 3.1.1 to windows github runners
* ensure linux matrix build compiles on the correct arch
---------
Co-authored-by: Neo <321592+Neo-Desktop@users.noreply.github.com>
This commit is contained in:
847
src/libumskt/confid/confid.cpp
Normal file
847
src/libumskt/confid/confid.cpp
Normal file
@@ -0,0 +1,847 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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
|
||||
* }
|
||||
*/
|
||||
|
||||
#include "confid.h"
|
||||
|
||||
#define MOD 0x16A6B036D7F2A79ULL
|
||||
#define NON_RESIDUE 43
|
||||
static const QWORD f[6] = {0, 0x21840136C85381ULL, 0x44197B83892AD0ULL, 0x1400606322B3B04ULL, 0x1400606322B3B04ULL, 1};
|
||||
|
||||
QWORD ConfirmationID::residue_add(QWORD x, QWORD y)
|
||||
{
|
||||
QWORD z = x + y;
|
||||
//z = z - (z >= MOD ? MOD : 0);
|
||||
if (z >= MOD)
|
||||
z -= MOD;
|
||||
return z;
|
||||
}
|
||||
|
||||
QWORD ConfirmationID::residue_sub(QWORD x, QWORD y)
|
||||
{
|
||||
QWORD z = x - y;
|
||||
//z += (x < y ? MOD : 0);
|
||||
if (x < y)
|
||||
z += MOD;
|
||||
return z;
|
||||
}
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_AMD64) || defined(__aarch64__) || (defined(__arm64__) && defined(__APPLE__))
|
||||
#ifdef __GNUC__
|
||||
inline QWORD ConfirmationID::__umul128(QWORD a, QWORD b, QWORD* hi)
|
||||
{
|
||||
OWORD r = (OWORD)a * (OWORD)b;
|
||||
*hi = r >> 64;
|
||||
return (QWORD) r;
|
||||
}
|
||||
#else
|
||||
#define __umul128 _umul128
|
||||
#endif
|
||||
#elif defined(__i386__) || defined(_M_IX86) || defined(__arm__) || defined(__EMSCRIPTEN__)
|
||||
inline QWORD ConfirmationID::__umul128(QWORD multiplier, QWORD multiplicand, QWORD *product_hi) {
|
||||
// multiplier = ab = a * 2^32 + b
|
||||
// multiplicand = cd = c * 2^32 + d
|
||||
// ab * cd = a * c * 2^64 + (a * d + b * c) * 2^32 + b * d
|
||||
QWORD a = multiplier >> 32;
|
||||
QWORD b = multiplier & 0xFFFFFFFF;
|
||||
QWORD c = multiplicand >> 32;
|
||||
QWORD d = multiplicand & 0xFFFFFFFF;
|
||||
|
||||
//QWORD ac = a * c;
|
||||
QWORD ad = a * d;
|
||||
//QWORD bc = b * c;
|
||||
QWORD bd = b * d;
|
||||
|
||||
QWORD adbc = ad + (b * c);
|
||||
QWORD adbc_carry = adbc < ad ? 1 : 0;
|
||||
|
||||
// multiplier * multiplicand = product_hi * 2^64 + product_lo
|
||||
QWORD product_lo = bd + (adbc << 32);
|
||||
QWORD product_lo_carry = product_lo < bd ? 1 : 0;
|
||||
*product_hi = (a * c) + (adbc >> 32) + (adbc_carry << 32) + product_lo_carry;
|
||||
|
||||
return product_lo;
|
||||
}
|
||||
#else
|
||||
#error Unknown architecture detected - please edit confid.cpp to tailor __umul128() your architecture
|
||||
#endif
|
||||
|
||||
QWORD ConfirmationID::ui128_quotient_mod(QWORD lo, QWORD hi)
|
||||
{
|
||||
// hi:lo * ceil(2**170/MOD) >> (64 + 64 + 42)
|
||||
QWORD prod1;
|
||||
__umul128(lo, 0x604fa6a1c6346a87, &prod1);
|
||||
QWORD part1hi;
|
||||
QWORD part1lo = __umul128(lo, 0x2d351c6d04f8b, &part1hi);
|
||||
QWORD part2hi;
|
||||
QWORD part2lo = __umul128(hi, 0x604fa6a1c6346a87, &part2hi);
|
||||
QWORD sum1 = part1lo + part2lo;
|
||||
unsigned sum1carry = (sum1 < part1lo);
|
||||
sum1 += prod1;
|
||||
sum1carry += (sum1 < prod1);
|
||||
QWORD prod2 = part1hi + part2hi + sum1carry;
|
||||
QWORD prod3hi;
|
||||
QWORD prod3lo = __umul128(hi, 0x2d351c6d04f8b, &prod3hi);
|
||||
prod3lo += prod2;
|
||||
prod3hi += (prod3lo < prod2);
|
||||
return (prod3lo >> 42) | (prod3hi << 22);
|
||||
}
|
||||
|
||||
QWORD ConfirmationID::residue_mul(QWORD x, QWORD y)
|
||||
{
|
||||
// * ceil(2**170/MOD) = 0x2d351 c6d04f8b|604fa6a1 c6346a87 for (p-1)*(p-1) max
|
||||
QWORD hi;
|
||||
QWORD lo = __umul128(x, y, &hi);
|
||||
QWORD quotient = ui128_quotient_mod(lo, hi);
|
||||
return lo - quotient * MOD;
|
||||
}
|
||||
|
||||
QWORD ConfirmationID::residue_pow(QWORD x, QWORD y)
|
||||
{
|
||||
if (y == 0)
|
||||
return 1;
|
||||
QWORD cur = x;
|
||||
while (!(y & 1)) {
|
||||
cur = residue_mul(cur, cur);
|
||||
y >>= 1;
|
||||
}
|
||||
QWORD res = cur;
|
||||
while ((y >>= 1) != 0) {
|
||||
cur = residue_mul(cur, cur);
|
||||
if (y & 1)
|
||||
res = residue_mul(res, cur);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
QWORD ConfirmationID::inverse(QWORD u, QWORD v)
|
||||
{
|
||||
//assert(u);
|
||||
int64_t tmp;
|
||||
int64_t xu = 1, xv = 0;
|
||||
QWORD v0 = v;
|
||||
while (u > 1) {
|
||||
QWORD d = v / u; QWORD remainder = v % u;
|
||||
tmp = u; u = remainder; v = tmp;
|
||||
tmp = xu; xu = xv - d * xu; xv = tmp;
|
||||
}
|
||||
xu += (xu < 0 ? v0 : 0);
|
||||
return xu;
|
||||
}
|
||||
|
||||
QWORD ConfirmationID::residue_inv(QWORD x)
|
||||
{
|
||||
return inverse(x, MOD);
|
||||
// return residue_pow(x, MOD - 2);
|
||||
}
|
||||
|
||||
#define BAD 0xFFFFFFFFFFFFFFFFull
|
||||
|
||||
QWORD ConfirmationID::residue_sqrt(QWORD what)
|
||||
{
|
||||
if (!what) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QWORD g = NON_RESIDUE, z, y, r, x, b, t;
|
||||
QWORD e = 0, q = MOD - 1;
|
||||
|
||||
while (!(q & 1)) {
|
||||
e++, q >>= 1;
|
||||
}
|
||||
|
||||
z = residue_pow(g, q);
|
||||
y = z;
|
||||
r = e;
|
||||
x = residue_pow(what, (q - 1) / 2);
|
||||
b = residue_mul(residue_mul(what, x), x);
|
||||
x = residue_mul(what, x);
|
||||
while (b != 1) {
|
||||
QWORD m = 0, b2 = b;
|
||||
|
||||
do {
|
||||
m++;
|
||||
b2 = residue_mul(b2, b2);
|
||||
} while (b2 != 1);
|
||||
|
||||
if (m == r) {
|
||||
return BAD;
|
||||
}
|
||||
|
||||
t = residue_pow(y, 1 << (r - m - 1));
|
||||
y = residue_mul(t, t);
|
||||
r = m;
|
||||
x = residue_mul(x, t);
|
||||
b = residue_mul(b, y);
|
||||
}
|
||||
|
||||
if (residue_mul(x, x) != what) {
|
||||
//printf("internal error in sqrt\n");
|
||||
return BAD;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
int ConfirmationID::find_divisor_v(TDivisor* d)
|
||||
{
|
||||
// u | v^2 - f
|
||||
// u = u0 + u1*x + x^2
|
||||
// f%u = f0 + f1*x
|
||||
QWORD v1, f2[6];
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
f2[i] = f[i];
|
||||
}
|
||||
|
||||
const QWORD u0 = d->u[0];
|
||||
const QWORD u1 = d->u[1];
|
||||
for (int j = 4; j--; ) {
|
||||
f2[j] = residue_sub(f2[j], residue_mul(u0, f2[j + 2]));
|
||||
f2[j + 1] = residue_sub(f2[j + 1], residue_mul(u1, f2[j + 2]));
|
||||
f2[j + 2] = 0;
|
||||
}
|
||||
// v = v0 + v1*x
|
||||
// u | (v0^2 - f0) + (2*v0*v1 - f1)*x + v1^2*x^2 = u0*v1^2 + u1*v1^2*x + v1^2*x^2
|
||||
// v0^2 - f0 = u0*v1^2
|
||||
// 2*v0*v1 - f1 = u1*v1^2
|
||||
// v0^2 = f0 + u0*v1^2 = (f1 + u1*v1^2)^2 / (2*v1)^2
|
||||
// (f1^2) + 2*(f1*u1-2*f0) * v1^2 + (u1^2-4*u0) * v1^4 = 0
|
||||
// v1^2 = ((2*f0-f1*u1) +- 2*sqrt(-f0*f1*u1 + f0^2 + f1^2*u0))) / (u1^2-4*u0)
|
||||
const QWORD f0 = f2[0];
|
||||
const QWORD f1 = f2[1];
|
||||
const QWORD u0double = residue_add(u0, u0);
|
||||
const QWORD coeff2 = residue_sub(residue_mul(u1, u1), residue_add(u0double, u0double));
|
||||
const QWORD coeff1 = residue_sub(residue_add(f0, f0), residue_mul(f1, u1));
|
||||
if (coeff2 == 0) {
|
||||
if (coeff1 == 0) {
|
||||
if (f1 == 0) {
|
||||
// impossible
|
||||
//printf("bad f(), double root detected\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
QWORD sqr = residue_mul(residue_mul(f1, f1), residue_inv(residue_add(coeff1, coeff1)));
|
||||
v1 = residue_sqrt(sqr);
|
||||
if (v1 == BAD) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
QWORD d = residue_add(residue_mul(f0, f0), residue_mul(f1, residue_sub(residue_mul(f1, u0), residue_mul(f0, u1))));
|
||||
d = residue_sqrt(d);
|
||||
if (d == BAD) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
d = residue_add(d, d);
|
||||
QWORD inv = residue_inv(coeff2);
|
||||
QWORD root = residue_mul(residue_add(coeff1, d), inv);
|
||||
v1 = residue_sqrt(root);
|
||||
if (v1 == BAD) {
|
||||
root = residue_mul(residue_sub(coeff1, d), inv);
|
||||
v1 = residue_sqrt(root);
|
||||
if (v1 == BAD) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QWORD v0 = residue_mul(residue_add(f1, residue_mul(u1, residue_mul(v1, v1))), residue_inv(residue_add(v1, v1)));
|
||||
d->v[0] = v0;
|
||||
d->v[1] = v1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// generic short slow code
|
||||
int ConfirmationID::polynomial_mul(int adeg, const QWORD a[], int bdeg, const QWORD b[], int resultprevdeg, QWORD result[])
|
||||
{
|
||||
if (adeg < 0 || bdeg < 0)
|
||||
return resultprevdeg;
|
||||
int i, j;
|
||||
for (i = resultprevdeg + 1; i <= adeg + bdeg; i++)
|
||||
result[i] = 0;
|
||||
resultprevdeg = i - 1;
|
||||
for (i = 0; i <= adeg; i++)
|
||||
for (j = 0; j <= bdeg; j++)
|
||||
result[i + j] = residue_add(result[i + j], residue_mul(a[i], b[j]));
|
||||
while (resultprevdeg >= 0 && result[resultprevdeg] == 0)
|
||||
--resultprevdeg;
|
||||
return resultprevdeg;
|
||||
}
|
||||
|
||||
int ConfirmationID::polynomial_div_monic(int adeg, QWORD a[], int bdeg, const QWORD b[], QWORD* quotient)
|
||||
{
|
||||
assert(bdeg >= 0);
|
||||
assert(b[bdeg] == 1);
|
||||
int i, j;
|
||||
for (i = adeg - bdeg; i >= 0; i--) {
|
||||
QWORD q = a[i + bdeg];
|
||||
if (quotient)
|
||||
quotient[i] = q;
|
||||
for (j = 0; j < bdeg; j++)
|
||||
a[i + j] = residue_sub(a[i + j], residue_mul(q, b[j]));
|
||||
a[i + j] = 0;
|
||||
}
|
||||
i += bdeg;
|
||||
while (i >= 0 && a[i] == 0)
|
||||
i--;
|
||||
return i;
|
||||
}
|
||||
void ConfirmationID::polynomial_xgcd(int adeg, const QWORD a[3], int bdeg, const QWORD b[3], int* pgcddeg, QWORD gcd[3], int* pmult1deg, QWORD mult1[3], int* pmult2deg, QWORD mult2[3])
|
||||
{
|
||||
int sdeg = -1;
|
||||
QWORD s[3] = {0, 0, 0};
|
||||
int mult1deg = 0;
|
||||
mult1[0] = 1; mult1[1] = 0; mult1[2] = 0;
|
||||
int tdeg = 0;
|
||||
QWORD t[3] = {1, 0, 0};
|
||||
int mult2deg = -1;
|
||||
mult2[0] = 0; mult2[1] = 0; mult2[2] = 0;
|
||||
int rdeg = bdeg;
|
||||
QWORD r[3] = {b[0], b[1], b[2]};
|
||||
int gcddeg = adeg;
|
||||
gcd[0] = a[0]; gcd[1] = a[1]; gcd[2] = a[2];
|
||||
// s*u1 + t*u2 = r
|
||||
// mult1*u1 + mult2*u2 = gcd
|
||||
while (rdeg >= 0) {
|
||||
if (rdeg > gcddeg) {
|
||||
unsigned tmp;
|
||||
int tmpi;
|
||||
tmp = rdeg; rdeg = gcddeg; gcddeg = tmp;
|
||||
tmpi = sdeg; sdeg = mult1deg; mult1deg = tmpi;
|
||||
tmpi = tdeg; tdeg = mult2deg; mult2deg = tmpi;
|
||||
QWORD tmp2;
|
||||
tmp2 = r[0]; r[0] = gcd[0]; gcd[0] = tmp2;
|
||||
tmp2 = r[1]; r[1] = gcd[1]; gcd[1] = tmp2;
|
||||
tmp2 = r[2]; r[2] = gcd[2]; gcd[2] = tmp2;
|
||||
tmp2 = s[0]; s[0] = mult1[0]; mult1[0] = tmp2;
|
||||
tmp2 = s[1]; s[1] = mult1[1]; mult1[1] = tmp2;
|
||||
tmp2 = s[2]; s[2] = mult1[2]; mult1[2] = tmp2;
|
||||
tmp2 = t[0]; t[0] = mult2[0]; mult2[0] = tmp2;
|
||||
tmp2 = t[1]; t[1] = mult2[1]; mult2[1] = tmp2;
|
||||
tmp2 = t[2]; t[2] = mult2[2]; mult2[2] = tmp2;
|
||||
continue;
|
||||
}
|
||||
int delta = gcddeg - rdeg;
|
||||
QWORD mult = residue_mul(gcd[gcddeg], residue_inv(r[rdeg]));
|
||||
// quotient = mult * x**delta
|
||||
assert(rdeg + delta < 3);
|
||||
for (int i = 0; i <= rdeg; i++)
|
||||
gcd[i + delta] = residue_sub(gcd[i + delta], residue_mul(mult, r[i]));
|
||||
while (gcddeg >= 0 && gcd[gcddeg] == 0)
|
||||
gcddeg--;
|
||||
assert(sdeg + delta < 3);
|
||||
for (int i = 0; i <= sdeg; i++)
|
||||
mult1[i + delta] = residue_sub(mult1[i + delta], residue_mul(mult, s[i]));
|
||||
if (mult1deg < sdeg + delta)
|
||||
mult1deg = sdeg + delta;
|
||||
while (mult1deg >= 0 && mult1[mult1deg] == 0)
|
||||
mult1deg--;
|
||||
assert(tdeg + delta < 3);
|
||||
for (int i = 0; i <= tdeg; i++)
|
||||
mult2[i + delta] = residue_sub(mult2[i + delta], residue_mul(mult, t[i]));
|
||||
if (mult2deg < tdeg + delta)
|
||||
mult2deg = tdeg + delta;
|
||||
while (mult2deg >= 0 && mult2[mult2deg] == 0)
|
||||
mult2deg--;
|
||||
}
|
||||
// d1 = gcd, e1 = mult1, e2 = mult2
|
||||
*pgcddeg = gcddeg;
|
||||
*pmult1deg = mult1deg;
|
||||
*pmult2deg = mult2deg;
|
||||
}
|
||||
|
||||
int ConfirmationID::u2poly(const TDivisor* src, QWORD polyu[3], QWORD polyv[2])
|
||||
{
|
||||
if (src->u[1] != BAD) {
|
||||
polyu[0] = src->u[0];
|
||||
polyu[1] = src->u[1];
|
||||
polyu[2] = 1;
|
||||
polyv[0] = src->v[0];
|
||||
polyv[1] = src->v[1];
|
||||
return 2;
|
||||
}
|
||||
if (src->u[0] != BAD) {
|
||||
polyu[0] = src->u[0];
|
||||
polyu[1] = 1;
|
||||
polyv[0] = src->v[0];
|
||||
polyv[1] = 0;
|
||||
return 1;
|
||||
}
|
||||
polyu[0] = 1;
|
||||
polyv[0] = 0;
|
||||
polyv[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConfirmationID::divisor_add(const TDivisor* src1, const TDivisor* src2, TDivisor* dst)
|
||||
{
|
||||
QWORD u1[3], u2[3], v1[2], v2[2];
|
||||
int u1deg = u2poly(src1, u1, v1);
|
||||
int u2deg = u2poly(src2, u2, v2);
|
||||
// extended gcd: d1 = gcd(u1, u2) = e1*u1 + e2*u2
|
||||
int d1deg, e1deg, e2deg;
|
||||
QWORD d1[3], e1[3], e2[3];
|
||||
polynomial_xgcd(u1deg, u1, u2deg, u2, &d1deg, d1, &e1deg, e1, &e2deg, e2);
|
||||
assert(e1deg <= 1);
|
||||
assert(e2deg <= 1);
|
||||
// extended gcd again: d = gcd(d1, v1+v2) = c1*d1 + c2*(v1+v2)
|
||||
QWORD b[3] = {residue_add(v1[0], v2[0]), residue_add(v1[1], v2[1]), 0};
|
||||
int bdeg = (b[1] == 0 ? (b[0] == 0 ? -1 : 0) : 1);
|
||||
int ddeg, c1deg, c2deg;
|
||||
QWORD d[3], c1[3], c2[3];
|
||||
polynomial_xgcd(d1deg, d1, bdeg, b, &ddeg, d, &c1deg, c1, &c2deg, c2);
|
||||
assert(c1deg <= 0);
|
||||
assert(c2deg <= 1);
|
||||
assert(ddeg >= 0);
|
||||
QWORD dmult = residue_inv(d[ddeg]);
|
||||
int i;
|
||||
for (i = 0; i < ddeg; i++)
|
||||
d[i] = residue_mul(d[i], dmult);
|
||||
d[i] = 1;
|
||||
for (i = 0; i <= c1deg; i++)
|
||||
c1[i] = residue_mul(c1[i], dmult);
|
||||
for (i = 0; i <= c2deg; i++)
|
||||
c2[i] = residue_mul(c2[i], dmult);
|
||||
QWORD u[5];
|
||||
int udeg = polynomial_mul(u1deg, u1, u2deg, u2, -1, u);
|
||||
// u is monic
|
||||
QWORD v[7], tmp[7];
|
||||
int vdeg, tmpdeg;
|
||||
// c1*(e1*u1*v2 + e2*u2*v1) + c2*(v1*v2 + f)
|
||||
// c1*(e1*u1*(v2-v1) + d1*v1) + c2*(v1*v2 + f)
|
||||
v[0] = residue_sub(v2[0], v1[0]);
|
||||
v[1] = residue_sub(v2[1], v1[1]);
|
||||
tmpdeg = polynomial_mul(e1deg, e1, 1, v, -1, tmp);
|
||||
vdeg = polynomial_mul(u1deg, u1, tmpdeg, tmp, -1, v);
|
||||
vdeg = polynomial_mul(d1deg, d1, 1, v1, vdeg, v);
|
||||
for (i = 0; i <= vdeg; i++)
|
||||
v[i] = residue_mul(v[i], c1[0]);
|
||||
memcpy(tmp, f, 6 * sizeof(f[0]));
|
||||
tmpdeg = 5;
|
||||
tmpdeg = polynomial_mul(1, v1, 1, v2, tmpdeg, tmp);
|
||||
vdeg = polynomial_mul(c2deg, c2, tmpdeg, tmp, vdeg, v);
|
||||
if (ddeg > 0) {
|
||||
assert(udeg >= 2*ddeg);
|
||||
QWORD udiv[5];
|
||||
polynomial_div_monic(udeg, u, ddeg, d, udiv); udeg -= ddeg;
|
||||
polynomial_div_monic(udeg, udiv, ddeg, d, u); udeg -= ddeg;
|
||||
if (vdeg >= 0) {
|
||||
assert(vdeg >= ddeg);
|
||||
polynomial_div_monic(vdeg, v, ddeg, d, udiv); vdeg -= ddeg;
|
||||
memcpy(v, udiv, (vdeg + 1) * sizeof(v[0]));
|
||||
}
|
||||
}
|
||||
vdeg = polynomial_div_monic(vdeg, v, udeg, u, NULL);
|
||||
while (udeg > 2) {
|
||||
assert(udeg <= 4);
|
||||
assert(vdeg <= 3);
|
||||
// u' = monic((f-v^2)/u), v'=-v mod u'
|
||||
tmpdeg = polynomial_mul(vdeg, v, vdeg, v, -1, tmp);
|
||||
for (i = 0; i <= tmpdeg && i <= 5; i++)
|
||||
tmp[i] = residue_sub(f[i], tmp[i]);
|
||||
for (; i <= tmpdeg; i++)
|
||||
tmp[i] = residue_sub(0, tmp[i]);
|
||||
for (; i <= 5; i++)
|
||||
tmp[i] = f[i];
|
||||
tmpdeg = i - 1;
|
||||
QWORD udiv[5];
|
||||
polynomial_div_monic(tmpdeg, tmp, udeg, u, udiv);
|
||||
udeg = tmpdeg - udeg;
|
||||
QWORD mult = residue_inv(udiv[udeg]);
|
||||
for (i = 0; i < udeg; i++)
|
||||
u[i] = residue_mul(udiv[i], mult);
|
||||
u[i] = 1;
|
||||
for (i = 0; i <= vdeg; i++)
|
||||
v[i] = residue_sub(0, v[i]);
|
||||
vdeg = polynomial_div_monic(vdeg, v, udeg, u, NULL);
|
||||
}
|
||||
if (udeg == 2) {
|
||||
dst->u[0] = u[0];
|
||||
dst->u[1] = u[1];
|
||||
dst->v[0] = (vdeg >= 0 ? v[0] : 0);
|
||||
dst->v[1] = (vdeg >= 1 ? v[1] : 0);
|
||||
} else if (udeg == 1) {
|
||||
dst->u[0] = u[0];
|
||||
dst->u[1] = BAD;
|
||||
dst->v[0] = (vdeg >= 0 ? v[0] : 0);
|
||||
dst->v[1] = BAD;
|
||||
} else {
|
||||
assert(udeg == 0);
|
||||
dst->u[0] = BAD;
|
||||
dst->u[1] = BAD;
|
||||
dst->v[0] = BAD;
|
||||
dst->v[1] = BAD;
|
||||
}
|
||||
}
|
||||
|
||||
#define divisor_double(src, dst) divisor_add(src, src, dst)
|
||||
|
||||
void ConfirmationID::divisor_mul(const TDivisor* src, QWORD mult, TDivisor* dst)
|
||||
{
|
||||
if (mult == 0) {
|
||||
dst->u[0] = BAD;
|
||||
dst->u[1] = BAD;
|
||||
dst->v[0] = BAD;
|
||||
dst->v[1] = BAD;
|
||||
return;
|
||||
}
|
||||
TDivisor cur = *src;
|
||||
while (!(mult & 1)) {
|
||||
divisor_double(&cur, &cur);
|
||||
mult >>= 1;
|
||||
}
|
||||
*dst = cur;
|
||||
while ((mult >>= 1) != 0) {
|
||||
divisor_double(&cur, &cur);
|
||||
if (mult & 1)
|
||||
divisor_add(dst, &cur, dst);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmationID::divisor_mul128(const TDivisor* src, QWORD mult_lo, QWORD mult_hi, TDivisor* dst)
|
||||
{
|
||||
if (mult_lo == 0 && mult_hi == 0) {
|
||||
dst->u[0] = BAD;
|
||||
dst->u[1] = BAD;
|
||||
dst->v[0] = BAD;
|
||||
dst->v[1] = BAD;
|
||||
return;
|
||||
}
|
||||
TDivisor cur = *src;
|
||||
while (!(mult_lo & 1)) {
|
||||
divisor_double(&cur, &cur);
|
||||
mult_lo >>= 1;
|
||||
if (mult_hi & 1)
|
||||
mult_lo |= (1ULL << 63);
|
||||
mult_hi >>= 1;
|
||||
}
|
||||
*dst = cur;
|
||||
for (;;) {
|
||||
mult_lo >>= 1;
|
||||
if (mult_hi & 1)
|
||||
mult_lo |= (1ULL << 63);
|
||||
mult_hi >>= 1;
|
||||
if (mult_lo == 0 && mult_hi == 0)
|
||||
break;
|
||||
divisor_double(&cur, &cur);
|
||||
if (mult_lo & 1)
|
||||
divisor_add(dst, &cur, dst);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned ConfirmationID::rol(unsigned x, int shift)
|
||||
{
|
||||
//assert(shift > 0 && shift < 32);
|
||||
return (x << shift) | (x >> (32 - shift));
|
||||
}
|
||||
|
||||
void ConfirmationID::sha1_single_block(unsigned char input[64], unsigned char output[20])
|
||||
{
|
||||
unsigned a, b, c, d, e;
|
||||
a = 0x67452301;
|
||||
b = 0xEFCDAB89;
|
||||
c = 0x98BADCFE;
|
||||
d = 0x10325476;
|
||||
e = 0xC3D2E1F0;
|
||||
unsigned w[80];
|
||||
size_t i;
|
||||
for (i = 0; i < 16; i++)
|
||||
w[i] = input[4*i] << 24 | input[4*i+1] << 16 | input[4*i+2] << 8 | input[4*i+3];
|
||||
for (i = 16; i < 80; i++)
|
||||
w[i] = rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
|
||||
for (i = 0; i < 20; i++) {
|
||||
unsigned tmp = rol(a, 5) + ((b & c) | (~b & d)) + e + w[i] + 0x5A827999;
|
||||
e = d;
|
||||
d = c;
|
||||
c = rol(b, 30);
|
||||
b = a;
|
||||
a = tmp;
|
||||
}
|
||||
for (i = 20; i < 40; i++) {
|
||||
unsigned tmp = rol(a, 5) + (b ^ c ^ d) + e + w[i] + 0x6ED9EBA1;
|
||||
e = d;
|
||||
d = c;
|
||||
c = rol(b, 30);
|
||||
b = a;
|
||||
a = tmp;
|
||||
}
|
||||
for (i = 40; i < 60; i++) {
|
||||
unsigned tmp = rol(a, 5) + ((b & c) | (b & d) | (c & d)) + e + w[i] + 0x8F1BBCDC;
|
||||
e = d;
|
||||
d = c;
|
||||
c = rol(b, 30);
|
||||
b = a;
|
||||
a = tmp;
|
||||
}
|
||||
for (i = 60; i < 80; i++) {
|
||||
unsigned tmp = rol(a, 5) + (b ^ c ^ d) + e + w[i] + 0xCA62C1D6;
|
||||
e = d;
|
||||
d = c;
|
||||
c = rol(b, 30);
|
||||
b = a;
|
||||
a = tmp;
|
||||
}
|
||||
a += 0x67452301;
|
||||
b += 0xEFCDAB89;
|
||||
c += 0x98BADCFE;
|
||||
d += 0x10325476;
|
||||
e += 0xC3D2E1F0;
|
||||
output[0] = a >> 24; output[1] = a >> 16; output[2] = a >> 8; output[3] = a;
|
||||
output[4] = b >> 24; output[5] = b >> 16; output[6] = b >> 8; output[7] = b;
|
||||
output[8] = c >> 24; output[9] = c >> 16; output[10] = c >> 8; output[11] = c;
|
||||
output[12] = d >> 24; output[13] = d >> 16; output[14] = d >> 8; output[15] = d;
|
||||
output[16] = e >> 24; output[17] = e >> 16; output[18] = e >> 8; output[19] = e;
|
||||
}
|
||||
|
||||
void ConfirmationID::Mix(unsigned char* buffer, size_t bufSize, const unsigned char* key, size_t keySize)
|
||||
{
|
||||
unsigned char sha1_input[64];
|
||||
unsigned char sha1_result[20];
|
||||
size_t half = bufSize / 2;
|
||||
//assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
|
||||
int external_counter;
|
||||
for (external_counter = 0; external_counter < 4; external_counter++) {
|
||||
memset(sha1_input, 0, sizeof(sha1_input));
|
||||
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;
|
||||
sha1_single_block(sha1_input, sha1_result);
|
||||
size_t i;
|
||||
for (i = half & ~3; i < half; i++)
|
||||
sha1_result[i] = sha1_result[i + 4 - (half & 3)];
|
||||
for (i = 0; i < half; i++) {
|
||||
unsigned char tmp = buffer[i + half];
|
||||
buffer[i + half] = buffer[i] ^ sha1_result[i];
|
||||
buffer[i] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmationID::Unmix(unsigned char* buffer, size_t bufSize, const unsigned char* key, size_t keySize)
|
||||
{
|
||||
unsigned char sha1_input[64];
|
||||
unsigned char sha1_result[20];
|
||||
size_t half = bufSize / 2;
|
||||
//assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
|
||||
int external_counter;
|
||||
for (external_counter = 0; external_counter < 4; external_counter++) {
|
||||
memset(sha1_input, 0, sizeof(sha1_input));
|
||||
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;
|
||||
sha1_single_block(sha1_input, sha1_result);
|
||||
size_t i;
|
||||
for (i = half & ~3; i < half; i++)
|
||||
sha1_result[i] = sha1_result[i + 4 - (half & 3)];
|
||||
for (i = 0; i < half; i++) {
|
||||
unsigned char tmp = buffer[i];
|
||||
buffer[i] = buffer[i + half] ^ sha1_result[i];
|
||||
buffer[i + half] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ConfirmationID::Generate(const char* installation_id_str, char confirmation_id[49])
|
||||
{
|
||||
unsigned char installation_id[19]; // 10**45 < 256**19
|
||||
size_t installation_id_len = 0;
|
||||
const char* p = installation_id_str;
|
||||
size_t count = 0, totalCount = 0;
|
||||
unsigned check = 0;
|
||||
size_t i;
|
||||
for (; *p; p++) {
|
||||
if (*p == ' ' || *p == '-')
|
||||
continue;
|
||||
int d = *p - '0';
|
||||
if (d < 0 || d > 9)
|
||||
return ERR_INVALID_CHARACTER;
|
||||
if (count == 5 || p[1] == 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;
|
||||
static const unsigned char iid_key[4] = {0x6A, 0xC8, 0x5E, 0xD4};
|
||||
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;
|
||||
unsigned char ProductIDHigh;
|
||||
unsigned short KeySHA1;
|
||||
} parsed;
|
||||
#pragma pack(pop)
|
||||
memcpy(&parsed, installation_id, sizeof(parsed));
|
||||
unsigned productId1 = parsed.ProductIDLow & ((1 << 17) - 1);
|
||||
unsigned productId2 = (parsed.ProductIDLow >> 17) & ((1 << 10) - 1);
|
||||
unsigned productId3 = (parsed.ProductIDLow >> 27) & ((1 << 25) - 1);
|
||||
unsigned version = (parsed.ProductIDLow >> 52) & 7;
|
||||
unsigned productId4 = (parsed.ProductIDLow >> 55) | (parsed.ProductIDHigh << 9);
|
||||
if (version != (totalCount == 41 ? 4 : 5))
|
||||
return ERR_UNKNOWN_VERSION;
|
||||
//printf("Product ID: %05u-%03u-%07u-%05u\n", productId1, productId2, productId3, productId4);
|
||||
|
||||
unsigned char keybuf[16];
|
||||
memcpy(keybuf, &parsed.HardwareID, 8);
|
||||
QWORD productIdMixed = (QWORD)productId1 << 41 | (QWORD)productId2 << 58 | (QWORD)productId3 << 17 | productId4;
|
||||
memcpy(keybuf + 8, &productIdMixed, 8);
|
||||
|
||||
TDivisor d;
|
||||
unsigned char attempt;
|
||||
for (attempt = 0; attempt <= 0x80; attempt++) {
|
||||
union {
|
||||
unsigned char buffer[14];
|
||||
struct {
|
||||
QWORD lo;
|
||||
QWORD hi;
|
||||
};
|
||||
} u;
|
||||
u.lo = 0;
|
||||
u.hi = 0;
|
||||
u.buffer[7] = attempt;
|
||||
Mix(u.buffer, 14, keybuf, 16);
|
||||
QWORD x2 = ui128_quotient_mod(u.lo, u.hi);
|
||||
QWORD x1 = u.lo - x2 * MOD;
|
||||
x2++;
|
||||
d.u[0] = residue_sub(residue_mul(x1, x1), residue_mul(NON_RESIDUE, residue_mul(x2, x2)));
|
||||
d.u[1] = residue_add(x1, x1);
|
||||
if (find_divisor_v(&d))
|
||||
break;
|
||||
}
|
||||
if (attempt > 0x80)
|
||||
return ERR_UNLUCKY;
|
||||
divisor_mul128(&d, 0x04e21b9d10f127c1, 0x40da7c36d44c, &d);
|
||||
union {
|
||||
struct {
|
||||
QWORD encoded_lo, encoded_hi;
|
||||
};
|
||||
struct {
|
||||
uint32_t encoded[4];
|
||||
};
|
||||
} e;
|
||||
if (d.u[0] == BAD) {
|
||||
// we can not get the zero divisor, actually...
|
||||
e.encoded_lo = __umul128(MOD + 2, MOD, &e.encoded_hi);
|
||||
} else if (d.u[1] == BAD) {
|
||||
// O(1/MOD) chance
|
||||
//encoded = (unsigned __int128)(MOD + 1) * d.u[0] + MOD; // * MOD + d.u[0] is fine too
|
||||
e.encoded_lo = __umul128(MOD + 1, d.u[0], &e.encoded_hi);
|
||||
e.encoded_lo += MOD;
|
||||
e.encoded_hi += (e.encoded_lo < MOD);
|
||||
} else {
|
||||
QWORD x1 = (d.u[1] % 2 ? d.u[1] + MOD : d.u[1]) / 2;
|
||||
QWORD x2sqr = residue_sub(residue_mul(x1, x1), d.u[0]);
|
||||
QWORD x2 = residue_sqrt(x2sqr);
|
||||
if (x2 == BAD) {
|
||||
x2 = residue_sqrt(residue_mul(x2sqr, residue_inv(NON_RESIDUE)));
|
||||
assert(x2 != BAD);
|
||||
e.encoded_lo = __umul128(MOD + 1, MOD + x2, &e.encoded_hi);
|
||||
e.encoded_lo += x1;
|
||||
e.encoded_hi += (e.encoded_lo < 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[0], residue_mul(d.v[1], x1a));
|
||||
QWORD x2a = residue_add(x1, x2);
|
||||
QWORD y2 = residue_sub(d.v[0], residue_mul(d.v[1], x2a));
|
||||
if (x1a > x2a) {
|
||||
QWORD tmp = x1a;
|
||||
x1a = x2a;
|
||||
x2a = tmp;
|
||||
}
|
||||
if ((y1 ^ y2) & 1) {
|
||||
QWORD tmp = x1a;
|
||||
x1a = x2a;
|
||||
x2a = tmp;
|
||||
}
|
||||
e.encoded_lo = __umul128(MOD + 1, x1a, &e.encoded_hi);
|
||||
e.encoded_lo += x2a;
|
||||
e.encoded_hi += (e.encoded_lo < x2a);
|
||||
}
|
||||
}
|
||||
unsigned char decimal[35];
|
||||
for (i = 0; i < 35; i++) {
|
||||
unsigned c = e.encoded[3] % 10;
|
||||
e.encoded[3] /= 10;
|
||||
unsigned c2 = ((QWORD)c << 32 | e.encoded[2]) % 10;
|
||||
e.encoded[2] = ((QWORD)c << 32 | e.encoded[2]) / 10;
|
||||
unsigned c3 = ((QWORD)c2 << 32 | e.encoded[1]) % 10;
|
||||
e.encoded[1] = ((QWORD)c2 << 32 | e.encoded[1]) / 10;
|
||||
unsigned c4 = ((QWORD)c3 << 32 | e.encoded[0]) % 10;
|
||||
e.encoded[0] = ((QWORD)c3 << 32 | e.encoded[0]) / 10;
|
||||
decimal[34 - i] = c4;
|
||||
}
|
||||
|
||||
assert(e.encoded[0] == 0 && e.encoded[1] == 0 && e.encoded[2] == 0 && e.encoded[3] == 0);
|
||||
char* q = confirmation_id;
|
||||
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;
|
||||
}
|
||||
*q++ = 0;
|
||||
return 0;
|
||||
}
|
||||
71
src/libumskt/confid/confid.h
Normal file
71
src/libumskt/confid/confid.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 6/6/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#ifndef UMSKT_CONFID_H
|
||||
#define UMSKT_CONFID_H
|
||||
|
||||
#include "../libumskt.h"
|
||||
|
||||
// Confirmation ID generator constants
|
||||
#define SUCCESS 0
|
||||
#define ERR_TOO_SHORT 1
|
||||
#define ERR_TOO_LARGE 2
|
||||
#define ERR_INVALID_CHARACTER 3
|
||||
#define ERR_INVALID_CHECK_DIGIT 4
|
||||
#define ERR_UNKNOWN_VERSION 5
|
||||
#define ERR_UNLUCKY 6
|
||||
|
||||
|
||||
typedef struct {
|
||||
QWORD u[2];
|
||||
QWORD v[2];
|
||||
} TDivisor;
|
||||
|
||||
EXPORT class ConfirmationID {
|
||||
static QWORD residue_add(QWORD x, QWORD y);
|
||||
static QWORD residue_sub(QWORD x, QWORD y);
|
||||
static QWORD __umul128(QWORD a, QWORD b, QWORD* hi);
|
||||
static QWORD ui128_quotient_mod(QWORD lo, QWORD hi);
|
||||
static QWORD residue_mul(QWORD x, QWORD y);
|
||||
static QWORD residue_pow(QWORD x, QWORD y);
|
||||
static QWORD inverse(QWORD u, QWORD v);
|
||||
static QWORD residue_inv(QWORD x);
|
||||
static QWORD residue_sqrt(QWORD what);
|
||||
static int find_divisor_v(TDivisor* d);
|
||||
static int polynomial_mul(int adeg, const QWORD a[], int bdeg, const QWORD b[], int resultprevdeg, QWORD result[]);
|
||||
static int polynomial_div_monic(int adeg, QWORD a[], int bdeg, const QWORD b[], QWORD* quotient);
|
||||
static void polynomial_xgcd(int adeg, const QWORD a[3], int bdeg, const QWORD b[3], int* pgcddeg, QWORD gcd[3], int* pmult1deg, QWORD mult1[3], int* pmult2deg, QWORD mult2[3]);
|
||||
static int u2poly(const TDivisor* src, QWORD polyu[3], QWORD polyv[2]);
|
||||
static void divisor_add(const TDivisor* src1, const TDivisor* src2, TDivisor* dst);
|
||||
static void divisor_mul(const TDivisor* src, QWORD mult, TDivisor* dst);
|
||||
static void divisor_mul128(const TDivisor* src, QWORD mult_lo, QWORD mult_hi, TDivisor* dst);
|
||||
static unsigned rol(unsigned x, int shift);
|
||||
static void sha1_single_block(unsigned char input[64], unsigned char output[20]);
|
||||
static void Mix(unsigned char* buffer, size_t bufSize, const unsigned char* key, size_t keySize);
|
||||
static void Unmix(unsigned char* buffer, size_t bufSize, const unsigned char* key, size_t keySize);
|
||||
|
||||
public:
|
||||
static int Generate(const char* installation_id_str, char confirmation_id[49]);
|
||||
//EXPORT static int CLIRun();
|
||||
};
|
||||
|
||||
#endif //UMSKT_CONFID_H
|
||||
35
src/libumskt/debugoutput.cpp
Normal file
35
src/libumskt/debugoutput.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 6/25/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#include "libumskt.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
std::FILE* UMSKT::debug = std::fopen("NUL:", "w");
|
||||
#else
|
||||
std::FILE* UMSKT::debug = std::fopen("/dev/null", "w");
|
||||
#endif
|
||||
|
||||
|
||||
void UMSKT::setDebugOutput(std::FILE* input) {
|
||||
debug = input;
|
||||
}
|
||||
60
src/libumskt/libumskt.cpp
Normal file
60
src/libumskt/libumskt.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 6/25/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#include "libumskt.h"
|
||||
#include "confid/confid.h"
|
||||
#include "pidgen3/PIDGEN3.h"
|
||||
#include "pidgen3/BINK1998.h"
|
||||
#include "pidgen3/BINK2002.h"
|
||||
#include "pidgen2/PIDGEN2.h"
|
||||
|
||||
FNEXPORT int ConfirmationID_Generate(const char* installation_id_str, char confirmation_id[49]) {
|
||||
return ConfirmationID::Generate(installation_id_str, confirmation_id);
|
||||
}
|
||||
|
||||
FNEXPORT EC_GROUP* PIDGEN3_initializeEllipticCurve(char* pSel, char* aSel, char* bSel, char* generatorXSel, char* generatorYSel, char* publicKeyXSel, char* publicKeyYSel, EC_POINT *&genPoint, EC_POINT *&pubPoint) {
|
||||
return PIDGEN3::initializeEllipticCurve(pSel, aSel, bSel, generatorXSel, generatorYSel, publicKeyXSel, publicKeyYSel, genPoint, pubPoint);
|
||||
}
|
||||
|
||||
FNEXPORT bool PIDGEN3_BINK1998_Verify(EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, char (&pKey)[25]) {
|
||||
return PIDGEN3::BINK1998::Verify(eCurve, basePoint, publicKey, pKey);
|
||||
}
|
||||
|
||||
FNEXPORT void PIDGEN3_BINK1998_Generate(EC_GROUP *eCurve, EC_POINT *basePoint, BIGNUM *genOrder, BIGNUM *privateKey, DWORD pSerial, BOOL pUpgrade,char (&pKey)[25]) {
|
||||
return PIDGEN3::BINK1998::Generate(eCurve, basePoint, genOrder, privateKey, pSerial, pUpgrade, pKey);
|
||||
}
|
||||
|
||||
FNEXPORT bool PIDGEN3_BINK2002_Verify(EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, char (&cdKey)[25]) {
|
||||
return PIDGEN3::BINK2002::Verify(eCurve, basePoint, publicKey, cdKey);
|
||||
}
|
||||
|
||||
FNEXPORT void PIDGEN3_BINK2002_Generate(EC_GROUP *eCurve, EC_POINT *basePoint, BIGNUM *genOrder, BIGNUM *privateKey, DWORD pChannelID, DWORD pAuthInfo, BOOL pUpgrade, char (&pKey)[25]) {
|
||||
return PIDGEN3::BINK2002::Generate(eCurve, basePoint, genOrder, privateKey, pChannelID, pAuthInfo, pUpgrade, pKey);
|
||||
}
|
||||
|
||||
FNEXPORT int PIDGEN2_GenerateRetail(char* channelID, char* &keyout) {
|
||||
return PIDGEN2::GenerateRetail(channelID, keyout);
|
||||
}
|
||||
|
||||
FNEXPORT int PIDGEN2_GenerateOEM(char* year, char* day, char* oem, char* keyout) {
|
||||
return PIDGEN2::GenerateOEM(year, day, oem, keyout);
|
||||
}
|
||||
73
src/libumskt/libumskt.h
Normal file
73
src/libumskt/libumskt.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 6/24/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#ifndef UMSKT_LIBUMSKT_H
|
||||
#define UMSKT_LIBUMSKT_H
|
||||
|
||||
#include "../typedefs.h"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
// Algorithm macros
|
||||
#define PK_LENGTH 25
|
||||
#define NULL_TERMINATOR 1
|
||||
|
||||
#define FIELD_BITS 384
|
||||
#define FIELD_BYTES 48
|
||||
#define FIELD_BITS_2003 512
|
||||
#define FIELD_BYTES_2003 64
|
||||
|
||||
#define SHA_MSG_LENGTH_XP (4 + 2 * FIELD_BYTES)
|
||||
#define SHA_MSG_LENGTH_2003 (3 + 2 * FIELD_BYTES_2003)
|
||||
|
||||
#define NEXTSNBITS(field, n, offset) (((QWORD)(field) >> (offset)) & ((1ULL << (n)) - 1))
|
||||
#define FIRSTNBITS(field, n) NEXTSNBITS((field), (n), 0)
|
||||
|
||||
#define HIBYTES(field, bytes) NEXTSNBITS((QWORD)(field), ((bytes) * 8), ((bytes) * 8))
|
||||
#define LOBYTES(field, bytes) FIRSTNBITS((QWORD)(field), ((bytes) * 8))
|
||||
|
||||
#define BYDWORD(n) (DWORD)(*((n) + 0) | *((n) + 1) << 8 | *((n) + 2) << 16 | *((n) + 3) << 24)
|
||||
#define BITMASK(n) ((1ULL << (n)) - 1)
|
||||
|
||||
class UMSKT {
|
||||
public:
|
||||
static std::FILE* debug;
|
||||
class PIDGEN2;
|
||||
class PIDGEN3;
|
||||
class ConfigurationID;
|
||||
|
||||
static void setDebugOutput(std::FILE* input);
|
||||
};
|
||||
|
||||
|
||||
#endif //UMSKT_LIBUMSKT_H
|
||||
135
src/libumskt/pidgen2/PIDGEN2.cpp
Normal file
135
src/libumskt/pidgen2/PIDGEN2.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 06/17/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#include "PIDGEN2.h"
|
||||
|
||||
const char* channelIDBlacklist [7] = {"333", "444", "555", "666", "777", "888", "999"};
|
||||
const char* validYears[8] = { "95", "96", "97", "98", "99", "00", "01", "02"};
|
||||
|
||||
bool PIDGEN2::isNumericString(char* input) {
|
||||
for(int i = 0; i < strlen(input); i++) {
|
||||
if (input[i] < '0' || input[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int PIDGEN2::addDigits(char* input) {
|
||||
int output = 0;
|
||||
|
||||
if (!isNumericString(input)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(int i = 0; i < strlen(input); i++) {
|
||||
output += input[i] - '0';
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool PIDGEN2::isValidChannelID(char* channelID) {
|
||||
if (strlen(channelID) > 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= 6; i++) {
|
||||
if (strcmp(channelID, channelIDBlacklist[i]) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PIDGEN2::isValidOEMID(char* OEMID) {
|
||||
if (!isNumericString(OEMID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen(OEMID) > 5) {
|
||||
if (OEMID[0] != '0' || OEMID[1] != '0') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int mod = addDigits(OEMID);
|
||||
|
||||
return (mod % 21 == 0);
|
||||
}
|
||||
|
||||
bool PIDGEN2::isValidYear(char* year) {
|
||||
for (int i = 0; i <= 7; i++) {
|
||||
if (year == validYears[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PIDGEN2::isValidDay(char* day) {
|
||||
if (!isNumericString(day)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int iDay = std::stoi(day);
|
||||
if (iDay == 0 || iDay >= 365) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PIDGEN2::isValidRetailProductID(char* productID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int PIDGEN2::GenerateRetail(char* channelID, char* &keyout) {
|
||||
if (!isValidChannelID(channelID)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PIDGEN2::GenerateOEM(char* year, char* day, char* oem, char* &keyout) {
|
||||
if (!isValidOEMID(oem)) {
|
||||
int mod = addDigits(oem);
|
||||
mod += mod % 21;
|
||||
|
||||
strcpy(oem, fmt::format("{:07d}", mod).c_str());
|
||||
}
|
||||
|
||||
if (!isValidYear(year)) {
|
||||
strcpy(year, validYears[0]);
|
||||
}
|
||||
|
||||
if (!isValidDay(day)) {
|
||||
int iday = std::stoi(day);
|
||||
iday = (iday + 1) % 365;
|
||||
}
|
||||
|
||||
strcpy(keyout, fmt::format("{}{}-OEM-{}-{}", year, day, oem, oem).c_str());
|
||||
|
||||
return 0;
|
||||
}
|
||||
41
src/libumskt/pidgen2/PIDGEN2.h
Normal file
41
src/libumskt/pidgen2/PIDGEN2.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 06/17/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#ifndef UMSKT_PIDGEN2_H
|
||||
#define UMSKT_PIDGEN2_H
|
||||
|
||||
#include "../libumskt.h"
|
||||
|
||||
EXPORT class PIDGEN2 {
|
||||
public:
|
||||
static bool isNumericString(char* input);
|
||||
static bool isValidChannelID(char* channelID);
|
||||
static bool isValidOEMID(char* OEMID);
|
||||
static bool isValidYear(char* year);
|
||||
static bool isValidDay(char* day);
|
||||
static bool isValidRetailProductID(char* productID);
|
||||
static int addDigits(char* input);
|
||||
static int GenerateRetail(char* channelID, char* &keyout);
|
||||
static int GenerateOEM(char* year, char* day, char* oem, char* &keyout);
|
||||
};
|
||||
|
||||
#endif //UMSKT_PIDGEN2_H
|
||||
289
src/libumskt/pidgen3/BINK1998.cpp
Normal file
289
src/libumskt/pidgen3/BINK1998.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Andrew on 01/06/2023
|
||||
* @Maintainer Andrew
|
||||
*
|
||||
* @History {
|
||||
* Algorithm was initially written and open sourced by z22
|
||||
* and uploaded to GitHub by TheMCHK in August of 2019
|
||||
*
|
||||
* Endermanch (Andrew) rewrote the algorithm in May of 2023
|
||||
* }
|
||||
*/
|
||||
|
||||
#include "BINK1998.h"
|
||||
|
||||
/* Unpacks a Windows XP-like Product Key. */
|
||||
void PIDGEN3::BINK1998::Unpack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL &pUpgrade,
|
||||
DWORD &pSerial,
|
||||
DWORD &pHash,
|
||||
QWORD &pSignature
|
||||
) {
|
||||
// We're assuming that the quantity of information within the product key is at most 114 bits.
|
||||
// log2(24^25) = 114.
|
||||
|
||||
// Upgrade = Bit 0
|
||||
pUpgrade = FIRSTNBITS(pRaw[0], 1);
|
||||
|
||||
// Serial = Bits [1..30] -> 30 bits
|
||||
pSerial = NEXTSNBITS(pRaw[0], 30, 1);
|
||||
|
||||
// Hash = Bits [31..58] -> 28 bits
|
||||
pHash = NEXTSNBITS(pRaw[0], 28, 31);
|
||||
|
||||
// Signature = Bits [59..113] -> 56 bits
|
||||
pSignature = FIRSTNBITS(pRaw[1], 51) << 5 | NEXTSNBITS(pRaw[0], 5, 59);
|
||||
}
|
||||
|
||||
/* Packs a Windows XP-like Product Key. */
|
||||
void PIDGEN3::BINK1998::Pack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL pUpgrade,
|
||||
DWORD pSerial,
|
||||
DWORD pHash,
|
||||
QWORD pSignature
|
||||
) {
|
||||
// The quantity of information the key provides is 114 bits.
|
||||
// We're storing it in 2 64-bit quad-words with 14 trailing bits.
|
||||
// 64 * 2 = 128
|
||||
|
||||
// Signature [114..59] <- Hash [58..31] <- Serial [30..1] <- Upgrade [0]
|
||||
pRaw[0] = FIRSTNBITS(pSignature, 5) << 59 | FIRSTNBITS(pHash, 28) << 31 | pSerial << 1 | pUpgrade;
|
||||
pRaw[1] = NEXTSNBITS(pSignature, 51, 5);
|
||||
}
|
||||
|
||||
/* Verifies a Windows XP-like Product Key. */
|
||||
bool PIDGEN3::BINK1998::Verify(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
EC_POINT *publicKey,
|
||||
char (&pKey)[25]
|
||||
) {
|
||||
BN_CTX *numContext = BN_CTX_new();
|
||||
|
||||
QWORD pRaw[2]{},
|
||||
pSignature;
|
||||
|
||||
DWORD pData,
|
||||
pSerial,
|
||||
pHash;
|
||||
|
||||
BOOL pUpgrade;
|
||||
|
||||
// Convert Base24 CD-key to bytecode.
|
||||
PIDGEN3::unbase24((BYTE *)pRaw, pKey);
|
||||
|
||||
// Extract RPK, hash and signature from bytecode.
|
||||
Unpack(pRaw, pUpgrade, pSerial, pHash, pSignature);
|
||||
|
||||
fmt::print(UMSKT::debug, "Validation results:\n");
|
||||
fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade);
|
||||
fmt::print(UMSKT::debug, " Serial: 0x{:08x}\n", pSerial);
|
||||
fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash);
|
||||
fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature);
|
||||
fmt::print(UMSKT::debug, "\n");
|
||||
|
||||
pData = pSerial << 1 | pUpgrade;
|
||||
|
||||
/*
|
||||
*
|
||||
* Scalars:
|
||||
* e = Hash
|
||||
* s = Schnorr Signature
|
||||
*
|
||||
* Points:
|
||||
* G(x, y) = Generator (Base Point)
|
||||
* K(x, y) = Public Key
|
||||
*
|
||||
* Equation:
|
||||
* P = sG + eK
|
||||
*
|
||||
*/
|
||||
|
||||
BIGNUM *e = BN_lebin2bn((BYTE *)&pHash, sizeof(pHash), nullptr),
|
||||
*s = BN_lebin2bn((BYTE *)&pSignature, sizeof(pSignature), nullptr),
|
||||
*x = BN_new(),
|
||||
*y = BN_new();
|
||||
|
||||
// Create 2 points on the elliptic curve.
|
||||
EC_POINT *t = EC_POINT_new(eCurve);
|
||||
EC_POINT *p = EC_POINT_new(eCurve);
|
||||
|
||||
// t = sG
|
||||
EC_POINT_mul(eCurve, t, nullptr, basePoint, s, numContext);
|
||||
|
||||
// P = eK
|
||||
EC_POINT_mul(eCurve, p, nullptr, publicKey, e, numContext);
|
||||
|
||||
// P += t
|
||||
EC_POINT_add(eCurve, p, t, p, numContext);
|
||||
|
||||
// x = P.x; y = P.y;
|
||||
EC_POINT_get_affine_coordinates(eCurve, p, x, y, numContext);
|
||||
|
||||
BYTE msgDigest[SHA_DIGEST_LENGTH]{},
|
||||
msgBuffer[SHA_MSG_LENGTH_XP]{},
|
||||
xBin[FIELD_BYTES]{},
|
||||
yBin[FIELD_BYTES]{};
|
||||
|
||||
// Convert resulting point coordinates to bytes.
|
||||
BN_bn2lebin(x, xBin, FIELD_BYTES);
|
||||
BN_bn2lebin(y, yBin, FIELD_BYTES);
|
||||
|
||||
// Assemble the SHA message.
|
||||
memcpy((void *)&msgBuffer[0], (void *)&pData, 4);
|
||||
memcpy((void *)&msgBuffer[4], (void *)xBin, FIELD_BYTES);
|
||||
memcpy((void *)&msgBuffer[4 + FIELD_BYTES], (void *)yBin, FIELD_BYTES);
|
||||
|
||||
// compHash = SHA1(pSerial || P.x || P.y)
|
||||
SHA1(msgBuffer, SHA_MSG_LENGTH_XP, msgDigest);
|
||||
|
||||
// Translate the byte digest into a 32-bit integer - this is our computed hash.
|
||||
// Truncate the hash to 28 bits.
|
||||
DWORD compHash = BYDWORD(msgDigest) >> 4 & BITMASK(28);
|
||||
|
||||
BN_free(e);
|
||||
BN_free(s);
|
||||
BN_free(x);
|
||||
BN_free(y);
|
||||
|
||||
BN_CTX_free(numContext);
|
||||
|
||||
EC_POINT_free(t);
|
||||
EC_POINT_free(p);
|
||||
|
||||
// If the computed hash checks out, the key is valid.
|
||||
return compHash == pHash;
|
||||
}
|
||||
|
||||
/* Generates a Windows XP-like Product Key. */
|
||||
void PIDGEN3::BINK1998::Generate(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
BIGNUM *genOrder,
|
||||
BIGNUM *privateKey,
|
||||
DWORD pSerial,
|
||||
BOOL pUpgrade,
|
||||
char (&pKey)[25]
|
||||
) {
|
||||
BN_CTX *numContext = BN_CTX_new();
|
||||
|
||||
BIGNUM *c = BN_new(),
|
||||
*s = BN_new(),
|
||||
*x = BN_new(),
|
||||
*y = BN_new();
|
||||
|
||||
QWORD pRaw[2]{},
|
||||
pSignature = 0;
|
||||
|
||||
// Data segment of the RPK.
|
||||
DWORD pData = pSerial << 1 | pUpgrade;
|
||||
|
||||
do {
|
||||
EC_POINT *r = EC_POINT_new(eCurve);
|
||||
|
||||
// Generate a random number c consisting of 384 bits without any constraints.
|
||||
BN_rand(c, FIELD_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY);
|
||||
|
||||
// Pick a random derivative of the base point on the elliptic curve.
|
||||
// R = cG;
|
||||
EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext);
|
||||
|
||||
// Acquire its coordinates.
|
||||
// x = R.x; y = R.y;
|
||||
EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext);
|
||||
|
||||
BYTE msgDigest[SHA_DIGEST_LENGTH]{},
|
||||
msgBuffer[SHA_MSG_LENGTH_XP]{},
|
||||
xBin[FIELD_BYTES]{},
|
||||
yBin[FIELD_BYTES]{};
|
||||
|
||||
// Convert coordinates to bytes.
|
||||
BN_bn2lebin(x, xBin, FIELD_BYTES);
|
||||
BN_bn2lebin(y, yBin, FIELD_BYTES);
|
||||
|
||||
// Assemble the SHA message.
|
||||
memcpy((void *)&msgBuffer[0], (void *)&pData, 4);
|
||||
memcpy((void *)&msgBuffer[4], (void *)xBin, FIELD_BYTES);
|
||||
memcpy((void *)&msgBuffer[4 + FIELD_BYTES], (void *)yBin, FIELD_BYTES);
|
||||
|
||||
// pHash = SHA1(pSerial || R.x || R.y)
|
||||
SHA1(msgBuffer, SHA_MSG_LENGTH_XP, msgDigest);
|
||||
|
||||
// Translate the byte digest into a 32-bit integer - this is our computed pHash.
|
||||
// Truncate the pHash to 28 bits.
|
||||
DWORD pHash = BYDWORD(msgDigest) >> 4 & BITMASK(28);
|
||||
|
||||
/*
|
||||
*
|
||||
* Scalars:
|
||||
* c = Random multiplier
|
||||
* e = Hash
|
||||
* s = Signature
|
||||
* n = Order of G
|
||||
* k = Private Key
|
||||
*
|
||||
* Points:
|
||||
* G(x, y) = Generator (Base Point)
|
||||
* R(x, y) = Random derivative of the generator
|
||||
* K(x, y) = Public Key
|
||||
*
|
||||
* We need to find the signature s that satisfies the equation with a given hash:
|
||||
* P = sG + eK
|
||||
* s = ek + c (mod n) <- computation optimization
|
||||
*/
|
||||
|
||||
// s = ek;
|
||||
BN_copy(s, privateKey);
|
||||
BN_mul_word(s, pHash);
|
||||
|
||||
// s += c (mod n)
|
||||
BN_mod_add(s, s, c, genOrder, numContext);
|
||||
|
||||
// Translate resulting scalar into a 64-bit integer (the byte order is little-endian).
|
||||
BN_bn2lebinpad(s, (BYTE *)&pSignature, BN_num_bytes(s));
|
||||
|
||||
// Pack product key.
|
||||
Pack(pRaw, pUpgrade, pSerial, pHash, pSignature);
|
||||
|
||||
fmt::print(UMSKT::debug, "Generation results:\n");
|
||||
fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade);
|
||||
fmt::print(UMSKT::debug, " Serial: 0x{:08x}\n", pSerial);
|
||||
fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash);
|
||||
fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature);
|
||||
fmt::print(UMSKT::debug, "\n");
|
||||
|
||||
EC_POINT_free(r);
|
||||
} while (pSignature > BITMASK(55));
|
||||
// ↑ ↑ ↑
|
||||
// The signature can't be longer than 55 bits, else it will
|
||||
// make the CD-key longer than 25 characters.
|
||||
|
||||
// Convert bytecode to Base24 CD-key.
|
||||
base24(pKey, (BYTE *)pRaw);
|
||||
|
||||
BN_free(c);
|
||||
BN_free(s);
|
||||
BN_free(x);
|
||||
BN_free(y);
|
||||
|
||||
BN_CTX_free(numContext);
|
||||
}
|
||||
64
src/libumskt/pidgen3/BINK1998.h
Normal file
64
src/libumskt/pidgen3/BINK1998.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 6/6/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#ifndef UMSKT_BINK1998_H
|
||||
#define UMSKT_BINK1998_H
|
||||
|
||||
#include "PIDGEN3.h"
|
||||
|
||||
EXPORT class PIDGEN3::BINK1998 {
|
||||
public:
|
||||
static void Unpack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL &pUpgrade,
|
||||
DWORD &pSerial,
|
||||
DWORD &pHash,
|
||||
QWORD &pSignature
|
||||
);
|
||||
|
||||
static void Pack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL pUpgrade,
|
||||
DWORD pSerial,
|
||||
DWORD pHash,
|
||||
QWORD pSignature
|
||||
);
|
||||
|
||||
static bool Verify(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
EC_POINT *publicKey,
|
||||
char (&pKey)[25]
|
||||
);
|
||||
|
||||
static void Generate(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
BIGNUM *genOrder,
|
||||
BIGNUM *privateKey,
|
||||
DWORD pSerial,
|
||||
BOOL pUpgrade,
|
||||
char (&pKey)[25]
|
||||
);
|
||||
};
|
||||
|
||||
#endif //UMSKT_BINK1998_H
|
||||
386
src/libumskt/pidgen3/BINK2002.cpp
Normal file
386
src/libumskt/pidgen3/BINK2002.cpp
Normal file
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Andrew on 01/06/2023
|
||||
* @Maintainer Andrew
|
||||
*
|
||||
* @History {
|
||||
* Algorithm was initially written and open sourced by z22
|
||||
* and uploaded to GitHub by TheMCHK in August of 2019
|
||||
*
|
||||
* Endermanch (Andrew) rewrote the algorithm in May of 2023
|
||||
* }
|
||||
*/
|
||||
|
||||
#include "BINK2002.h"
|
||||
|
||||
/* Unpacks a Windows Server 2003-like Product Key. */
|
||||
void PIDGEN3::BINK2002::Unpack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL &pUpgrade,
|
||||
DWORD &pChannelID,
|
||||
DWORD &pHash,
|
||||
QWORD &pSignature,
|
||||
DWORD &pAuthInfo
|
||||
) {
|
||||
// We're assuming that the quantity of information within the product key is at most 114 bits.
|
||||
// log2(24^25) = 114.
|
||||
|
||||
// Upgrade = Bit 0
|
||||
pUpgrade = FIRSTNBITS(pRaw[0], 1);
|
||||
|
||||
// Channel ID = Bits [1..10] -> 10 bits
|
||||
pChannelID = NEXTSNBITS(pRaw[0], 10, 1);
|
||||
|
||||
// Hash = Bits [11..41] -> 31 bits
|
||||
pHash = NEXTSNBITS(pRaw[0], 31, 11);
|
||||
|
||||
// Signature = Bits [42..103] -> 62 bits
|
||||
// The quad-word signature overlaps AuthInfo in bits 104 and 105,
|
||||
// hence Microsoft employs a secret technique called: Signature = HIDWORD(Signature) >> 2 | LODWORD(Signature)
|
||||
pSignature = NEXTSNBITS(pRaw[1], 30, 10) << 32 | FIRSTNBITS(pRaw[1], 10) << 22 | NEXTSNBITS(pRaw[0], 22, 42);
|
||||
|
||||
// AuthInfo = Bits [104..113] -> 10 bits
|
||||
pAuthInfo = NEXTSNBITS(pRaw[1], 10, 40);
|
||||
}
|
||||
|
||||
/* Packs a Windows Server 2003-like Product Key. */
|
||||
void PIDGEN3::BINK2002::Pack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL pUpgrade,
|
||||
DWORD pChannelID,
|
||||
DWORD pHash,
|
||||
QWORD pSignature,
|
||||
DWORD pAuthInfo
|
||||
) {
|
||||
// AuthInfo [113..104] <- Signature [103..42] <- Hash [41..11] <- Channel ID [10..1] <- Upgrade [0]
|
||||
pRaw[0] = FIRSTNBITS(pSignature, 22) << 42 | (QWORD)pHash << 11 | pChannelID << 1 | pUpgrade;
|
||||
pRaw[1] = FIRSTNBITS(pAuthInfo, 10) << 40 | NEXTSNBITS(pSignature, 40, 22);
|
||||
}
|
||||
|
||||
/* Verifies a Windows Server 2003-like Product Key. */
|
||||
bool PIDGEN3::BINK2002::Verify(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
EC_POINT *publicKey,
|
||||
char (&cdKey)[25]
|
||||
) {
|
||||
BN_CTX *context = BN_CTX_new();
|
||||
|
||||
QWORD bKey[2]{},
|
||||
pSignature = 0;
|
||||
|
||||
DWORD pData,
|
||||
pChannelID,
|
||||
pHash,
|
||||
pAuthInfo;
|
||||
|
||||
BOOL pUpgrade;
|
||||
|
||||
// Convert Base24 CD-key to bytecode.
|
||||
unbase24((BYTE *)bKey, cdKey);
|
||||
|
||||
// Extract product key segments from bytecode.
|
||||
Unpack(bKey, pUpgrade, pChannelID, pHash, pSignature, pAuthInfo);
|
||||
|
||||
pData = pChannelID << 1 | pUpgrade;
|
||||
|
||||
fmt::print(UMSKT::debug, "Validation results:\n");
|
||||
fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade);
|
||||
fmt::print(UMSKT::debug, "Channel ID: 0x{:08x}\n", pChannelID);
|
||||
fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash);
|
||||
fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature);
|
||||
fmt::print(UMSKT::debug, " AuthInfo: 0x{:08x}\n", pAuthInfo);
|
||||
fmt::print(UMSKT::debug, "\n");
|
||||
|
||||
BYTE msgDigest[SHA_DIGEST_LENGTH]{},
|
||||
msgBuffer[SHA_MSG_LENGTH_2003]{},
|
||||
xBin[FIELD_BYTES_2003]{},
|
||||
yBin[FIELD_BYTES_2003]{};
|
||||
|
||||
// Assemble the first SHA message.
|
||||
msgBuffer[0x00] = 0x5D;
|
||||
msgBuffer[0x01] = (pData & 0x00FF);
|
||||
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
|
||||
msgBuffer[0x03] = (pHash & 0x000000FF);
|
||||
msgBuffer[0x04] = (pHash & 0x0000FF00) >> 8;
|
||||
msgBuffer[0x05] = (pHash & 0x00FF0000) >> 16;
|
||||
msgBuffer[0x06] = (pHash & 0xFF000000) >> 24;
|
||||
msgBuffer[0x07] = (pAuthInfo & 0x00FF);
|
||||
msgBuffer[0x08] = (pAuthInfo & 0xFF00) >> 8;
|
||||
msgBuffer[0x09] = 0x00;
|
||||
msgBuffer[0x0A] = 0x00;
|
||||
|
||||
// newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00)
|
||||
SHA1(msgBuffer, 11, msgDigest);
|
||||
|
||||
// Translate the byte digest into a 64-bit integer - this is our computed intermediate signature.
|
||||
// As the signature is only 62 bits long at most, we have to truncate it by shifting the high DWORD right 2 bits (per spec).
|
||||
QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
|
||||
|
||||
/*
|
||||
*
|
||||
* Scalars:
|
||||
* e = Hash
|
||||
* s = Schnorr Signature
|
||||
*
|
||||
* Points:
|
||||
* G(x, y) = Generator (Base Point)
|
||||
* K(x, y) = Public Key
|
||||
*
|
||||
* Equation:
|
||||
* P = s(sG + eK)
|
||||
*
|
||||
*/
|
||||
|
||||
BIGNUM *e = BN_lebin2bn((BYTE *)&iSignature, sizeof(iSignature), nullptr),
|
||||
*s = BN_lebin2bn((BYTE *)&pSignature, sizeof(pSignature), nullptr),
|
||||
*x = BN_new(),
|
||||
*y = BN_new();
|
||||
|
||||
// Create 2 points on the elliptic curve.
|
||||
EC_POINT *p = EC_POINT_new(eCurve);
|
||||
EC_POINT *t = EC_POINT_new(eCurve);
|
||||
|
||||
// t = sG
|
||||
EC_POINT_mul(eCurve, t, nullptr, basePoint, s, context);
|
||||
|
||||
// p = eK
|
||||
EC_POINT_mul(eCurve, p, nullptr, publicKey, e, context);
|
||||
|
||||
// p += t
|
||||
EC_POINT_add(eCurve, p, t, p, context);
|
||||
|
||||
// p *= s
|
||||
EC_POINT_mul(eCurve, p, nullptr, p, s, context);
|
||||
|
||||
// x = p.x; y = p.y;
|
||||
EC_POINT_get_affine_coordinates(eCurve, p, x, y, context);
|
||||
|
||||
// Convert resulting point coordinates to bytes.
|
||||
BN_bn2lebin(x, xBin, FIELD_BYTES_2003);
|
||||
BN_bn2lebin(y, yBin, FIELD_BYTES_2003);
|
||||
|
||||
// Assemble the second SHA message.
|
||||
msgBuffer[0x00] = 0x79;
|
||||
msgBuffer[0x01] = (pData & 0x00FF);
|
||||
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
|
||||
|
||||
memcpy((void *)&msgBuffer[3], (void *)xBin, FIELD_BYTES_2003);
|
||||
memcpy((void *)&msgBuffer[3 + FIELD_BYTES_2003], (void *)yBin, FIELD_BYTES_2003);
|
||||
|
||||
// compHash = SHA1(79 || Channel ID || p.x || p.y)
|
||||
SHA1(msgBuffer, SHA_MSG_LENGTH_2003, msgDigest);
|
||||
|
||||
// Translate the byte digest into a 32-bit integer - this is our computed hash.
|
||||
// Truncate the hash to 31 bits.
|
||||
DWORD compHash = BYDWORD(msgDigest) & BITMASK(31);
|
||||
|
||||
BN_free(s);
|
||||
BN_free(e);
|
||||
BN_free(x);
|
||||
BN_free(y);
|
||||
|
||||
BN_CTX_free(context);
|
||||
|
||||
EC_POINT_free(p);
|
||||
EC_POINT_free(t);
|
||||
|
||||
// If the computed hash checks out, the key is valid.
|
||||
return compHash == pHash;
|
||||
}
|
||||
|
||||
/* Generates a Windows Server 2003-like Product Key. */
|
||||
void PIDGEN3::BINK2002::Generate(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
BIGNUM *genOrder,
|
||||
BIGNUM *privateKey,
|
||||
DWORD pChannelID,
|
||||
DWORD pAuthInfo,
|
||||
BOOL pUpgrade,
|
||||
char (&pKey)[25]
|
||||
) {
|
||||
BN_CTX *numContext = BN_CTX_new();
|
||||
|
||||
BIGNUM *c = BN_new(),
|
||||
*e = BN_new(),
|
||||
*s = BN_new(),
|
||||
*x = BN_new(),
|
||||
*y = BN_new();
|
||||
|
||||
QWORD pRaw[2]{},
|
||||
pSignature = 0;
|
||||
|
||||
// Data segment of the RPK.
|
||||
DWORD pData = pChannelID << 1 | pUpgrade;
|
||||
|
||||
BOOL noSquare;
|
||||
|
||||
do {
|
||||
EC_POINT *r = EC_POINT_new(eCurve);
|
||||
|
||||
// Generate a random number c consisting of 512 bits without any constraints.
|
||||
BN_rand(c, FIELD_BITS_2003, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY);
|
||||
|
||||
// R = cG
|
||||
EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext);
|
||||
|
||||
// Acquire its coordinates.
|
||||
// x = R.x; y = R.y;
|
||||
EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext);
|
||||
|
||||
BYTE msgDigest[SHA_DIGEST_LENGTH]{},
|
||||
msgBuffer[SHA_MSG_LENGTH_2003]{},
|
||||
xBin[FIELD_BYTES_2003]{},
|
||||
yBin[FIELD_BYTES_2003]{};
|
||||
|
||||
// Convert resulting point coordinates to bytes.
|
||||
BN_bn2lebin(x, xBin, FIELD_BYTES_2003);
|
||||
BN_bn2lebin(y, yBin, FIELD_BYTES_2003);
|
||||
|
||||
// Assemble the first SHA message.
|
||||
msgBuffer[0x00] = 0x79;
|
||||
msgBuffer[0x01] = (pData & 0x00FF);
|
||||
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
|
||||
|
||||
memcpy((void *)&msgBuffer[3], (void *)xBin, FIELD_BYTES_2003);
|
||||
memcpy((void *)&msgBuffer[3 + FIELD_BYTES_2003], (void *)yBin, FIELD_BYTES_2003);
|
||||
|
||||
// pHash = SHA1(79 || Channel ID || R.x || R.y)
|
||||
SHA1(msgBuffer, SHA_MSG_LENGTH_2003, msgDigest);
|
||||
|
||||
// Translate the byte digest into a 32-bit integer - this is our computed hash.
|
||||
// Truncate the hash to 31 bits.
|
||||
DWORD pHash = BYDWORD(msgDigest) & BITMASK(31);
|
||||
|
||||
// Assemble the second SHA message.
|
||||
msgBuffer[0x00] = 0x5D;
|
||||
msgBuffer[0x01] = (pData & 0x00FF);
|
||||
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
|
||||
msgBuffer[0x03] = (pHash & 0x000000FF);
|
||||
msgBuffer[0x04] = (pHash & 0x0000FF00) >> 8;
|
||||
msgBuffer[0x05] = (pHash & 0x00FF0000) >> 16;
|
||||
msgBuffer[0x06] = (pHash & 0xFF000000) >> 24;
|
||||
msgBuffer[0x07] = (pAuthInfo & 0x00FF);
|
||||
msgBuffer[0x08] = (pAuthInfo & 0xFF00) >> 8;
|
||||
msgBuffer[0x09] = 0x00;
|
||||
msgBuffer[0x0A] = 0x00;
|
||||
|
||||
// newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00)
|
||||
SHA1(msgBuffer, 11, msgDigest);
|
||||
|
||||
// Translate the byte digest into a 64-bit integer - this is our computed intermediate signature.
|
||||
// As the signature is only 62 bits long at most, we have to truncate it by shifting the high DWORD right 2 bits (per spec).
|
||||
QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
|
||||
|
||||
BN_lebin2bn((BYTE *)&iSignature, sizeof(iSignature), e);
|
||||
|
||||
/*
|
||||
*
|
||||
* Scalars:
|
||||
* c = Random multiplier
|
||||
* e = Intermediate Signature
|
||||
* s = Signature
|
||||
* n = Order of G
|
||||
* k = Private Key
|
||||
*
|
||||
* Points:
|
||||
* G(x, y) = Generator (Base Point)
|
||||
* R(x, y) = Random derivative of the generator
|
||||
* K(x, y) = Public Key
|
||||
*
|
||||
* Equation:
|
||||
* s(sG + eK) = R (mod p)
|
||||
* ↓ K = kG; R = cG ↓
|
||||
*
|
||||
* s(sG + ekG) = cG (mod p)
|
||||
* s(s + ek)G = cG (mod p)
|
||||
* ↓ G cancels out, the scalar arithmetic shrinks to order n ↓
|
||||
*
|
||||
* s(s + ek) = c (mod n)
|
||||
* s² + (ek)s - c = 0 (mod n)
|
||||
* ↓ This is a quadratic equation in respect to the signature ↓
|
||||
*
|
||||
* s = (-ek ± √((ek)² + 4c)) / 2 (mod n)
|
||||
*/
|
||||
|
||||
// e = ek (mod n)
|
||||
BN_mod_mul(e, e, privateKey, genOrder, numContext);
|
||||
|
||||
// s = e
|
||||
BN_copy(s, e);
|
||||
|
||||
// s = (ek (mod n))²
|
||||
BN_mod_sqr(s, s, genOrder, numContext);
|
||||
|
||||
// c *= 4 (c <<= 2)
|
||||
BN_lshift(c, c, 2);
|
||||
|
||||
// s += c
|
||||
BN_add(s, s, c);
|
||||
|
||||
// Around half of numbers modulo a prime are not squares -> BN_sqrt_mod fails about half of the times,
|
||||
// hence if BN_sqrt_mod returns NULL, we need to restart with a different seed.
|
||||
// s = √((ek)² + 4c (mod n))
|
||||
noSquare = BN_mod_sqrt(s, s, genOrder, numContext) == nullptr;
|
||||
|
||||
// s = -ek + √((ek)² + 4c) (mod n)
|
||||
BN_mod_sub(s, s, e, genOrder, numContext);
|
||||
|
||||
// If s is odd, add order to it.
|
||||
// The order is a prime, so it can't be even.
|
||||
if (BN_is_odd(s))
|
||||
|
||||
// s = -ek + √((ek)² + 4c) + n
|
||||
BN_add(s, s, genOrder);
|
||||
|
||||
// s /= 2 (s >>= 1)
|
||||
BN_rshift1(s, s);
|
||||
|
||||
// Translate resulting scalar into a 64-bit integer (the byte order is little-endian).
|
||||
BN_bn2lebinpad(s, (BYTE *)&pSignature, BN_num_bytes(s));
|
||||
|
||||
// Pack product key.
|
||||
Pack(pRaw, pUpgrade, pChannelID, pHash, pSignature, pAuthInfo);
|
||||
|
||||
fmt::print(UMSKT::debug, "Generation results:\n");
|
||||
fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade);
|
||||
fmt::print(UMSKT::debug, "Channel ID: 0x{:08x}\n", pChannelID);
|
||||
fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash);
|
||||
fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature);
|
||||
fmt::print(UMSKT::debug, " AuthInfo: 0x{:08x}\n", pAuthInfo);
|
||||
fmt::print(UMSKT::debug, "\n");
|
||||
|
||||
EC_POINT_free(r);
|
||||
} while (pSignature > BITMASK(62) || noSquare);
|
||||
// ↑ ↑ ↑
|
||||
// The signature can't be longer than 62 bits, else it will
|
||||
// overlap with the AuthInfo segment next to it.
|
||||
|
||||
// Convert bytecode to Base24 CD-key.
|
||||
base24(pKey, (BYTE *)pRaw);
|
||||
|
||||
BN_free(c);
|
||||
BN_free(s);
|
||||
BN_free(x);
|
||||
BN_free(y);
|
||||
BN_free(e);
|
||||
|
||||
BN_CTX_free(numContext);
|
||||
}
|
||||
67
src/libumskt/pidgen3/BINK2002.h
Normal file
67
src/libumskt/pidgen3/BINK2002.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 6/6/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#ifndef UMSKT_BINK2002_H
|
||||
#define UMSKT_BINK2002_H
|
||||
|
||||
#include "PIDGEN3.h"
|
||||
|
||||
EXPORT class PIDGEN3::BINK2002 {
|
||||
public:
|
||||
static void Unpack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL &pUpgrade,
|
||||
DWORD &pChannelID,
|
||||
DWORD &pHash,
|
||||
QWORD &pSignature,
|
||||
DWORD &pAuthInfo
|
||||
);
|
||||
|
||||
static void Pack(
|
||||
QWORD (&pRaw)[2],
|
||||
BOOL pUpgrade,
|
||||
DWORD pChannelID,
|
||||
DWORD pHash,
|
||||
QWORD pSignature,
|
||||
DWORD pAuthInfo
|
||||
);
|
||||
|
||||
static bool Verify(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
EC_POINT *publicKey,
|
||||
char (&cdKey)[25]
|
||||
);
|
||||
|
||||
static void Generate(
|
||||
EC_GROUP *eCurve,
|
||||
EC_POINT *basePoint,
|
||||
BIGNUM *genOrder,
|
||||
BIGNUM *privateKey,
|
||||
DWORD pChannelID,
|
||||
DWORD pAuthInfo,
|
||||
BOOL pUpgrade,
|
||||
char (&pKey)[25]
|
||||
);
|
||||
};
|
||||
|
||||
#endif //UMSKT_BINK2002_H
|
||||
54
src/libumskt/pidgen3/PIDGEN3.h
Normal file
54
src/libumskt/pidgen3/PIDGEN3.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 6/24/2023
|
||||
* @Maintainer Neo
|
||||
*/
|
||||
|
||||
#ifndef UMSKT_PIDGEN3_H
|
||||
#define UMSKT_PIDGEN3_H
|
||||
|
||||
#include "../libumskt.h"
|
||||
|
||||
class PIDGEN3 {
|
||||
public:
|
||||
class BINK1998;
|
||||
class BINK2002;
|
||||
|
||||
// util.cpp
|
||||
static int BN_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen); // Hello OpenSSL developers, please tell me, where is this function at?
|
||||
static void endian(BYTE *data, int length);
|
||||
static EC_GROUP* initializeEllipticCurve(
|
||||
std::string pSel,
|
||||
std::string aSel,
|
||||
std::string bSel,
|
||||
std::string generatorXSel,
|
||||
std::string generatorYSel,
|
||||
std::string publicKeyXSel,
|
||||
std::string publicKeyYSel,
|
||||
EC_POINT *&genPoint,
|
||||
EC_POINT *&pubPoint
|
||||
);
|
||||
|
||||
// key.cpp
|
||||
static constexpr char pKeyCharset[] = "BCDFGHJKMPQRTVWXY2346789";
|
||||
static void unbase24(BYTE *byteSeq, const char *cdKey);
|
||||
static void base24(char *cdKey, BYTE *byteSeq);
|
||||
};
|
||||
|
||||
#endif //UMSKT_PIDGEN3_H
|
||||
86
src/libumskt/pidgen3/key.cpp
Normal file
86
src/libumskt/pidgen3/key.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Neo on 5/26/2023
|
||||
* @Maintainer Andrew
|
||||
*/
|
||||
|
||||
#include "PIDGEN3.h"
|
||||
|
||||
/* Converts from CD-key to a byte sequence. */
|
||||
void PIDGEN3::unbase24(BYTE *byteSeq, const char *cdKey) {
|
||||
BYTE pDecodedKey[PK_LENGTH + NULL_TERMINATOR]{};
|
||||
BIGNUM *y = BN_new();
|
||||
|
||||
BN_zero(y);
|
||||
|
||||
// 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] == pKeyCharset[j]) {
|
||||
pDecodedKey[k++] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty byte sequence.
|
||||
memset(byteSeq, 0, 16);
|
||||
|
||||
// Calculate the weighed sum of byte array elements.
|
||||
for (int i = 0; i < PK_LENGTH; i++) {
|
||||
BN_mul_word(y, PK_LENGTH - 1);
|
||||
BN_add_word(y, pDecodedKey[i]);
|
||||
}
|
||||
|
||||
// Acquire length.
|
||||
int n = BN_num_bytes(y);
|
||||
|
||||
// Place the generated code into the byte sequence.
|
||||
BN_bn2bin(y, byteSeq);
|
||||
BN_free(y);
|
||||
|
||||
// Reverse the byte sequence.
|
||||
endian(byteSeq, n);
|
||||
}
|
||||
|
||||
/* Converts from byte sequence to the CD-key. */
|
||||
void PIDGEN3::base24(char *cdKey, BYTE *byteSeq) {
|
||||
BYTE rbyteSeq[16];
|
||||
BIGNUM *z;
|
||||
|
||||
// Copy byte sequence to the reversed byte sequence.
|
||||
memcpy(rbyteSeq, byteSeq, sizeof(rbyteSeq));
|
||||
|
||||
// Skip trailing zeroes and reverse y.
|
||||
int length;
|
||||
|
||||
for (length = 15; rbyteSeq[length] == 0; length--);
|
||||
endian(rbyteSeq, ++length);
|
||||
|
||||
// Convert reversed byte sequence to BigNum z.
|
||||
z = BN_bin2bn(rbyteSeq, length, nullptr);
|
||||
|
||||
// Divide z by 24 and convert the remainder to a CD-key char.
|
||||
cdKey[25] = 0;
|
||||
|
||||
for (int i = 24; i >= 0; i--)
|
||||
cdKey[i] = pKeyCharset[BN_div_word(z, 24)];
|
||||
|
||||
BN_free(z);
|
||||
}
|
||||
120
src/libumskt/pidgen3/util.cpp
Normal file
120
src/libumskt/pidgen3/util.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* This file is a part of the UMSKT Project
|
||||
*
|
||||
* Copyleft (C) 2019-2023 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 Andrew on 01/06/2023
|
||||
* @Maintainer Andrew
|
||||
*/
|
||||
|
||||
#include "PIDGEN3.h"
|
||||
|
||||
int randomRange() {
|
||||
return 4; // chosen by fair dice roll
|
||||
// guaranteed to be random
|
||||
}
|
||||
|
||||
/* Convert data between endianness types. */
|
||||
void PIDGEN3::endian(BYTE *data, int length) {
|
||||
for (int i = 0; i < length / 2; i++) {
|
||||
BYTE temp = data[i];
|
||||
data[i] = data[length - i - 1];
|
||||
data[length - i - 1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Initializes the elliptic curve. */
|
||||
EC_GROUP* PIDGEN3::initializeEllipticCurve(
|
||||
const std::string pSel,
|
||||
const std::string aSel,
|
||||
const std::string bSel,
|
||||
const std::string generatorXSel,
|
||||
const std::string generatorYSel,
|
||||
const std::string publicKeyXSel,
|
||||
const std::string publicKeyYSel,
|
||||
EC_POINT *&genPoint,
|
||||
EC_POINT *&pubPoint
|
||||
) {
|
||||
// Initialize BIGNUM and BIGNUMCTX structures.
|
||||
// BIGNUM - Large numbers
|
||||
// BIGNUMCTX - Context large numbers (temporary)
|
||||
BIGNUM *a, *b, *p, *generatorX, *generatorY, *publicKeyX, *publicKeyY;
|
||||
BN_CTX *context;
|
||||
|
||||
// We're presented with an elliptic curve, a multivariable function y(x; p; a; b), where
|
||||
// y^2 % p = x^3 + ax + b % p.
|
||||
a = BN_new();
|
||||
b = BN_new();
|
||||
p = BN_new();
|
||||
|
||||
// Public key will consist of the resulting (x; y) values.
|
||||
publicKeyX = BN_new();
|
||||
publicKeyY = BN_new();
|
||||
|
||||
// G(x; y) is a generator function, its return value represents a point on the elliptic curve.
|
||||
generatorX = BN_new();
|
||||
generatorY = BN_new();
|
||||
|
||||
// Context variable
|
||||
context = BN_CTX_new();
|
||||
|
||||
/* Public data */
|
||||
BN_dec2bn(&p, pSel.c_str());
|
||||
BN_dec2bn(&a, aSel.c_str());
|
||||
BN_dec2bn(&b, bSel.c_str());
|
||||
BN_dec2bn(&generatorX, generatorXSel.c_str());
|
||||
BN_dec2bn(&generatorY, generatorYSel.c_str());
|
||||
|
||||
BN_dec2bn(&publicKeyX, publicKeyXSel.c_str());
|
||||
BN_dec2bn(&publicKeyY, publicKeyYSel.c_str());
|
||||
|
||||
/* Elliptic Curve calculations. */
|
||||
// The group is defined via Fp = all integers [0; p - 1], where p is prime.
|
||||
// The function EC_POINT_set_affine_coordinates() sets the x and y coordinates for the point p defined over the curve given in group.
|
||||
EC_GROUP *eCurve = EC_GROUP_new_curve_GFp(p, a, b, context);
|
||||
|
||||
// Create new point for the generator on the elliptic curve and set its coordinates to (genX; genY).
|
||||
genPoint = EC_POINT_new(eCurve);
|
||||
EC_POINT_set_affine_coordinates(eCurve, genPoint, generatorX, generatorY, context);
|
||||
|
||||
// Create new point for the public key on the elliptic curve and set its coordinates to (pubX; pubY).
|
||||
pubPoint = EC_POINT_new(eCurve);
|
||||
EC_POINT_set_affine_coordinates(eCurve, pubPoint, publicKeyX, publicKeyY, context);
|
||||
|
||||
// If generator and public key points are not on the elliptic curve, either the generator or the public key values are incorrect.
|
||||
assert(EC_POINT_is_on_curve(eCurve, genPoint, context) == true);
|
||||
assert(EC_POINT_is_on_curve(eCurve, pubPoint, context) == true);
|
||||
|
||||
// Cleanup
|
||||
BN_CTX_free(context);
|
||||
|
||||
return eCurve;
|
||||
}
|
||||
|
||||
int PIDGEN3::BN_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen) {
|
||||
if (a == nullptr || to == nullptr)
|
||||
return 0;
|
||||
|
||||
int len = BN_bn2bin(a, to);
|
||||
|
||||
if (len > tolen)
|
||||
return -1;
|
||||
|
||||
// Choke point inside BN_bn2lebinpad: OpenSSL uses len instead of tolen.
|
||||
endian(to, tolen);
|
||||
|
||||
return len;
|
||||
}
|
||||
Reference in New Issue
Block a user