5 Commits

Author SHA1 Message Date
1cedbff4ef [Neo-Desktop Refactor] [CLI] off-by-one fix and flavour pidgen type override (#108)
* Minor bugfixes in CLI

* Add 1 to modulus operations, fixes user-supplied all-nines channel ID and serial number

* Use product instead of flavor when checking pidgen type (fixes BINK ID autoselection for products with flavors)

* Fix sanity check for invalid serial and channel ID

* Much better way. Now flavor pidgen type is checked for and overridden in case it's different from the product's pidgen type. This allows Visual Studio 5/6 key generation to work properly again

thank you @aplumafreak500 !
2024-11-10 20:42:24 -08:00
7a0faa42e8 attempt to fix actions build 2024-02-21 18:33:32 -08:00
c7eb049c0b update build scripts, add unit tests, pidgen2 needs polishing, confid is wip 2024-02-21 17:37:02 -08:00
015fd00d3f fix pidgen3, finally plumb in pid 2.0 2024-02-17 08:07:37 -08:00
2ac7f9bc1e WIP - CryptoPP Port, remove refrences to OpenSSL, many things are currently broken 2024-02-04 20:26:29 -08:00
51 changed files with 3314 additions and 2319 deletions

View File

@ -1,2 +1,2 @@
build*/ build*/
cmake-*/ cmake*/

View File

@ -24,20 +24,33 @@ on:
push: push:
branches: [ "*" ] branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip
pull_request:
branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip
workflow_dispatch: workflow_dispatch:
env:
CC: ${{ github.workspace }}/djgpp/bin/i586-pc-msdosdjgpp-gcc
CXX: ${{ github.workspace }}/djgpp/bin/i586-pc-msdosdjgpp-g++
CMAKE_FIND_ROOT_PATH: ${{ github.workspace }}/djgpp
WATT_ROOT: ${{ github.workspace }}/djgpp/watt32
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CC: ${{ github.workspace }}/djgpp/bin/i586-pc-msdosdjgpp-gcc
CXX: ${{ github.workspace }}/djgpp/bin/i586-pc-msdosdjgpp-g++
CMAKE_FIND_ROOT_PATH: ${{ github.workspace }}/djgpp
GCC_EXEC_PREFIX: ${{ github.workspace }}/lib/gcc/
DJDIR: ${{ github.workspace }}/djgpp/i586-pc-msdosdjgpp
steps: steps:
- name: Checkout Source Tree - name: Checkout Source Tree
uses: actions/checkout@v3 uses: actions/checkout@v4.1.1
- name: Set up CPM cache
id: cache-cpm
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cpm-cache
key: ${{ runner.os }}-cpm-${{ hashFiles('**/') }}
restore-keys: |
${{ runner.os }}-cpm-
- name: Setup build environment - name: Setup build environment
run: | run: |
@ -46,34 +59,19 @@ jobs:
- name: Download and Setup DJGPP Toolchain - name: Download and Setup DJGPP Toolchain
run: | run: |
pushd ${{ github.workspace }}
wget https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/djgpp-linux64-gcc1220.tar.bz2 wget https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/djgpp-linux64-gcc1220.tar.bz2
tar xjf djgpp-linux64-gcc1220.tar.bz2 tar xjf djgpp-linux64-gcc1220.tar.bz2
cd ${{ github.workspace }}/djgpp echo "${{ github.workspace }}/djgpp/i586-pc-msdosdjgpp/bin/" >> "$GITHUB_ENV"
git clone https://github.com/UMSKT/Watt-32.git watt32 echo "${{ github.workspace }}/djgpp/bin/" >> "$GITHUB_ENV"
cd watt32/util
make clean && make linux
cd ../src
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 - name: Build UMSKT for MSDOS via DJGPP
run: | uses: threeal/cmake-action@v1.3.0
git clone https://github.com/UMSKT/openssl.git openssl with:
pushd openssl options: UMSKT_DJGPP_COMPILE=On CMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} CMAKE_BUILD_TYPE=Release
source ${{ github.workspace }}/djgpp/setenv build-dir: build
./Configure no-threads -DOPENSSL_DEV_NO_ATOMICS --prefix=${{ github.workspace }}/djgpp DJGPP c-compiler: ${CC}
make && make install cxx-compiler: ${CXX}
popd build-args: -j 2
- 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
- name: Move executable to upload directory - name: Move executable to upload directory
run: | run: |
@ -81,7 +79,68 @@ jobs:
mv build/umskt.exe build/actions_upload/ mv build/umskt.exe build/actions_upload/
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v3.1.2 uses: actions/upload-artifact@v4.3.1
with: with:
name: UMSKT-DOS name: UMSKT-DOS
path: build/actions_upload path: build/actions_upload
build-dos-win32-combined:
needs: build
runs-on: windows-latest
steps:
# https://github.com/actions/runner-images/issues/6067#issuecomment-1213069040
- 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: Setup MSBuild
uses: microsoft/setup-msbuild@v1
- name: Checkout Source Tree
uses: actions/checkout@v4.1.1
- name: Set up CPM cache
id: cache-cpm
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cpm-cache
key: ${{ runner.os }}-cpm-${{ hashFiles('**/') }}
restore-keys: |
${{ runner.os }}-cpm-
- name: Download Built DOS Artifact
uses: actions/download-artifact@v4.1.2
with:
name: UMSKT-DOS
- name: Configure and build UMSKT for DOS+Win32
uses: threeal/cmake-action@v1.3.0
with:
options: UMSKT_MSVC_WINXP=On CMAKE_BUILD_TYPE=Release CPM_SOURCE_CACHE=${{ github.workspace }}/.cpm-cache UMSKT_MSVC_MSDOS_STUB=${{ github.workspace }}/umskt.exe
generator: "Visual Studio 17 2022"
args: -A "Win32" -T "v141_xp"
run-build: true
build-args: "--config Release -j 2"
- name: Upload build artifact
uses: actions/upload-artifact@v4.3.1
with:
name: UMSKT-DOS+Win32
path: build/Release

View File

@ -27,31 +27,40 @@
pull_request: pull_request:
branches: [ "*" ] branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: build-x86_64 name: build-x86_64
steps: steps:
- uses: actions/checkout@v3 - name: Checkout Source Tree
uses: actions/checkout@v4.1.1
- name: Set up CPM cache
id: cache-cpm
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cpm-cache
key: ${{ runner.os }}-cpm-${{ hashFiles('**/') }}
restore-keys: |
${{ runner.os }}-cpm-
- name: Build & Test in FreeBSD - name: Build & Test in FreeBSD
id: test id: test
uses: vmactions/freebsd-vm@v1 uses: vmactions/freebsd-vm@v1.0.6
with: with:
envs: 'MYTOKEN MYTOKEN2'
usesh: true usesh: true
prepare: | prepare: |
pkg install -y cmake openssl git bash pkg install -y cmake git bash
run: | run: |
mkdir build mkdir build
cmake -DCMAKE_BUILD_TYPE=Release -DCPM_SOURCE_CACHE= ${{ github.workspace }}/.cpm-cache -DBUILD_TESTING=On -B build/
cmake --build build/ -j 2
cd build cd build
cmake .. ctest
make
./umskt # Execute the test here
- name: Move files to correct directory - name: Move files to correct directory
run: | run: |
@ -59,7 +68,7 @@
mv build/umskt build/actions_upload/umskt mv build/umskt build/actions_upload/umskt
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v3.1.2 uses: actions/upload-artifact@v4.3.1
with: with:
name: UMSKT-FreeBSD name: UMSKT-FreeBSD
path: build/actions_upload path: build/actions_upload

View File

@ -24,6 +24,9 @@ on:
push: push:
branches: [ "*" ] branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip
pull_request:
branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@ -31,13 +34,27 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
include: arch:
- arch: x86 - x86
- arch: x86_64 - x86_64
- arch: aarch64 - aarch64
- armhf
- armv7
- ppc64le
- s390x
steps: steps:
- name: Checkout Source Tree - name: Checkout Source Tree
uses: actions/checkout@v3 uses: actions/checkout@v4.1.1
- name: Set up CPM cache
id: cache-cpm
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cpm-cache
key: ${{ runner.os }}-cpm-${{ hashFiles('**/') }}
restore-keys: |
${{ runner.os }}-cpm-
- name: Setup latest Alpine Linux for ${{ matrix.arch }} - name: Setup latest Alpine Linux for ${{ matrix.arch }}
uses: jirutka/setup-alpine@v1 uses: jirutka/setup-alpine@v1
@ -48,17 +65,16 @@ jobs:
cmake cmake
git git
musl-dev musl-dev
openssl-dev
openssl-libs-static
zlib-dev
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
shell-name: alpine-target.sh shell-name: alpine-target.sh
- name: Configure and build UMSKT - name: Configure and build UMSKT
uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 uses: threeal/cmake-action@v1.3.0
with: with:
options: MUSL_STATIC=ON options: UMSKT_MUSL_STATIC=ON CMAKE_BUILD_TYPE=Release CPM_SOURCE_CACHE=${{ github.workspace }}/.cpm-cache BUILD_TESTING=On
run-build: true run-build: true
build-args: -j 2
run-test: true
shell: alpine-target.sh {0} shell: alpine-target.sh {0}
- name: Move files to correct directory - name: Move files to correct directory
@ -67,21 +83,7 @@ jobs:
mv build/umskt build/actions_upload/umskt mv build/umskt build/actions_upload/umskt
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v3.1.2 uses: actions/upload-artifact@v4.3.1
with: with:
name: UMSKT-linux-${{ matrix.arch }}-static name: UMSKT-linux-${{ matrix.arch }}-static
path: build/actions_upload 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

@ -24,37 +24,48 @@ on:
push: push:
branches: [ "*" ] branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip
pull_request:
branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**' ] # If only these files are edited, skip
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-x86: build:
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
matrix: matrix:
include: arch:
- arch: x86_64 - { name: arm64;x86_64, displayName: all-x86_64_arm64 }
- { name: x86_64, displayName: x86_64 }
- { name: arm64, displayName: arm64 }
steps: steps:
- name: Checkout Source Tree - name: Checkout Source Tree
uses: actions/checkout@v3 uses: actions/checkout@v4.1.1
- name: Configure and build UMSKT - name: Set up CPM cache
run: | id: cache-cpm
cd build uses: actions/cache@v4
cmake -DCMAKE_BUILD_TYPE=Release .. with:
make path: ${{ github.workspace }}/.cpm-cache
key: ${{ runner.os }}-cpm-${{ hashFiles('**/') }}
restore-keys: |
${{ runner.os }}-cpm-
- name: Configure and build UMSKT for ${{ matrix.arch.displayName }}
uses: threeal/cmake-action@v1.3.0
with:
options: MUSL_STATIC=ON CMAKE_BUILD_TYPE=Release CPM_SOURCE_CACHE=${{ github.workspace }}/.cpm-cache CMAKE_OSX_ARCHITECTURES="${{ matrix.arch.name }}" BUILD_TESTING=On
run-build: true
build-args: -j 2
run-test: true
- name: Move files to correct directory - name: Move files to correct directory
run: | run: |
mkdir -p build/actions_upload mkdir -p build/actions_upload
mv build/umskt build/actions_upload/umskt mv build/umskt build/actions_upload/umskt
- name: Run tests
run: |
cd build/actions_upload
./umskt
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v3.1.2 uses: actions/upload-artifact@v4.3.1
with: with:
name: UMSKT-macOS-${{ matrix.arch }} name: UMSKT-macOS-${{ matrix.arch.displayName }}
path: build/actions_upload path: build/actions_upload

View File

