fix pidgen3, finally plumb in pid 2.0

This commit is contained in:
Neo-Desktop 2024-02-17 08:07:37 -08:00
parent 2ac7f9bc1e
commit 015fd00d3f
29 changed files with 1651 additions and 1211 deletions

View File

@ -57,23 +57,13 @@ jobs:
source ${{ github.workspace }}/djgpp/setenv
./configur.sh djgpp
make -f djgpp.mak
ln -s ${WATT_ROOT}/lib/libwatt.a ${{ github.workspace }}/djgpp/lib
- name: Checkout and Cross Compile OpenSSL 3.1.2
run: |
git clone https://github.com/UMSKT/openssl.git openssl
pushd openssl
source ${{ github.workspace }}/djgpp/setenv
./Configure no-threads -DOPENSSL_DEV_NO_ATOMICS --prefix=${{ github.workspace }}/djgpp DJGPP
make && make install
popd
ln -s ${WATT_ROOT}/lib/libwatt.a ${CMAKE_FIND_ROOT_PATH}/lib
- name: Build
run: |
source ${{ github.workspace }}/djgpp/setenv
pushd build
cmake ../ -D DJGPP_WATT32=${WATT_ROOT}/lib/libwatt.a -D CMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH}
make
cmake -DDJGPP_WATT32=${WATT_ROOT}/lib/libwatt.a -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} -DCMAKE_BUILD_TYPE=Release build/
cmake --build build/
- name: Move executable to upload directory
run: |

View File

@ -40,18 +40,15 @@
- name: Build & Test in FreeBSD
id: test
uses: vmactions/freebsd-vm@v1
with:
envs: 'MYTOKEN MYTOKEN2'
with:
usesh: true
prepare: |
pkg install -y cmake openssl git bash
pkg install -y cmake git bash
run: |
mkdir build
cd build
cmake ..
make
./umskt # Execute the test here
cmake -DCMAKE_BUILD_TYPE=Release build/
cmake --build build/
- name: Move files to correct directory
run: |

View File

@ -31,10 +31,10 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: x86
- arch: x86_64
- arch: aarch64
arch:
- x86
- x86_64
- aarch64
steps:
- name: Checkout Source Tree
uses: actions/checkout@v3
@ -48,16 +48,13 @@ jobs:
cmake
git
musl-dev
openssl-dev
openssl-libs-static
zlib-dev
arch: ${{ matrix.arch }}
shell-name: alpine-target.sh
- name: Configure and build UMSKT
uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8
uses: threeal/cmake-action@v1.3.0
with:
options: MUSL_STATIC=ON
options: MUSL_STATIC=ON CMAKE_BUILD_TYPE=Release
run-build: true
shell: alpine-target.sh {0}
@ -71,17 +68,3 @@ jobs:
with:
name: UMSKT-linux-${{ matrix.arch }}-static
path: build/actions_upload
- name: Configure and build static internal deps UMSKT
uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8
with:
options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=OFF
run-build: true
shell: alpine-target.sh {0}
- name: Configure and build shared deps UMSKT
uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8
with:
options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=ON
run-build: true
shell: alpine-target.sh {0}

View File

@ -27,34 +27,30 @@ on:
workflow_dispatch:
jobs:
build-x86:
build:
runs-on: macos-latest
strategy:
matrix:
include:
- arch: x86_64
arch:
- { name: arm64;x86_64, displayName: all-x86_64_arm64}
- { name: x86_64, displayName: x86_64 }
- { name: arm64, displayName: arm64 }
steps:
- name: Checkout Source Tree
uses: actions/checkout@v3
- name: Configure and build UMSKT
- name: Configure and build UMSKT ${{ matrix.arch.displayName }}
run: |
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch.name }} build/
cmake --build build/
- name: Move files to correct directory
run: |
mkdir -p build/actions_upload
mv build/umskt build/actions_upload/umskt
- name: Run tests
run: |
cd build/actions_upload
./umskt
- name: Upload build artifact
uses: actions/upload-artifact@v3.1.2
with:
name: UMSKT-macOS-${{ matrix.arch }}
name: UMSKT-macOS-${{ matrix.arch.displayName }}
path: build/actions_upload

View File

@ -27,7 +27,7 @@ on:
workflow_dispatch:
jobs:
build-32bit:
prepare:
runs-on: windows-latest
steps:
# https://github.com/actions/runner-images/issues/6067#issuecomment-1213069040
@ -52,97 +52,30 @@ jobs:
exit 1
}
- name: Download And Install 32-bit OpenSSL 3.1.4
run: |
$installDir = "$Env:ProgramFiles\OpenSSL"
$installerURL = "https://slproweb.com/download/Win32OpenSSL-3_1_4.exe"
$installerName = "Win32OpenSSL-3_1_4.exe"
$installerPath = Join-Path -Path "${env:Temp}" -ChildPath "$installerName"
(New-Object System.Net.WebClient).DownloadFile($installerURL, $installerPath)
Remove-Item "$installDir" -Force -Recurse
$installerArgs = '/silent', '/sp-', '/suppressmsgboxes', "/DIR=`"$installDir`""
Start-Process -FilePath $installerPath -ArgumentList $installerArgs -Wait -PassThru
- name: Checkout Source Tree
uses: actions/checkout@v3
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1
- name: Configure UMSKT
uses: threeal/cmake-action@v1.2.0
with:
generator: "Visual Studio 17 2022"
options: CMAKE_SYSTEM_VERSION="5.1.2600"
args: -A "Win32" -T v141_xp
- name: Build UMSKT
working-directory: build
run: msbuild ALL_BUILD.vcxproj /P:Configuration=Release
- name: Upload build artifact
uses: actions/upload-artifact@v3.1.2
with:
name: UMSKT-Win32
path: build/Release
build-64bit:
build:
runs-on: windows-latest
needs: prepare
strategy:
matrix:
arch: [ { name: Win32, displayName: x86 }, { name: x64, displayName: x64} ]
steps:
- name: Install Windows XP Support for Visual Studio
run: |
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
$InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
$componentsToAdd = @(
"Microsoft.VisualStudio.Component.WinXP"
)
[string]$workloadArgs = $componentsToAdd | ForEach-Object {" --add " + $_}
$Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"",$workloadArgs, '--quiet', '--norestart', '--nocache')
$process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden
if ($process.ExitCode -eq 0)
{
Write-Host "components have been successfully added"
Get-ChildItem C:\ProgramData\Microsoft\VisualStudio\Packages\Microsoft.Windows.XPSupport.*
}
else
{
Write-Host "components were not installed"
exit 1
}
- name: Download And Install 64-bit OpenSSL 3.1.4
run: |
$installDir = "$Env:ProgramFiles\OpenSSL"
$installerURL = "https://slproweb.com/download/Win64OpenSSL-3_1_4.exe"
$installerName = "Win64OpenSSL-3_1_4.exe"
$installerPath = Join-Path -Path "${env:Temp}" -ChildPath "$installerName"
(New-Object System.Net.WebClient).DownloadFile($installerURL, $installerPath)
Remove-Item "$installDir" -Force -Recurse
$installerArgs = '/silent', '/sp-', '/suppressmsgboxes', "/DIR=`"$installDir`""
Start-Process -FilePath $installerPath -ArgumentList $installerArgs -Wait -PassThru
- name: Checkout Source Tree
uses: actions/checkout@v3
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1
- name: Configure UMSKT
uses: threeal/cmake-action@v1.2.0
- name: Configure and build UMSKT for ${{ matrix.values.displayName }}
uses: threeal/cmake-action@v1.3.0
with:
options: CMAKE_BUILD_TYPE=Release
generator: "Visual Studio 17 2022"
args: -A "x64" -T "v141_xp"
- name: Build UMSKT
working-directory: build
run: msbuild ALL_BUILD.vcxproj /P:Configuration=Release
args: -A "${{ matrix.arch.name }}" -T "v141_xp"
run-build: true
build-args: "--config Release"
- name: Upload build artifact
uses: actions/upload-artifact@v3.1.2
with:
name: UMSKT-Win64
name: UMSKT-${{ matrix.values.displayName }}
path: build/Release

View File

@ -34,11 +34,12 @@ SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS})
SET(UMSKT_LINK_DIRS ${UMSKT_LINK_DIRS})
IF (NOT MSVC)
SET(CMAKE_CXX_FLAGS "-Os -fdata-sections -ffunction-sections -flto -Wl,--gc-sections")
SET(CMAKE_CXX_FLAGS "-Os -fdata-sections -ffunction-sections -flto=auto -Wl,--gc-sections")
SET(CMAKE_CXX_FLAGS_DEBUG "-g")
SET(CMAKE_CXX_FLAGS_DEBUG_INIT "-Wall -Wextra")
SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -Wl,--gc-sections")
SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -Wl,--gc-sections")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "-Wall -Wextra")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
ENDIF ()
IF (DJGPP_WATT32)
@ -135,7 +136,7 @@ CMRC_ADD_RESOURCE_LIBRARY(umskt-rc ALIAS umskt::rc NAMESPACE umskt keys.json)
SET(LIBUMSKT_PIDGEN2 src/libumskt/pidgen2/PIDGEN2.cpp)
SET(LIBUMSKT_PIDGEN3 src/libumskt/pidgen3/PIDGEN3.cpp src/libumskt/pidgen3/BINK1998.cpp src/libumskt/pidgen3/BINK2002.cpp)
SET(LIBUMSKT_CONFID src/libumskt/confid/confid.cpp src/libumskt/confid/polynomial.cpp src/libumskt/confid/residue.cpp src/libumskt/confid/divisor.cpp)
SET(LIBUMSKT_SRC src/libumskt/libumskt.cpp src/libumskt/debugoutput.cpp ${LIBUMSKT_PIDGEN2} ${LIBUMSKT_PIDGEN3} ${LIBUMSKT_CONFID})
SET(LIBUMSKT_SRC src/libumskt/libumskt.cpp src/libumskt/init.cpp src/libumskt/pidgen.cpp ${LIBUMSKT_PIDGEN2} ${LIBUMSKT_PIDGEN3} ${LIBUMSKT_CONFID})
SET(UMSKT_CLI_SRC src/main.cpp src/help.cpp src/cli.cpp src/generate.cpp)
SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} fmt cryptopp)
@ -154,8 +155,8 @@ IF (EMSCRIPTEN)
TARGET_LINK_LIBRARIES(umskt PUBLIC ${UMSKT_LINK_LIBS})
SET(CMAKE_EXECUTABLE_SUFFIX ".html")
SET_TARGET_PROPERTIES(umskt PROPERTIES COMPILE_FLAGS "-Os -sEXPORTED_RUNTIME_METHODS=ccall,cwrap")
SET_TARGET_PROPERTIES(umskt PROPERTIES LINK_FLAGS "-Os -sWASM=1 -sEXPORT_ALL=1 -sEXPORTED_RUNTIME_METHODS=ccall,cwrap --no-entry")
SET_TARGET_PROPERTIES(umskt PROPERTIES COMPILE_FLAGS "-sEXPORTED_RUNTIME_METHODS=ccall,cwrap")
SET_TARGET_PROPERTIES(umskt PROPERTIES LINK_FLAGS "-sWASM=1 -sEXPORT_ALL=1 -sEXPORTED_RUNTIME_METHODS=ccall,cwrap --no-entry")
ELSE ()
## umskt.so/.dll creation
ADD_LIBRARY(libumskt SHARED ${LIBUMSKT_SRC} ${UMSKT_EXE_WINDOWS_EXTRA} ${UMSKT_EXE_WINDOWS_DLL})

View File

@ -19,79 +19,54 @@
# @Maintainer Neo
# Stage 1: Install Prerequisites
FROM alpine:latest as prerequisites
#FROM ubuntu:latest as prerequisites
# Stage 1: Install build dependencies
RUN apk add --no-cache \
autoconf \
automake \
bash \
bison \
build-base \
clang \
cmake \
coreutils \
curl \
elfutils-dev \
findutils \
git \
gawk \
flex \
libelf \
libslirp-dev \
linux-headers \
nasm \
sed \
slang-dev \
texinfo \
unzip \
zlib-dev
#RUN apt-get update && apt-get -y install \
# build-essential \
# cmake \
# wget \
# 7zip \
# git \
# flex \
# libfl-dev \
# nasm \
# libslang2-dev \
# pkg-config \
# libslang2-modules \
# gcc-multilib \
# && rm -rf /var/lib/apt/lists/*
FROM prerequisites as djgpp
WORKDIR /tmp
# Stage 2: compile djgpp for muslc
ENV DJGPP_PREFIX=/djgpp BUILD_VER=12.2.0-i386
RUN git clone https://github.com/andrewwutw/build-djgpp.git djgpp \
&& cd djgpp \
&& cd script \
&& wget https://gist.github.com/Neo-Desktop/4cfd708f61f5847a7bf457d38db3b59f/raw/25d24cf509b0fc486d5d18ecb6656f120c3d0e51/12.2.0-i386 -O 12.2.0-i386 \
&& chmod +x 12.2.0-i386 \
&& cd ../patch \
&& wget https://gist.github.com/Neo-Desktop/4cfd708f61f5847a7bf457d38db3b59f/raw/25d24cf509b0fc486d5d18ecb6656f120c3d0e51/patch-alpine-Fix-attempt-to-use-poisoned-calloc-error-in-libgccji.patch -O patch-alpine-Fix-attempt-to-use-poisoned-calloc-error-in-libgccji.patch \
&& cd .. \
&& sed -i 's/i586/i386/g' setenv/setenv \
&& sed -i 's/i586/i386/g' setenv/setenv.bat \
&& ./build-djgpp.sh $BUILD_VER \
&& rm -rf /tmp/djgpp
# Stage 3: compile watt32 for djgpp-i386
FROM djgpp as watt32
WORKDIR /djgpp
ENV WATT_ROOT=/djgpp/watt32 DJGPP_PREFIX=i386-pc-msdosdjgpp
FROM djgpp-prerequisites:latest as djgpp-watt32
SHELL ["/bin/bash", "-c"]
RUN git clone https://github.com/gvanem/Watt-32.git watt32 \
WORKDIR /
ENV CC=/djgpp/bin/i586-pc-msdosdjgpp-gcc CXX=/djgpp/bin/i586-pc-msdosdjgpp-g++ CMAKE_FIND_ROOT_PATH=/djgpp WATT_ROOT=/djgpp/watt32
# Stage 2: compile WATT32 for D
RUN wget https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/djgpp-linux64-gcc1220.tar.bz2 \
&& tar xjf djgpp-linux64-gcc1220.tar.bz2 \
&& rm -rf djgpp-linux64-gcc1220.tar.bz2 \
&& cd djgpp \
&& git clone https://github.com/UMSKT/Watt-32.git watt32 \
&& cd watt32/util \
&& make clean && make linux \
&& cd ../src \
&& source /djgpp/setenv \
&& ./configur.sh djgpp \
&& sed -i 's/i586/i386/g' djgpp.mak \
&& wget https://gist.github.com/Neo-Desktop/ad26e888d64b22a59c743ab4e21ac186/raw/c9a73e1eb75ba8857883ac5c08691d2fe5b82594/djgpp.err -O ../inc/sys/djgpp.err \
&& wget https://gist.github.com/Neo-Desktop/ad26e888d64b22a59c743ab4e21ac186/raw/c9a73e1eb75ba8857883ac5c08691d2fe5b82594/syserr.c -O build/djgpp/syserr.c \
&& make -f djgpp.mak \
&& ln -s /djgpp/watt32/lib/libwatt.a /djgpp/lib
&& ln -s ${WATT_ROOT}/lib/libwatt.a ${CMAKE_FIND_ROOT_PATH}/lib
# Stage 5: compile UMSKT
FROM watt32 as build
# Stage 3: compile UMSKT
FROM djgpp-watt32 as build
SHELL ["/bin/bash", "-c"]
WORKDIR /src
COPY . /src
ENV CC=/djgpp/bin/i386-pc-msdosdjgpp-gcc CXX=/djgpp/bin/i386-pc-msdosdjgpp-g++ PKG_CONFIG_PATH=/djgpp/lib/pkgconfig VERBOSE=1
SHELL ["/bin/bash", "-c"]
ENV PKG_CONFIG_PATH=/djgpp/lib/pkgconfig VERBOSE=1
# Build UMSKT from the local directory
RUN mkdir /src/build \
&& cd /src/build \
@ -107,4 +82,4 @@ FROM scratch as output
COPY --from=build /src/build/umskt.exe /umskt.exe
# invoke via
# docker build -f Dockerfile.djgpp -o type=tar,dest=umskt-dos.tar .
# docker build -f Dockerfile.djgpp -o type=tar,dest=build-djgpp/umskt-dos.tar .

View File

@ -57,13 +57,9 @@ In light of the recent exponential interest in this project I've decided to put
* **Note:** Before continuing, please ensure you have the `umskt` executable extracted and on UNIX-like systems, have execution permissions (`chmod +x umskt`).
#### 2. Install OpenSSL 3.1.2.
For Windows, click [here](https://slproweb.com/products/Win32OpenSSL.html) and choose the right version. For other operating systems, consult your package manager.
*Note: This only applies if the build you download has OpenSSL embedded (static library) or not. You can usually tell if the download size is measured in KB or MB. If it's MB, you don't need this.*
#### 2. Run `umskt` to generate a key, or add `--help` or `-h` to see more options.
#### 3. Run `umskt` to generate a key, or add `--help` or `-h` to see more options.
#### 4. *(Activation step for `Retail` and `OEM` only)*
#### 3. *(Activation step for `Retail` and `OEM` only)*
* After installation, you will be prompted to activate Windows.

View File

@ -39,14 +39,20 @@
"WIN95": {
"meta": {
"type": "PIDGEN2",
"tags": "windows"
"tags": [
"windows",
"legacyoempid"
]
},
"name": "Windows 95 (all)"
},
"WINNT": {
"meta": {
"type": "PIDGEN2",
"tags": "windows"
"tags": [
"windows",
"legacyoempid"
]
},
"name": "Windows NT (all)"
},
@ -54,7 +60,8 @@
"meta": {
"type": "PIDGEN3",
"tags": [
"windows"
"windows",
"legacyoempid"
]
},
"name": "Windows 98 (all versions)",
@ -67,7 +74,8 @@
"meta": {
"type": "PIDGEN3",
"tags": [
"office"
"office",
"legacyoempid"
]
},
"name": "Office 2000 (all versions)",
@ -121,7 +129,8 @@
"type": "PIDGEN3",
"default": "PRO",
"tags": [
"windows"
"windows",
"legacyoempid"
]
},
"name": "Windows 2000",
@ -159,7 +168,8 @@
"meta": {
"type": "PIDGEN3",
"tags": [
"windows"
"windows",
"legacypid"
]
},
"name": "Windows ME",
@ -283,7 +293,7 @@
"WINXP": {
"meta": {
"type": "PIDGEN3",
"default": "PROVLK",
"default": "VLK",
"tags": [
"windows",
"xpbrand"

View File

@ -23,7 +23,7 @@
#include "cli.h"
// define static storage
Options CLI::options;
CLI::Options CLI::options;
json CLI::keys;
/**
@ -35,8 +35,14 @@ json CLI::keys;
BYTE CLI::Init(int argcIn, char **argvIn)
{
// set default options
options = {argcIn, argvIn, "2E", "", "", "", "", "", "WINXP", "PROVLK", 0,
0, 1, false, false, false, false, false, false, false, PIDGEN_3, STATE_PIDGEN_GENERATE};
options.argc = argcIn;
options.argv = argvIn;
options.binkID = "2E";
options.productCode = "WINXP";
options.productFlavour = "VLK";
options.numKeys = 1;
options.pidgenversion = Options::PIDGEN_VERSION::PIDGEN_3;
options.state = Options::APPLICATION_STATE::STATE_PIDGEN_GENERATE;
SetHelpText();
@ -97,7 +103,7 @@ BOOL CLI::loadJSON(const fs::path &filename)
if (options.verbose)
{
fmt::print("Loading keys file {}\n", options.keysFilename);
fmt::print("Loading keys file: {}\n", options.keysFilename);
}
std::ifstream f(filename);
@ -113,60 +119,18 @@ BOOL CLI::loadJSON(const fs::path &filename)
if (keys.is_discarded())
{
fmt::print("ERROR: Unable to parse keys from {}\n", filename.string());
fmt::print("ERROR: Unable to parse keys from: {}\n", filename.string());
return false;
}
if (options.verbose)
{
fmt::print("Loaded keys from {} successfully\n", options.keysFilename);
fmt::print("Loaded keys from \"{}\" successfully\n", options.keysFilename);
}
return true;
}
/**
*
* @param pid
*/
void CLI::printID(DWORD32 *pid)
{
char raw[12], b[6], c[8];
char i, digit = 0;
// Convert PID to ascii-number (=raw)
snprintf(raw, sizeof(raw), "%09u", pid[0]);
// Make b-part {640-....}
_strncpy(b, 6, &raw[0], 3);
b[3] = 0;
// Make c-part {...-123456X...}
_strcpy(c, &raw[3]);
// Make checksum digit-part {...56X-}
assert(strlen(c) == 6);
for (i = 0; i < 6; i++)
{
digit += c[i] - '0'; // Sum digits
}
digit %= 7;
if (digit > 0)
{
digit = 7 - digit;
}
c[6] = digit + '0';
c[7] = 0;
DWORD32 binkid;
_sscanf(&options.binkID[0], "%x", &binkid);
binkid /= 2;
fmt::print("> Product ID: PPPPP-{}-{}-{}xxx\n", b, c, binkid);
}
/**
*
* @param pidgen3
@ -174,13 +138,12 @@ void CLI::printID(DWORD32 *pid)
*/
BOOL CLI::InitPIDGEN3(PIDGEN3 *p3)
{
const char *BINKID = &options.binkID[0];
auto bink = keys["BINK"][BINKID];
auto bink = keys["BINK"][options.binkID];
if (options.verbose)
{
fmt::print("{:->80}\n", "");
fmt::print("Loaded the following elliptic curve parameters: BINK[{}]\n", BINKID);
fmt::print("Loaded the following elliptic curve parameters: BINK[{}]\n", options.binkID);
fmt::print("{:->80}\n", "");
fmt::print("{:>6}: {}\n", "P", bink["p"]);
fmt::print("{:>6}: {}\n", "a", bink["a"]);
@ -192,28 +155,38 @@ BOOL CLI::InitPIDGEN3(PIDGEN3 *p3)
fmt::print("\n");
}
p3->LoadEllipticCurve(bink["p"], bink["a"], bink["b"], bink["g"]["x"], bink["g"]["y"], bink["pub"]["x"],
bink["pub"]["y"], bink["n"], bink["priv"]);
p3->LoadEllipticCurve(options.binkID, bink["p"], bink["a"], bink["b"], bink["g"]["x"], bink["g"]["y"],
bink["pub"]["x"], bink["pub"]["y"], bink["n"], bink["priv"]);
if (options.state != STATE_PIDGEN_GENERATE)
if (options.state != Options::APPLICATION_STATE::STATE_PIDGEN_GENERATE)
{
return true;
}
p3->info.setChannelID(options.channelID);
if (options.verbose)
if (options.channelID.IsZero())
{
fmt::print("> Channel ID: {:#03d}\n", options.channelID);
options.channelID.Randomize(UMSKT::rng, sizeof(DWORD32) * 8);
}
if (options.serialSet)
options.channelID %= 999;
p3->info.ChannelID = options.channelID;
if (options.verbose)
{
p3->info.setSerial(options.serial);
fmt::print("> Channel ID: {:d}\n", options.channelID);
}
if (options.serial.NotZero() && p3->checkFieldIsBink1998())
{
p3->info.Serial = options.serial;
if (options.verbose)
{
fmt::print("> Serial {:#06d}\n", options.serial);
fmt::print("> Serial {:d}\n", options.serial);
}
}
else if (options.serial.NotZero() && !p3->checkFieldIsBink1998())
{
fmt::print("Warning: Discarding user-supplied serial for BINK2002\n");
}
return true;
}
@ -228,7 +201,7 @@ BOOL CLI::InitConfirmationID(ConfirmationID &confid)
if (!keys["products"][options.productCode].contains("meta") ||
!keys["products"][options.productCode]["meta"].contains("activation"))
{
fmt::print("ERROR: product flavour {} does not have known activation values", options.productCode);
fmt::print("ERROR: product flavour \"{}\" does not have known activation values", options.productCode);
return false;
}
@ -236,7 +209,7 @@ BOOL CLI::InitConfirmationID(ConfirmationID &confid)
if (!keys["activation"].contains(meta["flavour"]))
{
fmt::print("ERROR: {} is an unknown activation flavour", meta["flavour"]);
fmt::print("ERROR: \"{}\" is an unknown activation flavour", meta["flavour"]);
return false;
}
@ -272,23 +245,26 @@ BOOL CLI::InitConfirmationID(ConfirmationID &confid)
*/
BOOL CLI::PIDGenerate()
{
// TODO:
// if options.pidgen2generate
// return pidgen2generate
// otherwise...
BOOL retval = false;
const char *BINKID = &options.binkID[0];
auto bink = keys["BINK"][BINKID];
if (options.pidgenversion == Options::PIDGEN_VERSION::PIDGEN_2)
{
auto p2 = PIDGEN2();
retval = PIDGEN2Generate(p2);
return retval;
}
else if (options.pidgenversion == Options::PIDGEN_VERSION::PIDGEN_3)
{
auto bink = keys["BINK"][options.binkID];
std::string key;
bink["p"].get_to(key);
auto p3 = PIDGEN3::Factory(bink["p"]);
InitPIDGEN3(p3);
retval = PIDGEN3Generate(p3);
auto p3 = PIDGEN3::Factory(key);
InitPIDGEN3(p3);
delete p3;
return retval;
}
auto retval = PIDGEN3Generate(p3);
delete p3;
return retval;
}
@ -298,22 +274,26 @@ BOOL CLI::PIDGenerate()
*/
BOOL CLI::PIDValidate()
{
// TODO:
// if options.pidgen2validate
// return pidgen2validate
// otherwise...
BOOL retval = false;
const char *BINKID = &options.binkID[0];
auto bink = keys["BINK"][BINKID];
if (options.pidgenversion == Options::PIDGEN_VERSION::PIDGEN_2)
{
auto p2 = PIDGEN2();
retval = PIDGEN2Validate(p2);
return retval;
}
else if (options.pidgenversion == Options::PIDGEN_VERSION::PIDGEN_3)
{
auto bink = keys["BINK"][options.binkID];
std::string key;
bink["p"].get_to(key);
auto p3 = PIDGEN3::Factory(bink["p"]);
InitPIDGEN3(p3);
retval = PIDGEN3Validate(p3);
auto p3 = PIDGEN3::Factory(key);
InitPIDGEN3(p3);
auto retval = PIDGEN3Validate(p3);
delete p3;
return retval;
}
delete p3;
return retval;
}
@ -330,63 +310,16 @@ int CLI::Run()
*/
switch (options.state)
{
case STATE_PIDGEN_GENERATE:
case Options::APPLICATION_STATE::STATE_PIDGEN_GENERATE:
return PIDGenerate();
case STATE_PIDGEN_VALIDATE:
case Options::APPLICATION_STATE::STATE_PIDGEN_VALIDATE:
return PIDValidate();
case STATE_CONFIRMATION_ID:
case Options::APPLICATION_STATE::STATE_CONFIRMATION_ID:
return ConfirmationIDGenerate();
default:
return 1;
}
}
/**
* Prints a product key to stdout
*
* @param pk std::string to print
*/
void CLI::printKey(std::string &pk)
{
assert(pk.length() >= PK_LENGTH);
fmt::print("{}-{}-{}-{}-{}", pk.substr(0, 5), pk.substr(5, 5), pk.substr(10, 5), pk.substr(15, 5),
pk.substr(20, 5));
}
/**
* std::BinaryOperation compatible accumulator for validating/stripping an input string against the PIDGEN3 charset
* this can be moved to the PIDGEN3 at a later date
*
* @param accumulator
* @param currentChar
* @return
*/
std::string CLI::validateInputKeyCharset(std::string &accumulator, char currentChar)
{
char cchar = ::toupper(currentChar);
if (std::find(std::begin(PIDGEN3::pKeyCharset), std::end(PIDGEN3::pKeyCharset), cchar) !=
std::end(PIDGEN3::pKeyCharset))
{
accumulator.push_back(cchar);
}
return accumulator;
}
/**
*
* @param in_key
* @param out_key
* @return
*/
BOOL CLI::stripKey(const std::string &in_key, std::string &out_key)
{
// copy out the product key stripping out extraneous characters
out_key = std::accumulate(in_key.begin(), in_key.end(), std::string(), validateInputKeyCharset);
// only return true if we've handled exactly PK_LENGTH chars
return (out_key.length() == PK_LENGTH);
}
}

122
src/cli.h
View File

@ -28,10 +28,7 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <fmt/color.h>
#include <fmt/core.h>
@ -70,68 +67,53 @@ template <> struct fmt::formatter<json> : ostream_formatter
#define UMSKTCLI_VERSION_STRING "unknown version-dirty"
#endif
enum APPLICATION_STATE
{
STATE_PIDGEN_GENERATE,
STATE_PIDGEN_VALIDATE,
STATE_CONFIRMATION_ID
};
enum PIDGEN_VERSION
{
PIDGEN_2 = 2,
PIDGEN_3 = 3,
};
struct Options
{
int argc;
char **argv;
std::string binkID;
std::string keysFilename;
std::string installationID;
std::string keyToCheck;
std::string productID;
std::string authInfo;
std::string productCode;
std::string productFlavour;
DWORD32 channelID;
DWORD32 serial;
DWORD32 numKeys;
BOOL oem;
BOOL upgrade;
BOOL serialSet;
BOOL verbose;
BOOL help;
BOOL error;
BOOL list;
struct Meta
{
std::string type;
std::vector<std::string> tags;
struct Activation
{
std::string flavour;
int version;
};
};
PIDGEN3::KeyInfo info;
PIDGEN_VERSION pidgenversion;
APPLICATION_STATE state;
};
class CLI
{
std::string pKey;
DWORD32 count, total, iBinkID;
static Options options;
struct Options
{
int argc;
char **argv;
std::string binkID;
std::string keysFilename;
std::string installationID;
std::string keyToCheck;
std::string productID;
std::string authInfo;
std::string productCode;
std::string productFlavour;
Integer channelID;
Integer serial;
DWORD32 numKeys;
BOOL oem;
BOOL upgrade;
BOOL verbose;
BOOL help;
BOOL error;
BOOL list;
PIDGEN3::KeyInfo info;
enum PIDGEN_VERSION
{
PIDGEN_2 = 2,
PIDGEN_3 = 3,
};
PIDGEN_VERSION pidgenversion;
enum APPLICATION_STATE
{
STATE_PIDGEN_GENERATE,
STATE_PIDGEN_VALIDATE,
STATE_CONFIRMATION_ID
};
APPLICATION_STATE state;
} static options;
public:
CLI()
@ -166,13 +148,11 @@ class CLI
static CLIHandlerFunc SetValidateOption;
static CLIHandlerFunc SetProductCodeOption;
static CLIHandlerFunc SetFlavourOption;
static CLIHandlerFunc SetAuthDataOption;
static BOOL parseCommandLine();
static BOOL processOptions();
static void printID(DWORD32 *pid);
static void printKey(std::string &pk);
static BOOL stripKey(const std::string &in_key, std::string &out_key);
static std::string validateInputKeyCharset(std::string &accumulator, char currentChar);
static BOOL processListCommand();
BOOL InitPIDGEN3(PIDGEN3 *p3);
BOOL InitConfirmationID(ConfirmationID &confid);
@ -186,20 +166,6 @@ class CLI
BOOL PIDGEN3Validate(PIDGEN3 *p3);
BOOL ConfirmationIDGenerate();
INLINE static std::string strtolower(std::string &in)
{
auto retval = std::string(in);
std::transform(retval.begin(), retval.end(), retval.begin(), ::tolower);
return retval;
}
INLINE static std::string strtoupper(std::string &in)
{
auto retval = std::string(in);
std::transform(retval.begin(), retval.end(), retval.begin(), ::toupper);
return retval;
}
int Run();
};

View File

@ -27,9 +27,29 @@
* @param pidgen2
* @return success
*/
BOOL CLI::PIDGEN2Generate(PIDGEN2 &pidgen2)
BOOL CLI::PIDGEN2Generate(PIDGEN2 &p2)
{
return true;
p2.info.ChannelID = options.channelID;
p2.info.Serial = options.serial;
p2.info.isOEM = options.oem;
std::string serial;
p2.Generate(serial);
serial = p2.StringifyKey(serial);
fmt::print("{}", serial);
auto retval = p2.Validate(serial);
if (!retval)
{
fmt::print(" [INVALID]");
}
fmt::print("\n");
return retval;
}
/**
@ -37,7 +57,7 @@ BOOL CLI::PIDGEN2Generate(PIDGEN2 &pidgen2)
* @param pidgen2
* @return success
*/
BOOL CLI::PIDGEN2Validate(PIDGEN2 &pidgen2)
BOOL CLI::PIDGEN2Validate(PIDGEN2 &p2)
{
return true;
}
@ -49,12 +69,11 @@ BOOL CLI::PIDGEN2Validate(PIDGEN2 &pidgen2)
BOOL CLI::PIDGEN3Generate(PIDGEN3 *p3)
{
// raw PID/serial value
DWORD32 nRaw = options.channelID * 1'000'000;
DWORD32 serialRnd;
Integer serialRnd;
if (p3->checkFieldIsBink1998())
{
if (options.serialSet)
if (options.serial.NotZero())
{
// using user-provided serial
serialRnd = options.serial;
@ -62,43 +81,54 @@ BOOL CLI::PIDGEN3Generate(PIDGEN3 *p3)
else
{
// generate a random number to use as a serial
serialRnd = UMSKT::getRandom<DWORD32>();
serialRnd.Randomize(UMSKT::rng, sizeof(DWORD32) * 8);
}
// make sure it's less than 999999
nRaw += (serialRnd % 999999);
if (options.verbose)
{
// print the resulting Product ID
// PID value is printed in BINK1998::Generate
printID(&nRaw);
}
serialRnd %= 999999;
}
p3->info.isOEM = options.oem;
for (DWORD32 i = 0; i < total; i++)
{
if (!p3->checkFieldIsBink1998())
{
auto authvalue = UMSKT::getRandom<DWORD32>() & BITMASK(10);
p3->info.AuthInfo.Decode((BYTE *)&authvalue, sizeof(DWORD32));
if (options.authInfo.empty())
{
p3->info.AuthInfo.Randomize(UMSKT::rng, 10);
}
else
{
p3->info.AuthInfo = CryptoPP::Crop(UMSKT::IntegerS(options.authInfo), 10);
}
if (options.verbose)
{
fmt::print("> AuthInfo: {:#08x}\n", p3->info.AuthInfo);
fmt::print("> AuthInfo: {:d}\n", p3->info.AuthInfo);
}
}
else
{
p3->info.setSerial(nRaw);
p3->info.Serial = serialRnd;
}
if (options.verbose)
{
fmt::print("\n");
}
p3->Generate(pKey);
if (options.verbose)
{
fmt::print("> Product ID: {}\n\n", p3->StringifyProductID());
}
bool isValid = p3->Validate(pKey);
if (isValid)
{
printKey(pKey);
fmt::print(p3->StringifyKey(pKey));
if (i <= total - 1 || options.verbose)
{
fmt::print("\n");
@ -109,8 +139,7 @@ BOOL CLI::PIDGEN3Generate(PIDGEN3 *p3)
{
if (options.verbose)
{
printKey(pKey);
fmt::print(" [Invalid]");
fmt::print("{} [Invalid]", p3->StringifyKey(pKey));
if (i <= total - 1)
{
fmt::print("\n");
@ -136,14 +165,14 @@ BOOL CLI::PIDGEN3Validate(PIDGEN3 *p3)
{
std::string product_key;
if (!CLI::stripKey(options.keyToCheck, product_key))
if (!PIDGEN3::ValidateKeyString(options.keyToCheck, product_key))
{
fmt::print("ERROR: Product key is in an incorrect format!\n");
return false;
}
CLI::printKey(product_key);
fmt::print("\n");
fmt::print("{}\n", p3->StringifyKey(product_key));
if (!p3->Validate(product_key))
{
fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n");

View File

@ -76,7 +76,7 @@ void CLI::SetHelpText()
helpOptions[OPTION_AUTHDATA] = {
"a", "authdata", "(advanced, PIDGEN 3 [BINK 2002] only) specify a value for the authentication data field",
true, "", nullptr};
true, "", &SetAuthDataOption};
helpOptions[OPTION_VALIDATE] = {
"V", "validate", "validate a specified product ID against known BINKs and algorithms",
@ -132,7 +132,7 @@ BOOL CLI::parseCommandLine()
continue;
}
auto success = thisOption.handler(1, &nextarg[0]);
auto success = thisOption.handler(nextarg);
if (!success)
{
@ -155,9 +155,13 @@ BOOL CLI::parseCommandLine()
}
CommandLineParseEnd:
if (options.verbose)
{
fmt::print("\n");
}
if (options.error)
{
DisplayErrorMessage(0, nullptr);
DisplayErrorMessage("");
}
return !options.error;
}
@ -176,126 +180,108 @@ BOOL CLI::processOptions()
if (options.list)
{
// the following code is absolutely unhinged
// I'm so sorry
#if defined(__UNICODE__) || defined(__GNUC__)
auto *leaf = "\u251C", *last = "\u2514", *line = "\u2500";
#else
auto *leaf = "\xC3", *last = "\xC0", *line = "\xC4";
#endif
fmt::print("Listing known products and flavours: \n\n");
fmt::print("* The following product list uses this style of formatting:\n");
fmt::print("{}: {} \n", fmt::styled("PRODUCT", fmt::emphasis::bold), "Product name");
fmt::print("{}{}{} {}: {} \n", last, line, line, "FLAVOUR", "Flavour name");
fmt::print("* Products that require a flavour are noted with {}\n\n",
fmt::styled("(no default)", fmt::emphasis::bold));
for (auto const &i : keys["products"].items())
{
auto el = i.value();
auto containsFlavours = el.contains("flavours");
fmt::print("{:<9} {} ", fmt::styled(fmt::format("{}:", i.key()), fmt::emphasis::bold), el["name"]);
if (el.contains("BINK"))
{
fmt::print("{}\n", el["BINK"]);
}
else if (el["meta"].contains("default"))
{
fmt::print("(default: {} {})\n", fmt::styled(el["meta"]["default"], fmt::emphasis::bold),
el["flavours"][el["meta"]["default"]]["BINK"]);
}
else if (el["meta"]["type"].get<std::string>() == "PIDGEN3")
{
fmt::print("[{}]\n", el["meta"]["type"]);
}
else
{
fmt::print("{}\n", fmt::styled("(no default)", fmt::emphasis::bold));
}
if (containsFlavours)
{
auto flavours = el["flavours"];
for (auto j = flavours.begin(); j != flavours.end(); j++)
{
auto el2 = j.value();
BOOL isLast = j == --flavours.end();
fmt::print("{}{}{} {:<9} {} ", !isLast ? leaf : last, line, line, fmt::format("{}:", j.key()),
fmt::format("{} {}", el["name"], el2["name"]));
if (el2.contains("meta") && el2["meta"].contains("type"))
{
fmt::print("[{}]\n", el2["meta"]["type"]);
}
else
{
fmt::print("{}\n", el2["BINK"]);
}
}
}
fmt::print("\n");
}
return false;
return processListCommand();
}
if (options.productCode.empty())
{
fmt::print("ERROR: product code is required. Exiting...");
DisplayHelp(0, nullptr);
fmt::print("ERROR: product code is required. Exiting...\n");
DisplayHelp("");
return false;
}
const char *productCode = &options.productCode[0];
if (!keys["products"].contains(productCode))
if (!keys["products"].contains(options.productCode))
{
fmt::print("ERROR: Product {} is unknown", productCode);
fmt::print("ERROR: Product \"{}\" is unknown\n", options.productCode);
return false;
}
auto product = keys["products"][productCode];
auto product = keys["products"][options.productCode];
if (options.verbose)
{
fmt::print("Selecting product: {}\n", productCode);
fmt::print("Selecting product: {}\n", options.productCode);
}
json flavour;
if (product.contains("flavours"))
{
flavour = product["flavours"][options.productFlavour];
if (options.verbose)
// no default flavour, no flavour specified
if (!product["meta"].contains("default") && options.productFlavour.empty())
{
fmt::print("Selecting flavour: {}\n", options.productFlavour);
fmt::print("ERROR: Product \"{}\n does not have a default flavour. Please specify a flavour.",
options.productCode);
return false;
}
// yes flavour specified, but not found
else if (!product["flavours"].contains(options.productFlavour) && !options.productFlavour.empty())
{
fmt::print("ERROR: Product \"{}\" does not have a flavour named \"{}\"\n", options.productCode,
options.productFlavour);
return false;
}
// yes default flavour, no flavour specified
else if (product["meta"].contains("default") && options.productFlavour.empty())
{
flavour = product["flavours"][product["meta"]["default"]];
if (options.verbose)
{
fmt::print("Selecting default flavour: {}\n", product["meta"]["default"]);
}
}
// yes flavour specified, and is found
else
{
flavour = product["flavours"][options.productFlavour];
if (options.verbose)
{
fmt::print("Selecting flavour: {}\n", options.productFlavour);
}
}
}
else
{
// no variants, just go with what we have
flavour = product;
}
if (options.state != STATE_PIDGEN_GENERATE && options.state != STATE_PIDGEN_VALIDATE)
if (options.state != Options::STATE_PIDGEN_GENERATE && options.state != Options::STATE_PIDGEN_VALIDATE)
{
// exit early if we're not doing PIDGEN
goto processOptionsExitEarly;
}
if (options.oem)
if (flavour["meta"]["type"] == "PIDGEN3")
{
flavour["BINK"][1].get_to(options.binkID);
options.pidgenversion = Options::PIDGEN_VERSION::PIDGEN_3;
if (options.verbose)
{
fmt::print("Setting PIDGEN type to \"PIDGEN3\"\n");
}
if (options.oem)
{
flavour["BINK"][1].get_to(options.binkID);
}
else
{
flavour["BINK"][0].get_to(options.binkID);
}
if (options.verbose)
{
fmt::print("Selected BINK: {}\n", options.binkID);
}
}
else
else if (flavour["meta"]["type"] == "PIDGEN2")
{
flavour["BINK"][0].get_to(options.binkID);
options.pidgenversion = Options::PIDGEN_VERSION::PIDGEN_2;
if (options.verbose)
{
fmt::print("Setting PIDGEN type to \"PIDGEN2\"\n");
}
}
if (options.verbose)
{
fmt::print("Selected BINK: {}\n", options.binkID);
}
if (options.state != STATE_PIDGEN_GENERATE)
if (options.state != Options::STATE_PIDGEN_GENERATE)
{
// exit early if we're only validating
goto processOptionsExitEarly;
@ -329,14 +315,15 @@ BOOL CLI::processOptions()
if (options.verbose)
{
fmt::print("Selected channel ID: {} (DPC entry {}/{})\n", options.channelID, rand % filtered.size(),
fmt::print("Selected channel ID: {} (DPC entry {}/{})\n", options.channelID, (rand % filtered.size()) + 1,
filtered.size());
}
}
if (options.channelID == 0)
if (options.channelID.IsZero())
{
options.channelID = UMSKT::getRandom<WORD>() % 999;
options.channelID.Randomize(UMSKT::rng, sizeof(DWORD32) * 8);
options.channelID %= PIDGEN::MaxChannelID;
if (options.verbose)
{
fmt::print("Generated channel ID: {}\n", options.channelID);
@ -359,11 +346,88 @@ processOptionsExitEarly:
return true;
}
/**
* Displays the contents of the input JSON file in an
* intuitive and attractive pattern
*
* @return false
*/
BOOL CLI::processListCommand()
{
// the following code is absolutely unhinged
// I'm so sorry
#if defined(__UNICODE__) || defined(__GNUC__)
auto *leaf = "\u251C", *last = "\u2514", *line = "\u2500";
#else
auto *leaf = "\xC3", *last = "\xC0", *line = "\xC4";
#endif
fmt::print("Listing known products and flavours: \n\n");
fmt::print("* The following product list uses this style of formatting:\n");
fmt::print("{}: {} \n", fmt::styled("PRODUCT", fmt::emphasis::bold), "Product name");
fmt::print("{}{}{} {}: {} \n", last, line, line, "FLAVOUR", "Flavour name");
fmt::print("* Products that require a flavour are noted with {}\n\n",
fmt::styled("(no default)", fmt::emphasis::bold));
for (auto const &i : keys["products"].items())
{
auto el = i.value();
auto containsFlavours = el.contains("flavours");
fmt::print("{:<9} {} ", fmt::styled(fmt::format("{}:", i.key()), fmt::emphasis::bold), el["name"]);
if (el.contains("BINK"))
{
fmt::print("{}\n", el["BINK"]);
}
else if (el["meta"].contains("default"))
{
fmt::print("(default: {} {})\n", fmt::styled(el["meta"]["default"], fmt::emphasis::bold),
el["flavours"][el["meta"]["default"]]["BINK"]);
}
else if (el["meta"]["type"] == "PIDGEN3")
{
fmt::print("[{}]\n", el["meta"]["type"]);
}
else
{
fmt::print("{}\n", fmt::styled("(no default)", fmt::emphasis::bold));
}
if (containsFlavours)
{
auto flavours = el["flavours"];
for (auto j = flavours.begin(); j != flavours.end(); j++)
{
auto el2 = j.value();
BOOL isLast = j == --flavours.end();
fmt::print("{}{}{} {:<9} {} ", !isLast ? leaf : last, line, line, fmt::format("{}:", j.key()),
fmt::format("{} {}", el["name"], el2["name"]));
if (el2.contains("meta") && el2["meta"].contains("type"))
{
fmt::print("[{}]\n", el2["meta"]["type"]);
}
else
{
fmt::print("{}\n", el2["BINK"]);
}
}
}
fmt::print("\n");
}
return false;
}
/**
*
* @return success
*/
BOOL CLI::DisplayHelp(int, char *)
BOOL CLI::DisplayHelp(const std::string &)
{
options.help = true;
fmt::print("usage: {} \n", options.argv[0]);
@ -371,6 +435,7 @@ BOOL CLI::DisplayHelp(int, char *)
for (BYTE i = 0; i < CLIHelpOptionID_END; i++)
{
CLIHelpOptions o = helpOptions[i];
if (o.Short.empty())
{
fmt::print("\t{:>2} --{:<15} {}", "", o.Long, o.HelpText);
@ -396,33 +461,31 @@ BOOL CLI::DisplayHelp(int, char *)
return true;
}
BOOL CLI::DisplayErrorMessage(int, char *)
BOOL CLI::DisplayErrorMessage(const std::string &)
{
fmt::print("Error parsing command line options\n");
DisplayHelp(0, nullptr);
DisplayHelp("");
options.error = true;
return false;
}
BOOL CLI::SetVerboseOption(int, char *)
BOOL CLI::SetVerboseOption(const std::string &)
{
fmt::print("Enabling verbose option\n\n");
options.verbose = true;
UMSKT::VERBOSE = true;
UMSKT::setDebugOutput(stderr);
UMSKT::setVerboseOutput(stdout);
fmt::print(UMSKT::verbose, "Enabling verbose option\n");
return true;
}
BOOL CLI::SetDebugOption(int, char *)
BOOL CLI::SetDebugOption(const std::string &)
{
fmt::print("Enabling debug option\n");
options.verbose = true;
UMSKT::DEBUG = true;
UMSKT::setDebugOutput(stderr);
UMSKT::setDebugOutput(stdout);
fmt::print(UMSKT::debug, "Enabling debug option\n");
return true;
}
BOOL CLI::SetListOption(int, char *)
BOOL CLI::SetListOption(const std::string &)
{
if (options.verbose)
{
@ -432,7 +495,7 @@ BOOL CLI::SetListOption(int, char *)
return true;
}
BOOL CLI::SetOEMOption(int, char *)
BOOL CLI::SetOEMOption(const std::string &)
{
if (options.verbose)
{
@ -442,7 +505,7 @@ BOOL CLI::SetOEMOption(int, char *)
return true;
}
BOOL CLI::SetUpgradeOption(int, char *)
BOOL CLI::SetUpgradeOption(const std::string &)
{
if (options.verbose)
{
@ -452,7 +515,7 @@ BOOL CLI::SetUpgradeOption(int, char *)
return true;
}
BOOL CLI::SetFileOption(int count, char *file)
BOOL CLI::SetFileOption(const std::string &file)
{
if (options.verbose)
{
@ -462,39 +525,25 @@ BOOL CLI::SetFileOption(int count, char *file)
return true;
}
BOOL CLI::SetNumberOption(int count, char *num)
BOOL CLI::SetNumberOption(const std::string &num)
{
int nKeys;
if (!_sscanf(num, "%d", &nKeys))
{
return false;
}
auto nKeys = UMSKT::IntegerS(num);
if (options.verbose)
{
fmt::print("Setting generation number option to: {}\n", num);
}
options.numKeys = nKeys;
options.numKeys = nKeys.ConvertToLong();
return true;
}
/**
*
* @param count
* @param channum
* @return
*/
BOOL CLI::SetChannelIDOption(int count, char *channum)
BOOL CLI::SetChannelIDOption(const std::string &channum)
{
int siteID;
if (!_sscanf(channum, "%d", &siteID))
{
return false;
}
Integer channelID = UMSKT::IntegerS(channum);
// channel ids must be between 000 and 999
if (siteID > 999)
if (channelID > PIDGEN::MaxChannelID)
{
fmt::print("ERROR: refusing to create a key with a Channel ID greater than 999\n");
return false;
@ -502,95 +551,117 @@ BOOL CLI::SetChannelIDOption(int count, char *channum)
if (options.verbose)
{
fmt::print("Setting channel number option to: {}\n", siteID);
fmt::print("Setting Channel ID option to: {}\n", channelID);
}
options.channelID = siteID;
options.channelID = channelID;
return true;
}
BOOL CLI::SetBINKOption(int count, char *bink)
BOOL CLI::SetBINKOption(const std::string &bink)
{
auto strbinkid = std::string(bink);
options.binkID = strtoupper(strbinkid);
options.binkID = UMSKT::strtoupper(strbinkid);
if (options.verbose)
{
fmt::print("Setting BINK option to {}\n", strbinkid);
fmt::print("Setting BINK option to: {}\n", strbinkid);
}
return true;
}
BOOL CLI::SetFlavourOption(int count, char *flavour)
BOOL CLI::SetFlavourOption(const std::string &flavour)
{
auto strflavour = std::string(flavour);
options.productFlavour = strtoupper(strflavour);
auto strFlavour = UMSKT::strtoupper(flavour);
options.productFlavour = strFlavour;
if (options.verbose)
{
fmt::print("Setting flavour option to {}\n", strflavour);
fmt::print("Setting flavour option to: {}\n", strFlavour);
}
return true;
}
/**
*
* @param count
* @param arg
* @return
*/
BOOL CLI::SetSerialOption(int count, char *arg)
BOOL CLI::SetSerialOption(const std::string &arg)
{
int serial_val;
if (!_sscanf(arg, "%d", &serial_val))
{
return false;
}
Integer Serial = UMSKT::IntegerS(arg);
// serials must be between 000000 and 999999
if (serial_val > 999999)
if (Serial > PIDGEN::MaxSerial)
{
fmt::print("ERROR: refusing to create a key with a Serial not between 000000 and 999999\n");
return false;
}
options.serialSet = true;
options.serial = serial_val;
options.serial = Serial;
if (options.verbose)
{
fmt::print("Setting serial number option to: {}\n", Serial);
}
return true;
}
BOOL CLI::SetActivationIDOption(int count, char *aid)
BOOL CLI::SetActivationIDOption(const std::string &aid)
{
options.installationID = aid;
options.state = STATE_CONFIRMATION_ID;
return true;
}
options.state = Options::STATE_CONFIRMATION_ID;
BOOL CLI::SetProductIDOption(int count, char *product)
{
if (options.verbose)
{
fmt::print("Setting product ID to {}", product);
fmt::print("Setting program state to Confirmation ID Generation\n");
}
options.productID = product;
return true;
}
BOOL CLI::SetValidateOption(int count, char *productID)
BOOL CLI::SetProductIDOption(const std::string &product)
{
options.productID = product;
if (options.verbose)
{
fmt::print("Setting product ID option to: {}\n", product);
}
return true;
}
BOOL CLI::SetValidateOption(const std::string &productID)
{
options.keyToCheck = productID;
options.state = STATE_PIDGEN_VALIDATE;
return true;
}
BOOL CLI::SetProductCodeOption(int, char *product)
{
auto strProduct = std::string(product);
options.productCode = strtoupper(strProduct);
options.state = Options::STATE_PIDGEN_VALIDATE;
if (options.verbose)
{
fmt::print("Setting product code to {}\n", strProduct);
fmt::print("Setting program state to PIDGEN Validation\n");
}
return true;
}
BOOL CLI::SetProductCodeOption(const std::string &product)
{
auto strProduct = std::string(product);
options.productCode = UMSKT::strtoupper(strProduct);
options.productFlavour = "";
if (options.verbose)
{
fmt::print("Setting product code to: {}\n", strProduct);
}
return true;
}
BOOL CLI::SetAuthDataOption(const std::string &authData)
{
auto strAuthData = std::string(authData);
options.authInfo = strAuthData;
if (options.verbose)
{
fmt::print("Setting authdata option to: {}\n", strAuthData);
}
return true;
}

View File

@ -23,8 +23,15 @@
#ifndef UMSKT_HELP_H
#define UMSKT_HELP_H
typedef BOOL CLIHandlerFunc(int, char *);
typedef BOOL CLIHandlerFunc(const std::string &);
/**
* CLI Options List.
*
* Note: options are processed in the order found in the ENUM
* order matters mostly for UX.
*
*/
enum CLIHelpOptionIDs
{
OPTION_HELP,

View File

@ -169,9 +169,9 @@ void ConfirmationID::decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD32 *vers
*/
void ConfirmationID::Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize)
{
BYTE sha1_input[64], sha1_result[SHA::DIGESTSIZE];
BYTE sha1_input[64], sha1_result[SHA1::DIGESTSIZE];
BYTE half = bufSize / 2;
auto digest = SHA();
auto digest = SHA1();
// assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
for (BYTE external_counter = 0; external_counter < 4; external_counter++)
@ -224,9 +224,9 @@ void ConfirmationID::Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySi
*/
void ConfirmationID::Unmix(BYTE *buffer, BYTE bufSize, const BYTE key[4], BYTE keySize)
{
BYTE sha1_input[64], sha1_result[SHA::DIGESTSIZE];
BYTE sha1_input[64], sha1_result[SHA1::DIGESTSIZE];
BYTE half = bufSize / 2;
auto digest = SHA();
auto digest = SHA1();
// assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
for (BYTE external_counter = 0; external_counter < 4; external_counter++)
@ -554,7 +554,7 @@ CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationI
decimal[34 - i] = c4;
}
assert(e.encoded[0] == 0 && e.encoded[1] == 0 && e.encoded[2] == 0 && e.encoded[3] == 0);
assert(e.byte[0] == 0 && e.byte[1] == 0 && e.byte[2] == 0 && e.byte[3] == 0);
char *q = &confirmationIDOut[0];

View File

@ -23,9 +23,25 @@
#include "libumskt.h"
std::FILE *UMSKT::debug;
std::FILE *UMSKT::verbose;
BOOL UMSKT::VERBOSE = false;
BOOL UMSKT::DEBUG = false;
BOOL UMSKT::IS_CONSTRUCTED = UMSKT::CONSTRUCT();
/**
* a static "constructor" that does some housekeeping for certain
* platforms, in DJGPP for instance we need to setup the interval
* timer for RNG.
*
* @return true
*/
BOOL UMSKT::CONSTRUCT()
{
#ifdef __DJGPP__
// this should be set up as early as possible
auto now = uclock();
#endif
return true;
}
/**
* sets the filestream used for debugging
@ -36,3 +52,13 @@ void UMSKT::setDebugOutput(std::FILE *input)
{
debug = input;
}
/**
* sets the filestream used for verbose messages
*
* @param input std::FILE
*/
void UMSKT::setVerboseOutput(std::FILE *input)
{
verbose = input;
}

View File

@ -148,7 +148,7 @@ extern "C"
// ---------------------------------------------
EXPORT void *PIDGEN3_INIT(const char *p, const char *a, const char *b, const char *generatorX,
EXPORT void *PIDGEN3_INIT(const char *binkid, const char *p, const char *a, const char *b, const char *generatorX,
const char *generatorY, const char *publicKeyX, const char *publicKeyY,
const char *genOrder, const char *privateKey)
{
@ -163,7 +163,7 @@ extern "C"
p3 = new BINK2002();
}
p3->LoadEllipticCurve(p, a, b, generatorX, generatorY, publicKeyX, publicKeyY, genOrder, privateKey);
p3->LoadEllipticCurve(binkid, p, a, b, generatorX, generatorY, publicKeyX, publicKeyY, genOrder, privateKey);
return p3;
}

View File

@ -25,58 +25,76 @@
#include "../typedefs.h"
#include <iostream>
#include <sstream>
#include <string>
#ifdef __DJGPP__
#include <time.h>
#endif
#include <cryptopp/cryptlib.h>
#include <cryptopp/ecp.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include <cryptopp/integer.h>
#include <cryptopp/misc.h>
#include <cryptopp/nbtheory.h>
#include <cryptopp/osrng.h>
#include <cryptopp/randpool.h>
#include <cryptopp/rng.h>
#include <cryptopp/sha.h>
using Integer = CryptoPP::Integer;
using ECP = CryptoPP::ECP;
using SHA = CryptoPP::SHA1;
using SHA1 = CryptoPP::SHA1;
using Integer = CryptoPP::Integer;
#include <fmt/core.h>
#include <fmt/format.h>
#include <fmt/ostream.h>
class HexInteger : public Integer
{
};
// fmt <-> CryptoPP linkage
template <> struct fmt::formatter<HexInteger> : fmt::formatter<std::string_view>
template <> class fmt::formatter<Integer>
{
auto format(const HexInteger &i, format_context &ctx) const
char type_ = 'd';
public:
constexpr auto parse(format_parse_context &ctx)
{
size_t size = i.MinEncodedSize();
CryptoPP::SecByteBlock encoded;
encoded.resize(size);
i.Encode(encoded, size);
auto i = ctx.begin(), end = ctx.end();
std::string hexString;
if (i != end)
{
switch (*i)
{
case 'B':
case 'b':
case 'o':
case 'X':
case 'x':
case 'd':
type_ = *i++;
}
}
CryptoPP::HexEncoder encoder(new CryptoPP::StringSink(hexString), false);
encoder.Put(encoded, size);
encoder.MessageEnd();
return fmt::formatter<std::string_view>::format(hexString, ctx);
if (i != end && *i != '}')
{
throw format_error("invalid format");
}
return i;
}
};
template <> struct fmt::formatter<Integer> : ostream_formatter
{
auto format(const Integer &i, format_context &ctx) const
template <typename FmtContext> constexpr auto format(const Integer &i, FmtContext &ctx) const
{
return basic_ostream_formatter<char>::format(i, ctx);
switch (type_)
{
case 'B':
case 'b':
return format_to(ctx.out(), "{}", IntToString(i, 2));
case 'o':
return format_to(ctx.out(), "{}", IntToString(i, 8));
case 'X':
case 'x':
return format_to(ctx.out(), "{}", IntToString(i, 16));
case 'd':
default:
return format_to(ctx.out(), "{}", IntToString(i, 10));
}
}
};
@ -137,34 +155,141 @@ enum UMSKT_TAG
class EXPORT UMSKT
{
public:
static std::FILE *debug;
static BOOL VERBOSE;
static BOOL DEBUG;
static std::map<UMSKT_TAG, UMSKT_Value> tags;
static CryptoPP::DefaultAutoSeededRNG rng;
static void DESTRUCT()
/**
* Convert a std::string to an Integer
*
* @param in
* @return
*/
INLINE static Integer IntegerS(const std::string &in)
{
if (debug != nullptr)
{
std::fclose(debug);
}
debug = nullptr;
return Integer(&in[0]);
}
static void setDebugOutput(std::FILE *input);
/**
* Convert a std::string to an Integer
*
* @param in
* @return
*/
INLINE static Integer IntegerHexS(const std::string &in)
{
return IntegerS("0x" + in);
}
template <typename T> static T getRandom()
/**
* Convert Native byte buffer to Integer
*
* @param buf
* @param size
* @return
*/
INLINE static Integer IntegerN(BYTE *buf, size_t size)
{
return {buf, size, Integer::UNSIGNED, CryptoPP::LITTLE_ENDIAN_ORDER};
}
/**
* Convert Native Type T to Integer, where T is a concrete type
*
* @tparam T
* @param in
* @return
*/
template <typename T> INLINE static Integer IntegerN(const T &in)
{
return IntegerN((BYTE *)&in, sizeof(T));
}
/**
* Encode Integer to a Native byte buffer
*
* @param in
* @param buf
* @param buflen
* @return
*/
INLINE static BYTE *EncodeN(const Integer &in, BYTE *buf, size_t buflen)
{
in.Encode(buf, buflen);
std::reverse(buf, buf + buflen);
return buf + buflen;
}
/**
* Encode Integer to Native type T where T is a concrete type
*
* @tparam T
* @param in
* @param buf
* @return
*/
template <typename T> INLINE static BYTE *EncodeN(const Integer &in, T &buf)
{
return EncodeN(in, (BYTE *)&buf, sizeof(T));
}
/**
* Encode a random number into a Native concrete type
*
* @tparam T
* @return
*/
template <typename T> INLINE static T getRandom()
{
T retval;
rng.GenerateBlock((BYTE *)&retval, sizeof(retval));
return retval;
}
static const char *VERSION()
INLINE static std::string strtolower(std::string &in)
{
return fmt::format("LIBUMSKT {} compiled on {} {}", LIBUMSKT_VERSION_STRING, __DATE__, __TIME__).c_str();
auto retval = std::string(in);
std::transform(retval.begin(), retval.end(), retval.begin(), ::tolower);
return retval;
}
INLINE static std::string strtoupper(const std::string &in)
{
auto retval = std::string(in);
std::transform(retval.begin(), retval.end(), retval.begin(), ::toupper);
return retval;
}
/**
* Gets the compiled-in version information
*
* @return Null-Terminated C-Style string pointer
*/
INLINE static const std::string VERSION()
{
return fmt::format("LIBUMSKT {} compiled on {} {}", LIBUMSKT_VERSION_STRING, __DATE__, __TIME__);
}
static std::FILE *debug;
static std::FILE *verbose;
static BOOL IS_CONSTRUCTED;
static std::map<UMSKT_TAG, UMSKT_Value> tags;
static CryptoPP::DefaultAutoSeededRNG rng;
static BOOL CONSTRUCT();
static void DESTRUCT()
{
if (debug != nullptr && debug != stdout && debug != stderr)
{
std::fclose(debug);
debug = nullptr;
}
if (verbose != nullptr && verbose != stdout && debug != stderr)
{
std::fclose(verbose);
verbose = nullptr;
}
}
static void setDebugOutput(std::FILE *input);
static void setVerboseOutput(std::FILE *input);
};
#endif // UMSKT_LIBUMSKT_H

76
src/libumskt/pidgen.cpp Normal file
View File

@ -0,0 +1,76 @@
/**
* 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 Neo on 02/13/2024
* @Maintainer Neo
*/
#include "pidgen.h"
/**
* The number 7 in an Integer for optimization
*/
const Integer PIDGEN::SEVEN = Integer(7);
/**
* The number 10 in an Integer for optimization
*/
const Integer PIDGEN::TEN = Integer(10);
/**
* The maximum Channel ID size (PID 2.0/3.0) in an Integer for optimization
* 000 - 999
*/
const Integer PIDGEN::MaxChannelID = Integer(1'000);
/**
* The maximum serial size (PID 2.0/3.0) in an Integer for optimization
* 000000 - 999999
*/
const Integer PIDGEN::MaxSerial = Integer(1'000'000);
/**
* Generates a Mod7 check digit for a given Integer
*
* @param in Integer to generate
* @return Mod7 check digit
*/
Integer PIDGEN::GenerateMod7(const Integer &in)
{
Integer Sum = 0, CheckNum = in;
while (CheckNum.NotZero())
{
Sum += CheckNum % TEN;
CheckNum /= TEN;
}
return SEVEN - (Sum % SEVEN);
}
/**
* Tests if the last digit (one's place) of a given Integer
* is the expected check digit.
*
* @param in Integer to validate
* @return validity
*/
BOOL PIDGEN::isValidMod7(const Integer &in)
{
return GenerateMod7(in / TEN) == (in % TEN);
}

51
src/libumskt/pidgen.h Normal file
View File

@ -0,0 +1,51 @@
/**
* 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 Neo on 02/13/2024
* @Maintainer Neo
*/
#include "libumskt.h"
#ifndef UMSKT_PIDGEN_H
#define UMSKT_PIDGEN_H
/**
* PIDGEN Interface
*
* Defines three entry points:
* Generate, Validate, StringifyKey
*/
class PIDGEN : public UMSKT
{
public:
static const Integer SEVEN;
static const Integer TEN;
static const Integer MaxChannelID;
static const Integer MaxSerial;
virtual BOOL Generate(std::string &pKey) = 0;
virtual BOOL Validate(const std::string &pKey) = 0;
virtual std::string StringifyKey(const std::string &pKey) = 0;
virtual std::string StringifyProductID() = 0;
Integer GenerateMod7(const Integer &in);
BOOL isValidMod7(const Integer &in);
};
#endif // UMSKT_PIDGEN_H

View File

@ -22,187 +22,304 @@
#include "PIDGEN2.h"
const std::vector<std::string> PIDGEN2::channelIDDisallowList = {"333", "444", "555", "666", "777", "888", "999"};
const std::vector<std::string> PIDGEN2::validYears = {"95", "96", "97", "98", "99", "00", "01", "02"};
/**
* Generates a PID 2.0 key, output is placed in pKey
*
* @param input
* @return
* @param pKey
* @return true
*/
BOOL PIDGEN2::isNumericString(char *input)
BOOL PIDGEN2::Generate(std::string &pKey)
{
for (int i = 0; i < strlen(input); i++)
Integer random;
random.Randomize(rng, sizeof(DWORD32) * 8);
info.ChannelID = random % MaxChannelID;
if (!isValidChannelID())
{
if (input[i] < '0' || input[i] > '9')
info.ChannelID++;
if (info.ChannelID <= Integer::Zero())
{
return false;
info.ChannelID = Integer::One();
}
else if (info.ChannelID >= 999)
{
info.ChannelID = 998;
}
}
random.Randomize(rng, sizeof(DWORD32) * 8);
info.Serial = random % MaxSerial;
if (info.isOEM)
{
info.Day = (random % Integer(365)) + Integer::One();
info.Year = IntegerS(validYears[random % validYears.size()]);
info.OEMID = (info.ChannelID * TEN) + (info.Serial / (MaxSerial / TEN));
info.Serial %= (MaxSerial / TEN);
info.OEMID = (info.OEMID * TEN) + GenerateMod7(info.OEMID);
DWORD32 day, year, serial, oemid;
EncodeN(info.Day, day);
EncodeN(info.Year, year);
EncodeN(info.Serial, serial);
EncodeN(info.OEMID, oemid);
if (debug)
{
fmt::print("\n{:03d}{:02d}-OEM-{:07d}-{:05d}\n", day, year, oemid, serial);
}
pKey = fmt::format("{:03d}{:02d}{:07d}{:05d}", day, year, oemid, serial);
}
else if (info.isOffice)
{
info.ChannelID = (info.ChannelID * TEN) + ((info.ChannelID % TEN) + 1);
info.Serial = (info.Serial * TEN) + GenerateMod7(info.Serial);
DWORD32 channelid, serial;
EncodeN(info.ChannelID, channelid);
EncodeN(info.Serial, serial);
if (debug)
{
fmt::print("\n{:04d}-{:07d}\n", channelid, serial);
}
pKey = fmt::format("{:04d}{:07d}", channelid, serial);
}
else
{
info.Serial = (info.Serial * TEN) + GenerateMod7(info.Serial);
fmt::print("{}\n", info.Serial);
DWORD32 channelid, serial;
EncodeN(info.ChannelID, channelid);
EncodeN(info.Serial, serial);
if (debug)
{
fmt::print("\n{:03d}-{:07d}\n", channelid, serial);
}
pKey = fmt::format("{:03d}{:07d}", channelid, serial);
}
return true;
}
/**
* Valid serial types are:
*
* @param input
* C = Channel/Site ID (001 - 998)
* E = Office Channel ID (+1) Check Digit
* N = Serial
* K = Mod7 Check Digit
*
* -- OEM Specific
* D = 3 Digit day (001 - 366)
* Y = 2 Digit year
* O = OEM ID - typically seen as a channel ID + the first digit of the serial + mod7 check digit
*
* note that the N segment for OEM serials do not have a Mod7 check
*
* CCC-NNNNNNK
* CCCE-NNNNNNK
* DDDYY-ZZOOONK-NNNNN
* DDDYY-OEM-ZZOOONK-NNNNN
*
* we can determine what type of key we have
* simply by counting the numeric characters
*
* @param pKey
* @return
*/
int PIDGEN2::addDigits(char *input)
BOOL PIDGEN2::Validate(const std::string &pKey)
{
int output = 0;
std::string filtered;
std::copy_if(pKey.begin(), pKey.end(), std::back_inserter(filtered), [](char c) { return std::isdigit(c); });
if (!isNumericString(input))
bool bIsValidChannelID, bIsValidSerial, bIsValidOEMDay, bIsValidOEMYear, bIsValidOEMID;
switch (filtered.length())
{
return -1;
}
case KeySize::FPP:
// standard FPP/CCP has 10 digits
info.ChannelID = IntegerS(filtered.substr(0, 3));
info.Serial = IntegerS(filtered.substr(3, 7));
for (int i = 0; i < strlen(input); i++)
{
output += input[i] - '0';
}
bIsValidChannelID = isValidChannelID();
bIsValidSerial = isValidSerial();
return output;
if (debug)
{
fmt::print("\n\nisValidChannelID: {} isValidSerial: {}\n", bIsValidChannelID, bIsValidSerial);
}
return bIsValidChannelID && bIsValidSerial;
case KeySize::Office:
// so far only office 97 has been documented using this
info.isOffice = true;
info.ChannelID = IntegerS(filtered.substr(0, 4));
info.Serial = IntegerS(filtered.substr(4, 7));
bIsValidChannelID = isValidChannelID();
bIsValidSerial = isValidSerial();
if (debug)
{
fmt::print("\n\nisValidChannelID: {} isValidSerial: {}\n", bIsValidChannelID, bIsValidSerial);
}
return bIsValidChannelID && bIsValidSerial;
case KeySize::OEM:
// all OEM keys follow this pattern
info.isOEM = true;
info.Day = IntegerS(filtered.substr(0, 3));
info.Year = IntegerS(filtered.substr(3, 2));
info.OEMID = IntegerS(filtered.substr(5, 7)); // 6 + check digit
info.Serial = IntegerS(filtered.substr(12, 5));
bIsValidOEMDay = isValidOEMDay();
bIsValidOEMYear = isValidOEMYear();
bIsValidOEMID = isValidOEMID();
if (debug)
{
fmt::print("\n\nisValidOEMDay: {} isValidOEMYear: {} isValidOEMID: {}\n", bIsValidOEMDay, bIsValidOEMYear,
bIsValidOEMID);
}
return bIsValidOEMDay && bIsValidOEMYear && bIsValidOEMID;
default:
return false;
}
}
/**
*
* @param channelID
* @param pKey
* @return
*/
BOOL PIDGEN2::isValidChannelID(char *channelID)
std::string PIDGEN2::StringifyKey(const std::string &pKey)
{
if (strlen(channelID) > 3)
switch (pKey.length())
{
case KeySize::FPP:
return fmt::format("{}-{}", pKey.substr(0, 3), pKey.substr(3, 7));
case KeySize::Office:
return fmt::format("{}-{}", pKey.substr(0, 4), pKey.substr(4, 7));
case KeySize::OEM:
return fmt::format("{}-OEM-{}-{}", pKey.substr(0, 5), pKey.substr(5, 7), pKey.substr(12, 5));
default:
return "";
}
}
/**
*
* @return
*/
std::string PIDGEN2::StringifyProductID()
{
if (info.isOEM)
{
return fmt::format("{:d}{:d}-OEM-{:d}-{:d}", info.Year, info.Day, info.OEMID, info.Serial);
}
return fmt::format("{}-{}", info.ChannelID, info.Serial);
}
/**
* Is the Serial with check digit a valid serial?
*
* standard Mod7 Check
*
* @return validity
*/
BOOL PIDGEN2::isValidSerial()
{
return isValidMod7(info.Serial);
}
/**
* Is the OEMID a valid?
*
* @return validity
*/
BOOL PIDGEN2::isValidOEMID()
{
if (info.OEMID.IsZero())
{
return false;
}
for (int i = 0; i <= 6; i++)
return isValidMod7(info.OEMID);
}
/**
* Is the Channel ID a valid Channel ID?
* also validates Channel ID check digit if applicable
*
* Known invalid Channel IDs are:
* 333, 444, 555, 666, 777, 888, 999
*
* @return validity
*/
BOOL PIDGEN2::isValidChannelID() const
{
// if we're office, do the last digit +1 checksum
if (info.isOffice)
{
if (strcmp(channelID, channelIDBlacklist[i]) != 0)
Integer CheckDigit = (info.ChannelID % TEN), ChannelID = (info.ChannelID / TEN);
if (std::find(channelIDDisallowList.begin(), channelIDDisallowList.end(), IntToString(ChannelID)) !=
channelIDDisallowList.end())
{
return false;
}
return (ChannelID % TEN) + 1 == CheckDigit;
}
return true;
// otherwise just make sure we're not in the disallow list
return std::find(channelIDDisallowList.begin(), channelIDDisallowList.end(), IntToString(info.ChannelID)) ==
channelIDDisallowList.end();
}
/**
* Is the OEM year in the allow list?
*
* @param OEMID
* @return
* Known allowed years are:
* 95, 96, 97, 98, 99, 00, 01, 02
*
* @return validity
*/
BOOL PIDGEN2::isValidOEMID(char *OEMID)
BOOL PIDGEN2::isValidOEMYear() const
{
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);
auto year = fmt::format("{:02d}", info.Year.ConvertToLong());
return std::find(validYears.begin(), validYears.end(), year) != validYears.end();
}
/**
* Is the OEM Day an allowed day?
*
* @param year
* @return
* Allowed days are 1 - 366 inclusive
*
* @return validity
*/
BOOL PIDGEN2::isValidYear(char *year)
BOOL PIDGEN2::isValidOEMDay() const
{
for (int i = 0; i <= 7; i++)
{
if (year == validYears[i])
{
return false;
}
}
return true;
return info.Day >= 0 && info.Day <= 366;
}
/**
*
* @param day
* @return
*/
BOOL PIDGEN2::isValidDay(char *day)
{
if (!isNumericString(day))
{
return false;
}
int iDay = std::stoi(day);
if (iDay == 0 || iDay >= 365)
{
return false;
}
return true;
}
/**
*
* @param productID
* @return
*/
BOOL PIDGEN2::isValidRetailProductID(char *productID)
{
return true;
}
/**
*
* @param channelID
* @param keyout
* @return
*/
int PIDGEN2::GenerateRetail(char *channelID, char *&keyout)
{
if (!isValidChannelID(channelID))
{
return 1;
}
return 0;
}
/**
*
* @param year
* @param day
* @param oem
* @param keyout
* @return
*/
int PIDGEN2::GenerateOEM(char *year, char *day, char *oem, char *&keyout)
{
if (!isValidOEMID(oem))
{
int mod = addDigits(oem);
mod += mod % 21;
snprintf(oem, 8, "%07u", mod);
}
if (!isValidYear(year))
{
_strncpy(year, 4, validYears[0], 4);
}
if (!isValidDay(day))
{
auto iday = UMSKT::getRandom<int>();
iday = (iday + NULL_TERMINATOR) % 365;
}
_strncpy(keyout, 32, fmt::format("{}{}-OEM-{}-{}", year, day, oem, oem).c_str(), 32);
return 0;
}

View File

@ -23,28 +23,37 @@
#ifndef UMSKT_PIDGEN2_H
#define UMSKT_PIDGEN2_H
#include "../libumskt.h"
#include "../pidgen.h"
class EXPORT PIDGEN2
class EXPORT PIDGEN2 : public PIDGEN
{
DWORD32 year;
DWORD32 day;
BOOL isOEM;
BOOL isOffice;
static const std::vector<std::string> channelIDDisallowList;
static const std::vector<std::string> validYears;
static constexpr char channelIDBlacklist[7][4] = {"333", "444", "555", "666", "777", "888", "999"};
static constexpr char validYears[8][3] = {"95", "96", "97", "98", "99", "00", "01", "02"};
enum KeySize
{
FPP = 10,
Office = 11,
OEM = 17
};
public:
BOOL isNumericString(char *input);
BOOL isValidChannelID(char *channelID);
BOOL isValidOEMID(char *OEMID);
BOOL isValidYear(char *year);
BOOL isValidDay(char *day);
BOOL isValidRetailProductID(char *productID);
int addDigits(char *input);
int GenerateRetail(char *channelID, char *&keyout);
int GenerateOEM(char *year, char *day, char *oem, char *&keyout);
struct KeyInfo
{
BOOL isOEM, isOffice;
Integer Day, Year, OEMID, ChannelID, Serial;
} info;
BOOL Generate(std::string &pKey) override;
BOOL Validate(const std::string &pKey) override;
std::string StringifyKey(const std::string &pKey) override;
std::string StringifyProductID() override;
BOOL isValidSerial();
BOOL isValidOEMID();
[[nodiscard]] BOOL isValidChannelID() const;
[[nodiscard]] BOOL isValidOEMYear() const;
[[nodiscard]] BOOL isValidOEMDay() const;
};
#endif // UMSKT_PIDGEN2_H

View File

@ -24,6 +24,7 @@
* and uploaded to GitHub by TheMCHK in August of 2019
*
* Endermanch (Andrew) rewrote the algorithm in May of 2023
* Neo ported Endermanch's algorithm to CryptoPP in February of 2024
* }
*/
@ -32,73 +33,78 @@
/**
* Packs a Windows XP-like Product Key.
*
* @param pRaw [in] *QWORD[2] raw product key input
**/
BOOL BINK1998::Pack(Q_OWORD *pRaw)
* @param ki
* @return Integer representation of KeyInfo
*/
Integer BINK1998::Pack(const KeyInfo &ki)
{
// 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
auto serial = (ki.ChannelID * MaxSerial) + ki.Serial;
// Signature [114..59] <- Hash [58..31] <- Serial [30..1] <- Upgrade [0]
Integer raw;
raw += info.Signature << 59;
raw += (info.Hash & BITMASK(28)) << 31;
raw += info.Serial << 1;
raw += info.isUpgrade;
Integer raw = CryptoPP::Crop(ki.Signature, 56) << 59 | CryptoPP::Crop(ki.Hash, 28) << 31 |
CryptoPP::Crop(serial, 30) << 1 | ki.isUpgrade;
raw.Encode(pRaw->byte, sizeof(Q_OWORD));
if (debug)
{
fmt::print(debug, "pack: {:x}\n\n", raw);
}
return true;
return raw;
}
/**
* Unpacks a Windows XP-like Product Key.
*
* @param pRaw [out] *QWORD[2] raw product key output
**/
BOOL BINK1998::Unpack(Q_OWORD *pRaw)
* @param raw Integer to unpack
* @return populated PIDGEN3::KeyInfo struct
*/
BINK1998::KeyInfo BINK1998::Unpack(const Integer &raw)
{
KeyInfo ki;
// We're assuming that the quantity of information within the product key is at most 114 bits.
// log2(24^25) = 114.
// Upgrade = Bit 0
info.isUpgrade = FIRSTNBITS(pRaw->qword[0], 1);
ki.isUpgrade = CryptoPP::Crop(raw, 1).ConvertToLong();
// Serial = Bits [1..30] -> 30 bits
info.Serial = NEXTSNBITS(pRaw->qword[0], 30, 1);
auto serialPack = CryptoPP::Crop((raw >> 1), 30);
ki.Serial = serialPack % MaxSerial;
ki.ChannelID = ((serialPack - ki.Serial) / MaxSerial);
// Hash = Bits [31..58] -> 28 bits
info.Hash = NEXTSNBITS(pRaw->qword[0], 28, 31);
ki.Hash = CryptoPP::Crop((raw >> 31), 28);
// Signature = Bits [59..113] -> 56 bits
info.Signature = FIRSTNBITS(pRaw->qword[1], 51) << 5 | NEXTSNBITS(pRaw->qword[0], 5, 59);
ki.Signature = CryptoPP::Crop((raw >> 59), 56);
return true;
return ki;
}
/**
* Generates a Windows XP-like Product Key.
*
* @param pKey [out]
*
* @return true on success, false on fail
*/
BOOL BINK1998::Generate(std::string &pKey)
{
Integer c, s;
Integer c, s, pRaw;
SHA1 sha1;
Q_OWORD pRaw;
// copy initial state from object
auto ki = info;
// Data segment of the RPK.
Integer pData = info.Serial << 1 | info.isUpgrade;
Integer serialPack = (ki.ChannelID * MaxSerial) + ki.Serial;
Integer pData = (serialPack << 1) | ki.isUpgrade;
// prepare the private key for generation
privateKey -= genOrder;
Integer limit;
limit.SetBit(55);
limit -= 1;
privateKey = genOrder - privateKey;
do
{
@ -110,32 +116,47 @@ BOOL BINK1998::Generate(std::string &pKey)
// Pick a random derivative of the base point on the elliptic curve.
// R = cG;
R = eCurve.Multiply(c, genPoint);
if (debug)
{
fmt::print(debug, "c: {:x}\n\n", c);
fmt::print(debug, "R[x,y] [{:x},\n{:x}]\n\n", R.x, R.y);
}
// Acquire its coordinates.
// x = R.x; y = R.y;
BYTE msgDigest[SHA::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
// Assemble the SHA message.
pData.Encode((CryptoPP::byte *)msgBuffer, 4);
pMsgBuffer += 4;
R.x.Encode(pMsgBuffer, FieldBytes);
pMsgBuffer += FieldBytes;
R.y.Encode(pMsgBuffer, FieldBytes);
pMsgBuffer += FieldBytes;
pMsgBuffer = EncodeN(pData, pMsgBuffer, 4);
pMsgBuffer = EncodeN(R.x, pMsgBuffer, FieldBytes);
EncodeN(R.y, pMsgBuffer, FieldBytes);
// pHash = SHA1(pSerial || R.x || R.y)
auto digest = SHA();
digest.Update(msgBuffer, SHAMessageLength);
digest.Final(msgDigest);
sha1.CalculateDigest(msgDigest, msgBuffer, sizeof(msgBuffer));
if (debug)
{
fmt::print(debug, "msgBuffer: ");
for (BYTE b : msgBuffer)
{
fmt::print(debug, "{:x}", b);
}
fmt::print(debug, "\n\n");
fmt::print(debug, "msgDigest: ");
for (BYTE b : msgDigest)
{
fmt::print(debug, "{:x}", b);
}
fmt::print(debug, "\n\n");
}
// Translate the byte digest into a 32-bit integer - this is our computed pHash.
// Truncate the pHash to 28 bits.
info.Hash.Decode(msgDigest, SHAMessageLength);
info.Hash = BYDWORD(msgDigest) >> 4 & BITMASK(28);
ki.Hash = IntegerN(msgDigest, 4) >> 4;
ki.Hash = CryptoPP::Crop(ki.Hash, 28);
/*
*
@ -157,33 +178,38 @@ BOOL BINK1998::Generate(std::string &pKey)
*/
// s = ek;
s = privateKey * info.Hash;
s = privateKey * ki.Hash;
// s += c (mod n)
s = s + c % genOrder;
s += c;
s %= genOrder;
// Translate resulting scalar into a 64-bit integer (the byte order is little-endian).
info.Signature = s;
// Translate resulting scalar into an Integer.
ki.Signature = s;
// Pack product key.
Pack(&pRaw);
pRaw = Pack(ki);
auto serial = fmt::format("{:d}", info.Serial);
fmt::print(UMSKT::debug, "Generation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Upgrade", (bool)info.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Channel ID", serial.substr(0, 3));
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Sequence", serial.substr(3));
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Hash", info.Hash);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Signature", info.Signature);
fmt::print(UMSKT::debug, "\n");
if (verbose)
{
fmt::print(verbose, "Generation results:\n");
fmt::print(verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(verbose, "{:>10}: {}\n", "Sequence", ki.Serial);
fmt::print(verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(verbose, "{:>10}: {:x}\n", "Signature", ki.Signature);
fmt::print(verbose, "\n");
}
} while (info.Signature > limit);
} while (ki.Signature.BitCount() > 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, pRaw.byte);
pKey = base24(pRaw);
info = ki;
return true;
}
@ -195,31 +221,33 @@ BOOL BINK1998::Generate(std::string &pKey)
*
* @return true if provided key validates against loaded curve
*/
BOOL BINK1998::Validate(std::string &pKey)
BOOL BINK1998::Validate(const std::string &pKey)
{
if (pKey.length() != 25)
{
return false;
}
Q_OWORD pRaw;
// Convert Base24 CD-key to bytecode.
unbase24(pRaw.byte, pKey);
Integer pRaw = unbase24(pKey);
SHA1 sha1;
// Extract RPK, hash and signature from bytecode.
Unpack(&pRaw);
KeyInfo ki = Unpack(pRaw);
auto serial = fmt::format("{:d}", info.Serial);
fmt::print(UMSKT::debug, "Validation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Upgrade", (bool)info.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Channel ID", serial.substr(0, 3));
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Sequence", serial.substr(3));
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Hash", info.Hash);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Signature", info.Signature);
fmt::print(UMSKT::debug, "\n");
if (verbose)
{
fmt::print(UMSKT::verbose, "Validation results:\n");
fmt::print(UMSKT::verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(UMSKT::verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(UMSKT::verbose, "{:>10}: {}\n", "Sequence", ki.Serial);
fmt::print(UMSKT::verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(UMSKT::verbose, "{:>10}: {:x}\n", "Signature", ki.Signature);
fmt::print(UMSKT::verbose, "\n");
}
Integer pData = info.Serial << 1 | info.isUpgrade;
Integer serialPack = (ki.ChannelID * MaxSerial) + ki.Serial;
Integer pData = serialPack << 1 | ki.isUpgrade;
/*
*
@ -236,8 +264,7 @@ BOOL BINK1998::Validate(std::string &pKey)
*
*/
Integer e = info.Hash, s;
s.Decode((BYTE *)&info.Signature, sizeof(info.Signature));
Integer e = ki.Hash, s = ki.Signature;
// Create 2 points on the elliptic curve.
ECP::Point t, P;
@ -251,29 +278,35 @@ BOOL BINK1998::Validate(std::string &pKey)
// P += t
P = eCurve.Add(P, t);
BYTE msgDigest[SHA::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
if (debug)
{
fmt::print("\nP[x,y]: [{:x},\n{:x}]\n\n", P.x, P.y);
}
BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
// Convert resulting point coordinates to bytes.
// Assemble the SHA message.
pData.Encode(pMsgBuffer, 4);
pMsgBuffer += 4;
P.x.Encode(pMsgBuffer, FieldBytes);
pMsgBuffer += FieldBytes;
P.y.Encode(pMsgBuffer, FieldBytes);
pMsgBuffer += FieldBytes;
pMsgBuffer = EncodeN(pData, pMsgBuffer, 4);
pMsgBuffer = EncodeN(P.x, pMsgBuffer, FieldBytes);
EncodeN(P.y, pMsgBuffer, FieldBytes);
// compHash = SHA1(pSerial || P.x || P.y)
auto digest = SHA();
digest.Update(msgBuffer, SHAMessageLength);
digest.Final(msgDigest);
sha1.CalculateDigest(msgDigest, msgBuffer, SHAMessageLength);
// Translate the byte digest into a 32-bit integer - this is our computed hash.
auto intDigest = IntegerN(msgDigest);
if (debug)
{
fmt::print(debug, "hash: {:x}\n\n", intDigest);
}
info = ki;
// Translate the byte sha1 into a 32-bit integer - this is our computed hash.
// Truncate the hash to 28 bits.
Integer compHash = BYDWORD(msgDigest) >> 4 & BITMASK(28);
Integer compHash = CryptoPP::Crop(intDigest >> 4, 28);
// If the computed hash checks out, the key is valid.
return compHash == info.Hash;
return compHash == ki.Hash;
}

View File

@ -39,21 +39,21 @@ class EXPORT BINK1998 : public PIDGEN3
eCurve = p3->eCurve;
}
static constexpr DWORD32 FieldBits = 384;
static constexpr DWORD32 FieldBytes = FieldBits / 8;
static constexpr DWORD32 FieldBits = (48 * 8);
static constexpr DWORD32 FieldBytes = (FieldBits / 8);
static constexpr DWORD32 SHAMessageLength = (4 + 2 * FieldBytes);
using PIDGEN3::Pack;
BOOL Pack(Q_OWORD *pRaw) override;
Integer Pack(const KeyInfo &ki) override;
using PIDGEN3::Unpack;
BOOL Unpack(Q_OWORD *pRaw) override;
KeyInfo Unpack(const Integer &raw) override;
using PIDGEN3::Generate;
BOOL Generate(std::string &pKey) override;
using PIDGEN3::Validate;
BOOL Validate(std::string &pKey) override;
BOOL Validate(const std::string &pKey) override;
};
#endif // UMSKT_BINK1998_H

View File

@ -24,6 +24,7 @@
* and uploaded to GitHub by TheMCHK in August of 2019
*
* Endermanch (Andrew) rewrote the algorithm in May of 2023
* Neo ported Endermanch's algorithm to CryptoPP in February of 2024
* }
*/
@ -32,52 +33,53 @@
/**
* Packs a Windows Server 2003-like Product Key.
*
* @param pRaw [out] *OWORD raw product key
**/
BOOL BINK2002::Pack(Q_OWORD *pRaw)
* @param ki PIDGEN3::KeyInfo struct to pack
* @return Integer representation of the Product Key
*/
Integer BINK2002::Pack(const KeyInfo &ki)
{
Integer raw;
// AuthInfo [113..104] <- Signature [103..42] <- Hash [41..11] <- Channel ID [10..1] <- Upgrade [0];
raw += (info.AuthInfo & ((1 << 11) - 1)) << 104;
raw += info.Signature << 42;
raw += info.Hash << 11;
raw += info.ChannelID << 1;
raw += info.isUpgrade;
Integer raw = CryptoPP::Crop(ki.AuthInfo, 10) << 104 | CryptoPP::Crop(ki.Signature, 62) << 42 |
CryptoPP::Crop(ki.Hash, 31) << 11 | CryptoPP::Crop(ki.ChannelID, 10) << 1 | ki.isUpgrade;
raw.Encode((BYTE *)pRaw, sizeof(QWORD) * 2);
if (debug)
{
fmt::print("pack: {:x}\n\n", raw);
}
return true;
return raw;
}
/**
* Unpacks a Windows Server 2003-like Product Key.
*
* @param pRaw [in] *OWORD raw product key input
**/
BOOL BINK2002::Unpack(Q_OWORD *pRaw)
* @param raw Integer representation of the product key
* @return unpacked PIDGEN3::KeyInfo struct
*/
BINK2002::KeyInfo BINK2002::Unpack(const Integer &raw)
{
// We're assuming that the quantity of information within the product key is at most 114 bits.
// log2(24^25) = 114.
KeyInfo ki;
// Upgrade = Bit 0
info.isUpgrade = FIRSTNBITS(pRaw->qword[0], 1);
ki.isUpgrade = CryptoPP::Crop(raw, 1).ConvertToLong();
// Channel ID = Bits [1..10] -> 10 bits
info.ChannelID = NEXTSNBITS(pRaw->qword[0], 10, 1);
ki.ChannelID = CryptoPP::Crop(raw >> 1, 10);
// Hash = Bits [11..41] -> 31 bits
info.Hash = NEXTSNBITS(pRaw->qword[0], 31, 11);
// Hash = Bits [11..41] -> 30 bits
ki.Hash = CryptoPP::Crop(raw >> 11, 31);
// 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)
info.Signature = NEXTSNBITS(pRaw->qword[0], 30, 10) << 32 | FIRSTNBITS(pRaw->qword[1], 10) << 22 |
NEXTSNBITS(pRaw->qword[0], 22, 42);
ki.Signature = CryptoPP::Crop(raw >> 42, 62);
// AuthInfo = Bits [104..113] -> 10 bits
info.AuthInfo = NEXTSNBITS(pRaw->qword[1], 10, 40);
ki.AuthInfo = CryptoPP::Crop(raw >> 104, 10);
return true;
return ki;
}
/**
@ -89,16 +91,14 @@ BOOL BINK2002::Unpack(Q_OWORD *pRaw)
*/
BOOL BINK2002::Generate(std::string &pKey)
{
Integer c, e, s;
// copy the starting state from the class
KeyInfo ki = info;
SHA1 sha1;
Q_OWORD pRaw;
Integer limit;
limit.SetBit(62);
limit--;
Integer c, e, s, pRaw;
// Data segment of the RPK.
Integer pData = info.ChannelID << 1 | info.isUpgrade;
Integer pData = ki.ChannelID << 1 | ki.isUpgrade;
BOOL noSquare;
@ -111,61 +111,114 @@ BOOL BINK2002::Generate(std::string &pKey)
// R = cG
R = eCurve.Multiply(c, genPoint);
if (debug)
{
fmt::print(debug, "c: {:x}\n\n", c);
fmt::print(debug, "R[x,y] [{:x},\n{:x}]\n\n", R.x, R.y);
}
BYTE msgDigest[SHA::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
// Assemble the first SHA message.
msgBuffer[0] = 0x79;
*pMsgBuffer = 0x79;
pMsgBuffer++;
pData.Encode(pMsgBuffer, 2);
pMsgBuffer += 2;
pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
// Convert resulting point coordinates to bytes.
// and flip the endianness
R.x.Encode(pMsgBuffer, FieldBytes);
std::reverse(pMsgBuffer, pMsgBuffer + FieldBytes);
pMsgBuffer += FieldBytes;
R.y.Encode(pMsgBuffer, FieldBytes);
std::reverse(pMsgBuffer, pMsgBuffer + FieldBytes);
pMsgBuffer += FieldBytes;
pMsgBuffer = EncodeN(R.x, pMsgBuffer, FieldBytes);
EncodeN(R.y, pMsgBuffer, FieldBytes);
// pHash = SHA1(79 || Channel ID || R.x || R.y)
auto digest = SHA();
digest.Update(msgBuffer, FieldBytes);
digest.Final(msgDigest);
sha1.CalculateDigest(msgDigest, msgBuffer, SHAMessageLength);
// Translate the byte digest into a 32-bit integer - this is our computed hash.
if (debug)
{
fmt::print("msgBuffer[1]: ");
for (BYTE b : msgBuffer)
{
fmt::print("{:x}", b);
}
fmt::print("\n\n");
fmt::print("msgDigest[1]: ");
for (BYTE b : msgDigest)
{
fmt::print("{:x}", b);
}
fmt::print("\n\n");
}
// Translate the byte sha1 into a 32-bit integer - this is our computed hash.
// Truncate the hash to 31 bits.
info.Hash = BYDWORD(msgDigest) & BITMASK(31);
ki.Hash = CryptoPP::Crop(IntegerN(msgDigest), 31);
if (verbose)
{
BYTE buf[8];
sha1.CalculateTruncatedDigest(buf, sizeof(buf), msgBuffer, SHAMessageLength);
fmt::print("truncated buffer: ");
for (BYTE b : buf)
{
fmt::print("{:x}", b);
}
fmt::print("\n\n");
DWORD h0 = ((DWORD)buf[0] | ((DWORD)buf[1] << 8) | ((DWORD)buf[2] << 16) | ((DWORD)buf[3] << 24));
DWORD h1 =
((((DWORD)buf[4]) | ((DWORD)buf[5] << 8) | ((DWORD)buf[6] << 16) | ((DWORD)buf[7] << 24)) >> (32 - 19))
<< 1;
h1 |= (h0 >> 31) & 1;
fmt::print("h0,1: {:x} {:x}\n\n", h0, h1);
ki.Serial = IntegerN(h1);
fmt::print("serial: {:d}\n\n", ki.Serial);
}
// Assemble the second SHA message.
pMsgBuffer = msgBuffer;
msgBuffer[0x00] = 0x5D;
pMsgBuffer++;
pData.Encode(pMsgBuffer, 2);
pMsgBuffer += 2;
pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
pMsgBuffer = EncodeN(ki.Hash, pMsgBuffer, 4);
pMsgBuffer = EncodeN(ki.AuthInfo, pMsgBuffer, 2);
info.Hash.Encode(pMsgBuffer, 4);
pMsgBuffer += 4;
*pMsgBuffer = 0x00;
pMsgBuffer++;
info.AuthInfo.Encode(pMsgBuffer, 2);
pMsgBuffer += 2;
msgBuffer[0x09] = 0x00;
msgBuffer[0x0A] = 0x00;
*pMsgBuffer = 0x00;
pMsgBuffer++;
// newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00)
digest = SHA();
digest.Update(msgBuffer, 0x0B);
digest.Final(msgDigest);
sha1.CalculateDigest(msgDigest, msgBuffer, pMsgBuffer - msgBuffer);
// Translate the byte digest into a 64-bit integer - this is our computed intermediate signature.
if (debug)
{
fmt::print("msgBuffer[2]: ");
for (BYTE b : msgBuffer)
{
fmt::print("{:x}", b);
}
fmt::print("\n\n");
fmt::print("msgDigest[2]: ");
for (BYTE b : msgDigest)
{
fmt::print("{:x}", b);
}
fmt::print("\n\n");
}
// Translate the byte sha1 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).
Integer iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
/*
*
@ -197,13 +250,13 @@ BOOL BINK2002::Generate(std::string &pKey)
*/
// e = ek (mod n)
e = iSignature * privateKey % genOrder;
e = CryptoPP::ModularMultiplication(IntegerN(iSignature), privateKey, genOrder);
// s = (ek (mod n))²
s = CryptoPP::ModularExponentiation(e, Integer::Two(), genOrder);
// c *= 4 (c <<= 2)
c <<= 2;
c *= 4;
// s += c
s += c;
@ -212,9 +265,11 @@ BOOL BINK2002::Generate(std::string &pKey)
// hence if BN_sqrt_mod returns NULL, we need to restart with a different seed.
// s = √((ek)² + 4c (mod n))
s = CryptoPP::ModularSquareRoot(s, genOrder);
noSquare = s.IsZero();
// s = -ek + √((ek)² + 4c) (mod n)
s = s - e % genOrder;
s -= e;
s %= genOrder;
// If s is odd, add order to it.
// The order is a prime, so it can't be even.
@ -225,29 +280,32 @@ BOOL BINK2002::Generate(std::string &pKey)
}
// s /= 2 (s >>= 1)
s >>= 1;
s /= 2;
// Translate resulting scalar into a 64-bit integer (the byte order is little-endian).
info.Signature = s;
ki.Signature = s;
// Pack product key.
Pack(&pRaw);
pRaw = Pack(ki);
fmt::print(UMSKT::debug, "Generation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Upgrade", (bool)info.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Channel ID", info.ChannelID);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Hash", info.Hash);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Signature", info.Signature);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "AuthInfo", info.AuthInfo);
fmt::print(UMSKT::debug, "\n");
} while (info.Signature > limit || noSquare);
if (verbose)
{
fmt::print(verbose, "Generation results:\n");
fmt::print(verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(verbose, "{:>10}: {:x}\n", "Signature", ki.Signature);
fmt::print(verbose, "{:>10}: {:x}\n", "AuthInfo", ki.AuthInfo);
fmt::print(verbose, "\n");
}
} while (ki.Signature.BitCount() > 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, pRaw.byte);
pKey = base24(pRaw);
info = ki;
return true;
}
@ -257,53 +315,59 @@ BOOL BINK2002::Generate(std::string &pKey)
*
* @param pKey
**/
BOOL BINK2002::Validate(std::string &pKey)
BOOL BINK2002::Validate(const std::string &pKey)
{
Q_OWORD bKey;
Integer pRaw;
SHA1 sha1;
// Convert Base24 CD-key to bytecode.
unbase24(bKey.byte, &pKey[0]);
pRaw = unbase24(pKey);
// Extract product key segments from bytecode.
Unpack(&bKey);
KeyInfo ki = Unpack(pRaw);
Integer pData = info.ChannelID << 1 | info.isUpgrade;
Integer pData = ki.ChannelID << 1 | ki.isUpgrade;
fmt::print(UMSKT::debug, "Validation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Upgrade", (bool)info.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Channel ID", info.ChannelID);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Hash", info.Hash);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Signature", info.Signature);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "AuthInfo", info.AuthInfo);
fmt::print(UMSKT::debug, "\n");
if (verbose)
{
fmt::print(verbose, "Validation results:\n");
fmt::print(verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(verbose, "{:>10}: {:x}\n", "Signature", ki.Signature);
fmt::print(verbose, "{:>10}: {:x}\n", "AuthInfo", ki.AuthInfo);
fmt::print(verbose, "\n");
}
BYTE msgDigest[SHA::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
// Assemble the first SHA message.
msgBuffer[0x00] = 0x5D;
pMsgBuffer++;
pData.Encode(pMsgBuffer, 2);
pMsgBuffer += 2;
pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
pMsgBuffer = EncodeN(ki.Hash, pMsgBuffer, 4);
pMsgBuffer = EncodeN(ki.AuthInfo, pMsgBuffer, 2);
info.Hash.Encode(pMsgBuffer, 4);
pMsgBuffer += 4;
*pMsgBuffer = 0x00;
pMsgBuffer++;
info.AuthInfo.Encode(pMsgBuffer, 2);
pMsgBuffer += 2;
msgBuffer[0x09] = 0x00;
msgBuffer[0x0A] = 0x00;
*pMsgBuffer = 0x00;
pMsgBuffer++;
// newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00)
auto digest = SHA();
digest.Update(msgBuffer, 0x0B);
digest.Final(msgDigest);
sha1.CalculateDigest(msgDigest, msgBuffer, pMsgBuffer - msgBuffer);
// Translate the byte digest into a 64-bit integer - this is our computed intermediate signature.
if (debug)
{
auto intDigest = IntegerN(msgDigest);
fmt::print(debug, "\nhash 1: {:x}\n\n", intDigest);
}
// Translate the byte sha1 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).
Integer iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
/*
*
@ -319,45 +383,52 @@ BOOL BINK2002::Validate(std::string &pKey)
* P = s(sG + eK)
*
*/
Integer e = iSignature, s = info.Signature;
Integer e = IntegerN(iSignature), s = ki.Signature;
// Create 2 points on the elliptic curve.
ECP::Point p, t;
ECP::Point P, t;
// t = sG
t = eCurve.Multiply(s, genPoint);
// p = eK
p = eCurve.Multiply(e, pubPoint);
// P = eK
P = eCurve.Multiply(e, pubPoint);
// p += t
p = eCurve.Add(p, t);
// P += t
P = eCurve.Add(P, t);
// p *= s
p = eCurve.Multiply(s, p);
// P *= s
P = eCurve.Multiply(s, P);
if (debug)
{
fmt::print("P[x,y]: [{:x},\n{:x}]\n\n", P.x, P.y);
}
// Assemble the second SHA message.
pMsgBuffer = msgBuffer;
msgBuffer[0x00] = 0x79;
pMsgBuffer++;
pData.Encode(pMsgBuffer, 2);
pMsgBuffer += 2;
pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
pMsgBuffer = EncodeN(P.x, pMsgBuffer, FieldBytes);
EncodeN(P.y, pMsgBuffer, FieldBytes);
p.x.Encode(pMsgBuffer, FieldBytes);
pMsgBuffer += FieldBytes;
// compHash = SHA1(79 || Channel ID || P.x || P.y)
sha1.CalculateDigest(msgDigest, msgBuffer, SHAMessageLength);
p.y.Encode(pMsgBuffer, FieldBytes);
pMsgBuffer += FieldBytes;
auto intDigest = IntegerN(msgDigest);
if (debug)
{
fmt::print(debug, "hash 2: {:x}\n\n", intDigest);
}
// compHash = SHA1(79 || Channel ID || p.x || p.y)
digest.Update(msgBuffer, SHAMessageLength);
digest.Final(msgDigest);
// Translate the byte digest into a 32-bit integer - this is our computed hash.
// Translate the byte sha1 into a 32-bit integer - this is our computed hash.
// Truncate the hash to 31 bits.
Integer compHash = BYDWORD(msgDigest) & BITMASK(31);
Integer compHash = CryptoPP::Crop(intDigest, 31);
info = ki;
// If the computed hash checks out, the key is valid.
return compHash == info.Hash;
return compHash == ki.Hash;
}

View File

@ -39,21 +39,21 @@ class EXPORT BINK2002 : public PIDGEN3
eCurve = p3->eCurve;
}
static constexpr DWORD32 FieldBits = 512;
static constexpr DWORD32 FieldBytes = FieldBits / 8;
static constexpr DWORD32 FieldBits = (64 * 8);
static constexpr DWORD32 FieldBytes = (FieldBits / 8);
static constexpr DWORD32 SHAMessageLength = (3 + 2 * FieldBytes);
using PIDGEN3::Pack;
BOOL Pack(Q_OWORD *pRaw) override;
Integer Pack(const KeyInfo &ki) override;
using PIDGEN3::Unpack;
BOOL Unpack(Q_OWORD *pRaw) override;
KeyInfo Unpack(const Integer &raw) override;
using PIDGEN3::Generate;
BOOL Generate(std::string &pKey) override;
using PIDGEN3::Validate;
BOOL Validate(std::string &pKey) override;
BOOL Validate(const std::string &pKey) override;
};
#endif // UMSKT_BINK2002_H

View File

@ -25,7 +25,19 @@
#include "BINK2002.h"
/**
* https://xkcd.com/221/
* PID 3.0 Product Key Character Set
*/
const std::string PIDGEN3::pKeyCharset = "BCDFGHJKMPQRTVWXY2346789";
/**
* Maximum Field size for BINK 1998
*/
const DWORD32 PIDGEN3::MaxSizeBINK1998 = BINK1998::FieldBits + 1;
/**
* RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.
*
* see also: https://xkcd.com/221/
*
* @return 4
*/
@ -35,24 +47,6 @@ int getRandomNumber()
// guaranteed to be random
}
/**
* Creates an Integer that populates PIDGEN3::MaxSizeBINK1998
* Invoked during Runtime startup
*
* @return
*/
Integer MakeMaxSizeBINK1998()
{
Integer max;
// 1 << 385 (or max size of BINK1998 field in bits + 1)
max.SetBit((12 * 4 * 8) + 1);
return max;
}
const Integer PIDGEN3::MaxSizeBINK1998 = MakeMaxSizeBINK1998();
/**
* Initializes the elliptic curve
*
@ -68,10 +62,11 @@ const Integer PIDGEN3::MaxSizeBINK1998 = MakeMaxSizeBINK1998();
*
* @return true on success, false on fail
*/
BOOL PIDGEN3::LoadEllipticCurve(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,
const std::string genOrderSel, const std::string privateKeySel)
BOOL PIDGEN3::LoadEllipticCurve(const std::string &BinkIDSel, 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, const std::string &genOrderSel,
const std::string &privateKeySel)
{
// We cannot produce a valid key without knowing the private key k. The reason for this is that
// we need the result of the function K(x; y) = kG(x; y).
@ -79,23 +74,21 @@ BOOL PIDGEN3::LoadEllipticCurve(const std::string pSel, const std::string aSel,
// We can, however, validate any given key using the available public key: {p, a, b, G, K}.
// genOrder the order of the generator G, a value we have to reverse -> Schoof's Algorithm.
// Initialize BIGNUM and BIGNUMCTX structures.
// BIGNUM - Large numbers
// BIGNUMCTX - Context large numbers (temporary)
BINKID = IntegerHexS(BinkIDSel);
// We're presented with an elliptic curve, a multivariable function y(x; p; a; b), where
// y^2 % p = x^3 + ax + b % p.
auto p = Integer(&pSel[0]), a = Integer(&aSel[0]), b = Integer(&bSel[0]),
auto p = IntegerS(pSel), a = IntegerS(aSel), b = IntegerS(bSel);
// Public key will consist of the resulting (x; y) values.
generatorX = Integer(&generatorXSel[0]), generatorY = Integer(&generatorYSel[0]),
// Public key will consist of the resulting (x; y) values.
auto generatorX = IntegerS(generatorXSel), generatorY = IntegerS(generatorYSel);
// G(x; y) is a generator function, its return value represents a point on the elliptic curve.
publicKeyX = Integer(&publicKeyXSel[0]), publicKeyY = Integer(&publicKeyYSel[0]);
// G(x; y) is a generator function, its return value represents a point on the elliptic curve.
auto publicKeyX = IntegerS(publicKeyXSel), publicKeyY = IntegerS(publicKeyYSel);
/* Computed Data */
genOrder = Integer(&genOrderSel[0]);
privateKey = Integer(&privateKeySel[0]);
genOrder = IntegerS(genOrderSel);
privateKey = IntegerS(privateKeySel);
/* Elliptic Curve calculations. */
// The group is defined via Fp = all integers [0; p - 1], where p is prime.
@ -117,6 +110,12 @@ BOOL PIDGEN3::LoadEllipticCurve(const std::string pSel, const std::string aSel,
return true;
}
/**
* Instantiates a PID 3.0 generator based on a given field on the heap
*
* @param field
* @return PIDGEN3 based on the field type
*/
PIDGEN3 *PIDGEN3::Factory(const std::string &field)
{
if (checkFieldStrIsBink1998(field))
@ -126,141 +125,192 @@ PIDGEN3 *PIDGEN3::Factory(const std::string &field)
return new BINK2002();
}
/**
* Factory-style Generate function, checks the currently instantiated field
* creates the correct PIDGEN for the field type using the copy constructor
* and invokes its Generate()
*
* @param pKey
* @return successfulness
*/
BOOL PIDGEN3::Generate(std::string &pKey)
{
BOOL retval;
if (checkFieldIsBink1998())
{
auto p3 = BINK1998();
retval = p3.Generate(pKey);
}
else
{
auto p3 = BINK2002();
retval = p3.Generate(pKey);
}
return retval;
}
BOOL PIDGEN3::Validate(std::string &pKey)
{
BOOL retval;
if (checkFieldIsBink1998())
{
auto p3 = BINK1998(this);
retval = p3.Validate(pKey);
}
else
{
auto p3 = BINK2002(this);
retval = p3.Validate(pKey);
return p3.Generate(pKey);
}
return retval;
auto p3 = BINK2002(this);
return p3.Generate(pKey);
}
/**
* Factory style Validate function, see Generate() for more info
*
* @param pKey
* @return successfulness
*/
BOOL PIDGEN3::Validate(const std::string &pKey)
{
if (checkFieldIsBink1998())
{
auto p3 = BINK1998(this);
return p3.Validate(pKey);
}
auto p3 = BINK2002(this);
return p3.Validate(pKey);
}
/**
* Converts from byte sequence to the CD-key.
*
* @param cdKey [out] std::string CDKey input
* @param byteSeq [in] BYTE*
* @param seq Integer representation
* @return std::string CDKey output
**/
void PIDGEN3::base24(std::string &cdKey, BYTE *byteSeq)
std::string PIDGEN3::base24(Integer &seq)
{
BYTE rbyteSeq[16], output[26];
// 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--)
{
; // do nothing, just counting
}
// Convert reversed byte sequence to BigNum z.
auto z = Integer((BYTE *)&rbyteSeq, sizeof(rbyteSeq));
std::string cdKey;
cdKey.reserve(PK_LENGTH);
// Divide z by 24 and convert the remainder to a CD-key char.
for (int i = 24; i >= 0; i--)
Integer r, q, a = seq;
for (int i = PK_LENGTH - 1; i >= 0; i--)
{
output[i] = pKeyCharset[z.Modulo(24)];
Integer::Divide(r, q, a, (WORD)pKeyCharset.length());
cdKey.insert(cdKey.begin(), pKeyCharset[r.ConvertToLong()]);
a = q;
}
output[25] = 0;
cdKey = (char *)output;
return cdKey;
}
/**
* Converts from CD-key to a byte sequence.
*
* @param byteSeq [out] *BYTE representation of the CDKey
* @param cdKey [in] std::string CDKey to convert
* @param cdKey std::string CDKey to convert
* @return Integer raw representation of the CDKey
**/
void PIDGEN3::unbase24(BYTE *byteSeq, std::string cdKey)
Integer PIDGEN3::unbase24(const std::string &cdKey)
{
BYTE pDecodedKey[PK_LENGTH + NULL_TERMINATOR]{};
Integer y;
Integer result;
// Remove dashes from the CD-key and put it into a Base24 byte array.
for (int i = 0, k = 0; i < cdKey.length() && k < PK_LENGTH; i++)
for (char ch : cdKey)
{
for (int j = 0; j < 24; j++)
auto val = std::find(pKeyCharset.begin(), pKeyCharset.end(), ch);
// character is not in set, return early
if (val == pKeyCharset.end())
{
if (cdKey[i] != '-' && cdKey[i] == pKeyCharset[j])
{
pDecodedKey[k++] = j;
break;
}
return result;
}
// add the weighted sum to result
result *= (int)pKeyCharset.length();
result += (int)(val - pKeyCharset.begin());
}
// Empty byte sequence.
memset(byteSeq, 0, 16);
return result;
}
// Calculate the weighed sum of byte array elements.
for (int i = 0; i < PK_LENGTH; i++)
/**
* Takes the currently loaded Class-level KeyInfo and calculates the check digit for display.
*
* Algorithm directly taken from PIDGEN
*
* @return std::string representation of the Product ID as Displayed on the Product
*/
std::string PIDGEN3::StringifyProductID()
{
if (info.isOEM)
{
y *= PK_LENGTH - 1;
y += pDecodedKey[i];
Integer OEMID = info.ChannelID * Integer(100);
OEMID += ((info.Serial / (MaxSerial / TEN)) * TEN);
OEMID += GenerateMod7(OEMID);
Integer Serial = info.Serial % (MaxSerial / TEN);
DWORD32 iOEMID = OEMID.ConvertToLong(), iSerial = Serial.ConvertToLong();
return fmt::format("PPPPP-OEM-{:07d}-{:05d}", iOEMID, iSerial);
}
else
{
DWORD32 ChannelID = info.ChannelID.ConvertToLong(),
Serial = (info.Serial * TEN + GenerateMod7(info.Serial)).ConvertToLong(),
BinkID = (BINKID / Integer::Two()).ConvertToLong();
return fmt::format("PPPPP-{:03d}-{:07d}-{:d}xxx", ChannelID, Serial, BinkID);
}
// Acquire length.
auto n = y.ByteCount();
// Place the generated code into the byte sequence.
y.Encode(byteSeq, 16);
}
/**
* Checks to see if the currently instantiated PIDGEN3 object has a
* field size greater than the maximum known BINK1998 size.
*
* @return
* @return boolean value
*/
BOOL PIDGEN3::checkFieldIsBink1998()
{
// is max > privateKey?
return (MaxSizeBINK1998 > privateKey);
// is fieldSize < max?
return (eCurve.FieldSize().BitCount() < MaxSizeBINK1998);
}
/**
* Checks if a given field, in a std::string, is greater than
* the maximum known BINK1998 size
*
* @param keyin
* @return
* @param keyin std::string representation of a Field
* @return boolean value
*/
BOOL PIDGEN3::checkFieldStrIsBink1998(std::string keyin)
{
Integer check(&keyin[0]);
auto check = IntegerS(keyin);
// is max > check?
return (MaxSizeBINK1998 > check);
// is fieldSize < max?
return (check.BitCount() < MaxSizeBINK1998);
}
/**
* Prints a product key to stdout
*
* @param pk std::string to print
*/
std::string PIDGEN3::StringifyKey(const std::string &pKey)
{
assert(pKey.length() >= PK_LENGTH);
return fmt::format("{}-{}-{}-{}-{}", pKey.substr(0, 5), pKey.substr(5, 5), pKey.substr(10, 5), pKey.substr(15, 5),
pKey.substr(20, 5));
}
/**
* std::BinaryOperation compatible accumulator for validating/stripping an input string against the PIDGEN3 charset
*
* @param accumulator
* @param currentChar
* @return
*/
std::string PIDGEN3::ValidateStringKeyInputCharset(std::string &accumulator, char currentChar)
{
char cchar = (char)::toupper(currentChar);
if (std::find(pKeyCharset.begin(), pKeyCharset.begin(), cchar) != pKeyCharset.end())
{
accumulator.push_back(cchar);
}
return accumulator;
}
/**
*
* @param in_key
* @param out_key
* @return
*/
BOOL PIDGEN3::ValidateKeyString(const std::string &in_key, std::string &out_key)
{
// copy out the product key stripping out extraneous characters
out_key = std::accumulate(in_key.begin(), in_key.end(), std::string(), ValidateStringKeyInputCharset);
// only return true if we've handled exactly PK_LENGTH chars
return (out_key.length() == PK_LENGTH);
}

View File

@ -23,19 +23,20 @@
#ifndef UMSKT_PIDGEN3_H
#define UMSKT_PIDGEN3_H
#include "../libumskt.h"
#include "../pidgen.h"
class BINK1998;
class BINK2002;
class EXPORT PIDGEN3
class EXPORT PIDGEN3 : public PIDGEN
{
friend class BINK1998;
friend class BINK2002;
protected:
Integer BINKID;
Integer privateKey, genOrder;
ECP::Point genPoint, pubPoint;
ECP::Point pubPoint, genPoint;
ECP eCurve;
public:
@ -45,8 +46,8 @@ class EXPORT PIDGEN3
{
privateKey = p3.privateKey;
genOrder = p3.genOrder;
genPoint = p3.genPoint;
pubPoint = p3.pubPoint;
genPoint = p3.genPoint;
eCurve = p3.eCurve;
}
@ -56,43 +57,50 @@ class EXPORT PIDGEN3
struct KeyInfo
{
Integer Serial = 0, AuthInfo = 0, ChannelID = 0, Hash = 0, Signature = 0;
BOOL isUpgrade = false;
Integer Serial = 0, AuthInfo = 0, ChannelID = 0, Hash = 0, Signature = 0, Rand = 0;
BOOL isUpgrade = false, isOEM = false;
void setSerial(DWORD32 serialIn)
void setSerial(DWORD32 SerialIn)
{
Serial.Decode((BYTE *)&serialIn, sizeof(serialIn));
Serial = IntegerN(SerialIn);
}
void setAuthInfo(DWORD32 AuthInfoIn)
{
AuthInfo.Decode((BYTE *)&AuthInfoIn, sizeof(AuthInfoIn));
AuthInfo = IntegerN(AuthInfoIn);
}
void setChannelID(DWORD32 ChannelIDIn)
{
ChannelID.Decode((BYTE *)&ChannelIDIn, sizeof(ChannelIDIn));
ChannelID = IntegerN(ChannelIDIn);
}
} info;
static constexpr char pKeyCharset[] = "BCDFGHJKMPQRTVWXY2346789";
static const Integer MaxSizeBINK1998;
static const std::string pKeyCharset;
static const DWORD32 MaxSizeBINK1998;
BOOL LoadEllipticCurve(std::string pSel, std::string aSel, std::string bSel, std::string generatorXSel,
std::string generatorYSel, std::string publicKeyXSel, std::string publicKeyYSel,
std::string genOrderSel, std::string privateKeySel);
BOOL LoadEllipticCurve(const std::string &BinkIDSel, 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,
const std::string &genOrderSel, const std::string &privateKeySel);
virtual BOOL Pack(Q_OWORD *pRaw) = 0;
virtual BOOL Unpack(Q_OWORD *pRaw) = 0;
virtual BOOL Generate(std::string &pKey);
virtual BOOL Validate(std::string &pKey);
BOOL Generate(std::string &pKey) override;
BOOL Validate(const std::string &pKey) override;
std::string StringifyKey(const std::string &pKey) override;
std::string StringifyProductID() override;
virtual Integer Pack(const KeyInfo &ki) = 0;
virtual KeyInfo Unpack(const Integer &raw) = 0;
// PIDGEN3.cpp
void base24(std::string &cdKey, BYTE *byteSeq);
void unbase24(BYTE *byteSeq, std::string cdKey);
BOOL checkFieldIsBink1998();
static BOOL checkFieldStrIsBink1998(std::string keyin);
static PIDGEN3 *Factory(const std::string &field);
static BOOL checkFieldStrIsBink1998(std::string keyin);
static std::string ValidateStringKeyInputCharset(std::string &accumulator, char currentChar);
static BOOL ValidateKeyString(const std::string &in_key, std::string &out_key);
static std::string base24(Integer &seq);
static Integer unbase24(const std::string &cdKey);
BOOL checkFieldIsBink1998();
};
#endif // UMSKT_PIDGEN3_H

View File

@ -28,16 +28,21 @@
#define WIN32_LEAN_AND_MEAN
#include <intrin.h>
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#endif // defined(WIN32)
#endif // defined(_MSC_VER)
#include <algorithm>
#include <cstdbool>
#include <cstdint>
#include <iostream>
#include <map>
#include <numeric>
#include <sstream>
#include <string>
#include <vector>
#ifdef DEBUG
#if defined(DEBUG) || 1
#include <cassert>
#else
#define assert(x) /* do nothing */
@ -76,23 +81,6 @@
#define INLINE FNINLINE
#endif // ifdef __EMSCRIPTEN__
// POSIX <-> Windows compatability layer, because MS just *had* to be different
#ifdef _MSC_VER
#ifndef _sscanf
#define _sscanf sscanf_s
#endif
#ifndef _strncpy
#define _strncpy strncpy_s
#endif
#ifndef _strcpy
#define _strcpy strcpy_s
#endif
#else
#define _sscanf sscanf
#define _strncpy(x, y, z, w) strncpy(x, z, w)
#define _strcpy strcpy
#endif // ifdef _MSC_VER
// Type definitions now with more windows compatability (unfortunately)
using BOOL = int32_t;
using BYTE = uint8_t;
@ -101,15 +89,14 @@ using DWORD = unsigned long;
using DWORD32 = uint32_t;
using QWORD = uint64_t;
#if defined(_MSC_VER) && defined(_M_ARM) // for Windows on ARM ??
using __m128 = __n128;
#endif
#if defined(__SIZEOF_INT128__) || defined(__int128)
using uint128_t = unsigned __int128;
#else // use the intel-supplied __m128 intrisic
#elif defined(_MSC_VER) && defined(_M_ARM) // for Windows on ARM ??
using uint128_t = __n128;
#else // use the intel-supplied __m128 intrisic from <intrin.h>
using uint128_t = __m128;
#endif
using OWORD = uint128_t;
typedef union {