@ -24,11 +24,17 @@ on:
push: push:
branches: [ "*" ] branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**'] # If only these files are edited, skip paths-ignore: [ '**.md', 'doc/**', '.idea/**'] # If only these files are edited, skip
pull_request:
branches: [ "*" ]
paths-ignore: [ '**.md', 'doc/**', '.idea/**'] # If only these files are edited, skip
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-32bit: build:
runs-on: windows-latest runs-on: windows-latest
strategy:
matrix:
arch: [ { name: Win32, displayName: x86 }, { name: x64, displayName: x64 } ]
steps: steps:
# https://github.com/actions/runner-images/issues/6067#issuecomment-1213069040 # https://github.com/actions/runner-images/issues/6067#issuecomment-1213069040
- name: Install Windows XP Support for Visual Studio - name: Install Windows XP Support for Visual Studio
@ -52,97 +58,33 @@ jobs:
exit 1 exit 1
} }
- name: Download And Install 32-bit OpenSSL 3.1.4 - name: Set up CPM cache
run: | id: cache-cpm
$installDir = "$Env:ProgramFiles\OpenSSL" uses: actions/cache@v4
$installerURL = "https://slproweb.com/download/Win32OpenSSL-3_1_4.exe" with:
$installerName = "Win32OpenSSL-3_1_4.exe" path: ${{ github.workspace }}/.cpm-cache
$installerPath = Join-Path -Path "${env:Temp}" -ChildPath "$installerName" key: ${{ runner.os }}-cpm-${{ hashFiles('**/') }}
restore-keys: |
(New-Object System.Net.WebClient).DownloadFile($installerURL, $installerPath) ${{ runner.os }}-cpm-
Remove-Item "$installDir" -Force -Recurse
$installerArgs = '/silent', '/sp-', '/suppressmsgboxes', "/DIR=`"$installDir`""
Start-Process -FilePath $installerPath -ArgumentList $installerArgs -Wait -PassThru
- name: Checkout Source Tree - name: Checkout Source Tree
uses: actions/checkout@v3 uses: actions/checkout@v4.1.1
- name: Setup MSBuild - name: Setup MSBuild
uses: microsoft/setup-msbuild@v1 uses: microsoft/setup-msbuild@v1
- name: Configure UMSKT - name: Configure and build UMSKT for ${{ matrix.values.displayName }}
uses: threeal/cmake-action@v1.2.0 uses: threeal/cmake-action@v1.3.0
with: with:
options: CMAKE_BUILD_TYPE=Release CPM_SOURCE_CACHE=${{ github.workspace }}/.cpm-cache BUILD_TESTING=On
generator: "Visual Studio 17 2022" generator: "Visual Studio 17 2022"
options: CMAKE_SYSTEM_VERSION="5.1.2600" args: -A "${{ matrix.arch.name }}" -T "v141_xp"
args: -A "Win32" -T v141_xp run-build: true
build-args: --config Release -j 2
- name: Build UMSKT run-test: true
working-directory: build
run: msbuild ALL_BUILD.vcxproj /P:Configuration=Release
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v3.1.2 uses: actions/upload-artifact@v4.3.1
with: with:
name: UMSKT-Win32 name: UMSKT-${{ matrix.values.displayName }}
path: build/Release
build-64bit:
runs-on: windows-latest
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
with:
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
- name: Upload build artifact
uses: actions/upload-artifact@v3.1.2
with:
name: UMSKT-Win64
path: build/Release path: build/Release

5
.gitignore vendored
View File

@ -1,8 +1,9 @@
build/* build*/
*.tar *.tar
*.exe *.exe
*.wasm *.wasm
umskt umskt
.cpm-cache
### NotepadPP template ### NotepadPP template
# Notepad++ backups # # Notepad++ backups #
@ -66,7 +67,7 @@ umskt
# *.ipr # *.ipr
# CMake # CMake
cmake-*/ cmake*/
# Mongo Explorer plugin # Mongo Explorer plugin
.idea/**/mongoSettings.xml .idea/**/mongoSettings.xml

View File

@ -23,3 +23,8 @@ repos:
rev: 'v17.0.6' # Use the sha / tag you want to point at rev: 'v17.0.6' # Use the sha / tag you want to point at
hooks: hooks:
- id: clang-format - id: clang-format
# - repo: https://github.com/cheshirekow/cmake-format-precommit
# rev: v0.6.10
# hooks:
# - id: cmake-format

View File

@ -19,117 +19,74 @@
# @Maintainer Neo # @Maintainer Neo
CMAKE_MINIMUM_REQUIRED(VERSION 3.12) CMAKE_MINIMUM_REQUIRED(VERSION 3.12)
PROJECT(UMSKT) SET(PROJECT_NAME UMSKT)
PROJECT(${PROJECT_NAME})
SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_CXX_STANDARD 17)
SET(CMAKE_OSX_SYSROOT "macosx" CACHE PATH "macOS SDK path") SET(CMAKE_OSX_SYSROOT "macosx" CACHE PATH "macOS SDK path")
SET(CMAKE_POSITION_INDEPENDENT_CODE ON) SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
OPTION(BUILD_SHARED_LIBS "Build all libraries as shared" OFF) OPTION(BUILD_TESTING "Build testing binaries for CTest" OFF)
OPTION(UMSKT_USE_SHARED_OPENSSL "Force linking against the system-wide OpenSSL library" OFF) OPTION(BUILD_SHARED_LIBS "Build all dependant libraries as shared" OFF)
OPTION(MUSL_STATIC "Enable fully static builds in a muslc environment (i.e. Alpine Linux)" OFF)
OPTION(DJGPP_WATT32 "Enable compilation and linking with DJGPP/WATT32/OpenSSL" OFF)
OPTION(MSVC_MSDOS_STUB "Specify a custom MS-DOS stub for a 32-bit MSVC compilation" OFF)
SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS}) OPTION(UMSKT_BUILD_SHARED_LIB "Build libumskt.so/.dll/.dylib" ON)
SET(UMSKT_LINK_DIRS ${UMSKT_LINK_DIRS}) OPTION(UMSKT_BUILD_STATIC_LIB "Build libumskt_static.a/.lib" ON)
OPTION(UMSKT_MUSL_STATIC "Enable fully static builds in a muslc environment (i.e. Alpine Linux)" OFF)
OPTION(UMSKT_DJGPP_COMPILE "Enable compilation and linking with DJGPP" OFF)
OPTION(UMSKT_MSVC_MSDOS_STUB "Specify a custom MS-DOS stub for a 32-bit MSVC compilation" OFF)
OPTION(UMSKT_MSVC_WINXP "Specify compile-time flag overrides for Windows XP" OFF)
# macOS does not support static build IF (NOT MSVC)
IF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") SET(CMAKE_CXX_FLAGS "-Os -fdata-sections -ffunction-sections -flto=auto -Wl,--gc-sections")
SET(UMSKT_USE_SHARED_OPENSSL ON) SET(CMAKE_CXX_FLAGS_DEBUG "-g")
SET(CMAKE_CXX_FLAGS_DEBUG_INIT "-Wall -Wextra -pedantic")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "-Wall -Wextra -pedantic")
SET(CMAKE_CXX_FLAGS_RELEASE_INIT "-s")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -Wl,--gc-sections")
ENDIF () ENDIF ()
# neither does dos idk i'm trying random stuff IF (UMSKT_DJGPP_COMPILE)
IF (DJGPP_WATT32)
SET(UMSKT_USE_SHARED_OPENSSL ON)
ENDIF ()
IF (UMSKT_USE_SHARED_OPENSSL)
SET(OPENSSL_USE_STATIC_LIBS FALSE)
SET(OPENSSL_MSVC_STATIC_RT FALSE)
MESSAGE(STATUS "[UMSKT] Requesting dynamic version of OpenSSL")
ELSE ()
SET(OPENSSL_USE_STATIC_LIBS TRUE)
SET(OPENSSL_MSVC_STATIC_RT TRUE)
MESSAGE(STATUS "[UMSKT] Requesting static version of OpenSSL")
ENDIF ()
IF (DJGPP_WATT32)
SET(CMAKE_SYSTEM_NAME MSDOS) SET(CMAKE_SYSTEM_NAME MSDOS)
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} ${DJGPP_WATT32}) SET(UMSKT_BUILD_SHARED_LIB OFF)
SET(UMSKT_LINK_DIRS ${UMSKT_LINK_DIRS} ${WATT_ROOT}/lib) SET(UMSKT_BUILD_STATIC_LIB OFF)
SET(CMAKE_POSITION_INDEPENDENT_CODE OFF)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes -march=i386 -mtune=i386")
MESSAGE(STATUS "[UMSKT] DJGPP Build requested - Shared libraries forced OFF")
ENDIF () ENDIF ()
IF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") IF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
SET(BUILD_SHARED_LIBS ON) SET(BUILD_SHARED_LIBS ON)
MESSAGE(STATUS "[UMSKT] macOS has no static library - Shared library forced on") MESSAGE(STATUS "[UMSKT] macOS Build requested - Shared libraries forced ON")
ENDIF () ENDIF ()
# if we're compiling with MSVC, respect the DEBUG compile option # if we're compiling with MSVC, respect the DEBUG compile option
IF (MSVC) IF (MSVC)
SET(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") SET(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
SET(CMAKE_CXX_FLAGS "/Os /LTCG /Gy /OPT:REF /OPT:ICF /EHsc")
SET(CMAKE_CXX_FLAGS_RELEASE "/MT") SET(CMAKE_CXX_FLAGS_RELEASE "/MT")
SET(CMAKE_CXX_FLAGS_DEBUG "/MTd") SET(CMAKE_CXX_FLAGS_DEBUG "/MTd /Z7")
SET(CMAKE_EXE_LINKER_FLAGS "/INCREMENTAL:NO /NODEFAULTLIB:MSVCRT") SET(CMAKE_EXE_LINKER_FLAGS "/INCREMENTAL:NO /NODEFAULTLIB:MSVCRT /LTCG")
SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG")
SET(CMAKE_ENABLE_EXPORTS ON) SET(CMAKE_ENABLE_EXPORTS ON)
SET(UMSKT_EXE_WINDOWS_EXTRA src/windows/umskt.rc) SET(UMSKT_EXE_WINDOWS_EXTRA src/windows/umskt.rc)
SET(UMSKT_EXE_WINDOWS_DLL src/windows/dllmain.cpp) SET(UMSKT_EXE_WINDOWS_DLL src/windows/dllmain.cpp)
IF (UMSKT_MSVC_WINXP)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /U_WIN32_WINNT /DWINVER=0x0501 /D_WIN32_WINNT=0x0501")
ENDIF ()
ENDIF () ENDIF ()
IF (MUSL_STATIC) IF (MUSL_STATIC)
MESSAGE(STATUS "[UMSKT] Performing a fully static build using muslc") MESSAGE(STATUS "[UMSKT] musl libc Build requested - Shared librares forced OFF")
SET(BUILD_SHARED_LIBS OFF) SET(BUILD_SHARED_LIBS OFF)
SET(OPENSSL_USE_STATIC_LIBS TRUE)
SET(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++")
SET(CMAKE_SHARED_LINKER_FLAGS "-static -static-libgcc -static-libstdc++")
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc -static-libstdc++") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc -static-libstdc++")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++")
ENDIF () SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
# find the system installed OpenSSL development library
FIND_PACKAGE(OpenSSL REQUIRED)
IF (NOT OPENSSL_FOUND)
MESSAGE(SEND_ERROR "OpenSSL Development Libraries Not Found")
MESSAGE(SEND_ERROR "Please consult your package manager of choice to install the prerequisite")
MESSAGE(SEND_ERROR "The package name is commonly called libssl-dev or openssl-dev depending on distribution")
MESSAGE(FATAL_ERROR "Can not continue without OpenSSL")
ENDIF ()
IF (NOT MUSL_STATIC)
# if we found shared libraries - do the following:
IF (OPENSSL_USE_STATIC_LIBS)
MESSAGE(STATUS "[UMSKT] requested static version of OpenSSL")
IF (NOT UMSKT_USE_SHARED_OPENSSL)
MESSAGE(STATUS "[UMSKT] not asked for shared version of OpenSSL")
ENDIF ()
IF (MSVC)
SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} "ws2_32.lib")
SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} "crypt32.lib")
MESSAGE(STATUS "[UMSKT] msvc adding ws2_32.lib crypt32.lib")
ENDIF ()
ENDIF ()
STRING(REGEX MATCH "(\\.so|\\.dll|\\.dylib)$" OPENSSL_CRYPTO_SHARED "${OPENSSL_CRYPTO_LIBRARY}")
IF (OPENSSL_CRYPTO_SHARED)
MESSAGE(STATUS "[UMSKT] Detected Shared library version of OpenSSL")
ELSE ()
MESSAGE(STATUS "[UMSKT] Detected Static Library version of OpenSSL")
# static libcrypto on Fedora needs -lz at link time
IF (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
FIND_PACKAGE(ZLIB REQUIRED)
IF (NOT ZLIB_FOUND)
MESSAGE(FATAL_ERROR "[UMSKT] linux static OpenSSL requires zlib")
ENDIF ()
ENDIF ()
ENDIF ()
ENDIF () ENDIF ()
# initialize cpm.CMake # initialize cpm.CMake
@ -140,6 +97,7 @@ FILE(
EXPECTED_HASH SHA256=cc155ce02e7945e7b8967ddfaff0b050e958a723ef7aad3766d368940cb15494 EXPECTED_HASH SHA256=cc155ce02e7945e7b8967ddfaff0b050e958a723ef7aad3766d368940cb15494
) )
INCLUDE(${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake) INCLUDE(${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake)
CPMUsePackageLock(${CMAKE_SOURCE_DIR}/package-lock.cmake)
# fetch cpm.CMake dependencies # fetch cpm.CMake dependencies
# Include JSON development library # Include JSON development library
@ -165,22 +123,22 @@ CPMAddPackage(
VERSION 2.0.1 VERSION 2.0.1
) )
# Include Crypto++ development library # Include stdint128
#CPMAddPackage( CPMAddPackage(
# NAME cryptopp-cmake NAME stdint128
# GITHUB_REPOSITORY abdes/cryptopp-cmake GITHUB_REPOSITORY UMSKT/stdint128-cmake
# GIT_TAG CRYPTOPP_8_8_0 GIT_TAG v1.0.0
# VERSION 8.8.0 VERSION 1.0.0
# OPTIONS "CRYPTOPP_BUILD_TESTING OFF" )
#)
#include googletest unit testing library # Include Crypto++ development library
#CPMAddPackage( CPMAddPackage(
# NAME googletest NAME cryptopp-cmake
# GITHUB_REPOSITORY google/googletest GITHUB_REPOSITORY UMSKT/cryptopp-cmake
# VERSION 1.13.0 GIT_TAG CRYPTOPP_8_9_0
# OPTIONS "INSTALL_GTEST OFF" "gtest_force_shared_crt" VERSION 8.9.0
#) OPTIONS "CRYPTOPP_BUILD_TESTING OFF"
)
### Resource compilation ### Resource compilation
CMRC_ADD_RESOURCE_LIBRARY(umskt-rc ALIAS umskt::rc NAMESPACE umskt keys.json) CMRC_ADD_RESOURCE_LIBRARY(umskt-rc ALIAS umskt::rc NAMESPACE umskt keys.json)
@ -188,9 +146,9 @@ CMRC_ADD_RESOURCE_LIBRARY(umskt-rc ALIAS umskt::rc NAMESPACE umskt keys.json)
SET(LIBUMSKT_PIDGEN2 src/libumskt/pidgen2/PIDGEN2.cpp) 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_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_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_CLI_SRC src/main.cpp src/cli/help.cpp src/cli/cli.cpp src/cli/confirmationid.cpp src/cli/options.cpp src/cli/pidgen.cpp)
SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} ${OPENSSL_CRYPTO_LIBRARIES} ${ZLIB_LIBRARIES} fmt) SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} fmt cryptopp stdint128)
IF (NOT MSVC) IF (NOT MSVC)
SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} umskt::rc) SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} umskt::rc)
@ -202,39 +160,58 @@ ENDIF()
#### Separate Build Path for emscripten #### Separate Build Path for emscripten
IF (EMSCRIPTEN) IF (EMSCRIPTEN)
ADD_EXECUTABLE(umskt ${UMSKT_CLI_SRC} ${LIBUMSKT_SRC}) ADD_EXECUTABLE(umskt ${UMSKT_CLI_SRC} ${LIBUMSKT_SRC})
TARGET_INCLUDE_DIRECTORIES(umskt PUBLIC ${OPENSSL_INCLUDE_DIR}) TARGET_INCLUDE_DIRECTORIES(umskt PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR})
TARGET_LINK_DIRECTORIES(umskt PUBLIC ${UMSKT_LINK_DIRS})
TARGET_LINK_LIBRARIES(umskt PUBLIC ${UMSKT_LINK_LIBS}) TARGET_LINK_LIBRARIES(umskt PUBLIC ${UMSKT_LINK_LIBS})
SET(CMAKE_EXECUTABLE_SUFFIX ".html") SET(CMAKE_EXECUTABLE_SUFFIX ".html")
SET_TARGET_PROPERTIES(umskt PROPERTIES COMPILE_FLAGS "-Os -sEXPORTED_RUNTIME_METHODS=ccall,cwrap") SET_TARGET_PROPERTIES(umskt PROPERTIES COMPILE_FLAGS "-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 LINK_FLAGS "-sWASM=1 -sEXPORT_ALL=1 -sEXPORTED_RUNTIME_METHODS=ccall,cwrap --no-entry")
ELSE () ELSE ()
## umskt.so/.dll creation ## umskt.so/.dll creation
ADD_LIBRARY(_umskt SHARED ${LIBUMSKT_SRC} ${UMSKT_EXE_WINDOWS_EXTRA} ${UMSKT_EXE_WINDOWS_DLL}) IF (UMSKT_BUILD_SHARED_LIB)
TARGET_INCLUDE_DIRECTORIES(_umskt PUBLIC ${OPENSSL_INCLUDE_DIR} ${CMAKE_BINARY_DIR}) ADD_LIBRARY(libumskt SHARED ${LIBUMSKT_SRC} ${UMSKT_EXE_WINDOWS_EXTRA} ${UMSKT_EXE_WINDOWS_DLL})
TARGET_LINK_DIRECTORIES(_umskt PUBLIC ${UMSKT_LINK_DIRS}) TARGET_INCLUDE_DIRECTORIES(libumskt PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR})
TARGET_LINK_LIBRARIES(_umskt PUBLIC ${UMSKT_LINK_LIBS}) TARGET_LINK_DIRECTORIES(libumskt PUBLIC ${UMSKT_LINK_DIRS})
TARGET_LINK_LIBRARIES(libumskt PUBLIC ${UMSKT_LINK_LIBS})
IF (MSVC) IF (MSVC)
SET_TARGET_PROPERTIES(_umskt PROPERTIES OUTPUT_NAME libumskt) SET_TARGET_PROPERTIES(libumskt PROPERTIES OUTPUT_NAME libumskt)
ELSE () ELSE ()
SET_TARGET_PROPERTIES(_umskt PROPERTIES OUTPUT_NAME umskt) SET_TARGET_PROPERTIES(libumskt PROPERTIES OUTPUT_NAME umskt)
ENDIF ()
ENDIF() ENDIF()
## umskt_static.a/.lib creation ## umskt_static.a/.lib creation
ADD_LIBRARY(umskt_static ${LIBUMSKT_SRC} ${UMSKT_EXE_WINDOWS_EXTRA} ${UMSKT_EXE_WINDOWS_DLL}) IF (UMSKT_BUILD_STATIC_LIB)
TARGET_INCLUDE_DIRECTORIES(umskt_static PUBLIC ${OPENSSL_INCLUDE_DIR} ${CMAKE_BINARY_DIR}) ADD_LIBRARY(libumskt_static ${LIBUMSKT_SRC} ${UMSKT_EXE_WINDOWS_EXTRA} ${UMSKT_EXE_WINDOWS_DLL})
TARGET_LINK_DIRECTORIES(umskt_static PUBLIC ${UMSKT_LINK_DIRS}) TARGET_INCLUDE_DIRECTORIES(libumskt_static PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR})
TARGET_LINK_LIBRARIES(umskt_static PUBLIC ${UMSKT_LINK_LIBS}) TARGET_LINK_DIRECTORIES(libumskt_static PUBLIC ${UMSKT_LINK_DIRS})
TARGET_LINK_LIBRARIES(libumskt_static PUBLIC ${UMSKT_LINK_LIBS})
IF (MSVC) IF (MSVC)
SET_TARGET_PROPERTIES(umskt_static PROPERTIES OUTPUT_NAME libumskt_static) SET_TARGET_PROPERTIES(libumskt_static PROPERTIES OUTPUT_NAME libumskt_static)
ELSE () ELSE ()
SET_TARGET_PROPERTIES(umskt_static PROPERTIES OUTPUT_NAME umskt_static) SET_TARGET_PROPERTIES(libumskt_static PROPERTIES OUTPUT_NAME umskt_static)
ENDIF ()
ENDIF() ENDIF()
### UMSKT executable compilation ### UMSKT executable compilation
IF (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
### only build CLI if we're building UMSKT explicitly
IF (UMSKT_BUILD_STATIC_LIB)
## Link against the static lib
ADD_EXECUTABLE(umskt ${UMSKT_CLI_SRC} ${UMSKT_EXE_WINDOWS_EXTRA}) ADD_EXECUTABLE(umskt ${UMSKT_CLI_SRC} ${UMSKT_EXE_WINDOWS_EXTRA})
TARGET_INCLUDE_DIRECTORIES(umskt PUBLIC ${OPENSSL_INCLUDE_DIR} ${CMAKE_BINARY_DIR}) TARGET_LINK_LIBRARIES(umskt PUBLIC libumskt_static ${UMSKT_LINK_LIBS} nlohmann_json::nlohmann_json)
TARGET_LINK_LIBRARIES(umskt PUBLIC umskt_static ${UMSKT_LINK_LIBS} nlohmann_json::nlohmann_json) ELSEIF (UMSKT_BUILD_SHARED_LIB)
## Link against the dynamic lib
ADD_EXECUTABLE(umskt ${UMSKT_CLI_SRC} ${UMSKT_EXE_WINDOWS_EXTRA})
TARGET_LINK_LIBRARIES(umskt PUBLIC libumskt ${UMSKT_LINK_LIBS} nlohmann_json::nlohmann_json)
ELSE()
## Don't link against our output, do a full libumskt+cli compile
ADD_EXECUTABLE(umskt ${LIBUMSKT_SRC} ${UMSKT_CLI_SRC} ${UMSKT_EXE_WINDOWS_EXTRA})
TARGET_LINK_LIBRARIES(umskt PUBLIC ${UMSKT_LINK_LIBS} nlohmann_json::nlohmann_json)
ENDIF()
TARGET_INCLUDE_DIRECTORIES(umskt PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR})
TARGET_LINK_DIRECTORIES(umskt PUBLIC ${UMSKT_LINK_DIRS}) TARGET_LINK_DIRECTORIES(umskt PUBLIC ${UMSKT_LINK_DIRS})
IF (MSVC) IF (MSVC)
SET_PROPERTY(TARGET umskt APPEND PROPERTY COMPILE_FLAGS /DUMSKT_CLI_WINRC_EMBED_JSON) SET_PROPERTY(TARGET umskt APPEND PROPERTY COMPILE_FLAGS /DUMSKT_CLI_WINRC_EMBED_JSON)
@ -246,10 +223,42 @@ ELSE ()
IF (${CMAKE_SYSTEM_NAME} MATCHES "Linux") IF (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
INSTALL(TARGETS umskt DESTINATION bin) INSTALL(TARGETS umskt DESTINATION bin)
ENDIF () ENDIF ()
ENDIF ()
### Copy Shared Libraries and dependency files ### Strip the built binary
IF (OPENSSL_CRYPTO_SHARED) IF (${CMAKE_BUILD_TYPE} MATCHES "Release" AND NOT MSVC)
GET_FILENAME_COMPONENT(OPENSSL_CRYPTO_LIBRARY_FILENAME ${OPENSSL_CRYPTO_LIBRARY} NAME) ADD_CUSTOM_COMMAND(TARGET umskt POST_BUILD
CONFIGURE_FILE(${OPENSSL_CRYPTO_LIBRARY} "${CMAKE_CURRENT_BINARY_DIR}/${OPENSSL_CRYPTO_LIBRARY_FILENAME}" COPYONLY) COMMAND strip $<TARGET_FILE:umskt>
COMMENT "Stripping symbols for release"
)
ENDIF() ENDIF()
ENDIF () ENDIF ()
#### Build Path for Unit Tests
IF ((CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR UMSKT_BUILD_TESTING) AND BUILD_TESTING)
INCLUDE(CTest)
ENABLE_TESTING()
#include googletest unit testing library
CPMAddPackage(
NAME googletest
GITHUB_REPOSITORY google/googletest
VERSION 1.14.0
OPTIONS "INSTALL_GTEST OFF" "gtest_force_shared_crt"
)
MACRO(ADD_GTEST TEST)
ADD_EXECUTABLE(${TEST} ${ARGN})
TARGET_LINK_LIBRARIES(${TEST} gtest gmock gtest_main libumskt_static)
TARGET_COMPILE_FEATURES(${TEST} PRIVATE cxx_std_17)
SET_TARGET_PROPERTIES(${TEST} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests)
ADD_TEST(${TEST} tests/${TEST})
ENDMACRO()
ADD_GTEST(TEST_PIDGEN2 src/libumskt/pidgen2/PIDGEN2_unittest.cpp)
ADD_GTEST(TEST_PIDGEN3_BINK1998 src/libumskt/pidgen3/BINK1998_unittest.cpp)
ADD_GTEST(TEST_PIDGEN3_BINK2002 src/libumskt/pidgen3/BINK2002_unittest.cpp)
ADD_GTEST(TEST_CONFIRMATIONID src/libumskt/confid/confid_unittest.cpp)
ENDIF ()

View File

@ -27,9 +27,7 @@ RUN apk add --no-cache \
build-base \ build-base \
cmake \ cmake \
git \ git \
musl-dev \ musl-dev
openssl-dev \
openssl-libs-static
# Stage 2: Build # Stage 2: Build
@ -40,14 +38,15 @@ COPY . /src
# Build UMSKT from the local directory # Build UMSKT from the local directory
RUN mkdir /src/build \ RUN mkdir /src/build \
&& cd /src/build \ && cmake -B /src/build -DCPM_SOURCE_CACHE=/src/.cpm-cache -DCMAKE_BUILD_TYPE=Release -DUMSKT_MUSL_STATIC=ON \
&& cmake -DMUSL_STATIC=ON .. \ && cmake --build /src/build -j 10
&& make
# Stage 3: Output # Stage 3: Output
FROM scratch as output FROM scratch as output
COPY --from=builder /src/build/umskt /umskt COPY --from=builder /src/build/umskt /
COPY --from=builder /src/build/libumskt_static.a /
COPY --from=builder /src/build/libumskt.so /
# invoke via # invoke via
# docker build -o type=tar,dest=umskt.tar . # docker build -o type=tar,dest=build-musl/umskt.tar .

View File

@ -19,105 +19,55 @@
# @Maintainer Neo # @Maintainer Neo
# Stage 1: Install Prerequisites # Stage 1: Install Prerequisites
FROM alpine:latest as prerequisites FROM ubuntu:latest as prerequisites
# Stage 1: Install build dependencies # Stage 1: Install build dependencies
RUN apk add --no-cache \ RUN apt-get update && apt-get -y install \
autoconf \ build-essential \
automake \
bash \
bison \
build-base \
clang \
cmake \ cmake \
coreutils \ wget \
curl \ 7zip \
elfutils-dev \
findutils \
git \ git \
gawk \
flex \ flex \
libelf \ libfl-dev \
libslirp-dev \
linux-headers \
nasm \ nasm \
sed \ libslang2-dev \
slang-dev \ pkg-config \
texinfo \ libslang2-modules \
unzip \ gcc-multilib \
zlib-dev && rm -rf /var/lib/apt/lists/*
FROM prerequisites as djgpp 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
SHELL ["/bin/bash", "-c"] SHELL ["/bin/bash", "-c"]
RUN git clone https://github.com/gvanem/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
# Stage 4: compile OpenSSL for djgpp-i386/watt32 WORKDIR /
FROM watt32 as openssl
WORKDIR /tmp ENV CC=/djgpp/bin/i586-pc-msdosdjgpp-gcc CXX=/djgpp/bin/i586-pc-msdosdjgpp-g++ CMAKE_FIND_ROOT_PATH=/djgpp
# Stage 2: setup djgpp
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
# Stage 3: compile UMSKT
FROM djgpp as build
SHELL ["/bin/bash", "-c"] SHELL ["/bin/bash", "-c"]
RUN git clone https://github.com/openssl/openssl.git openssl \
&& cd openssl \
&& git checkout openssl-3.1.1 \
&& source /djgpp/setenv \
&& wget https://gist.github.com/Neo-Desktop/ad26e888d64b22a59c743ab4e21ac186/raw/c9a73e1eb75ba8857883ac5c08691d2fe5b82594/50-djgpp.conf.patch -O Configurations/50-djgpp.conf.patch \
&& git apply Configurations/50-djgpp.conf.patch \
&& ./Configure no-threads -DOPENSSL_DEV_NO_ATOMICS --prefix=/djgpp DJGPP \
&& make && make install
# Stage 5: compile UMSKT
FROM openssl as build
WORKDIR /src WORKDIR /src
COPY . /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"]
# Build UMSKT from the local directory # Build UMSKT from the local directory
RUN mkdir /src/build \ RUN mkdir /src/build \
&& cd /src/build \
&& source /djgpp/setenv \ && source /djgpp/setenv \
&& cmake -DDJGPP_WATT32=${WATT_ROOT}/lib/libwatt.a .. \ && cmake -DUMSKT_DJGPP_COMPILE=On -DCMAKE_BUILD_TYPE=Release -DCPM_SOURCE_CACHE=`pwd`/.cpm-cache -B build \
&& make && cmake --build build -j 10 --verbose
CMD ["bash"] CMD ["bash"]
# Stage 6: Output # Stage 6: Output
FROM scratch as output FROM scratch as output
COPY --from=build /src/build/umskt.exe /umskt.exe COPY --from=build /src/build/umskt.exe /
# invoke via # 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

@ -23,8 +23,8 @@
# Stage 1: Install Visual Studio # Stage 1: Install Visual Studio
FROM mcr.microsoft.com/dotnet/framework/runtime:4.8.1 as visualstudio FROM mcr.microsoft.com/dotnet/framework/runtime:4.8.1 as visualstudio
# Download and install Build Tools for Visual Studio 2022 for native desktop workload. # Download and install Build Tools for Visual Studio 2022 for native desktop workload.
#ADD https://aka.ms/vs/17/release/vs_buildtools.exe C:\TEMP\vs_buildtools.exe ADD https://aka.ms/vs/17/release/vs_buildtools.exe C:\TEMP\vs_buildtools.exe
ADD vs_buildtools.exe C:\TEMP\vs_buildtools.exe #ADD vs_buildtools.exe C:\TEMP\vs_buildtools.exe
RUN C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache ` RUN C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache `
--add Microsoft.VisualStudio.Workload.VCTools ` --add Microsoft.VisualStudio.Workload.VCTools `
--add Microsoft.VisualStudio.Workload.MSBuildTools ` --add Microsoft.VisualStudio.Workload.MSBuildTools `
@ -40,26 +40,15 @@ RUN ["powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass", "[System.Net.Ser
RUN ["powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass", "choco feature enable -n allowGlobalConfirmation"] RUN ["powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass", "choco feature enable -n allowGlobalConfirmation"]
RUN ["powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass", "choco install git --params \"'/GitAndUnixToolsOnPath /WindowsTerminal /NoShellIntegration /NoGuiHereIntegration /NoShellHereIntegration /NoCredentialManager /SChannel'\""] RUN ["powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass", "choco install git --params \"'/GitAndUnixToolsOnPath /WindowsTerminal /NoShellIntegration /NoGuiHereIntegration /NoShellHereIntegration /NoCredentialManager /SChannel'\""]
#Install OpenSSL 32bit 3.1.1
#ADD https://slproweb.com/download/Win32OpenSSL-3_1_1.msi C:\TEMP\Win32OpenSSL-3_1_1.msi
ADD Win32OpenSSL-3_1_1.msi C:\TEMP\Win32OpenSSL-3_1_1.msi
RUN msiexec /i C:\TEMP\Win32OpenSSL-3_1_1.msi /quiet /qn /norestart
#Install OpenSSL 64bit 3.1.1
#ADD https://slproweb.com/download/Win64OpenSSL-3_1_1.msi C:\TEMP\Win64OpenSSL-3_1_1.msi
ADD Win64OpenSSL-3_1_1.msi C:\TEMP\Win64OpenSSL-3_1_1.msi
RUN msiexec /i C:\TEMP\Win64OpenSSL-3_1_1.msi /quiet /qn /norestart
# Stage 3: Build the 32-bit version of UMSKT # Stage 3: Build the 32-bit version of UMSKT
FROM prereqisites as Build32 FROM prereqisites as Build32
WORKDIR C:\umskt\ WORKDIR C:\umskt\
COPY . C:\umskt\ COPY . C:\umskt\
RUN C:\BuildTools\Common7\Tools\VsDevCmd.bat && ` RUN C:\BuildTools\Common7\Tools\VsDevCmd.bat && `
mkdir C:\umskt\build && ` mkdir C:\umskt\build && cd C:\umskt && `
cd C:\umskt\build && ` cmake -B build -DCPM_SOURCE_CACHE=../.cpm-cache -DUMSKT_MSVC_WINXP=On -DCMAKE_BUILD_TYPE=Release -DMSVC_MSDOS_STUB=..\umskt.exe -G "Visual Studio 17 2022" -A "Win32" -T v141_xp && `
cmake -DMSVC_MSDOS_STUB:string=..\umskt.exe .. -G "Visual Studio 17 2022" -A "Win32" -T v141_xp && ` cmake --build build --config Release -j 10
msbuild ALL_BUILD.vcxproj /P:Configuration=Release
# Stage 4: Build the 64-bit version of UMSKT # Stage 4: Build the 64-bit version of UMSKT
FROM prereqisites as Build64 FROM prereqisites as Build64
@ -67,12 +56,10 @@ FROM prereqisites as Build64
WORKDIR C:\umskt\ WORKDIR C:\umskt\
COPY . C:\umskt\ COPY . C:\umskt\
ENV OPENSSL_ROOT_DIR="C:\Program Files\OpenSSL-Win64"
RUN C:\BuildTools\Common7\Tools\VsDevCmd.bat && ` RUN C:\BuildTools\Common7\Tools\VsDevCmd.bat && `
mkdir C:\umskt\build && ` cd C:\umskt && `
cd C:\umskt\build && ` cmake -B build -DCPM_SOURCE_CACHE=../.cpm-cache -DCMAKE_BUILD_TYPE=Release && `
cmake .. && ` cmake --build build --config Release -j 10
msbuild ALL_BUILD.vcxproj /P:Configuration=Release
# Stage 5: Copy binaries to an output/runtime image # Stage 5: Copy binaries to an output/runtime image
FROM mcr.microsoft.com/dotnet/framework/runtime:4.8.1 as output FROM mcr.microsoft.com/dotnet/framework/runtime:4.8.1 as output

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`). * **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. #### 2. Run `umskt` to generate a key, or add `--help` or `-h` to see more options.
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.*
#### 3. Run `umskt` to generate a key, or add `--help` or `-h` to see more options. #### 3. *(Activation step for `Retail` and `OEM` only)*
#### 4. *(Activation step for `Retail` and `OEM` only)*
* After installation, you will be prompted to activate Windows. * After installation, you will be prompted to activate Windows.

View File

120
keys.json
View File

@ -39,14 +39,20 @@
"WIN95": { "WIN95": {
"meta": { "meta": {
"type": "PIDGEN2", "type": "PIDGEN2",
"tags": "windows" "tags": [
"windows",
"legacyoempid"
]
}, },
"name": "Windows 95 (all)" "name": "Windows 95 (all)"
}, },
"WINNT": { "WINNT": {
"meta": { "meta": {
"type": "PIDGEN2", "type": "PIDGEN2",
"tags": "windows" "tags": [
"windows",
"legacyoempid"
]
}, },
"name": "Windows NT (all)" "name": "Windows NT (all)"
}, },
@ -54,7 +60,8 @@
"meta": { "meta": {
"type": "PIDGEN3", "type": "PIDGEN3",
"tags": [ "tags": [
"windows" "windows",
"legacyoempid"
] ]
}, },
"name": "Windows 98 (all versions)", "name": "Windows 98 (all versions)",
@ -67,7 +74,8 @@
"meta": { "meta": {
"type": "PIDGEN3", "type": "PIDGEN3",
"tags": [ "tags": [
"office" "office",
"legacyoempid"
] ]
}, },
"name": "Office 2000 (all versions)", "name": "Office 2000 (all versions)",
@ -121,7 +129,8 @@
"type": "PIDGEN3", "type": "PIDGEN3",
"default": "PRO", "default": "PRO",
"tags": [ "tags": [
"windows" "windows",
"legacyoempid"
] ]
}, },
"name": "Windows 2000", "name": "Windows 2000",
@ -159,7 +168,8 @@
"meta": { "meta": {
"type": "PIDGEN3", "type": "PIDGEN3",
"tags": [ "tags": [
"windows" "windows",
"legacypid"
] ]
}, },
"name": "Windows ME", "name": "Windows ME",
@ -283,7 +293,7 @@
"WINXP": { "WINXP": {
"meta": { "meta": {
"type": "PIDGEN3", "type": "PIDGEN3",
"default": "PROVLK", "default": "VLK",
"tags": [ "tags": [
"windows", "windows",
"xpbrand" "xpbrand"
@ -463,17 +473,56 @@
"BINK": [ "BINK": [
"2C", "2C",
"2D" "2D"
] ],
"DPC": {
"2C": [
{
"min": 5,
"max": 85,
"isEvaluation": false
}, },
"PROK": { {
"name": "Professional K", "min": 337,
"BINK": [ "max": 359,
"30", "isEvaluation": false
"31"
]
}, },
"PROVLK": { {
"name": "Professional VLK", "min": 755,
"max": 779,
"isEvaluation": false
},
{
"min": 785,
"max": 789,
"isEvaluation": false
}
],
"2D": [
{
"min": 119,
"max": 119,
"isEvaluation": false
},
{
"min": 120,
"max": 169,
"isEvaluation": false
},
{
"min": 400,
"max": 699,
"isEvaluation": false
},
{
"min": 170,
"max": 269,
"isEvaluation": false
}
]
}
},
"VLK": {
"name": "Home/Professional VLK",
"BINK": [ "BINK": [
"2E", "2E",
"2F" "2F"
@ -484,16 +533,6 @@
"min": 640, "min": 640,
"max": 699, "max": 699,
"isEvaluation": false "isEvaluation": false
},
{
"min": 700,
"max": 701,
"isEvaluation": false
},
{
"min": 704,
"max": 705,
"isEvaluation": false
} }
] ]
}, },
@ -673,6 +712,20 @@
} }
] ]
} }
},
"CNOEMHOME": {
"name": "[CN] OEM Home",
"BINK": [
"30",
"31"
]
},
"CNOEMPRO": {
"name": "[CN] OEM Professional",
"BINK": [
"32",
"33"
]
} }
} }
}, },
@ -1182,21 +1235,6 @@
"y": "11647588982042777999933885074728841323429055317640349743317690400085264609368266409172384083304384956740124856614996" "y": "11647588982042777999933885074728841323429055317640349743317690400085264609368266409172384083304384956740124856614996"
} }
}, },
"0B": {
"a": "1",
"b": "0",
"g": {
"x": "17272533675023793624680016937607161394427776688401278127884215858369066406365237833207419170117031265147050748737186",
"y": "10897684556651576571671151674586120690608236542740270859915076272932083320838022698730208293779451126638581586588925"
},
"n": "44682719955829289",
"p": "31123778862031392435299439090755153401162704597024288571183830527113563344679315725116915983118187065183839828632113",
"priv": "30177475288172038",
"pub": {
"x": "10584120526089473026246191383792758367144927589909587205278073830223938861208553884400816982485323081066790399437204",
"y": "19710761542152200618172612283139324015316083022563473705358032993141026289202915973780473937312193485361804450068338"
}
},
"0C": { "0C": {
"a": "1", "a": "1",
"b": "0", "b": "0",

View File

@ -1,432 +0,0 @@
/**
* 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 Andrew on 01/06/2023
* @Maintainer Neo
*/
#include "cli.h"
// define static storage
Options CLI::options;
json CLI::keys;
/**
*
* @param argcIn
* @param argvIn
* @return exit status (where success is 0)
*/
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};
SetHelpText();
BOOL success = parseCommandLine();
if (!success)
{
return options.error;
}
// if we displayed help, without an error
// return success
if (options.help)
{
return -1;
}
success = processOptions();
if (!success)
{
return 2;
}
return 0;
}
/**
*
* @param filename
* @return success
*/
BOOL CLI::loadJSON(const fs::path &filename)
{
if (filename.empty())
{
if (options.verbose)
{
fmt::print("Loading internal keys file\n");
}
auto retval = loadEmbeddedJSON();
if (retval && options.verbose)
{
fmt::print("Loaded internal keys file successfully\n\n");
}
else if (!retval)
{
fmt::print("Error loading internal keys file...\n\n");
}
return retval;
}
if (!fs::exists(filename))
{
fmt::print("ERROR: File {} does not exist\n", filename.string());
return false;
}
if (options.verbose)
{
fmt::print("Loading keys file {}\n", options.keysFilename);
}
std::ifstream f(filename);
try
{
keys = json::parse(f, nullptr, false, false);
}
catch (const json::exception &e)
{
fmt::print("ERROR: Exception thrown while parsing {}: {}\n", filename.string(), e.what());
return false;
}
if (keys.is_discarded())
{
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);
}
return true;
}
/**
*
* @param pid
*/
void CLI::printID(DWORD *pid)
{
char raw[12], b[6], c[8];
char i, digit = 0;
// Convert PID to ascii-number (=raw)
snprintf(raw, sizeof(raw), "%09lu", 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;
DWORD binkid;
_sscanf(options.binkID.c_str(), "%lx", &binkid);
binkid /= 2;
fmt::print("> Product ID: PPPPP-{}-{}-{}xxx\n", b, c, binkid);
}
/**
*
* @param pidgen3
* @return success
*/
BOOL CLI::InitPIDGEN3(PIDGEN3 &pidgen3)
{
const char *BINKID = &options.binkID[0];
auto bink = keys["BINK"][BINKID];
if (options.verbose)
{
fmt::print("{:->80}\n", "");
fmt::print("Loaded the following elliptic curve parameters: BINK[{}]\n", BINKID);
fmt::print("{:->80}\n", "");
fmt::print("{:>6}: {}\n", "P", bink["p"]);
fmt::print("{:>6}: {}\n", "a", bink["a"]);
fmt::print("{:>6}: {}\n", "b", bink["b"]);
fmt::print("{:>6}: [{},\n{:>9}{}]\n", "G[x,y]", bink["g"]["x"], "", bink["g"]["y"]);
fmt::print("{:>6}: [{},\n{:>9}{}]\n", "K[x,y]", bink["pub"]["x"], "", bink["pub"]["y"]);
fmt::print("{:>6}: {}\n", "n", bink["n"]);
fmt::print("{:>6}: {}\n", "k", bink["priv"]);
fmt::print("\n");
}
pidgen3.LoadEllipticCurve(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)
{
return true;
}
pidgen3.info.setChannelID(options.channelID);
if (options.verbose)
{
fmt::print("> Channel ID: {:03d}\n", options.channelID);
}
if (options.serialSet)
{
pidgen3.info.setSerial(options.serial);
if (options.verbose)
{
fmt::print("> Serial {:#09d}\n", options.serial);
}
}
return true;
}
/**
*
* @param confid
* @return success
*/
BOOL CLI::InitConfirmationID(ConfirmationID &confid)
{
auto ctx = BN_CTX_new();
BIGNUM *pkey = BN_CTX_get(ctx), *nonresidue = BN_CTX_get(ctx), *modulous = BN_CTX_get(ctx);
BIGNUM *fvals[6];
QWORD fvalsq[6];
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);
return false;
}
auto meta = keys["products"][options.productCode]["meta"]["activation"];
if (!keys["activation"].contains(meta["flavour"]))
{
fmt::print("ERROR: {} is an unknown activation flavour", meta["flavour"]);
return false;
}
auto flavour = keys["activation"][meta["flavour"]];
if (options.verbose)
{
fmt::print("{:->80}\n", "");
fmt::print("Loaded the following hyperelliptic curve parameters: activation[{}]\n", meta["flavour"]);
fmt::print("{:->80}\n", "");
fmt::print("{:>7}: {}\n", "name", flavour["name"]);
fmt::print("{:>7}: {}\n", "version", meta["version"]);
fmt::print("{:>7}: {}\n", "Fp", flavour["p"]);
fmt::print("{:>7}: [{}, {}, {},\n{:>10}{}, {}, {}]\n", "F[]", flavour["x"]["0"], flavour["x"]["1"],
flavour["x"]["2"], "", flavour["x"]["3"], flavour["x"]["4"], flavour["x"]["5"]);
fmt::print("{:>7}: {}\n", "INV", flavour["quotient"]);
fmt::print("{:>7}: {}\n", "mqnr", flavour["non_residue"]);
fmt::print("{:>7}: {}\n", "k", flavour["priv"]);
fmt::print("{:>7}: {}\n", "IID", flavour["iid_key"]);
fmt::print("\n");
}
for (BYTE i = 0; i < 6; i++)
{
fvals[i] = BN_CTX_get(ctx);
auto xval = flavour["x"][fmt::format("{}", i)].get<std::string>();
BN_dec2bn(&fvals[i], xval.c_str());
UMSKT::BN_bn2lebin(fvals[i], (unsigned char *)&fvalsq[i], sizeof(*fvalsq));
}
// confid.LoadHyperellipticCurve(fvals, );
BN_CTX_free(ctx);
return false;
}
/**
*
* @return success
*/
BOOL CLI::PIDGENGenerate()
{
// TODO:
// if options.pidgen2generate
// return pidgen2generate
// otherwise...
const char *BINKID = &options.binkID[0];
auto bink = keys["BINK"][BINKID];
std::string key;
bink["p"].get_to(key);
if (PIDGEN3::checkFieldStrIsBink1998(key))
{
if (options.verbose)
{
fmt::print("Detected a BINK1998 key\n");
}
auto bink1998 = BINK1998();
InitPIDGEN3(bink1998);
return BINK1998Generate(bink1998);
}
else
{
if (options.verbose)
{
fmt::print("Detected a BINK2002 key\n");
}
auto bink2002 = BINK2002();
InitPIDGEN3(bink2002);
return BINK2002Generate(bink2002);
}
}
/**
*
* @return isValid
*/
BOOL CLI::PIDGENValidate()
{
// TODO:
// if options.pidgen2validate
// return pidgen2validate
// otherwise...
const char *BINKID = &options.binkID[0];
auto bink = keys["BINK"][BINKID];
std::string key;
bink["p"].get_to(key);
if (PIDGEN3::checkFieldStrIsBink1998(key))
{
if (options.verbose)
{
fmt::print("Detected a BINK1998 key\n");
}
auto bink1998 = BINK1998();
InitPIDGEN3(bink1998);
return BINK1998Validate(bink1998);
}
else
{
if (options.verbose)
{
fmt::print("Detected a BINK2002 key\n");
}
auto bink2002 = BINK2002();
InitPIDGEN3(bink2002);
return BINK2002Validate(bink2002);
}
}
/**
* Process application state
*
* @return application status code
*/
int CLI::Run()
{
/**
* TODO: we should be checking if the system's pseudorandom facilities work
* before attempting generation, validation does not require entropy
*/
switch (options.state)
{
case STATE_PIDGEN_GENERATE:
return PIDGENGenerate();
case STATE_PIDGEN_VALIDATE:
return PIDGENValidate();
case 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);
}

159
src/cli/cli.cpp Normal file
View File

@ -0,0 +1,159 @@
/**
* 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 Andrew on 01/06/2023
* @Maintainer Neo
*/
#include "cli.h"
// define static storage
CLI::Options CLI::options;
json CLI::keys;
/**
*
* @param argcIn
* @param argvIn
* @return exit status (where success is 0)
*/
BYTE CLI::Init(int argcIn, char **argvIn)
{
// set default options
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();
BOOL success = parseCommandLine();
if (!success)
{
return options.error;
}
// if we displayed help, without an error
// return success
if (options.help)
{
return -1;
}
success = processOptions();
if (!success)
{
return 2;
}
return 0;
}
/**
*
* @param filename
* @return success
*/
BOOL CLI::loadJSON(const fs::path &filename)
{
if (filename.empty())
{
if (options.verbose)
{
fmt::print("Loading internal keys file\n");
}
auto retval = loadEmbeddedJSON();
if (retval && options.verbose)
{
fmt::print("Loaded internal keys file successfully\n\n");
}
else if (!retval)
{
fmt::print("Error loading internal keys file...\n\n");
}
return retval;
}
if (!fs::exists(filename))
{
fmt::print("ERROR: File {} does not exist\n", filename.string());
return false;
}
if (options.verbose)
{
fmt::print("Loading keys file: {}\n", options.keysFilename);
}
std::ifstream f(filename);
try
{
keys = json::parse(f, nullptr, false, false);
}
catch (const json::exception &e)
{
fmt::print("ERROR: Exception thrown while parsing {}: {}\n", filename.string(), e.what());
return false;
}
if (keys.is_discarded())
{
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);
}
return true;
}
/**
* Process application state
*
* @return application status code
*/
int CLI::Run()
{
/**
* TODO: we should be checking if the system's pseudorandom facilities work
* before attempting generation, validation does not require entropy
*/
switch (options.state)
{
case Options::APPLICATION_STATE::STATE_PIDGEN_GENERATE:
return PIDGenerate();
case Options::APPLICATION_STATE::STATE_PIDGEN_VALIDATE:
return PIDValidate();
case Options::APPLICATION_STATE::STATE_CONFIRMATION_ID:
return ConfirmationIDGenerate();
default:
return 1;
}
}

View File

@ -23,18 +23,14 @@
#ifndef UMSKT_CLI_H #ifndef UMSKT_CLI_H
#define UMSKT_CLI_H #define UMSKT_CLI_H
#include "typedefs.h" #include <libumskt/libumskt.h>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream>
#include <string>
#include <unordered_map> #include <unordered_map>
#include <vector>
#include <fmt/color.h> #include <fmt/color.h>
#include <fmt/core.h> #include <fmt/core.h>
#include <fmt/ostream.h>
#include <fmt/ranges.h> #include <fmt/ranges.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -42,7 +38,7 @@ using json = nlohmann::json;
namespace fs = std::filesystem; namespace fs = std::filesystem;
// fmt <-> json linkage // fmt <-> json linkage
template <> struct fmt::formatter<json> : ostream_formatter template <> struct fmt::formatter<json> : formatter<string_view>
{ {
auto format(const json &j, format_context &ctx) const auto format(const json &j, format_context &ctx) const
{ {
@ -52,35 +48,29 @@ template <> struct fmt::formatter<json> : ostream_formatter
} }
else else
{ {
return basic_ostream_formatter<char>::format(j, ctx); std::stringstream s;
s << j;
return formatter<string_view>::format(s.str(), ctx);
} }
} }
}; };
#include "../libumskt/confid/confid.h"
#include "../libumskt/libumskt.h"
#include "../libumskt/pidgen2/PIDGEN2.h"
#include "../libumskt/pidgen3/BINK1998.h"
#include "../libumskt/pidgen3/BINK2002.h"
#include "../libumskt/pidgen3/PIDGEN3.h"
#include "help.h" #include "help.h"
#include "libumskt/confid/confid.h"
#include "libumskt/libumskt.h"
#include "libumskt/pidgen2/PIDGEN2.h"
#include "libumskt/pidgen3/BINK1998.h"
#include "libumskt/pidgen3/BINK2002.h"
#include "libumskt/pidgen3/PIDGEN3.h"
#ifndef UMSKTCLI_VERSION_STRING #ifndef UMSKTCLI_VERSION_STRING
#define UMSKTCLI_VERSION_STRING "unknown version-dirty" #define UMSKTCLI_VERSION_STRING "unknown version-dirty"
#endif #endif
enum APPLICATION_STATE class CLI
{ {
STATE_PIDGEN_GENERATE, std::string pKey;
STATE_PIDGEN_VALIDATE, DWORD32 count, total, iBinkID;
STATE_CONFIRMATION_ID
};
enum PIDGEN_VERSION
{
PIDGEN_2 = 2,
PIDGEN_3 = 3,
};
struct Options struct Options
{ {
@ -96,41 +86,34 @@ struct Options
std::string productCode; std::string productCode;
std::string productFlavour; std::string productFlavour;
DWORD channelID; Integer channelID;
DWORD serial; Integer serial;
DWORD numKeys; DWORD32 numKeys;
BOOL oem; BOOL oem;
BOOL upgrade; BOOL upgrade;
BOOL serialSet;
BOOL verbose; BOOL verbose;
BOOL help; BOOL help;
BOOL error; BOOL error;
BOOL list; BOOL list;
struct Meta
{
std::string type;
std::vector<std::string> tags;
struct Activation
{
std::string flavour;
int version;
};
};
PIDGEN3::KeyInfo info; PIDGEN3::KeyInfo info;
PIDGEN_VERSION pidgenversion; enum PIDGEN_VERSION
APPLICATION_STATE state;
};
class CLI
{ {
std::string pKey; PIDGEN_2 = 2,
DWORD count, total, iBinkID; PIDGEN_3 = 3,
};
PIDGEN_VERSION pidgenversion;
static Options options; enum APPLICATION_STATE
{
STATE_PIDGEN_GENERATE,
STATE_PIDGEN_VALIDATE,
STATE_CONFIRMATION_ID
};
APPLICATION_STATE state;
} static options;
public: public:
CLI() CLI()
@ -165,42 +148,24 @@ class CLI
static CLIHandlerFunc SetValidateOption; static CLIHandlerFunc SetValidateOption;
static CLIHandlerFunc SetProductCodeOption; static CLIHandlerFunc SetProductCodeOption;
static CLIHandlerFunc SetFlavourOption; static CLIHandlerFunc SetFlavourOption;
static CLIHandlerFunc SetAuthDataOption;
static BOOL parseCommandLine(); static BOOL parseCommandLine();
static BOOL processOptions(); static BOOL processOptions();
static void printID(DWORD *pid); static BOOL processListCommand();
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);
BOOL InitPIDGEN3(PIDGEN3 &pidgen3); BOOL InitPIDGEN3(PIDGEN3 *p3);
BOOL InitConfirmationID(ConfirmationID &confid); BOOL InitConfirmationID(ConfirmationID &confid);
BOOL PIDGENGenerate(); BOOL PIDGenerate();
BOOL PIDGENValidate(); BOOL PIDValidate();
BOOL PIDGEN2Generate(PIDGEN2 &pidgen2); BOOL PIDGEN2Generate(PIDGEN2 &p2);
BOOL PIDGEN2Validate(PIDGEN2 &pidgen2); BOOL PIDGEN2Validate(PIDGEN2 &p2);
BOOL BINK1998Generate(PIDGEN3 &pidgen3); BOOL PIDGEN3Generate(PIDGEN3 *p3);
BOOL BINK1998Validate(PIDGEN3 &pidgen3); BOOL PIDGEN3Validate(PIDGEN3 *p3);
BOOL BINK2002Generate(PIDGEN3 &pidgen3);
BOOL BINK2002Validate(PIDGEN3 &pidgen3);
BOOL ConfirmationIDGenerate(); 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(); int Run();
}; };

127
src/cli/confirmationid.cpp Normal file
View File

@ -0,0 +1,127 @@
/**
* 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/18/2024
* @Maintainer Neo
*/
#include "cli.h"
/**
*
* @param confid
* @return success
*/
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);
return false;
}
auto meta = keys["products"][options.productCode]["meta"]["activation"];
if (!keys["activation"].contains(meta["flavour"]))
{
fmt::print("ERROR: \"{}\" is an unknown activation flavour", meta["flavour"]);
return false;
}
auto flavour = keys["activation"][meta["flavour"]];
if (options.verbose)
{
fmt::print("{:->80}\n", "");
fmt::print("Loaded the following hyperelliptic curve parameters: activation[{}]\n", meta["flavour"]);
fmt::print("{:->80}\n", "");
fmt::print("{:>7}: {}\n", "name", flavour["name"]);
fmt::print("{:>7}: {}\n", "version", meta["version"]);
fmt::print("{:>7}: {}\n", "Fp", flavour["p"]);
fmt::print("{:>7}: [{}, {}, {},\n{:>10}{}, {}, {}]\n", "F[]", flavour["x"]["0"], flavour["x"]["1"],
flavour["x"]["2"], "", flavour["x"]["3"], flavour["x"]["4"], flavour["x"]["5"]);
fmt::print("{:>7}: {}\n", "INV", flavour["quotient"]);
fmt::print("{:>7}: {}\n", "mqnr", flavour["non_residue"]);
fmt::print("{:>7}: {}\n", "k", flavour["priv"]);
fmt::print("{:>7}: {}\n", "IID", flavour["iid_key"]);
fmt::print("\n");
}
confid.LoadHyperellipticCurve(flavour["x"]["0"], flavour["x"]["1"], flavour["x"]["2"], flavour["x"]["3"],
flavour["x"]["4"], flavour["x"]["5"], flavour["priv"], flavour["quotient"],
flavour["non_residue"], flavour["iid_key"], meta["tags"].contains("xpbrand"),
meta["tags"].contains("office"), meta["activation"]["version"]);
return false;
}
/**
*
* @return success
*/
BOOL CLI::ConfirmationIDGenerate()
{
auto confid = ConfirmationID();
std::string confirmation_id;
if (!InitConfirmationID(confid))
{
return false;
}
DWORD32 err = confid.Generate(options.installationID, confirmation_id, options.productID);
if (err == SUCCESS)
{
fmt::print("{}\n", confirmation_id);
return true;
}
switch (err)
{
case ERR_TOO_SHORT:
fmt::print("ERROR: Installation ID is too short.\n");
break;
case ERR_TOO_LARGE:
fmt::print("ERROR: Installation ID is too long.\n");
break;
case ERR_INVALID_CHARACTER:
fmt::print("ERROR: Invalid character in installation ID.\n");
break;
case ERR_INVALID_CHECK_DIGIT:
fmt::print("ERROR: Installation ID checksum failed. Please check that it is typed correctly.\n");
break;
case ERR_UNKNOWN_VERSION:
fmt::print("ERROR: Unknown installation ID version.\n");
break;
case ERR_UNLUCKY:
fmt::print("ERROR: Unable to generate valid confirmation ID.\n");
break;
default:
fmt::print("Unknown error occurred during Confirmation ID generation: {}\n", err);
break;
}
return false;
}

View File

@ -37,7 +37,7 @@ void CLI::SetHelpText()
helpOptions[OPTION_DEBUG] = {"d", "debug", "enable debug output", false, "", &SetDebugOption}; helpOptions[OPTION_DEBUG] = {"d", "debug", "enable debug output", false, "", &SetDebugOption};
helpOptions[OPTION_FILE] = { helpOptions[OPTION_FILE] = {
"F", "file", "(advanced) specify which keys JSON file to load", true, "[embedded file]", &SetFileOption}; "", "file", "(advanced) specify which keys JSON file to load", true, "[embedded file]", &SetFileOption};
helpOptions[OPTION_LIST] = {"l", "list", "list supported products", false, "", &SetListOption}; helpOptions[OPTION_LIST] = {"l", "list", "list supported products", false, "", &SetListOption};
@ -52,7 +52,7 @@ void CLI::SetHelpText()
true, "1", &SetNumberOption}; true, "1", &SetNumberOption};
helpOptions[OPTION_ACTIVATIONID] = { helpOptions[OPTION_ACTIVATIONID] = {
"i", "installationID", "(activation only) installation ID used to generate confirmation ID", true, "I", "installationID", "(activation only) installation ID used to generate confirmation ID", true,
"", &SetActivationIDOption}; "", &SetActivationIDOption};
helpOptions[OPTION_ACTIVATIONPID] = { helpOptions[OPTION_ACTIVATIONPID] = {
@ -64,19 +64,19 @@ void CLI::SetHelpText()
helpOptions[OPTION_UPGRADE] = {"U", "upgrade", "(PIDGEN 3 only) generate an upgrade key", helpOptions[OPTION_UPGRADE] = {"U", "upgrade", "(PIDGEN 3 only) generate an upgrade key",
false, "", &SetUpgradeOption}; false, "", &SetUpgradeOption};
helpOptions[OPTION_BINK] = {"b", "binkID", "(advanced) override which BINK identifier to load", helpOptions[OPTION_BINK] = {"", "bink", "(advanced) override which BINK identifier to load",
true, "", &SetBINKOption}; true, "", &SetBINKOption};
helpOptions[OPTION_CHANNELID] = {"c", "channelid", "(advanced) override which product channel to use", helpOptions[OPTION_CHANNELID] = {"", "channel", "(advanced) override which product channel to use",
true, "", &SetChannelIDOption}; true, "", &SetChannelIDOption};
helpOptions[OPTION_SERIAL] = { helpOptions[OPTION_SERIAL] = {
"s", "serial", "(advanced, PIDGEN 2/3 [BINK 1998] only) specify a serial to generate", "", "serial", "(advanced, PIDGEN 2/3 [BINK 1998] only) specify a serial to generate",
true, "", &SetSerialOption}; true, "", &SetSerialOption};
helpOptions[OPTION_AUTHDATA] = { helpOptions[OPTION_AUTHDATA] = {
"a", "authdata", "(advanced, PIDGEN 3 [BINK 2002] only) specify a value for the authentication data field", "a", "authdata", "(advanced, PIDGEN 3 [BINK 2002] only) specify a value for the authentication data field",
true, "", nullptr}; true, "", &SetAuthDataOption};
helpOptions[OPTION_VALIDATE] = { helpOptions[OPTION_VALIDATE] = {
"V", "validate", "validate a specified product ID against known BINKs and algorithms", "V", "validate", "validate a specified product ID against known BINKs and algorithms",
@ -89,7 +89,7 @@ void CLI::SetHelpText()
*/ */
BOOL CLI::parseCommandLine() BOOL CLI::parseCommandLine()
{ {
for (DWORD i = 1; i < options.argc; i++) for (DWORD32 i = 1; i < options.argc; i++)
{ {
std::string arg = options.argv[i]; std::string arg = options.argv[i];
@ -132,7 +132,7 @@ BOOL CLI::parseCommandLine()
continue; continue;
} }
auto success = thisOption.handler(1, &nextarg[0]); auto success = thisOption.handler(nextarg);
if (!success) if (!success)
{ {
@ -155,9 +155,13 @@ BOOL CLI::parseCommandLine()
} }
CommandLineParseEnd: CommandLineParseEnd:
if (options.verbose)
{
fmt::print("\n");
}
if (options.error) if (options.error)
{ {
DisplayErrorMessage(0, nullptr); DisplayErrorMessage("");
} }
return !options.error; return !options.error;
} }
@ -176,93 +180,56 @@ BOOL CLI::processOptions()
if (options.list) if (options.list)
{ {
// the following code is absolutely unhinged return processListCommand();
// 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;
} }
if (options.productCode.empty()) if (options.productCode.empty())
{ {
fmt::print("ERROR: product code is required. Exiting..."); fmt::print("ERROR: product code is required. Exiting...\n");
DisplayHelp(0, nullptr); DisplayHelp("");
return false; return false;
} }
const char *productCode = &options.productCode[0]; if (!keys["products"].contains(options.productCode))
if (!keys["products"].contains(productCode))
{ {
fmt::print("ERROR: Product {} is unknown", productCode); fmt::print("ERROR: Product \"{}\" is unknown\n", options.productCode);
return false; return false;
} }
auto product = keys["products"][productCode]; auto product = keys["products"][options.productCode];
if (options.verbose) if (options.verbose)
{ {
fmt::print("Selecting product: {}\n", productCode); fmt::print("Selecting product: {}\n", options.productCode);
} }
json flavour; json flavour;
if (product.contains("flavours")) if (product.contains("flavours"))
{
// no default flavour, no flavour specified
if (!product["meta"].contains("default") && options.productFlavour.empty())
{
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]; flavour = product["flavours"][options.productFlavour];
if (options.verbose) if (options.verbose)
@ -270,17 +237,33 @@ BOOL CLI::processOptions()
fmt::print("Selecting flavour: {}\n", options.productFlavour); fmt::print("Selecting flavour: {}\n", options.productFlavour);
} }
} }
}
else else
{ {
// no variants, just go with what we have
flavour = product; flavour = product;
} }
if (options.state != STATE_PIDGEN_GENERATE && options.state != STATE_PIDGEN_VALIDATE) auto pidtype = product["meta"]["type"];
if (flavour["meta"].contains("type"))
{
pidtype = flavour["meta"]["type"];
}
if (options.state != Options::STATE_PIDGEN_GENERATE && options.state != Options::STATE_PIDGEN_VALIDATE)
{ {
// exit early if we're not doing PIDGEN // exit early if we're not doing PIDGEN
goto processOptionsExitEarly; goto processOptionsExitEarly;
} }
if (pidtype == "PIDGEN3")
{
options.pidgenversion = Options::PIDGEN_VERSION::PIDGEN_3;
if (options.verbose)
{
fmt::print("Setting PIDGEN type to \"PIDGEN3\"\n");
}
if (options.oem) if (options.oem)
{ {
flavour["BINK"][1].get_to(options.binkID); flavour["BINK"][1].get_to(options.binkID);
@ -294,8 +277,17 @@ BOOL CLI::processOptions()
{ {
fmt::print("Selected BINK: {}\n", options.binkID); fmt::print("Selected BINK: {}\n", options.binkID);
} }
}
else if (pidtype == "PIDGEN2")
{
options.pidgenversion = Options::PIDGEN_VERSION::PIDGEN_2;
if (options.verbose)
{
fmt::print("Setting PIDGEN type to \"PIDGEN2\"\n");
}
}
if (options.state != STATE_PIDGEN_GENERATE) if (options.state != Options::STATE_PIDGEN_GENERATE)
{ {
// exit early if we're only validating // exit early if we're only validating
goto processOptionsExitEarly; goto processOptionsExitEarly;
@ -329,14 +321,15 @@ BOOL CLI::processOptions()
if (options.verbose) 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()); 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) if (options.verbose)
{ {
fmt::print("Generated channel ID: {}\n", options.channelID); fmt::print("Generated channel ID: {}\n", options.channelID);
@ -344,9 +337,9 @@ BOOL CLI::processOptions()
} }
// FE and FF are BINK 1998, but use a different, currently unhandled encoding scheme, we return an error here // FE and FF are BINK 1998, but use a different, currently unhandled encoding scheme, we return an error here
if (options.binkID == "FE" || options.binkID == "FF") if (options.binkID == "TS00" || options.binkID == "TS01")
{ {
fmt::print("ERROR: Terminal Services BINKs (FE and FF) are unsupported at this time\n"); fmt::print("ERROR: Terminal Services BINKs (TS00 and TS01) are unsupported at this time\n");
return false; return false;
} }
@ -359,11 +352,88 @@ processOptionsExitEarly:
return true; 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 * @return success
*/ */
BOOL CLI::DisplayHelp(int, char *) BOOL CLI::DisplayHelp(const std::string &)
{ {
options.help = true; options.help = true;
fmt::print("usage: {} \n", options.argv[0]); fmt::print("usage: {} \n", options.argv[0]);
@ -371,6 +441,7 @@ BOOL CLI::DisplayHelp(int, char *)
for (BYTE i = 0; i < CLIHelpOptionID_END; i++) for (BYTE i = 0; i < CLIHelpOptionID_END; i++)
{ {
CLIHelpOptions o = helpOptions[i]; CLIHelpOptions o = helpOptions[i];
if (o.Short.empty()) if (o.Short.empty())
{ {
fmt::print("\t{:>2} --{:<15} {}", "", o.Long, o.HelpText); fmt::print("\t{:>2} --{:<15} {}", "", o.Long, o.HelpText);
@ -396,33 +467,31 @@ BOOL CLI::DisplayHelp(int, char *)
return true; return true;
} }
BOOL CLI::DisplayErrorMessage(int, char *) BOOL CLI::DisplayErrorMessage(const std::string &)
{ {
fmt::print("Error parsing command line options\n"); fmt::print("Error parsing command line options\n");
DisplayHelp(0, nullptr); DisplayHelp("");
options.error = true; options.error = true;
return false; return false;
} }
BOOL CLI::SetVerboseOption(int, char *) BOOL CLI::SetVerboseOption(const std::string &)
{ {
fmt::print("Enabling verbose option\n\n");
options.verbose = true; options.verbose = true;
UMSKT::VERBOSE = true; UMSKT::setVerboseOutput(stdout);
UMSKT::setDebugOutput(stderr); fmt::print(UMSKT::verbose, "Enabling verbose option\n");
return true; return true;
} }
BOOL CLI::SetDebugOption(int, char *) BOOL CLI::SetDebugOption(const std::string &)
{ {
fmt::print("Enabling debug option\n");
options.verbose = true; options.verbose = true;
UMSKT::DEBUG = true; UMSKT::setDebugOutput(stdout);
UMSKT::setDebugOutput(stderr); fmt::print(UMSKT::debug, "Enabling debug option\n");
return true; return true;
} }
BOOL CLI::SetListOption(int, char *) BOOL CLI::SetListOption(const std::string &)
{ {
if (options.verbose) if (options.verbose)
{ {
@ -432,17 +501,17 @@ BOOL CLI::SetListOption(int, char *)
return true; return true;
} }
BOOL CLI::SetOEMOption(int, char *) BOOL CLI::SetOEMOption(const std::string &)
{ {
if (options.verbose) if (options.verbose)
{ {
fmt::print("Setting oem option\n"); fmt::print("Setting OEM option\n");
} }
options.oem = true; options.oem = true;
return true; return true;
} }
BOOL CLI::SetUpgradeOption(int, char *) BOOL CLI::SetUpgradeOption(const std::string &)
{ {
if (options.verbose) if (options.verbose)
{ {
@ -452,49 +521,36 @@ BOOL CLI::SetUpgradeOption(int, char *)
return true; return true;
} }
BOOL CLI::SetFileOption(int count, char *file) BOOL CLI::SetFileOption(const std::string &file)
{ {
if (options.verbose) if (options.verbose)
{ {
fmt::print("Setting file option to: {}\n", file); fmt::print("Setting file option to: {}\n", file);
} }
options.keysFilename = file; options.keysFilename = file;
return true; return true;
} }
BOOL CLI::SetNumberOption(int count, char *num) BOOL CLI::SetNumberOption(const std::string &num)
{ {
int nKeys; auto nKeys = UMSKT::IntegerS(num);
if (!_sscanf(num, "%d", &nKeys))
{
return false;
}
if (options.verbose) if (options.verbose)
{ {
fmt::print("Setting generation number option to: {}\n", num); fmt::print("Setting generation number option to: {}\n", num);
} }
options.numKeys = nKeys; options.numKeys = nKeys.ConvertToLong();
return true; return true;
} }
/** BOOL CLI::SetChannelIDOption(const std::string &channum)
*
* @param count
* @param channum
* @return
*/
BOOL CLI::SetChannelIDOption(int count, char *channum)
{ {
int siteID; Integer channelID = UMSKT::IntegerS(channum);
if (!_sscanf(channum, "%d", &siteID))
{
return false;
}
// channel ids must be between 000 and 999 // 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"); fmt::print("ERROR: refusing to create a key with a Channel ID greater than 999\n");
return false; return false;
@ -502,94 +558,117 @@ BOOL CLI::SetChannelIDOption(int count, char *channum)
if (options.verbose) 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; return true;
} }
BOOL CLI::SetBINKOption(int count, char *bink) BOOL CLI::SetBINKOption(const std::string &bink)
{ {
auto strbinkid = std::string(bink); auto strbinkid = std::string(bink);
options.binkID = strtoupper(strbinkid); options.binkID = UMSKT::strtoupper(strbinkid);
if (options.verbose) if (options.verbose)
{ {
fmt::print("Setting BINK option to {}\n", strbinkid); fmt::print("Setting BINK option to: {}\n", strbinkid);
} }
return true; return true;
} }
BOOL CLI::SetFlavourOption(int count, char *flavour) BOOL CLI::SetFlavourOption(const std::string &flavour)
{ {
auto strFlavour = UMSKT::strtoupper(flavour);
options.productFlavour = strFlavour;
if (options.verbose) if (options.verbose)
{ {
fmt::print("Setting flavour option to {}\n", flavour); fmt::print("Setting flavour option to: {}\n", strFlavour);
} }
options.productFlavour = flavour;
return true; return true;
} }
/** BOOL CLI::SetSerialOption(const std::string &arg)
*
* @param count
* @param arg
* @return
*/
BOOL CLI::SetSerialOption(int count, char *arg)
{ {
int serial_val; Integer Serial = UMSKT::IntegerS(arg);
if (!_sscanf(arg, "%d", &serial_val))
{
return false;
}
// serials must be between 000000 and 999999 // 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"); fmt::print("ERROR: refusing to create a key with a Serial not between 000000 and 999999\n");
return false; return false;
} }
options.serialSet = true; options.serial = Serial;
options.serial = serial_val;
if (options.verbose)
{
fmt::print("Setting serial number option to: {}\n", Serial);
}
return true; return true;
} }
BOOL CLI::SetActivationIDOption(int count, char *aid) BOOL CLI::SetActivationIDOption(const std::string &aid)
{ {
options.installationID = aid; options.installationID = aid;
options.state = STATE_CONFIRMATION_ID; options.state = Options::STATE_CONFIRMATION_ID;
return true;
}
BOOL CLI::SetProductIDOption(int count, char *product)
{
if (options.verbose) 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; 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.keyToCheck = productID;
options.state = STATE_PIDGEN_VALIDATE; options.state = Options::STATE_PIDGEN_VALIDATE;
return true;
}
BOOL CLI::SetProductCodeOption(int, char *product)
{
if (options.verbose) if (options.verbose)
{ {
fmt::print("Setting product code to {}\n", product); 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);
} }
auto strProduct = std::string(product);
options.productCode = strtoupper(strProduct);
return true; return true;
} }

View File

@ -23,8 +23,15 @@
#ifndef UMSKT_HELP_H #ifndef UMSKT_HELP_H
#define 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 enum CLIHelpOptionIDs
{ {
OPTION_HELP, OPTION_HELP,

3
src/cli/options.cpp Normal file
View File

@ -0,0 +1,3 @@
//
// Created by neo on 2/18/2024.
//

319
src/cli/pidgen.cpp Normal file
View File

@ -0,0 +1,319 @@
/**
* 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/18/2024
* @Maintainer Neo
*/
#include "cli.h"
/**
*
* @param pidgen3
* @return success
*/
BOOL CLI::InitPIDGEN3(PIDGEN3 *p3)
{
auto bink = keys["BINK"][options.binkID];
if (options.verbose)
{
fmt::print("{:->80}\n", "");
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"]);
fmt::print("{:>6}: {}\n", "b", bink["b"]);
fmt::print("{:>6}: [{},\n{:>9}{}]\n", "G[x,y]", bink["g"]["x"], "", bink["g"]["y"]);
fmt::print("{:>6}: [{},\n{:>9}{}]\n", "K[x,y]", bink["pub"]["x"], "", bink["pub"]["y"]);
fmt::print("{:>6}: {}\n", "n", bink["n"]);
fmt::print("{:>6}: {}\n", "k", bink["priv"]);
fmt::print("\n");
}
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 != Options::APPLICATION_STATE::STATE_PIDGEN_GENERATE)
{
return true;
}
if (options.channelID.IsZero())
{
options.channelID.Randomize(UMSKT::rng, sizeof(DWORD32) * 8);
}
options.channelID %= 1000;
p3->info.ChannelID = options.channelID;
if (options.verbose)
{
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 {:d}\n", options.serial);
}
}
else if (options.serial.NotZero() && !p3->checkFieldIsBink1998())
{
fmt::print("Warning: Discarding user-supplied serial for BINK2002\n");
}
return true;
}
/**
*
* @param pidgen2
* @return success
*/
BOOL CLI::PIDGEN2Generate(PIDGEN2 &p2)
{
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;
}
/**
*
* @param pidgen2
* @return success
*/
BOOL CLI::PIDGEN2Validate(PIDGEN2 &p2)
{
std::string product_key;
if (!p2.ValidateKeyString(options.keyToCheck, product_key))
{
fmt::print("ERROR: Product key is in an incorrect format!\n");
return false;
}
fmt::print("{}\n", p2.StringifyKey(product_key));
if (!p2.Validate(product_key))
{
fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n");
return false;
}
fmt::print("Key validated successfully!\n");
return true;
}
/**
*
* @return success
*/
BOOL CLI::PIDGEN3Generate(PIDGEN3 *p3)
{
// raw PID/serial value
Integer serialRnd;
if (p3->checkFieldIsBink1998())
{
if (options.serial.NotZero())
{
// using user-provided serial
serialRnd = options.serial;
}
else
{
// generate a random number to use as a serial
serialRnd.Randomize(UMSKT::rng, sizeof(DWORD32) * 8);
}
// make sure it's less than 999999
serialRnd %= 1000000;
}
p3->info.isOEM = options.oem;
for (DWORD32 i = 0; i < total; i++)
{
if (!p3->checkFieldIsBink1998())
{
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: {:d}\n", p3->info.AuthInfo);
}
}
else
{
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)
{
fmt::print(p3->StringifyKey(pKey));
if (i <= total - 1 || options.verbose)
{
fmt::print("\n");
}
count += isValid;
}
else
{
if (options.verbose)
{
fmt::print("{} [Invalid]", p3->StringifyKey(pKey));
if (i <= total - 1)
{
fmt::print("\n");
}
}
total++; // queue a redo, basically
}
}
if (options.verbose)
{
fmt::print("\nSuccess count: {}/{}\n", count, total);
}
return true;
}
/**
*
* @return success
*/
BOOL CLI::PIDGEN3Validate(PIDGEN3 *p3)
{
std::string product_key;
if (!p3->ValidateKeyString(options.keyToCheck, product_key))
{
fmt::print("ERROR: Product key is in an incorrect format!\n");
return false;
}
fmt::print("{}\n", p3->StringifyKey(product_key));
if (!p3->Validate(product_key))
{
fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n");
return false;
}
fmt::print("Key validated successfully!\n");
return true;
}
/**
*
* @return success
*/
BOOL CLI::PIDGenerate()
{
BOOL retval = false;
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];
auto p3 = PIDGEN3::Factory(bink["p"]);
InitPIDGEN3(p3);
retval = PIDGEN3Generate(p3);
delete p3;
return retval;
}
return retval;
}
/**
*
* @return isValid
*/
BOOL CLI::PIDValidate()
{
BOOL retval = false;
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];
auto p3 = PIDGEN3::Factory(bink["p"]);
InitPIDGEN3(p3);
retval = PIDGEN3Validate(p3);
delete p3;
return retval;
}
return retval;
}

View File

@ -1,271 +0,0 @@
/**
* 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 01/05/2024
* @Maintainer Neo
*/
#include "cli.h"
/**
*
* @param pidgen2
* @return success
*/
BOOL CLI::PIDGEN2Generate(PIDGEN2 &pidgen2)
{
return true;
}
/**
*
* @param pidgen2
* @return success
*/
BOOL CLI::PIDGEN2Validate(PIDGEN2 &pidgen2)
{
return true;
}
/**
*
* @return success
*/
BOOL CLI::BINK1998Generate(PIDGEN3 &pidgen3)
{
// raw PID/serial value
DWORD nRaw = options.channelID * 1'000'000;
DWORD serialRnd;
if (options.serialSet)
{
// using user-provided serial
serialRnd = options.serial;
}
else
{
// generate a random number to use as a serial
serialRnd = UMSKT::getRandom<DWORD>();
}
// 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);
}
for (int i = 0; i < total; i++)
{
pidgen3.info.setSerial(nRaw);
pidgen3.Generate(pKey);
bool isValid = pidgen3.Validate(pKey);
if (isValid)
{
printKey(pKey);
if (i <= total - 1 || options.verbose)
{
fmt::print("\n");
}
count += isValid;
}
else
{
if (options.verbose)
{
printKey(pKey);
fmt::print(" [Invalid]");
if (i <= total - 1)
{
fmt::print("\n");
}
}
total++; // queue a redo, basically
}
}
if (options.verbose)
{
fmt::print("\nSuccess count: {}/{}\n", count, total);
}
return true;
}
/**
*
* @return success
*/
BOOL CLI::BINK1998Validate(PIDGEN3 &bink1998)
{
std::string product_key;
if (!CLI::stripKey(options.keyToCheck, product_key))
{
fmt::print("ERROR: Product key is in an incorrect format!\n");
return false;
}
CLI::printKey(product_key);
fmt::print("\n");
if (!bink1998.Validate(product_key))
{
fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n");
return false;
}
fmt::print("Key validated successfully!\n");
return true;
}
/**
*
* @return success
*/
BOOL CLI::BINK2002Generate(PIDGEN3 &pidgen3)
{
// generate a key
for (int i = 0; i < total; i++)
{
pidgen3.info.AuthInfo = UMSKT::getRandom<DWORD>() & BITMASK(10);
if (options.verbose)
{
fmt::print("> AuthInfo: {:#08x}\n", pidgen3.info.AuthInfo);
}
pidgen3.Generate(pKey);
bool isValid = pidgen3.Validate(pKey);
if (isValid)
{
CLI::printKey(pKey);
if (i <= total - 1 || options.verbose)
{ // check if end of list or verbose
fmt::print("\n");
}
count += isValid; // add to count
}
else
{
if (options.verbose)
{
CLI::printKey(pKey); // print the key
fmt::print(" [Invalid]"); // and add " [Invalid]" to the key
if (i <= total - 1)
{ // check if end of list
fmt::print("\n");
}
}
total++; // queue a redo, basically
}
}
if (options.verbose)
{
fmt::print("\nSuccess count: {}/{}\n", count, total);
}
return true;
}
/**
*
* @return success
*/
BOOL CLI::BINK2002Validate(PIDGEN3 &pidgen3)
{
std::string product_key;
if (!CLI::stripKey(options.keyToCheck, product_key))
{
fmt::print("ERROR: Product key is in an incorrect format!\n");
return false;
}
CLI::printKey(product_key);
fmt::print("\n");
if (!pidgen3.Validate(product_key))
{
fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n");
return false;
}
fmt::print("Key validated successfully!\n");
return true;
}
/**
*
* @return success
*/
BOOL CLI::ConfirmationIDGenerate()
{
auto confid = ConfirmationID();
std::string confirmation_id;
if (!InitConfirmationID(confid))
{
return false;
}
DWORD err = confid.Generate(options.installationID, confirmation_id, options.productID);
if (err == SUCCESS)
{
fmt::print("{}\n", confirmation_id);
return true;
}
switch (err)
{
case ERR_TOO_SHORT:
fmt::print("ERROR: Installation ID is too short.\n");
break;
case ERR_TOO_LARGE:
fmt::print("ERROR: Installation ID is too long.\n");
break;
case ERR_INVALID_CHARACTER:
fmt::print("ERROR: Invalid character in installation ID.\n");
break;
case ERR_INVALID_CHECK_DIGIT:
fmt::print("ERROR: Installation ID checksum failed. Please check that it is typed correctly.\n");
break;
case ERR_UNKNOWN_VERSION:
fmt::print("ERROR: Unknown installation ID version.\n");
break;
case ERR_UNLUCKY:
fmt::print("ERROR: Unable to generate valid confirmation ID.\n");
break;
default:
fmt::print("Unknown error occurred during Confirmation ID generation: {}\n", err);
break;
}
return false;
}

View File

@ -24,6 +24,8 @@
* the history provided by diamondggg is that they are the originator of the code * the history provided by diamondggg is that they are the originator of the code
* and was created in tandem with an acquaintance who knows number theory. * and was created in tandem with an acquaintance who knows number theory.
* The file dates suggest this code was written sometime in 2017/2018 * The file dates suggest this code was written sometime in 2017/2018
*
* The algorithm was refactored by Neo in 2023
* } * }
*/ */
@ -38,7 +40,7 @@
* @param x4 * @param x4
* @param x5 * @param x5
* @param priv * @param priv
* @param modulous * @param modulus
* @param nonresidue * @param nonresidue
* @param isOffice * @param isOffice
* @param isXPBrand * @param isXPBrand
@ -46,58 +48,74 @@
* @return * @return
*/ */
BOOL ConfirmationID::LoadHyperellipticCurve(QWORD x0, QWORD x1, QWORD x2, QWORD x3, QWORD x4, QWORD x5, Q_OWORD priv, BOOL ConfirmationID::LoadHyperellipticCurve(QWORD x0, QWORD x1, QWORD x2, QWORD x3, QWORD x4, QWORD x5, Q_OWORD priv,
QWORD modulous, QWORD nonresidue, BOOL isOffice, BOOL isXPBrand, QWORD modulus, QWORD nonresidue, DWORD32 iidkey, BOOL isOffice,
BYTE flagVersion) BOOL isXPBrand, BYTE flagVersion)
{ {
curve[0] = x0; QWORD fvals[6] = {x0, x1, x2, x3, x4, x5};
curve[1] = x1;
curve[2] = x2;
curve[3] = x3;
curve[4] = x4;
curve[5] = x5;
memcpy(&privateKey, &priv, sizeof(Q_OWORD)); return LoadHyperellipticCurve(fvals, priv, modulus, nonresidue, iidkey, isOffice, isXPBrand, flagVersion);
MOD = modulous;
NON_RESIDUE = nonresidue;
this->isOffice = isOffice;
this->isXPBrand = isXPBrand;
this->flagVersion = flagVersion;
return true;
} }
/** /**
* *
* @param f * @param f
* @param priv * @param priv
* @param modulous * @param modulus
* @param nonresidue * @param nonresidue
* @param isOffice * @param isOffice
* @param isXPBrand * @param isXPBrand
* @param flagVersion * @param flagVersion
* @return * @return
*/ */
BOOL ConfirmationID::LoadHyperellipticCurve(QWORD *f, Q_OWORD priv, QWORD modulous, QWORD nonresidue, BOOL isOffice, BOOL ConfirmationID::LoadHyperellipticCurve(QWORD *f, Q_OWORD priv, QWORD modulus, QWORD nonresidue, DWORD32 iidkey,
BOOL isXPBrand, BYTE flagVersion) BOOL isOffice, BOOL isXPBrand, BYTE flagVersion)
{ {
memcpy(&curve, f, sizeof(curve)); memcpy(&curve, f, sizeof(curve));
memcpy(&privateKey, &priv, sizeof(Q_OWORD)); memcpy(&privateKey, &priv, sizeof(Q_OWORD));
MOD = modulous; MOD = modulus;
NON_RESIDUE = nonresidue; NON_RESIDUE = nonresidue;
this->isOffice = isOffice; this->isOffice = isOffice;
this->isXPBrand = isXPBrand; this->isXPBrand = isXPBrand;
this->flagVersion = flagVersion; this->flagVersion = flagVersion;
return true; return true;
} }
BOOL LoadHyperellipticCurve(const std::string &x0, const std::string &x1, const std::string &x2, const std::string &x3, BOOL ConfirmationID::LoadHyperellipticCurve(const std::string &x0, const std::string &x1, const std::string &x2,
const std::string &x4, const std::string &x5, const std::string &priv, const std::string &x3, const std::string &x4, const std::string &x5,
const std::string &modulous, const std::string &nonresidue, BOOL isOffice, BOOL isXPBrand, const std::string &priv, const std::string &modulus,
BYTE flagVersion) const std::string &nonresidue, const std::string &iidkey, BOOL isOffice,
BOOL isXPBrand, BYTE flagVersion)
{ {
std::string f[6];
f[0] = x0;
f[1] = x1;
f[2] = x2;
f[3] = x3;
f[4] = x4;
f[5] = x5;
return LoadHyperellipticCurve(f, priv, modulus, nonresidue, iidkey, isOffice, isXPBrand, flagVersion);
}
BOOL ConfirmationID::LoadHyperellipticCurve(const std::string *f, const std::string &priv, const std::string &modulus,
const std::string &nonresidue, const std::string &iidkey, BOOL isOffice,
BOOL isXPBrand, BYTE flagVersion)
{
for (int i = 0; i < 6; i++)
{
EncodeN(IntegerS(f[i]), curve[i]);
}
EncodeN(IntegerS(priv), privateKey);
EncodeN(IntegerS(modulus), MOD);
EncodeN(IntegerS(nonresidue), NON_RESIDUE);
this->isOffice = isOffice;
this->isXPBrand = isXPBrand;
this->flagVersion = flagVersion;
return true; return true;
} }
@ -107,9 +125,9 @@ BOOL LoadHyperellipticCurve(const std::string &x0, const std::string &x1, const
* @param pid * @param pid
* @return * @return
*/ */
DWORD ConfirmationID::calculateCheckDigit(DWORD pid) DWORD32 ConfirmationID::calculateCheckDigit(DWORD32 pid)
{ {
DWORD i = 0, j = 0, k = 0; DWORD32 i = 0, j = 0, k = 0;
for (j = pid; j; i += k) for (j = pid; j; i += k)
{ {
k = j % 10; k = j % 10;
@ -125,7 +143,7 @@ DWORD ConfirmationID::calculateCheckDigit(DWORD pid)
* @param hwid * @param hwid
* @param version * @param version
*/ */
void ConfirmationID::decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD *version) void ConfirmationID::decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD32 *version)
{ {
QWORD buffer[5]; QWORD buffer[5];
for (BYTE i = 0; i < 5; i++) for (BYTE i = 0; i < 5; i++)
@ -133,8 +151,8 @@ void ConfirmationID::decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD *versio
memcpy(&buffer[i], (iid + (4 * i)), 4); memcpy(&buffer[i], (iid + (4 * i)), 4);
} }
DWORD v1 = (buffer[3] & 0xFFFFFFF8) | 2; DWORD32 v1 = (buffer[3] & 0xFFFFFFF8) | 2;
DWORD v2 = ((buffer[3] & 7) << 29) | (buffer[2] >> 3); DWORD32 v2 = ((buffer[3] & 7) << 29) | (buffer[2] >> 3);
QWORD hardwareIDVal = ((QWORD)v1 << 32) | v2; QWORD hardwareIDVal = ((QWORD)v1 << 32) | v2;
for (BYTE i = 0; i < 8; ++i) for (BYTE i = 0; i < 8; ++i)
{ {
@ -153,8 +171,9 @@ void ConfirmationID::decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD *versio
*/ */
void ConfirmationID::Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize) void ConfirmationID::Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize)
{ {
BYTE sha1_input[64], sha1_result[20]; BYTE sha1_input[64], sha1_result[SHA1::DIGESTSIZE];
BYTE half = bufSize / 2; BYTE half = bufSize / 2;
auto digest = SHA1();
// assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9); // assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
for (BYTE external_counter = 0; external_counter < 4; external_counter++) for (BYTE external_counter = 0; external_counter < 4; external_counter++)
@ -181,7 +200,8 @@ void ConfirmationID::Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySi
sha1_input[sizeof(sha1_input) - 2] = (1 + half + keySize) * 8 / 0x100; sha1_input[sizeof(sha1_input) - 2] = (1 + half + keySize) * 8 / 0x100;
} }
SHA1(sha1_input, sizeof(sha1_input), sha1_result); digest.Update(sha1_input, sizeof(sha1_input));
digest.Final(sha1_result);
for (BYTE i = half & ~3; i < half; i++) for (BYTE i = half & ~3; i < half; i++)
{ {
@ -206,8 +226,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) void ConfirmationID::Unmix(BYTE *buffer, BYTE bufSize, const BYTE key[4], BYTE keySize)
{ {
BYTE sha1_input[64], sha1_result[20]; BYTE sha1_input[64], sha1_result[SHA1::DIGESTSIZE];
BYTE half = bufSize / 2; BYTE half = bufSize / 2;
auto digest = SHA1();
// assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9); // assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
for (BYTE external_counter = 0; external_counter < 4; external_counter++) for (BYTE external_counter = 0; external_counter < 4; external_counter++)
@ -232,7 +253,8 @@ void ConfirmationID::Unmix(BYTE *buffer, BYTE bufSize, const BYTE key[4], BYTE k
sha1_input[sizeof(sha1_input) - 2] = (1 + half + keySize) * 8 / 0x100; sha1_input[sizeof(sha1_input) - 2] = (1 + half + keySize) * 8 / 0x100;
} }
SHA1(sha1_input, sizeof(sha1_input), sha1_result); digest.Update(sha1_input, sizeof(sha1_input));
digest.Final(sha1_result);
for (BYTE i = half & ~3; i < half; i++) for (BYTE i = half & ~3; i < half; i++)
{ {
@ -258,13 +280,13 @@ void ConfirmationID::Unmix(BYTE *buffer, BYTE bufSize, const BYTE key[4], BYTE k
CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationIDIn, std::string &confirmationIDOut, CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationIDIn, std::string &confirmationIDOut,
std::string &productIDIn) std::string &productIDIn)
{ {
DWORD version; DWORD32 version;
BYTE hardwareID[8]; BYTE hardwareID[8];
BYTE installation_id[19]; // 10**45 < 256**19 BYTE installation_id[19]; // 10**45 < 256**19
BYTE productID[4]; BYTE productID[4];
BYTE installation_id_len = 0; BYTE installation_id_len = 0;
auto pid = installationIDIn.c_str(); auto pid = &installationIDIn[0];
BYTE count = 0, totalCount = 0; BYTE count = 0, totalCount = 0;
unsigned check = 0; unsigned check = 0;
@ -441,8 +463,8 @@ CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationI
QWORD x1 = ulowhi.qword[0] - x2 * MOD; QWORD x1 = ulowhi.qword[0] - x2 * MOD;
x2++; x2++;
d.u[0] = residue->sub(residue->mul(x1, x1), residue->mul(NON_RESIDUE, residue->mul(x2, x2))); d.u.qword[0] = residue->sub(residue->mul(x1, x1), residue->mul(NON_RESIDUE, residue->mul(x2, x2)));
d.u[1] = residue->add(x1, x1); d.u.qword[1] = residue->add(x1, x1);
if (divisor->find_divisor_v(&d)) if (divisor->find_divisor_v(&d))
{ {
break; break;
@ -462,23 +484,23 @@ CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationI
Q_OWORD e; Q_OWORD e;
memset(&e, 0, sizeof(e)); memset(&e, 0, sizeof(e));
if (d.u[0] == BAD) if (d.u.qword[0] == BAD)
{ {
// we can not get the zero divisor, actually... // we can not get the zero divisor, actually...
e.qword[0] = residue->__umul128(MOD + 2, MOD, &e.qword[1]); e.qword[0] = residue->__umul128(MOD + 2, MOD, &e.qword[1]);
} }
else if (d.u[1] == BAD) else if (d.u.qword[1] == BAD)
{ {
// O(1/MOD) chance // O(1/MOD) chance
// encoded = (unsigned __int128)(MOD + 1) * d.u[0] + MOD; // * MOD + d.u[0] is fine too // encoded = (unsigned __int128)(MOD + 1) * d.u[0] + MOD; // * MOD + d.u[0] is fine too
e.qword[0] = residue->__umul128(MOD + 1, d.u[0], &e.qword[1]); e.qword[0] = residue->__umul128(MOD + 1, d.u.qword[0], &e.qword[1]);
e.qword[0] += MOD; e.qword[0] += MOD;
e.qword[1] += (e.qword[0] < MOD); e.qword[1] += (e.qword[0] < MOD);
} }
else else
{ {
QWORD x1 = (d.u[1] % 2 ? d.u[1] + MOD : d.u[1]) / 2; QWORD x1 = (d.u.qword[1] % 2 ? d.u.qword[1] + MOD : d.u.qword[1]) / 2;
QWORD x2sqr = residue->sub(residue->mul(x1, x1), d.u[0]); QWORD x2sqr = residue->sub(residue->mul(x1, x1), d.u.qword[0]);
QWORD x2 = residue->sqrt(x2sqr); QWORD x2 = residue->sqrt(x2sqr);
if (x2 == BAD) if (x2 == BAD)
@ -493,9 +515,9 @@ CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationI
{ {
// points (-x1+x2, v(-x1+x2)) and (-x1-x2, v(-x1-x2)) // points (-x1+x2, v(-x1+x2)) and (-x1-x2, v(-x1-x2))
QWORD x1a = residue->sub(x1, x2); QWORD x1a = residue->sub(x1, x2);
QWORD y1 = residue->sub(d.v[0], residue->mul(d.v[1], x1a)); QWORD y1 = residue->sub(d.v.qword[0], residue->mul(d.v.qword[1], x1a));
QWORD x2a = residue->add(x1, x2); QWORD x2a = residue->add(x1, x2);
QWORD y2 = residue->sub(d.v[0], residue->mul(d.v[1], x2a)); QWORD y2 = residue->sub(d.v.qword[0], residue->mul(d.v.qword[1], x2a));
if (x1a > x2a) if (x1a > x2a)
{ {
QWORD tmp = x1a; QWORD tmp = x1a;
@ -534,7 +556,7 @@ CONFIRMATION_ID_STATUS ConfirmationID::Generate(const std::string &installationI
decimal[34 - i] = c4; 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]; char *q = &confirmationIDOut[0];

View File

@ -23,7 +23,7 @@
#ifndef UMSKT_CONFID_H #ifndef UMSKT_CONFID_H
#define UMSKT_CONFID_H #define UMSKT_CONFID_H
#include "../libumskt.h" #include <libumskt/libumskt.h>
// Confirmation ID generator constants // Confirmation ID generator constants
enum CONFIRMATION_ID_STATUS enum CONFIRMATION_ID_STATUS
@ -41,11 +41,10 @@ enum CONFIRMATION_ID_STATUS
typedef struct typedef struct
{ {
QWORD u[2]; Q_OWORD u, v;
QWORD v[2];
} TDivisor; } TDivisor;
class EXPORT ConfirmationID class EXPORT ConfirmationID : public UMSKT
{ {
QWORD MOD = 0, NON_RESIDUE = 0; QWORD MOD = 0, NON_RESIDUE = 0;
QWORD curve[6] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; QWORD curve[6] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
@ -55,8 +54,8 @@ class EXPORT ConfirmationID
BOOL isOffice = false, isXPBrand = false; BOOL isOffice = false, isXPBrand = false;
unsigned flagVersion = 0; unsigned flagVersion = 0;
DWORD calculateCheckDigit(DWORD pid); DWORD32 calculateCheckDigit(DWORD32 pid);
void decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD *version); void decode_iid_new_version(BYTE *iid, BYTE *hwid, DWORD32 *version);
void Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize); void Mix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize);
void Unmix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize); void Unmix(BYTE *buffer, BYTE bufSize, const BYTE *key, BYTE keySize);
@ -127,26 +126,31 @@ class EXPORT ConfirmationID
residue = new Residue(this); residue = new Residue(this);
polynomial = new Polynomial(this); polynomial = new Polynomial(this);
divisor = new Divisor(this); divisor = new Divisor(this);
privateKey.qword[0] = privateKey.qword[1] = 0x00;
} }
BOOL LoadHyperellipticCurve(const std::string *f, const std::string &priv, const std::string &modulus,
const std::string &nonresidue, const std::string &iidkey, BOOL isOffice, BOOL isXPBrand,
BYTE flagVersion);
BOOL LoadHyperellipticCurve(const std::string &x0, const std::string &x1, const std::string &x2, BOOL LoadHyperellipticCurve(const std::string &x0, const std::string &x1, const std::string &x2,
const std::string &x3, const std::string &x4, const std::string &x5, const std::string &x3, const std::string &x4, const std::string &x5,
const std::string &priv, const std::string &modulous, const std::string &nonresidue, const std::string &priv, const std::string &modulus, const std::string &nonresidue,
BOOL isOffice, BOOL isXPBrand, BYTE flagVersion); const std::string &iidkey, BOOL isOffice, BOOL isXPBrand, BYTE flagVersion);
BOOL LoadHyperellipticCurve(QWORD x0, QWORD x1, QWORD x2, QWORD x3, QWORD x4, QWORD x5, Q_OWORD priv, BOOL LoadHyperellipticCurve(QWORD x0, QWORD x1, QWORD x2, QWORD x3, QWORD x4, QWORD x5, Q_OWORD priv, QWORD modulus,
QWORD modulous, QWORD nonresidue, BOOL isOffice, BOOL isXPBrand, BYTE flagVersion); QWORD nonresidue, DWORD32 iidkey, BOOL isOffice, BOOL isXPBrand, BYTE flagVersion);
BOOL LoadHyperellipticCurve(QWORD *f, Q_OWORD priv, QWORD modulous, QWORD nonresidue, BOOL isOffice, BOOL isXPBrand, BOOL LoadHyperellipticCurve(QWORD *f, Q_OWORD priv, QWORD modulus, QWORD nonresidue, DWORD32 iidkey, BOOL isOffice,
BYTE flagVersion); BOOL isXPBrand, BYTE flagVersion);
CONFIRMATION_ID_STATUS Generate(const std::string &installation_id_str, std::string &confirmation_id, CONFIRMATION_ID_STATUS Generate(const std::string &installation_id_str, std::string &confirmation_id,
std::string &productid); std::string &productid);
~ConfirmationID() ~ConfirmationID()
{ {
delete residue, polynomial, divisor; delete residue;
delete polynomial;
delete divisor;
} }
}; };

View File

@ -0,0 +1,55 @@
/**
* 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/20/2024
* @Maintainer Neo
*/
#include "confid.h"
#include <libumskt/libumskt_unittest.h>
/**
* ConfirmationID must not be an abstract class.
*/
TEST(TestConfirmationID, InstantiateConfirmationID)
{
auto p3 = new ConfirmationID();
ASSERT_NE(p3, nullptr);
delete p3;
}
class TestConfirmationID : public libumsktUnitTests
{
protected:
ConfirmationID *cid;
void SetUp() override
{
cid = new ConfirmationID();
cid->LoadHyperellipticCurve()
}
void TearDown() override
{
if (cid != nullptr)
{
delete cid;
cid = nullptr;
}
}
};

View File

@ -39,7 +39,7 @@ int ConfirmationID::ConfirmationID::Divisor::find_divisor_v(TDivisor *d)
f2[i] = parent->curve[i]; f2[i] = parent->curve[i];
} }
const QWORD u0 = d->u[0], u1 = d->u[1]; const QWORD u0 = d->u.qword[0], u1 = d->u.qword[1];
for (BYTE j = 4; j--;) for (BYTE j = 4; j--;)
{ {
f2[j] = parent->residue->sub(f2[j], parent->residue->mul(u0, f2[j + 2])); f2[j] = parent->residue->sub(f2[j], parent->residue->mul(u0, f2[j + 2]));
@ -106,8 +106,8 @@ int ConfirmationID::ConfirmationID::Divisor::find_divisor_v(TDivisor *d)
QWORD v0 = parent->residue->mul(parent->residue->add(f1, parent->residue->mul(u1, parent->residue->mul(v1, v1))), QWORD v0 = parent->residue->mul(parent->residue->add(f1, parent->residue->mul(u1, parent->residue->mul(v1, v1))),
parent->residue->inv(parent->residue->add(v1, v1))); parent->residue->inv(parent->residue->add(v1, v1)));
d->v[0] = v0; d->v.qword[0] = v0;
d->v[1] = v1; d->v.qword[1] = v1;
return 1; return 1;
} }
@ -121,24 +121,24 @@ int ConfirmationID::ConfirmationID::Divisor::find_divisor_v(TDivisor *d)
*/ */
int ConfirmationID::ConfirmationID::Divisor::u2poly(const TDivisor *src, QWORD polyu[3], QWORD polyv[2]) int ConfirmationID::ConfirmationID::Divisor::u2poly(const TDivisor *src, QWORD polyu[3], QWORD polyv[2])
{ {
if (src->u[1] != BAD) if (src->u.qword[1] != BAD)
{ {
polyu[0] = src->u[0]; polyu[0] = src->u.qword[0];
polyu[1] = src->u[1]; polyu[1] = src->u.qword[1];
polyu[2] = 1; polyu[2] = 1;
polyv[0] = src->v[0]; polyv[0] = src->v.qword[0];
polyv[1] = src->v[1]; polyv[1] = src->v.qword[1];
return 2; return 2;
} }
if (src->u[0] != BAD) if (src->u.qword[0] != BAD)
{ {
polyu[0] = src->u[0]; polyu[0] = src->u.qword[0];
polyu[1] = 1; polyu[1] = 1;
polyv[0] = src->v[0]; polyv[0] = src->v.qword[0];
polyv[1] = 0; polyv[1] = 0;
return 1; return 1;
@ -237,7 +237,7 @@ void ConfirmationID::Divisor::add(const TDivisor *src1, const TDivisor *src2, TD
} }
} }
vdeg = parent->polynomial->div_monic(vdeg, v, udeg, u, NULL); vdeg = parent->polynomial->div_monic(vdeg, v, udeg, u, nullptr);
while (udeg > 2) while (udeg > 2)
{ {
@ -279,30 +279,30 @@ void ConfirmationID::Divisor::add(const TDivisor *src1, const TDivisor *src2, TD
v[i] = parent->residue->sub(0, v[i]); v[i] = parent->residue->sub(0, v[i]);
} }
vdeg = parent->polynomial->div_monic(vdeg, v, udeg, u, NULL); vdeg = parent->polynomial->div_monic(vdeg, v, udeg, u, nullptr);
} }
if (udeg == 2) if (udeg == 2)
{ {
dst->u[0] = u[0]; dst->u.qword[0] = u[0];
dst->u[1] = u[1]; dst->u.qword[1] = u[1];
dst->v[0] = (vdeg >= 0 ? v[0] : 0); dst->v.qword[0] = (vdeg >= 0 ? v[0] : 0);
dst->v[1] = (vdeg >= 1 ? v[1] : 0); dst->v.qword[1] = (vdeg >= 1 ? v[1] : 0);
} }
else if (udeg == 1) else if (udeg == 1)
{ {
dst->u[0] = u[0]; dst->u.qword[0] = u[0];
dst->u[1] = BAD; dst->u.qword[1] = BAD;
dst->v[0] = (vdeg >= 0 ? v[0] : 0); dst->v.qword[0] = (vdeg >= 0 ? v[0] : 0);
dst->v[1] = BAD; dst->v.qword[1] = BAD;
} }
else else
{ {
assert(udeg == 0); assert(udeg == 0);
dst->u[0] = BAD; dst->u.qword[0] = BAD;
dst->u[1] = BAD; dst->u.qword[1] = BAD;
dst->v[0] = BAD; dst->v.qword[0] = BAD;
dst->v[1] = BAD; dst->v.qword[1] = BAD;
} }
} }
@ -318,10 +318,10 @@ void ConfirmationID::Divisor::mul(const TDivisor *src, QWORD mult, TDivisor *dst
{ {
if (mult == 0) if (mult == 0)
{ {
dst->u[0] = BAD; dst->u.qword[0] = BAD;
dst->u[1] = BAD; dst->u.qword[1] = BAD;
dst->v[0] = BAD; dst->v.qword[0] = BAD;
dst->v[1] = BAD; dst->v.qword[1] = BAD;
return; return;
} }
@ -354,10 +354,10 @@ void ConfirmationID::Divisor::mul128(const TDivisor *src, QWORD mult_lo, QWORD m
{ {
if (mult_lo == 0 && mult_hi == 0) if (mult_lo == 0 && mult_hi == 0)
{ {
dst->u[0] = BAD; dst->u.qword[0] = BAD;
dst->u[1] = BAD; dst->u.qword[1] = BAD;
dst->v[0] = BAD; dst->v.qword[0] = BAD;
dst->v[1] = BAD; dst->v.qword[1] = BAD;
return; return;
} }

View File

@ -20,22 +20,28 @@
* @Maintainer Neo * @Maintainer Neo
*/ */
#include "libumskt.h" #include <libumskt/libumskt.h>
#ifdef _WIN32 std::FILE *UMSKT::debug;
// this seems janky but it works, and doesn't use storage that would otherwise get clobbered std::FILE *UMSKT::verbose;
std::FILE *getFileStreamToNul()
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()
{ {
fopen_s(&UMSKT::debug, "nul", "w"); #ifdef __DJGPP__
return UMSKT::debug; // this should be set up as early as possible
} uclock();
std::FILE *UMSKT::debug = getFileStreamToNul();
#else
std::FILE *UMSKT::debug = std::fopen("/dev/null", "w");
#endif #endif
return true;
BOOL UMSKT::VERBOSE = false; }
BOOL UMSKT::DEBUG = false;
/** /**
* sets the filestream used for debugging * sets the filestream used for debugging
@ -46,3 +52,13 @@ void UMSKT::setDebugOutput(std::FILE *input)
{ {
debug = input; debug = input;
} }
/**
* sets the filestream used for verbose messages
*
* @param input std::FILE
*/
void UMSKT::setVerboseOutput(std::FILE *input)
{
verbose = input;
}

View File

@ -20,18 +20,18 @@
* @Maintainer Neo * @Maintainer Neo
*/ */
#include "libumskt.h" #include <libumskt/confid/confid.h>
#include "confid/confid.h" #include <libumskt/libumskt.h>
#include "pidgen2/PIDGEN2.h" #include <libumskt/pidgen2/PIDGEN2.h>
#include "pidgen3/BINK1998.h" #include <libumskt/pidgen3/BINK1998.h>
#include "pidgen3/BINK2002.h" #include <libumskt/pidgen3/BINK2002.h>
#include "pidgen3/PIDGEN3.h" #include <libumskt/pidgen3/PIDGEN3.h>
std::map<UMSKT_TAG, UMSKT_Value> UMSKT::tags; std::map<UMSKT_TAG, UMSKT_Value> UMSKT::tags;
CryptoPP::DefaultAutoSeededRNG UMSKT::rng;
extern "C" extern "C"
{ {
/** /**
* Sets debug output to a given C++ File stream * Sets debug output to a given C++ File stream
* if the memory allocated at filestream is "STDOUT" or "STDERR" * if the memory allocated at filestream is "STDOUT" or "STDERR"
@ -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 *generatorY, const char *publicKeyX, const char *publicKeyY,
const char *genOrder, const char *privateKey) const char *genOrder, const char *privateKey)
{ {
@ -163,7 +163,7 @@ extern "C"
p3 = new BINK2002(); 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; return p3;
} }
@ -195,7 +195,7 @@ extern "C"
std::string str; std::string str;
BOOL retval = p3->Generate(str); BOOL retval = p3->Generate(str);
assert(pKeySizeIn >= str.length() + 1); assert(pKeySizeIn >= str.length() + NULL_TERMINATOR);
memcpy(pKeyOut, &str[0], str.length()); memcpy(pKeyOut, &str[0], str.length());
pKeyOut[str.length()] = 0; pKeyOut[str.length()] = 0;
@ -245,48 +245,3 @@ extern "C"
} }
} // extern "C" } // extern "C"
/**
* Convert data between endianness types.
*
* @param data [in]
* @param length [in]
**/
void UMSKT::endian(BYTE *data, int length)
{
for (int i = 0; i < length / 2; i++)
{
BYTE temp = data[i];
data[i] = data[length - i - 1];
data[length - i - 1] = temp;
}
}
/**
* Converts an OpenSSL BigNumber to it's Little Endian binary equivalent
*
* @param a [in] BigNumber to convert
* @param to [out] char* binary representation
* @param tolen [in] length of the char* array
*
* @return length of number in to
**/
int UMSKT::BN_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen)
{
if (a == nullptr || to == nullptr)
{
return 0;
}
int len = BN_bn2bin(a, to);
if (len > tolen)
{
return -1;
}
// Choke point inside BN_bn2lebinpad: OpenSSL uses len instead of tolen.
endian(to, tolen);
return len;
}

View File

@ -23,40 +23,91 @@
#ifndef UMSKT_LIBUMSKT_H #ifndef UMSKT_LIBUMSKT_H
#define UMSKT_LIBUMSKT_H #define UMSKT_LIBUMSKT_H
#include "../typedefs.h" #include <typedefs.h>
#include <iostream> #ifdef __DJGPP__
#include <sstream> #include <time.h>
#include <string> #endif
#include <openssl/bn.h> #include <cryptopp/cryptlib.h>
#include <openssl/ec.h> #include <cryptopp/ecp.h>
#include <openssl/evp.h> #include <cryptopp/integer.h>
#include <openssl/rand.h> #include <cryptopp/misc.h>
#include <openssl/sha.h> #include <cryptopp/nbtheory.h>
#include <cryptopp/osrng.h>
#include <cryptopp/sha.h>
using ECP = CryptoPP::ECP;
using SHA1 = CryptoPP::SHA1;
using Integer = CryptoPP::Integer;
#include <fmt/core.h> #include <fmt/core.h>
#include <fmt/format.h> #include <fmt/format.h>
// fmt <-> CryptoPP linkage
template <> class fmt::formatter<Integer>
{
char type_ = 'd';
public:
constexpr auto parse(format_parse_context &ctx)
{
auto i = ctx.begin(), end = ctx.end();
if (i != end)
{
switch (*i)
{
case 'B':
case 'b':
case 'o':
case 'X':
case 'x':
case 'd':
type_ = *i++;
}
}
if (i != end && *i != '}')
{
throw format_error("invalid format");
}
return i;
}
template <typename FmtContext> constexpr auto format(const Integer &i, FmtContext &ctx) const
{
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));
}
}
};
// Algorithm macros // Algorithm macros
#define PK_LENGTH 25 #define PK_LENGTH 25
#define NULL_TERMINATOR 1 #define NULL_TERMINATOR 1
#define FIELD_BITS 384
#define FIELD_BYTES 48
#define FIELD_BITS_2003 512
#define FIELD_BYTES_2003 64
#define SHA_MSG_LENGTH_XP (4 + 2 * FIELD_BYTES)
#define SHA_MSG_LENGTH_2003 (3 + 2 * FIELD_BYTES_2003)
#define NEXTSNBITS(field, n, offset) (((QWORD)(field) >> (offset)) & ((1ULL << (n)) - 1)) #define NEXTSNBITS(field, n, offset) (((QWORD)(field) >> (offset)) & ((1ULL << (n)) - 1))
#define FIRSTNBITS(field, n) NEXTSNBITS((field), (n), 0) #define FIRSTNBITS(field, n) NEXTSNBITS((field), (n), 0)
#define HIBYTES(field, bytes) NEXTSNBITS((QWORD)(field), ((bytes) * 8), ((bytes) * 8)) #define HIBYTES(field, bytes) NEXTSNBITS((QWORD)(field), ((bytes) * 8), ((bytes) * 8))
#define LOBYTES(field, bytes) FIRSTNBITS((QWORD)(field), ((bytes) * 8)) #define LOBYTES(field, bytes) FIRSTNBITS((QWORD)(field), ((bytes) * 8))
#define BYDWORD(n) (DWORD)(*((n) + 0) | *((n) + 1) << 8 | *((n) + 2) << 16 | *((n) + 3) << 24) #define BYDWORD(n) (DWORD32)(*((n) + 0) | *((n) + 1) << 8 | *((n) + 2) << 16 | *((n) + 3) << 24)
#define BITMASK(n) ((1ULL << (n)) - 1) #define BITMASK(n) ((1ULL << (n)) - 1)
#ifndef LIBUMSKT_VERSION_STRING #ifndef LIBUMSKT_VERSION_STRING
@ -79,7 +130,7 @@ struct UMSKT_Value
union { union {
BOOL boolean; BOOL boolean;
WORD word; WORD word;
DWORD dword; DWORD32 dword;
QWORD qword; QWORD qword;
OWORD oword; OWORD oword;
char *chars; char *chars;
@ -103,37 +154,156 @@ enum UMSKT_TAG
class EXPORT UMSKT class EXPORT UMSKT
{ {
public: public:
static std::FILE *debug; /**
static BOOL VERBOSE; * Convert a std::string to an Integer
static BOOL DEBUG; *
static std::map<UMSKT_TAG, UMSKT_Value> tags; * @param in
* @return
// Hello OpenSSL developers, please tell me, where is this function at? */
static int BN_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen); INLINE static Integer IntegerS(const std::string &in)
static void endian(BYTE *data, int length);
static void DESTRUCT()
{ {
if (debug != nullptr) return Integer(&in[0]);
{
std::fclose(debug);
}
debug = nullptr;
} }
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 Integer to Native type T where T is a concrete type
*
* @tparam T
* @param in
* @param buf
* @return
*/
template <typename T = DWORD32> INLINE static T EncodeN(const Integer &in)
{
T buf;
EncodeN(in, (BYTE *)&buf, sizeof(T));
return buf;
}
/**
* Encode a random number into a Native concrete type
*
* @tparam T
* @return
*/
template <typename T> INLINE static T getRandom()
{ {
T retval; T retval;
RAND_bytes((BYTE *)&retval, sizeof(retval)); rng.GenerateBlock((BYTE *)&retval, sizeof(retval));
return 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 #endif // UMSKT_LIBUMSKT_H

View File

@ -0,0 +1,39 @@
/**
* 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/19/2024
* @Maintainer Neo
*/
#ifndef UMSKT_LIBUMSKT_UNITTEST_H
#define UMSKT_LIBUMSKT_UNITTEST_H
#include <gtest/gtest.h>
#include <libumskt/libumskt.h>
class libumsktUnitTests : public testing::Test
{
public:
libumsktUnitTests()
{
// UMSKT::setVerboseOutput(stderr);
// UMSKT::setDebugOutput(stderr);
}
~libumsktUnitTests() override = default;
};
#endif // UMSKT_LIBUMSKT_UNITTEST_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);
}

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

@ -0,0 +1,72 @@
/**
* 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:
virtual ~PIDGEN() = default;
static const Integer SEVEN;
static const Integer TEN;
static const Integer MaxChannelID;
static const Integer MaxSerial;
virtual BOOL Generate(std::string &pKey)
{
throw std::runtime_error("PIDGEN::Generate() pure virtual function call");
}
virtual BOOL Validate(const std::string &pKey)
{
throw std::runtime_error("PIDGEN::Validate() pure virtual function call");
}
virtual std::string StringifyKey(const std::string &pKey)
{
throw std::runtime_error("PIDGEN::StringifyKey() pure virtual function call");
}
virtual std::string StringifyProductID()
{
throw std::runtime_error("PIDGEN::StringifyProductID() pure virtual function call");
}
virtual BOOL ValidateKeyString(const std::string &in_key, std::string &out_key)
{
throw std::runtime_error("PIDGEN::ValidateKeyString() pure virtual function call");
}
Integer GenerateMod7(const Integer &in);
BOOL isValidMod7(const Integer &in);
};
#endif // UMSKT_PIDGEN_H

View File

@ -22,187 +22,294 @@
#include "PIDGEN2.h" #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 * @param pKey
* @return * @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 = info.ChannelID % MaxChannelID;
info.Serial = info.Serial % MaxSerial;
if (info.isOEM)
{ {
if (input[i] < '0' || input[i] > '9') info.Day = info.Day % Integer(366);
// info.Year = info.Year;
info.OEMID = (info.ChannelID * TEN) + (info.Serial / (MaxSerial / TEN));
info.Serial %= (MaxSerial / TEN);
info.OEMID = (info.OEMID * TEN) + GenerateMod7(info.OEMID);
DWORD32 day = EncodeN(info.Day), year = EncodeN(info.Year), serial = EncodeN(info.Serial),
oemid = EncodeN(info.OEMID);
if (debug)
{ {
return false; 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 = EncodeN(info.ChannelID), serial = EncodeN(info.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);
DWORD32 channelid = EncodeN(info.ChannelID), serial = EncodeN(info.Serial);
if (debug)
{
fmt::print("\n{:03d}-{:07d}\n", channelid, serial);
}
pKey = fmt::format("{:03d}{:07d}", channelid, serial);
} }
return true; 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 * @return
*/ */
int PIDGEN2::addDigits(char *input) BOOL PIDGEN2::Validate(const std::string &pKey)
{ {
int output = 0; std::string filtered;
ValidateKeyString(pKey, filtered);
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));
bIsValidChannelID = isValidChannelID();
bIsValidSerial = isValidSerial();
if (debug)
{
fmt::print("\n\nisValidChannelID: {} isValidSerial: {}\n", bIsValidChannelID, bIsValidSerial);
} }
for (int i = 0; i < strlen(input); i++) 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)
{ {
output += input[i] - '0'; fmt::print("\n\nisValidChannelID: {} isValidSerial: {}\n", bIsValidChannelID, bIsValidSerial);
} }
return output; 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;
*
* @param channelID
* @return
*/
BOOL PIDGEN2::isValidChannelID(char *channelID)
{
if (strlen(channelID) > 3)
{
return false;
}
for (int i = 0; i <= 6; i++) default:
{
if (strcmp(channelID, channelIDBlacklist[i]) != 0)
{
return false; return false;
} }
} }
return true; /**
*
* @param pKey
* @return
*/
std::string PIDGEN2::StringifyKey(const std::string &pKey)
{
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 "";
}
} }
/** /**
* *
* @param OEMID
* @return * @return
*/ */
BOOL PIDGEN2::isValidOEMID(char *OEMID) std::string PIDGEN2::StringifyProductID()
{ {
if (!isNumericString(OEMID)) 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);
}
/**
*
* @param in_key
* @param out_key
* @return
*/
BOOL INLINE PIDGEN2::ValidateKeyString(const std::string &in_key, std::string &out_key)
{
std::copy_if(in_key.begin(), in_key.end(), std::back_inserter(out_key), [](char c) { return std::isdigit(c); });
return out_key.length() == KeySize::FPP || out_key.length() == KeySize::Office || out_key.length() == KeySize::OEM;
}
/**
* 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; return false;
} }
if (strlen(OEMID) > 5) return isValidMod7(info.OEMID);
{
if (OEMID[0] != '0' || OEMID[1] != '0')
{
return false;
}
}
int mod = addDigits(OEMID);
return (mod % 21 == 0);
} }
/** /**
* Is the Channel ID a valid Channel ID?
* also validates Channel ID check digit if applicable
* *
* @param year * Known invalid Channel IDs are:
* @return * 333, 444, 555, 666, 777, 888, 999
*
* @return validity
*/ */
BOOL PIDGEN2::isValidYear(char *year) BOOL PIDGEN2::isValidChannelID() const
{ {
for (int i = 0; i <= 7; i++) // if we're office, do the last digit +1 checksum
if (info.isOffice)
{ {
if (year == validYears[i]) Integer CheckDigit = (info.ChannelID % TEN), ChannelID = (info.ChannelID / TEN);
{
return false;
}
}
return true;
}
/** if (std::find(channelIDDisallowList.begin(), channelIDDisallowList.end(), IntToString(ChannelID)) !=
* channelIDDisallowList.end())
* @param day
* @return
*/
BOOL PIDGEN2::isValidDay(char *day)
{
if (!isNumericString(day))
{ {
return false; return false;
} }
int iDay = std::stoi(day); return (ChannelID % TEN) + 1 == CheckDigit;
if (iDay == 0 || iDay >= 365)
{
return false;
} }
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 productID * Known allowed years are:
* @return * 95, 96, 97, 98, 99, 00, 01, 02
*
* @return validity
*/ */
BOOL PIDGEN2::isValidRetailProductID(char *productID) BOOL PIDGEN2::isValidOEMYear() const
{ {
return true; 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 channelID * Allowed days are 1 - 366 inclusive
* @param keyout
* @return
*/
int PIDGEN2::GenerateRetail(char *channelID, char *&keyout)
{
if (!isValidChannelID(channelID))
{
return 1;
}
return 0;
}
/**
* *
* @param year * @return validity
* @param day
* @param oem
* @param keyout
* @return
*/ */
int PIDGEN2::GenerateOEM(char *year, char *day, char *oem, char *&keyout) BOOL PIDGEN2::isValidOEMDay() const
{ {
if (!isValidOEMID(oem)) return info.Day >= 0 && info.Day <= 366;
{
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 + 1) % 365;
}
_strncpy(keyout, 32, &fmt::format("{}{}-OEM-{}-{}", year, day, oem, oem).c_str()[0], 32);
return 0;
} }

View File

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

View File

@ -0,0 +1,115 @@
/**
* 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/19/2024
* @Maintainer Neo
*/
#include "PIDGEN2.h"
#include <libumskt/libumskt_unittest.h>
/**
* PIDGEN2 must not be an abstract class.
*/
TEST(PIDGEN2, InstantiatePIDGEN2)
{
auto p2 = new PIDGEN2();
ASSERT_NE(p2, nullptr);
delete p2;
}
class TestPIDGEN2 : public libumsktUnitTests
{
protected:
PIDGEN *p;
PIDGEN2 *p2;
PIDGEN2::KeyInfo valid_ki = {false, false, 60, 99, 0, 95, 111111};
void SetUp() override
{
p2 = new PIDGEN2();
}
void TearDown() override
{
if (p != nullptr)
{
delete p;
p = nullptr;
}
if (p2 != nullptr)
{
delete p2;
p2 = nullptr;
}
}
};
TEST_F(TestPIDGEN2, TestStringifyKeyFPP)
{
std::string pKey = "0951111111";
pKey = p2->StringifyKey(pKey);
ASSERT_STRCASEEQ(&pKey[0], "095-1111111");
}
TEST_F(TestPIDGEN2, TestStringifyKeyOffice)
{
std::string pKey = "09561111111";
pKey = p2->StringifyKey(pKey);
ASSERT_STREQ(&pKey[0], "0956-1111111");
}
TEST_F(TestPIDGEN2, TestStringifyKeyOEM)
{
std::string pKey = "06099000951611111";
pKey = p2->StringifyKey(pKey);
ASSERT_STREQ(&pKey[0], "06099-OEM-0009516-11111");
}
TEST_F(TestPIDGEN2, GenerateValidFPPKey)
{
p2->info = valid_ki;
std::string pKey;
p2->Generate(pKey);
pKey = p2->StringifyKey(pKey);
ASSERT_STREQ(&pKey[0], "095-1111111");
}
TEST_F(TestPIDGEN2, GenerateValidOfficeKey)
{
p2->info = valid_ki;
p2->info.isOffice = true;
std::string pKey;
p2->Generate(pKey);
pKey = p2->StringifyKey(pKey);
ASSERT_STREQ(&pKey[0], "0956-1111111");
}
TEST_F(TestPIDGEN2, GenerateValidOEMKey)
{
p2->info = valid_ki;
p2->info.isOEM = true;
std::string pKey;
p2->Generate(pKey);
pKey = p2->StringifyKey(pKey);
ASSERT_STREQ(&pKey[0], "06099-OEM-0009516-11111");
}

View File

@ -24,6 +24,7 @@
* and uploaded to GitHub by TheMCHK in August of 2019 * and uploaded to GitHub by TheMCHK in August of 2019
* *
* Endermanch (Andrew) rewrote the algorithm in May of 2023 * Endermanch (Andrew) rewrote the algorithm in May of 2023
* Neo ported Endermanch's algorithm to CryptoPP in February of 2024
* } * }
*/ */
@ -32,101 +33,138 @@
/** /**
* Packs a Windows XP-like Product Key. * Packs a Windows XP-like Product Key.
* *
* @param pRaw [in] *QWORD[2] raw product key input * @param ki
**/ * @return Integer representation of KeyInfo
BOOL BINK1998::Pack(QWORD *pRaw) */
Integer BINK1998::Pack(const KeyInfo &ki)
{ {
// The quantity of information the key provides is 114 bits. // The quantity of information the key provides is 114 bits.
// We're storing it in 2 64-bit quad-words with 14 trailing bits. // We're storing it in 2 64-bit quad-words with 14 trailing bits.
// 64 * 2 = 128 // 64 * 2 = 128
auto serial = (ki.ChannelID * MaxSerial) + ki.Serial;
// Signature [114..59] <- Hash [58..31] <- Serial [30..1] <- Upgrade [0] // Signature [114..59] <- Hash [58..31] <- Serial [30..1] <- Upgrade [0]
pRaw[0] = FIRSTNBITS(info.Signature, 5) << 59 | FIRSTNBITS(info.Hash, 28) << 31 | info.Serial << 1 | info.isUpgrade; Integer raw = CryptoPP::Crop(ki.Signature, 56) << 59 | CryptoPP::Crop(ki.Hash, 28) << 31 |
pRaw[1] = NEXTSNBITS(info.Signature, 51, 5); CryptoPP::Crop(serial, 30) << 1 | ki.isUpgrade;
return true; if (debug)
{
fmt::print(debug, "pack: {:x}\n\n", raw);
}
return raw;
} }
/** /**
* Unpacks a Windows XP-like Product Key. * Unpacks a Windows XP-like Product Key.
* *
* @param pRaw [out] *QWORD[2] raw product key output * @param raw Integer to unpack
**/ * @return populated PIDGEN3::KeyInfo struct
BOOL BINK1998::Unpack(QWORD *pRaw) */
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. // We're assuming that the quantity of information within the product key is at most 114 bits.
// log2(24^25) = 114. // log2(24^25) = 114.
// Upgrade = Bit 0 // Upgrade = Bit 0
info.isUpgrade = FIRSTNBITS(pRaw[0], 1); ki.isUpgrade = CryptoPP::Crop(raw, 1).ConvertToLong();
// Serial = Bits [1..30] -> 30 bits // Serial = Bits [1..30] -> 30 bits
info.Serial = NEXTSNBITS(pRaw[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 // Hash = Bits [31..58] -> 28 bits
info.Hash = NEXTSNBITS(pRaw[0], 28, 31); ki.Hash = CryptoPP::Crop((raw >> 31), 28);
// Signature = Bits [59..113] -> 56 bits // Signature = Bits [59..113] -> 56 bits
info.Signature = FIRSTNBITS(pRaw[1], 51) << 5 | NEXTSNBITS(pRaw[0], 5, 59); ki.Signature = CryptoPP::Crop((raw >> 59), 56);
return true; return ki;
} }
/** /**
* Generates a Windows XP-like Product Key. * Generates a Windows XP-like Product Key.
* *
* @param pKey [out] * @param pKey [out]
*
* @return true on success, false on fail * @return true on success, false on fail
*/ */
BOOL BINK1998::Generate(std::string &pKey) BOOL BINK1998::Generate(std::string &pKey)
{ {
BN_CTX *numContext = BN_CTX_new(); Integer c, s, pRaw;
SHA1 sha1;
BIGNUM *c = BN_CTX_get(numContext), *s = BN_CTX_get(numContext), *x = BN_CTX_get(numContext), // copy initial state from object
*y = BN_CTX_get(numContext); auto ki = info;
QWORD pRaw[2]; if (!ki.Rand.IsZero())
{
c = ki.Rand;
}
// Data segment of the RPK. // Data segment of the RPK.
DWORD 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 // prepare the private key for generation
BN_sub(privateKey, genOrder, privateKey); privateKey = genOrder - privateKey;
do do
{ {
EC_POINT *r = EC_POINT_new(eCurve); ECP::Point R;
if (ki.Rand.IsZero())
{
// Generate a random number c consisting of 384 bits without any constraints. // Generate a random number c consisting of 384 bits without any constraints.
BN_rand(c, FIELD_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); c.Randomize(UMSKT::rng, FieldBits);
}
// Pick a random derivative of the base point on the elliptic curve. // Pick a random derivative of the base point on the elliptic curve.
// R = cG; // R = cG;
EC_POINT_mul(eCurve, r, nullptr, genPoint, c, numContext); 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. // Acquire its coordinates.
// x = R.x; y = R.y; // x = R.x; y = R.y;
EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext);
BYTE msgDigest[SHA_DIGEST_LENGTH], msgBuffer[SHA_MSG_LENGTH_XP]; BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
BYTE xBin[FIELD_BYTES], yBin[FIELD_BYTES];
// Convert coordinates to bytes.
UMSKT::BN_bn2lebin(x, xBin, FIELD_BYTES);
UMSKT::BN_bn2lebin(y, yBin, FIELD_BYTES);
// Assemble the SHA message. // Assemble the SHA message.
memcpy(&msgBuffer[0], &pData, 4); pMsgBuffer = EncodeN(pData, pMsgBuffer, 4);
memcpy(&msgBuffer[4], xBin, FIELD_BYTES); pMsgBuffer = EncodeN(R.x, pMsgBuffer, FieldBytes);
memcpy(&msgBuffer[4 + FIELD_BYTES], yBin, FIELD_BYTES); EncodeN(R.y, pMsgBuffer, FieldBytes);
// pHash = SHA1(pSerial || R.x || R.y) // pHash = SHA1(pSerial || R.x || R.y)
SHA1(msgBuffer, SHA_MSG_LENGTH_XP, 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. // Translate the byte digest into a 32-bit integer - this is our computed pHash.
// Truncate the pHash to 28 bits. // Truncate the pHash to 28 bits.
info.Hash = BYDWORD(msgDigest) >> 4 & BITMASK(28);
ki.Hash = IntegerN(msgDigest, 4) >> 4;
ki.Hash = CryptoPP::Crop(ki.Hash, 28);
/* /*
* *
@ -148,37 +186,38 @@ BOOL BINK1998::Generate(std::string &pKey)
*/ */
// s = ek; // s = ek;
BN_copy(s, privateKey); s = privateKey * ki.Hash;
BN_mul_word(s, info.Hash);
// s += c (mod n) // s += c (mod n)
BN_mod_add(s, s, c, genOrder, numContext); s += c;
s %= genOrder;
// Translate resulting scalar into a 64-bit integer (the byte order is little-endian). // Translate resulting scalar into an Integer.
BN_bn2lebinpad(s, (BYTE *)&info.Signature, BN_num_bytes(s)); ki.Signature = s;
// Pack product key. // Pack product key.
Pack(pRaw); pRaw = Pack(ki);
auto serial = fmt::format("{:d}", info.Serial); if (verbose)
fmt::print(UMSKT::debug, "Generation results:\n"); {
fmt::print(UMSKT::debug, "{:>10}: {:b}\n", "Upgrade", (bool)info.isUpgrade); fmt::print(verbose, "Generation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Channel ID", serial.substr(0, 3)); fmt::print(verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Sequence", serial.substr(3)); fmt::print(verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Hash", info.Hash); fmt::print(verbose, "{:>10}: {}\n", "Sequence", ki.Serial);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Signature", info.Signature); fmt::print(verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(UMSKT::debug, "\n"); fmt::print(verbose, "{:>10}: {:x}\n", "Signature", ki.Signature);
fmt::print(verbose, "\n");
}
EC_POINT_free(r); } while (ki.Signature.BitCount() > 55 && ki.Rand.IsZero());
} while (info.Signature > BITMASK(55));
// ↑ ↑ ↑ // ↑ ↑ ↑
// The signature can't be longer than 55 bits, else it will // The signature can't be longer than 55 bits, else it will
// make the CD-key longer than 25 characters. // make the CD-key longer than 25 characters.
// Convert bytecode to Base24 CD-key. // Convert bytecode to Base24 CD-key.
base24(pKey, (BYTE *)pRaw); pKey = base24(pRaw);
BN_CTX_free(numContext); info = ki;
return true; return true;
} }
@ -190,33 +229,33 @@ BOOL BINK1998::Generate(std::string &pKey)
* *
* @return true if provided key validates against loaded curve * @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) if (pKey.length() != 25)
{ {
return false; return false;
} }
BN_CTX *numContext = BN_CTX_new();
QWORD pRaw[2];
// Convert Base24 CD-key to bytecode. // Convert Base24 CD-key to bytecode.
unbase24((BYTE *)pRaw, pKey); Integer pRaw = unbase24(pKey);
SHA1 sha1;
// Extract RPK, hash and signature from bytecode. // Extract RPK, hash and signature from bytecode.
Unpack(pRaw); KeyInfo ki = Unpack(pRaw);
auto serial = fmt::format("{:d}", info.Serial); if (verbose)
fmt::print(UMSKT::debug, "Validation results:\n"); {
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Upgrade", (bool)info.isUpgrade); fmt::print(verbose, "Validation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Channel ID", serial.substr(0, 3)); fmt::print(verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {}\n", "Sequence", serial.substr(3)); fmt::print(verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Hash", info.Hash); fmt::print(verbose, "{:>10}: {}\n", "Sequence", ki.Serial);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Signature", info.Signature); fmt::print(verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(UMSKT::debug, "\n"); fmt::print(verbose, "{:>10}: {:x}\n", "Signature", ki.Signature);
fmt::print(verbose, "\n");
}
DWORD pData = info.Serial << 1 | info.isUpgrade; Integer serialPack = (ki.ChannelID * MaxSerial) + ki.Serial;
Integer pData = serialPack << 1 | ki.isUpgrade;
/* /*
* *
@ -233,52 +272,49 @@ BOOL BINK1998::Validate(std::string &pKey)
* *
*/ */
BIGNUM *e = BN_lebin2bn((BYTE *)&info.Hash, sizeof(info.Hash), nullptr), Integer e = ki.Hash, s = ki.Signature;
*s = BN_lebin2bn((BYTE *)&info.Signature, sizeof(info.Signature), nullptr);
BIGNUM *x = BN_CTX_get(numContext), *y = BN_CTX_get(numContext);
// Create 2 points on the elliptic curve. // Create 2 points on the elliptic curve.
EC_POINT *t = EC_POINT_new(eCurve), *p = EC_POINT_new(eCurve); ECP::Point t, P;
// t = sG // t = sG
EC_POINT_mul(eCurve, t, nullptr, genPoint, s, numContext); t = eCurve.Multiply(s, genPoint);
// P = eK // P = eK
EC_POINT_mul(eCurve, p, nullptr, pubPoint, e, numContext); P = eCurve.Multiply(e, pubPoint);
// P += t // P += t
EC_POINT_add(eCurve, p, t, p, numContext); P = eCurve.Add(P, t);
// x = P.x; y = P.y; if (debug)
EC_POINT_get_affine_coordinates(eCurve, p, x, y, numContext); {
fmt::print(debug, "P[x,y]: [{:x},\n{:x}]\n\n", P.x, P.y);
}
BYTE msgDigest[SHA_DIGEST_LENGTH], msgBuffer[SHA_MSG_LENGTH_XP], xBin[FIELD_BYTES], yBin[FIELD_BYTES]; BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
// Convert resulting point coordinates to bytes. // Convert resulting point coordinates to bytes.
UMSKT::BN_bn2lebin(x, xBin, FIELD_BYTES);
UMSKT::BN_bn2lebin(y, yBin, FIELD_BYTES);
// Assemble the SHA message. // Assemble the SHA message.
memcpy(&msgBuffer[0], &pData, 4); pMsgBuffer = EncodeN(pData, pMsgBuffer, 4);
memcpy(&msgBuffer[4], xBin, FIELD_BYTES); pMsgBuffer = EncodeN(P.x, pMsgBuffer, FieldBytes);
memcpy(&msgBuffer[4 + FIELD_BYTES], yBin, FIELD_BYTES); EncodeN(P.y, pMsgBuffer, FieldBytes);
// compHash = SHA1(pSerial || P.x || P.y) // compHash = SHA1(pSerial || P.x || P.y)
SHA1(msgBuffer, SHA_MSG_LENGTH_XP, 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. // Truncate the hash to 28 bits.
DWORD compHash = BYDWORD(msgDigest) >> 4 & BITMASK(28); Integer compHash = CryptoPP::Crop(intDigest >> 4, 28);
BN_free(e);
BN_free(s);
BN_CTX_free(numContext);
EC_POINT_free(t);
EC_POINT_free(p);
// If the computed hash checks out, the key is valid. // If the computed hash checks out, the key is valid.
return compHash == info.Hash; return compHash == ki.Hash;
} }

View File

@ -29,6 +29,7 @@ class EXPORT BINK1998 : public PIDGEN3
{ {
public: public:
using PIDGEN3::PIDGEN3; using PIDGEN3::PIDGEN3;
BINK1998() = default;
explicit BINK1998(PIDGEN3 *p3) explicit BINK1998(PIDGEN3 *p3)
{ {
privateKey = p3->privateKey; privateKey = p3->privateKey;
@ -38,17 +39,23 @@ class EXPORT BINK1998 : public PIDGEN3
eCurve = p3->eCurve; eCurve = p3->eCurve;
} }
~BINK1998() override = default;
static constexpr DWORD32 FieldBits = (48 * 8);
static constexpr DWORD32 FieldBytes = (FieldBits / 8);
static constexpr DWORD32 SHAMessageLength = (4 + 2 * FieldBytes);
using PIDGEN3::Pack; using PIDGEN3::Pack;
BOOL Pack(QWORD *pRaw) override; Integer Pack(const KeyInfo &ki) override;
using PIDGEN3::Unpack; using PIDGEN3::Unpack;
BOOL Unpack(QWORD *pRaw) override; KeyInfo Unpack(const Integer &raw) override;
using PIDGEN3::Generate; using PIDGEN3::Generate;
BOOL Generate(std::string &pKey) override; BOOL Generate(std::string &pKey) override;
using PIDGEN3::Validate; using PIDGEN3::Validate;
BOOL Validate(std::string &pKey) override; BOOL Validate(const std::string &pKey) override;
}; };
#endif // UMSKT_BINK1998_H #endif // UMSKT_BINK1998_H

View File

@ -0,0 +1,110 @@
/**
* 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/19/2024
* @Maintainer Neo
*/
#include "../libumskt_unittest.h"
#include "BINK1998.h"
/**
* BINK1998 must not be an abstract class.
*/
TEST(PIDGEN3_BINK1998, InstantiateBINK1998)
{
auto p3 = new BINK1998();
ASSERT_NE(p3, nullptr);
delete p3;
}
class TestBINK1998 : public libumsktUnitTests
{
protected:
PIDGEN *p;
PIDGEN3 *p3;
BINK1998 *bink1998;
BINK1998::KeyInfo valid_ki = {false, false, 640, 111111, 0};
void SetUp() override
{
bink1998 = new BINK1998();
bink1998->LoadEllipticCurve("0x2E",
"2260481414313563299067995668434431120981995280321627195247220485552475627515144045"
"6421260165232069708317717961315241",
"1", "0",
"1091074492220651278115691316907175015302838688467620894706280834607253141127048943"
"2930252839559606812441712224597826",
"1917099366991720451749161800061981867915210969017264186834961288993048036527467509"
"6509477191800826190959228181870174",
"1439923035396364333971294001595406158106423983592682351741971676961393703934682226"
"9422480779920783799484349086780408",
"5484731395987446993229594927733430043632089703338918322171291299699820472711849119"
"800714736923107362018017833200634",
"61760995553426173", "37454031876727861");
bink1998->info = valid_ki;
}
void TearDown() override
{
if (bink1998 != nullptr)
{
delete bink1998;
bink1998 = nullptr;
}
if (p3 != nullptr)
{
delete p3;
p3 = nullptr;
}
if (p != nullptr)
{
p = nullptr;
}
}
};
TEST_F(TestBINK1998, ValidateValidKeyString)
{
std::string pKey;
auto ValidateKeyString = bink1998->ValidateKeyString("7KWK7-9W7H4-T64D6-DB8V7-BW7MW", pKey);
ASSERT_TRUE(ValidateKeyString);
ASSERT_STREQ(&pKey[0], "7KWK79W7H4T64D6DB8V7BW7MW");
}
TEST_F(TestBINK1998, ValidateValidKey)
{
std::string pKey = "7KWK79W7H4T64D6DB8V7BW7MW";
auto Validate = bink1998->Validate(pKey);
ASSERT_TRUE(Validate);
}
TEST_F(TestBINK1998, GenerateValidKey)
{
bink1998->info.Rand = UMSKT::IntegerS("3427338792529164195109698841758932126727183763685994848291878088992924483488"
"5768429236548372772607190036626858221847");
std::string pKey;
bink1998->Generate(pKey);
pKey = bink1998->StringifyKey(pKey);
ASSERT_STREQ(&pKey[0], "7KWK7-9W7H4-T64D6-DB8V7-BW7MW");
}

View File

@ -24,6 +24,7 @@
* and uploaded to GitHub by TheMCHK in August of 2019 * and uploaded to GitHub by TheMCHK in August of 2019
* *
* Endermanch (Andrew) rewrote the algorithm in May of 2023 * Endermanch (Andrew) rewrote the algorithm in May of 2023
* Neo ported Endermanch's algorithm to CryptoPP in February of 2024
* } * }
*/ */
@ -32,45 +33,53 @@
/** /**
* Packs a Windows Server 2003-like Product Key. * Packs a Windows Server 2003-like Product Key.
* *
* @param pRaw *QWORD[2] raw product key output * @param ki PIDGEN3::KeyInfo struct to pack
**/ * @return Integer representation of the Product Key
BOOL BINK2002::Pack(QWORD *pRaw) */
Integer BINK2002::Pack(const KeyInfo &ki)
{ {
// AuthInfo [113..104] <- Signature [103..42] <- Hash [41..11] <- Channel ID [10..1] <- Upgrade [0] // AuthInfo [113..104] <- Signature [103..42] <- Hash [41..11] <- Channel ID [10..1] <- Upgrade [0];
pRaw[0] = FIRSTNBITS(info.Signature, 22) << 42 | (QWORD)info.Hash << 11 | info.ChannelID << 1 | info.isUpgrade; Integer raw = CryptoPP::Crop(ki.AuthInfo, 10) << 104 | CryptoPP::Crop(ki.Signature, 62) << 42 |
pRaw[1] = FIRSTNBITS(info.AuthInfo, 10) << 40 | NEXTSNBITS(info.Signature, 40, 22); CryptoPP::Crop(ki.Hash, 31) << 11 | CryptoPP::Crop(ki.ChannelID, 10) << 1 | ki.isUpgrade;
return true; if (debug)
{
fmt::print(debug, "pack: {:x}\n\n", raw);
}
return raw;
} }
/** /**
* Unpacks a Windows Server 2003-like Product Key. * Unpacks a Windows Server 2003-like Product Key.
* *
* @param pRaw *QWORD[2] raw product key input * @param raw Integer representation of the product key
**/ * @return unpacked PIDGEN3::KeyInfo struct
BOOL BINK2002::Unpack(QWORD *pRaw) */
BINK2002::KeyInfo BINK2002::Unpack(const Integer &raw)
{ {
// We're assuming that the quantity of information within the product key is at most 114 bits. // We're assuming that the quantity of information within the product key is at most 114 bits.
// log2(24^25) = 114. // log2(24^25) = 114.
KeyInfo ki;
// Upgrade = Bit 0 // Upgrade = Bit 0
info.isUpgrade = FIRSTNBITS(pRaw[0], 1); ki.isUpgrade = CryptoPP::Crop(raw, 1).ConvertToLong();
// Channel ID = Bits [1..10] -> 10 bits // Channel ID = Bits [1..10] -> 10 bits
info.ChannelID = NEXTSNBITS(pRaw[0], 10, 1); ki.ChannelID = CryptoPP::Crop(raw >> 1, 10);
// Hash = Bits [11..41] -> 31 bits // Hash = Bits [11..41] -> 30 bits
info.Hash = NEXTSNBITS(pRaw[0], 31, 11); ki.Hash = CryptoPP::Crop(raw >> 11, 31);
// Signature = Bits [42..103] -> 62 bits // Signature = Bits [42..103] -> 62 bits
// The quad-word signature overlaps AuthInfo in bits 104 and 105, // The quad-word signature overlaps AuthInfo in bits 104 and 105,
// hence Microsoft employs a secret technique called: Signature = HIDWORD(Signature) >> 2 | LODWORD(Signature) // hence Microsoft employs a secret technique called: Signature = HIDWORD(Signature) >> 2 | LODWORD(Signature)
info.Signature = NEXTSNBITS(pRaw[1], 30, 10) << 32 | FIRSTNBITS(pRaw[1], 10) << 22 | NEXTSNBITS(pRaw[0], 22, 42); ki.Signature = CryptoPP::Crop(raw >> 42, 62);
// AuthInfo = Bits [104..113] -> 10 bits // AuthInfo = Bits [104..113] -> 10 bits
info.AuthInfo = NEXTSNBITS(pRaw[1], 10, 40); ki.AuthInfo = CryptoPP::Crop(raw >> 104, 10);
return true; return ki;
} }
/** /**
@ -82,77 +91,143 @@ BOOL BINK2002::Unpack(QWORD *pRaw)
*/ */
BOOL BINK2002::Generate(std::string &pKey) BOOL BINK2002::Generate(std::string &pKey)
{ {
BN_CTX *numContext = BN_CTX_new(); // copy the starting state from the class
KeyInfo ki = info;
SHA1 sha1;
BIGNUM *c = BN_CTX_get(numContext), *e = BN_CTX_get(numContext), *s = BN_CTX_get(numContext), Integer c, e, s, pRaw;
*x = BN_CTX_get(numContext), *y = BN_CTX_get(numContext);
QWORD pRaw[2]; if (!ki.Rand.IsZero())
{
c = ki.Rand;
}
// Data segment of the RPK. // Data segment of the RPK.
DWORD pData = info.ChannelID << 1 | info.isUpgrade; Integer pData = ki.ChannelID << 1 | ki.isUpgrade;
BOOL noSquare; BOOL noSquare;
do do
{ {
EC_POINT *r = EC_POINT_new(eCurve); ECP::Point R;
if (ki.Rand.IsZero())
{
// Generate a random number c consisting of 512 bits without any constraints. // Generate a random number c consisting of 512 bits without any constraints.
BN_rand(c, FIELD_BITS_2003, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); c.Randomize(UMSKT::rng, FieldBits);
}
// R = cG // R = cG
EC_POINT_mul(eCurve, r, nullptr, genPoint, c, numContext); 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. BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
// x = R.x; y = R.y;
EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext);
BYTE msgDigest[SHA_DIGEST_LENGTH], msgBuffer[SHA_MSG_LENGTH_2003], xBin[FIELD_BYTES_2003],
yBin[FIELD_BYTES_2003];
// Convert resulting point coordinates to bytes.
UMSKT::BN_bn2lebin(x, xBin, FIELD_BYTES_2003);
UMSKT::BN_bn2lebin(y, yBin, FIELD_BYTES_2003);
// Assemble the first SHA message. // Assemble the first SHA message.
msgBuffer[0x00] = 0x79; *pMsgBuffer = 0x79;
msgBuffer[0x01] = (pData & 0x00FF); pMsgBuffer++;
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
memcpy(&msgBuffer[3], xBin, FIELD_BYTES_2003); pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
memcpy(&msgBuffer[3 + FIELD_BYTES_2003], yBin, FIELD_BYTES_2003);
// Convert resulting point coordinates to bytes.
// and flip the endianness
pMsgBuffer = EncodeN(R.x, pMsgBuffer, FieldBytes);
EncodeN(R.y, pMsgBuffer, FieldBytes);
// pHash = SHA1(79 || Channel ID || R.x || R.y) // pHash = SHA1(79 || Channel ID || R.x || R.y)
SHA1(msgBuffer, SHA_MSG_LENGTH_2003, msgDigest); sha1.CalculateDigest(msgDigest, msgBuffer, SHAMessageLength);
// Translate the byte digest into a 32-bit integer - this is our computed hash. if (debug)
{
fmt::print(debug, "msgBuffer[1]: ");
for (BYTE b : msgBuffer)
{
fmt::print(debug, "{:x}", b);
}
fmt::print(debug, "\n\n");
fmt::print(debug, "msgDigest[1]: ");
for (BYTE b : msgDigest)
{
fmt::print(debug, "{:x}", b);
}
fmt::print(debug, "\n\n");
}
// Translate the byte sha1 into a 32-bit integer - this is our computed hash.
// Truncate the hash to 31 bits. // 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(verbose, "truncated buffer: ");
for (BYTE b : buf)
{
fmt::print(verbose, "{:x}", b);
}
fmt::print(verbose, "\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(verbose, "h0,1: {:x} {:x}\n\n", h0, h1);
ki.Serial = IntegerN(h1);
fmt::print(verbose, "serial: {:d}\n\n", ki.Serial);
}
// Assemble the second SHA message. // Assemble the second SHA message.
pMsgBuffer = msgBuffer;
msgBuffer[0x00] = 0x5D; msgBuffer[0x00] = 0x5D;
msgBuffer[0x01] = (pData & 0x00FF); pMsgBuffer++;
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
msgBuffer[0x03] = (info.Hash & 0x000000FF); pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
msgBuffer[0x04] = (info.Hash & 0x0000FF00) >> 8; pMsgBuffer = EncodeN(ki.Hash, pMsgBuffer, 4);
msgBuffer[0x05] = (info.Hash & 0x00FF0000) >> 16; pMsgBuffer = EncodeN(ki.AuthInfo, pMsgBuffer, 2);
msgBuffer[0x06] = (info.Hash & 0xFF000000) >> 24;
msgBuffer[0x07] = (info.AuthInfo & 0x00FF); *pMsgBuffer = 0x00;
msgBuffer[0x08] = (info.AuthInfo & 0xFF00) >> 8; pMsgBuffer++;
msgBuffer[0x09] = 0x00;
msgBuffer[0x0A] = 0x00; *pMsgBuffer = 0x00;
pMsgBuffer++;
// newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00) // newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00)
SHA1(msgBuffer, 11, 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(debug, "msgBuffer[2]: ");
for (BYTE b : msgBuffer)
{
fmt::print(debug, "{:x}", b);
}
fmt::print(debug, "\n\n");
fmt::print(debug, "msgDigest[2]: ");
for (BYTE b : msgDigest)
{
fmt::print(debug, "{:x}", b);
}
fmt::print(debug, "\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 // 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). // bits (per spec).
QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest); QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
BN_lebin2bn((BYTE *)&iSignature, sizeof(iSignature), e);
/* /*
* *
* Scalars: * Scalars:
@ -183,63 +258,62 @@ BOOL BINK2002::Generate(std::string &pKey)
*/ */
// e = ek (mod n) // e = ek (mod n)
BN_mod_mul(e, e, privateKey, genOrder, numContext); e = CryptoPP::ModularMultiplication(IntegerN(iSignature), privateKey, genOrder);
// s = e
BN_copy(s, e);
// s = (ek (mod n))² // s = (ek (mod n))²
BN_mod_sqr(s, s, genOrder, numContext); s = CryptoPP::ModularExponentiation(e, Integer::Two(), genOrder);
// c *= 4 (c <<= 2) // c *= 4 (c <<= 2)
BN_lshift(c, c, 2); c *= 4;
// s += c // s += c
BN_add(s, s, c); s += c;
// Around half of numbers modulo a prime are not squares -> BN_sqrt_mod fails about half of the times, // Around half of numbers modulo a prime are not squares -> BN_sqrt_mod fails about half of the times,
// hence if BN_sqrt_mod returns NULL, we need to restart with a different seed. // hence if BN_sqrt_mod returns NULL, we need to restart with a different seed.
// s = √((ek)² + 4c (mod n)) // s = √((ek)² + 4c (mod n))
noSquare = BN_mod_sqrt(s, s, genOrder, numContext) == nullptr; s = CryptoPP::ModularSquareRoot(s, genOrder);
noSquare = s.IsZero();
// s = -ek + √((ek)² + 4c) (mod n) // s = -ek + √((ek)² + 4c) (mod n)
BN_mod_sub(s, s, e, genOrder, numContext); s -= e;
s %= genOrder;
// If s is odd, add order to it. // If s is odd, add order to it.
// The order is a prime, so it can't be even. // The order is a prime, so it can't be even.
if (BN_is_odd(s)) if (s % Integer::Two() != 0)
{ {
// s = -ek + √((ek)² + 4c) + n // s = -ek + √((ek)² + 4c) + n
BN_add(s, s, genOrder); s += genOrder;
} }
// s /= 2 (s >>= 1) // s /= 2 (s >>= 1)
BN_rshift1(s, s); s /= 2;
// Translate resulting scalar into a 64-bit integer (the byte order is little-endian). // Translate resulting scalar into a 64-bit integer (the byte order is little-endian).
BN_bn2lebinpad(s, (BYTE *)&info.Signature, BN_num_bytes(s)); ki.Signature = s;
// Pack product key. // Pack product key.
Pack(pRaw); pRaw = Pack(ki);
fmt::print(UMSKT::debug, "Generation results:\n"); if (verbose)
fmt::print(UMSKT::debug, "{:>10}: {:b}\n", "Upgrade", (bool)info.isUpgrade); {
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Channel ID", info.ChannelID); fmt::print(verbose, "Generation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Hash", info.Hash); fmt::print(verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Signature", info.Signature); fmt::print(verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "AuthInfo", info.AuthInfo); fmt::print(verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(UMSKT::debug, "\n"); fmt::print(verbose, "{:>10}: {:x}\n", "Signature", ki.Signature);
fmt::print(verbose, "{:>10}: {:x}\n", "AuthInfo", ki.AuthInfo);
EC_POINT_free(r); fmt::print(verbose, "\n");
} while (info.Signature > BITMASK(62) || noSquare); }
} while ((ki.Signature.BitCount() > 62 || noSquare) && ki.Rand.IsZero());
// ↑ ↑ ↑ // ↑ ↑ ↑
// The signature can't be longer than 62 bits, else it will // The signature can't be longer than 62 bits, else it will
// overlap with the AuthInfo segment next to it. // overlap with the AuthInfo segment next to it.
// Convert bytecode to Base24 CD-key. // Convert bytecode to Base24 CD-key.
base24(pKey, (BYTE *)pRaw); pKey = base24(pRaw);
BN_CTX_free(numContext); info = ki;
return true; return true;
} }
@ -249,47 +323,56 @@ BOOL BINK2002::Generate(std::string &pKey)
* *
* @param pKey * @param pKey
**/ **/
BOOL BINK2002::Validate(std::string &pKey) BOOL BINK2002::Validate(const std::string &pKey)
{ {
BN_CTX *context = BN_CTX_new(); Integer pRaw;
SHA1 sha1;
QWORD bKey[2];
// Convert Base24 CD-key to bytecode. // Convert Base24 CD-key to bytecode.
unbase24((BYTE *)bKey, &pKey[0]); pRaw = unbase24(pKey);
// Extract product key segments from bytecode. // Extract product key segments from bytecode.
Unpack(bKey); KeyInfo ki = Unpack(pRaw);
DWORD pData = info.ChannelID << 1 | info.isUpgrade; Integer pData = ki.ChannelID << 1 | ki.isUpgrade;
fmt::print(UMSKT::debug, "Validation results:\n"); if (verbose)
fmt::print(UMSKT::debug, "{:>10}: {:b}\n", "Upgrade", (bool)info.isUpgrade); {
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Channel ID", info.ChannelID); fmt::print(verbose, "Validation results:\n");
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Hash", info.Hash); fmt::print(verbose, "{:>10}: {}\n", "Upgrade", (bool)ki.isUpgrade);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "Signature", info.Signature); fmt::print(verbose, "{:>10}: {}\n", "Channel ID", ki.ChannelID);
fmt::print(UMSKT::debug, "{:>10}: {:d}\n", "AuthInfo", info.AuthInfo); fmt::print(verbose, "{:>10}: {:x}\n", "Hash", ki.Hash);
fmt::print(UMSKT::debug, "\n"); 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_DIGEST_LENGTH], msgBuffer[SHA_MSG_LENGTH_2003], xBin[FIELD_BYTES_2003], yBin[FIELD_BYTES_2003]; BYTE msgDigest[SHA1::DIGESTSIZE], msgBuffer[SHAMessageLength], *pMsgBuffer = msgBuffer;
// Assemble the first SHA message. // Assemble the first SHA message.
msgBuffer[0x00] = 0x5D; msgBuffer[0x00] = 0x5D;
msgBuffer[0x01] = (pData & 0x00FF); pMsgBuffer++;
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
msgBuffer[0x03] = (info.Hash & 0x000000FF); pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
msgBuffer[0x04] = (info.Hash & 0x0000FF00) >> 8; pMsgBuffer = EncodeN(ki.Hash, pMsgBuffer, 4);
msgBuffer[0x05] = (info.Hash & 0x00FF0000) >> 16; pMsgBuffer = EncodeN(ki.AuthInfo, pMsgBuffer, 2);
msgBuffer[0x06] = (info.Hash & 0xFF000000) >> 24;
msgBuffer[0x07] = (info.AuthInfo & 0x00FF); *pMsgBuffer = 0x00;
msgBuffer[0x08] = (info.AuthInfo & 0xFF00) >> 8; pMsgBuffer++;
msgBuffer[0x09] = 0x00;
msgBuffer[0x0A] = 0x00; *pMsgBuffer = 0x00;
pMsgBuffer++;
// newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00) // newSignature = SHA1(5D || Channel ID || Hash || AuthInfo || 00 00)
SHA1(msgBuffer, 11, 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 // 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). // (per spec).
QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest); QWORD iSignature = NEXTSNBITS(BYDWORD(&msgDigest[4]), 30, 2) << 32 | BYDWORD(msgDigest);
@ -308,56 +391,52 @@ BOOL BINK2002::Validate(std::string &pKey)
* P = s(sG + eK) * P = s(sG + eK)
* *
*/ */
Integer e = IntegerN(iSignature), s = ki.Signature;
BIGNUM *e = BN_lebin2bn((BYTE *)&iSignature, sizeof(iSignature), nullptr),
*s = BN_lebin2bn((BYTE *)&info.Signature, sizeof(info.Signature), nullptr);
BIGNUM *x = BN_CTX_get(context), *y = BN_CTX_get(context);
// Create 2 points on the elliptic curve. // Create 2 points on the elliptic curve.
EC_POINT *p = EC_POINT_new(eCurve), *t = EC_POINT_new(eCurve); ECP::Point P, t;
// t = sG // t = sG
EC_POINT_mul(eCurve, t, nullptr, genPoint, s, context); t = eCurve.Multiply(s, genPoint);
// p = eK // P = eK
EC_POINT_mul(eCurve, p, nullptr, pubPoint, e, context); P = eCurve.Multiply(e, pubPoint);
// p += t // P += t
EC_POINT_add(eCurve, p, t, p, context); P = eCurve.Add(P, t);
// p *= s // P *= s
EC_POINT_mul(eCurve, p, nullptr, p, s, context); P = eCurve.Multiply(s, P);
// x = p.x; y = p.y; if (debug)
EC_POINT_get_affine_coordinates(eCurve, p, x, y, context); {
fmt::print(debug, "P[x,y]: [{:x},\n{:x}]\n\n", P.x, P.y);
// Convert resulting point coordinates to bytes. }
UMSKT::BN_bn2lebin(x, xBin, FIELD_BYTES_2003);
UMSKT::BN_bn2lebin(y, yBin, FIELD_BYTES_2003);
// Assemble the second SHA message. // Assemble the second SHA message.
pMsgBuffer = msgBuffer;
msgBuffer[0x00] = 0x79; msgBuffer[0x00] = 0x79;
msgBuffer[0x01] = (pData & 0x00FF); pMsgBuffer++;
msgBuffer[0x02] = (pData & 0xFF00) >> 8;
memcpy((void *)&msgBuffer[3], (void *)xBin, FIELD_BYTES_2003); pMsgBuffer = EncodeN(pData, pMsgBuffer, 2);
memcpy((void *)&msgBuffer[3 + FIELD_BYTES_2003], (void *)yBin, FIELD_BYTES_2003); pMsgBuffer = EncodeN(P.x, pMsgBuffer, FieldBytes);
EncodeN(P.y, pMsgBuffer, FieldBytes);
// compHash = SHA1(79 || Channel ID || p.x || p.y) // compHash = SHA1(79 || Channel ID || P.x || P.y)
SHA1(msgBuffer, SHA_MSG_LENGTH_2003, 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 2: {:x}\n\n", intDigest);
}
// Translate the byte sha1 into a 32-bit integer - this is our computed hash.
// Truncate the hash to 31 bits. // Truncate the hash to 31 bits.
DWORD compHash = BYDWORD(msgDigest) & BITMASK(31); Integer compHash = CryptoPP::Crop(intDigest, 31);
BN_free(s); info = ki;
BN_free(e);
EC_POINT_free(p);
EC_POINT_free(t);
BN_CTX_free(context);
// If the computed hash checks out, the key is valid. // If the computed hash checks out, the key is valid.
return compHash == info.Hash; return compHash == ki.Hash;
} }

View File

@ -29,6 +29,7 @@ class EXPORT BINK2002 : public PIDGEN3
{ {
public: public:
using PIDGEN3::PIDGEN3; using PIDGEN3::PIDGEN3;
BINK2002() = default;
explicit BINK2002(PIDGEN3 *p3) explicit BINK2002(PIDGEN3 *p3)
{ {
privateKey = p3->privateKey; privateKey = p3->privateKey;
@ -38,17 +39,23 @@ class EXPORT BINK2002 : public PIDGEN3
eCurve = p3->eCurve; eCurve = p3->eCurve;
} }
~BINK2002() override = default;
static constexpr DWORD32 FieldBits = (64 * 8);
static constexpr DWORD32 FieldBytes = (FieldBits / 8);
static constexpr DWORD32 SHAMessageLength = (3 + 2 * FieldBytes);
using PIDGEN3::Pack; using PIDGEN3::Pack;
BOOL Pack(QWORD *pRaw) override; Integer Pack(const KeyInfo &ki) override;
using PIDGEN3::Unpack; using PIDGEN3::Unpack;
BOOL Unpack(QWORD *pRaw) override; KeyInfo Unpack(const Integer &raw) override;
using PIDGEN3::Generate; using PIDGEN3::Generate;
BOOL Generate(std::string &pKey) override; BOOL Generate(std::string &pKey) override;
using PIDGEN3::Validate; using PIDGEN3::Validate;
BOOL Validate(std::string &pKey) override; BOOL Validate(const std::string &pKey) override;
}; };
#endif // UMSKT_BINK2002_H #endif // UMSKT_BINK2002_H

View File

@ -0,0 +1,121 @@
/**
* 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/19/2024
* @Maintainer Neo
*/
#include "BINK2002.h"
#include <libumskt/libumskt_unittest.h>
/**
* BINK2002 must not be an abstract class.
*/
TEST(PIDGEN3_BINK2002, InstantiateBINK2002)
{
auto p3 = new BINK2002();
ASSERT_NE(p3, nullptr);
delete p3;
}
class TestBINK2002 : public libumsktUnitTests
{
protected:
PIDGEN *p;
PIDGEN3 *p3;
BINK2002 *bink2002;
BINK2002::KeyInfo valid_ki = {false, false, 640, 0, 701};
void SetUp() override
{
bink2002 = new BINK2002();
bink2002->LoadEllipticCurve("0x54",
"1250964251969733259611431105354461862074700938981465222536952118871017192617497641"
"9995384745134703589248167610052719613586668754176591418831031596093374569",
"1", "0",
"8059057663701168311917532277618827622978515614146963913097592614451721430413021070"
"395782723330339842826599481063797559797462512297834269467666807971588275",
"1223930383017475319177970597922037862339473226753699711562597963240231208768364492"
"7405756146495100825573682155171145924668759419114616275413724686284123408",
"4895832170509729140211911021638266775170167022247175324972987673313207244495397975"
"379010973250279668424167408883454560376269866102669741515127286188717976",
"5846013328426281815512452704859777850382010968846722453046994319336479079120767834"
"777937190955827245502389471872759584209649693396095099112777776298051208",
"5622613991231344109", "1285511085175426271");
bink2002->info = valid_ki;
}
void TearDown() override
{
if (bink2002 != nullptr)
{
delete bink2002;
bink2002 = nullptr;
}
if (p3 != nullptr)
{
delete p3;
p3 = nullptr;
}
if (p != nullptr)
{
p = nullptr;
}
}
};
TEST_F(TestBINK2002, ValidateValidKeyString)
{
std::string pKey;
auto ValidateKeyString = bink2002->ValidateKeyString("QX7C7-6668G-RHTTC-9XXD6-4QKVM", pKey);
ASSERT_TRUE(ValidateKeyString);
ASSERT_STREQ(&pKey[0], "QX7C76668GRHTTC9XXD64QKVM");
}
TEST_F(TestBINK2002, ValidateInvalidKeyString)
{
std::string pKey;
auto ValidateKeyString = bink2002->ValidateKeyString("QX7C7-6668G-RHTTC-9XXD6-4QKVM-7", pKey);
ASSERT_FALSE(ValidateKeyString);
}
TEST_F(TestBINK2002, ValidateValidKey)
{
std::string pKey = "QX7C76668GRHTTC9XXD64QKVM";
auto Validate = bink2002->Validate(pKey);
ASSERT_TRUE(Validate);
}
TEST_F(TestBINK2002, ValidateInvalidKey)
{
std::string pKey = "QX7C76668GRHTTC9XXD64QKV7";
auto Validate = bink2002->Validate(pKey);
ASSERT_FALSE(Validate);
}
TEST_F(TestBINK2002, GenerateValidKey)
{
bink2002->info.Rand = UMSKT::IntegerS("2715417548459431244234182116258933974639514924173191881913315754156057922856"
"789413383072541627152533502894944768632184791880876163762899980230935");
std::string pKey;
bink2002->Generate(pKey);
pKey = bink2002->StringifyKey(pKey);
ASSERT_STREQ(&pKey[0], "QX7C7-6668G-RHTTC-9XXD6-4QKVM");
}

View File

@ -25,7 +25,19 @@
#include "BINK2002.h" #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 * @return 4
*/ */
@ -50,10 +62,11 @@ int getRandomNumber()
* *
* @return true on success, false on fail * @return true on success, false on fail
*/ */
BOOL PIDGEN3::LoadEllipticCurve(const std::string pSel, const std::string aSel, const std::string bSel, BOOL PIDGEN3::LoadEllipticCurve(const std::string &BinkIDSel, const std::string &pSel, const std::string &aSel,
const std::string generatorXSel, const std::string generatorYSel, const std::string &bSel, const std::string &generatorXSel,
const std::string publicKeyXSel, const std::string publicKeyYSel, const std::string &generatorYSel, const std::string &publicKeyXSel,
const std::string genOrderSel, const std::string privateKeySel) 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 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). // we need the result of the function K(x; y) = kG(x; y).
@ -61,217 +74,243 @@ 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}. // 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. // genOrder the order of the generator G, a value we have to reverse -> Schoof's Algorithm.
// Initialize BIGNUM and BIGNUMCTX structures. BINKID = IntegerHexS(BinkIDSel);
// BIGNUM - Large numbers
// BIGNUMCTX - Context large numbers (temporary)
// Context variable
BN_CTX *context = BN_CTX_new();
// We're presented with an elliptic curve, a multivariable function y(x; p; a; b), where // We're presented with an elliptic curve, a multivariable function y(x; p; a; b), where
// y^2 % p = x^3 + ax + b % p. // y^2 % p = x^3 + ax + b % p.
BIGNUM *a = BN_CTX_get(context), *b = BN_CTX_get(context), *p = BN_CTX_get(context); auto p = IntegerS(pSel), a = IntegerS(aSel), b = IntegerS(bSel);
// Public key will consist of the resulting (x; y) values. // Public key will consist of the resulting (x; y) values.
BIGNUM *publicKeyX = BN_CTX_get(context), *publicKeyY = BN_CTX_get(context); auto generatorX = IntegerS(generatorXSel), generatorY = IntegerS(generatorYSel);
// G(x; y) is a generator function, its return value represents a point on the elliptic curve. // G(x; y) is a generator function, its return value represents a point on the elliptic curve.
BIGNUM *generatorX = BN_CTX_get(context), *generatorY = BN_CTX_get(context); auto publicKeyX = IntegerS(publicKeyXSel), publicKeyY = IntegerS(publicKeyYSel);
genOrder = BN_new();
privateKey = BN_new();
/* Public data */
BN_dec2bn(&p, &pSel[0]);
BN_dec2bn(&a, &aSel[0]);
BN_dec2bn(&b, &bSel[0]);
BN_dec2bn(&generatorX, &generatorXSel[0]);
BN_dec2bn(&generatorY, &generatorYSel[0]);
BN_dec2bn(&publicKeyX, &publicKeyXSel[0]);
BN_dec2bn(&publicKeyY, &publicKeyYSel[0]);
/* Computed Data */ /* Computed Data */
BN_dec2bn(&genOrder, &genOrderSel[0]); genOrder = IntegerS(genOrderSel);
BN_dec2bn(&privateKey, &privateKeySel[0]); privateKey = IntegerS(privateKeySel);
/* Elliptic Curve calculations. */ /* Elliptic Curve calculations. */
// The group is defined via Fp = all integers [0; p - 1], where p is prime. // The group is defined via Fp = all integers [0; p - 1], where p is prime.
// The function EC_POINT_set_affine_coordinates() sets the x and y coordinates for the point p defined over the // The function EC_POINT_set_affine_coordinates() sets the x and y coordinates for the point p defined over the
// curve given in group. // curve given in group.
eCurve = EC_GROUP_new_curve_GFp(p, a, b, context); eCurve = ECP(p, a, b);
// Create new point for the generator on the elliptic curve and set its coordinates to (genX; genY). // Create new point N for the generator on the elliptic curve and set its coordinates to (genX; genY).
genPoint = EC_POINT_new(eCurve); genPoint = ECP::Point(generatorX, generatorY);
EC_POINT_set_affine_coordinates(eCurve, genPoint, generatorX, generatorY, context);
// Create new point for the public key on the elliptic curve and set its coordinates to (pubX; pubY). // Create new point Q for the public key on the elliptic curve and set its coordinates to (pubX; pubY).
pubPoint = EC_POINT_new(eCurve); pubPoint = ECP::Point(publicKeyX, publicKeyY);
EC_POINT_set_affine_coordinates(eCurve, pubPoint, publicKeyX, publicKeyY, context);
// If generator and public key points are not on the elliptic curve, either the generator or the public key values // If generator and public key points are not on the elliptic curve, either the generator or the public key values
// are incorrect. // are incorrect.
assert(EC_POINT_is_on_curve(eCurve, genPoint, context) == true); assert(eCurve.VerifyPoint(genPoint) == true);
assert(EC_POINT_is_on_curve(eCurve, pubPoint, context) == true); assert(eCurve.VerifyPoint(pubPoint) == true);
// Cleanup
BN_CTX_free(context);
return true; 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))
{
return new BINK1998();
}
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 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()) if (checkFieldIsBink1998())
{ {
auto p3 = BINK1998(this); auto p3 = BINK1998(this);
retval = p3.Validate(pKey); return p3.Generate(pKey);
}
else
{
auto p3 = BINK2002(this);
retval = p3.Validate(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. * Converts from byte sequence to the CD-key.
* *
* @param cdKey [out] std::string CDKey input * @param seq Integer representation
* @param byteSeq [in] BYTE* * @return std::string CDKey output
**/ **/
void PIDGEN3::base24(std::string &cdKey, BYTE *byteSeq) std::string PIDGEN3::base24(Integer &seq)
{ {
BYTE rbyteSeq[16], output[26]; std::string cdKey;
BIGNUM *z; cdKey.reserve(PK_LENGTH);
// 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
}
UMSKT::endian(rbyteSeq, ++length);
// Convert reversed byte sequence to BigNum z.
z = BN_bin2bn(rbyteSeq, length, nullptr);
// Divide z by 24 and convert the remainder to a CD-key char. // 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[BN_div_word(z, 24)]; Integer::Divide(r, q, a, (WORD)pKeyCharset.length());
cdKey.insert(cdKey.begin(), pKeyCharset[r.ConvertToLong()]);
a = q;
} }
output[25] = 0; return cdKey;
cdKey = (char *)output;
BN_free(z);
} }
/** /**
* Converts from CD-key to a byte sequence. * Converts from CD-key to a byte sequence.
* *
* @param byteSeq [out] *BYTE representation of the CDKey * @param cdKey std::string CDKey to convert
* @param cdKey [in] 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 result;
BIGNUM *y = BN_new();
// Remove dashes from the CD-key and put it into a Base24 byte array. for (char ch : cdKey)
for (int i = 0, k = 0; i < cdKey.length() && k < PK_LENGTH; i++)
{ {
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]) return result;
{
pDecodedKey[k++] = j;
break;
}
}
} }
// Empty byte sequence. // add the weighted sum to result
memset(byteSeq, 0, 16); result *= (int)pKeyCharset.length();
result += (int)(val - pKeyCharset.begin());
}
// Calculate the weighed sum of byte array elements. return result;
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()
{ {
BN_mul_word(y, PK_LENGTH - 1); if (info.isOEM)
BN_add_word(y, pDecodedKey[i]); {
} Integer OEMID = info.ChannelID * Integer(100);
OEMID += ((info.Serial / (MaxSerial / TEN)) * TEN);
// Acquire length. OEMID += GenerateMod7(OEMID);
int n = BN_num_bytes(y);
Integer Serial = info.Serial % (MaxSerial / TEN);
// Place the generated code into the byte sequence.
BN_bn2bin(y, byteSeq); DWORD32 iOEMID = OEMID.ConvertToLong(), iSerial = Serial.ConvertToLong();
BN_free(y); return fmt::format("PPPPP-OEM-{:07d}-{:05d}", iOEMID, iSerial);
}
// Reverse the byte sequence. else
UMSKT::endian(byteSeq, n); {
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);
}
} }
/**
* Checks to see if the currently instantiated PIDGEN3 object has a
* field size greater than the maximum known BINK1998 size.
*
* @return boolean value
*/
BOOL PIDGEN3::checkFieldIsBink1998() BOOL PIDGEN3::checkFieldIsBink1998()
{ {
auto *max = BN_new(); // is fieldSize < max?
return (eCurve.FieldSize().BitCount() < MaxSizeBINK1998);
// 1 << 385 (or max size of BINK1998 field in bits + 1)
BN_set_bit(max, (12 * 4 * 8) + 1);
// retval is -1 when (max < privateKey)
int retval = BN_cmp(max, privateKey);
BN_free(max);
// is max > privateKey?
return retval == 1;
} }
/**
* Checks if a given field, in a std::string, is greater than
* the maximum known BINK1998 size
*
* @param keyin std::string representation of a Field
* @return boolean value
*/
BOOL PIDGEN3::checkFieldStrIsBink1998(std::string keyin) BOOL PIDGEN3::checkFieldStrIsBink1998(std::string keyin)
{ {
auto *context = BN_CTX_new(); auto check = IntegerS(keyin);
auto max = BN_CTX_get(context), input = BN_CTX_get(context);
BN_dec2bn(&input, &keyin[0]); // is fieldSize < max?
return (check.BitCount() < MaxSizeBINK1998);
// 1 << 385 (or max size of BINK1998 field in bits + 1) }
BN_set_bit(max, (12 * 4 * 8) + 1);
/**
// retval is -1 when (max < privateKey) * Prints a product key to stdout
int retval = BN_cmp(max, input); *
* @param pk std::string to print
BN_CTX_free(context); */
std::string PIDGEN3::StringifyKey(const std::string &pKey)
// is max > privateKey? {
return retval == 1; 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 INLINE PIDGEN3::ValidateStringKeyInputCharset(std::string &accumulator, char currentChar)
{
char cchar = (char)::toupper(currentChar);
if (std::find(pKeyCharset.begin(), pKeyCharset.end(), 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,82 +23,89 @@
#ifndef UMSKT_PIDGEN3_H #ifndef UMSKT_PIDGEN3_H
#define UMSKT_PIDGEN3_H #define UMSKT_PIDGEN3_H
#include "../libumskt.h" #include <libumskt/pidgen.h>
class BINK1998; class BINK1998;
class BINK2002; class BINK2002;
class EXPORT PIDGEN3 class EXPORT PIDGEN3 : public PIDGEN
{ {
friend class BINK1998; friend class BINK1998;
friend class BINK2002; friend class BINK2002;
protected: protected:
BIGNUM *privateKey, *genOrder; Integer BINKID;
EC_POINT *genPoint, *pubPoint; Integer privateKey, genOrder;
EC_GROUP *eCurve; ECP::Point pubPoint, genPoint;
ECP eCurve;
public: public:
PIDGEN3() PIDGEN3() = default;
{
}
PIDGEN3(PIDGEN3 &p3) PIDGEN3(PIDGEN3 &p3)
{ {
privateKey = p3.privateKey; privateKey = p3.privateKey;
genOrder = p3.genOrder; genOrder = p3.genOrder;
genPoint = p3.genPoint;
pubPoint = p3.pubPoint; pubPoint = p3.pubPoint;
genPoint = p3.genPoint;
eCurve = p3.eCurve; eCurve = p3.eCurve;
} }
virtual ~PIDGEN3() ~PIDGEN3() override = default;
{
EC_GROUP_free(eCurve);
EC_POINT_free(genPoint);
EC_POINT_free(pubPoint);
BN_free(genOrder);
BN_free(privateKey);
}
struct KeyInfo struct KeyInfo
{ {
DWORD Serial = 0, AuthInfo = 0, ChannelID = 0, Hash = 0; BOOL isUpgrade = false, isOEM = false;
QWORD Signature = 0; Integer ChannelID = 0, Serial = 0, AuthInfo = 0, Rand = 0, Hash = 0, Signature = 0;
BOOL isUpgrade = false;
void setSerial(DWORD serialIn) void setSerial(DWORD32 SerialIn)
{ {
Serial = serialIn; Serial = IntegerN(SerialIn);
} }
void setAuthInfo(DWORD AuthInfoIn) void setAuthInfo(DWORD32 AuthInfoIn)
{ {
AuthInfo = AuthInfoIn; AuthInfo = IntegerN(AuthInfoIn);
} }
void setChannelID(DWORD ChannelIDIn) void setChannelID(DWORD32 ChannelIDIn)
{ {
ChannelID = ChannelIDIn; ChannelID = IntegerN(ChannelIDIn);
} }
} info; } info;
static constexpr char pKeyCharset[] = "BCDFGHJKMPQRTVWXY2346789"; static const std::string pKeyCharset;
static const DWORD32 MaxSizeBINK1998;
BOOL LoadEllipticCurve(std::string pSel, std::string aSel, std::string bSel, std::string generatorXSel, BOOL LoadEllipticCurve(const std::string &BinkIDSel, const std::string &pSel, const std::string &aSel,
std::string generatorYSel, std::string publicKeyXSel, std::string publicKeyYSel, const std::string &bSel, const std::string &generatorXSel, const std::string &generatorYSel,
std::string genOrderSel, std::string privateKeySel); const std::string &publicKeyXSel, const std::string &publicKeyYSel,
const std::string &genOrderSel, const std::string &privateKeySel);
virtual BOOL Pack(QWORD *pRaw) = 0; BOOL Generate(std::string &pKey) override;
virtual BOOL Unpack(QWORD *pRaw) = 0; BOOL Validate(const std::string &pKey) override;
virtual BOOL Generate(std::string &pKey); std::string StringifyKey(const std::string &pKey) override;
virtual BOOL Validate(std::string &pKey); std::string StringifyProductID() override;
BOOL ValidateKeyString(const std::string &in_key, std::string &out_key) override;
virtual Integer Pack(const KeyInfo &ki)
{
throw std::runtime_error("PIDGEN3::Pack() pure virtual function call");
}
virtual KeyInfo Unpack(const Integer &raw)
{
throw std::runtime_error("PIDGEN3::Unpack() pure virtual function call");
}
// PIDGEN3.cpp // PIDGEN3.cpp
void base24(std::string &cdKey, BYTE *byteSeq); static PIDGEN3 *Factory(const std::string &field);
void unbase24(BYTE *byteSeq, std::string cdKey);
BOOL checkFieldIsBink1998();
static BOOL checkFieldStrIsBink1998(std::string keyin); static BOOL checkFieldStrIsBink1998(std::string keyin);
static std::string ValidateStringKeyInputCharset(std::string &accumulator, char currentChar);
static std::string base24(Integer &seq);
static Integer unbase24(const std::string &cdKey);
BOOL checkFieldIsBink1998();
}; };
#endif // UMSKT_PIDGEN3_H #endif // UMSKT_PIDGEN3_H

View File

@ -20,7 +20,7 @@
* @Maintainer Neo * @Maintainer Neo
*/ */
#include "cli.h" #include <cli/cli.h>
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {

View File

@ -20,7 +20,7 @@
* @Maintainer Neo * @Maintainer Neo
*/ */
#include "cli.h" #include <cli/cli.h>
#include <cmrc/cmrc.hpp> #include <cmrc/cmrc.hpp>
CMRC_DECLARE(umskt); CMRC_DECLARE(umskt);

View File

@ -25,19 +25,27 @@
#if defined(_MSC_VER) #if defined(_MSC_VER)
// squelch the insane amount of warnings from cstdbool
#define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <intrin.h>
#include <windows.h> #include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#endif // defined(WIN32) #endif // defined(_MSC_VER)
#include <algorithm> #include <algorithm>
#include <cctype>
#include <cstdbool> #include <cstdbool>
#include <cstdint> #include <cstdint>
#include <iostream>
#include <map> #include <map>
#include <numeric>
#include <sstream>
#include <stdint128>
#include <string>
#include <vector> #include <vector>
#ifdef DEBUG #if defined(DEBUG) || 1
#include <cassert> #include <cassert>
#else #else
#define assert(x) /* do nothing */ #define assert(x) /* do nothing */
@ -76,45 +84,19 @@
#define INLINE FNINLINE #define INLINE FNINLINE
#endif // ifdef __EMSCRIPTEN__ #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) // Type definitions now with more windows compatability (unfortunately)
using BOOL = int32_t; using BOOL = int32_t;
using BYTE = uint8_t; using BYTE = uint8_t;
using WORD = uint16_t; using WORD = uint16_t;
using DWORD = unsigned long; using DWORD = unsigned long;
using DWORD32 = uint32_t;
using QWORD = uint64_t; using QWORD = uint64_t;
#if 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
using uint128_t = __m128;
#endif
using OWORD = uint128_t; using OWORD = uint128_t;
typedef union { typedef union {
OWORD oword; OWORD oword;
QWORD qword[2]; QWORD qword[2];
DWORD dword[4]; DWORD32 dword32[4];
WORD word[8]; WORD word[8];
BYTE byte[16]; BYTE byte[16];
} Q_OWORD; } Q_OWORD;

View File

@ -20,8 +20,8 @@
* @Maintainer Neo * @Maintainer Neo
*/ */
#include "../typedefs.h"
#include "resource.h" #include "resource.h"
#include "typedefs.h"
BOOLEAN WINAPI DllMain(IN HINSTANCE hDllHandle, IN DWORD nReason, IN LPVOID Reserved) BOOLEAN WINAPI DllMain(IN HINSTANCE hDllHandle, IN DWORD nReason, IN LPVOID Reserved)
{ {

View File

@ -20,7 +20,7 @@
* @Maintainer Neo * @Maintainer Neo
*/ */
#include "../cli.h" #include "cli/cli.h"
#include "resource.h" #include "resource.h"
/** /**

Binary file not shown.