From f9100e0871585a6d48372990c3ff49ed2a2f53a4 Mon Sep 17 00:00:00 2001 From: TheTank20 <57580668+thepwrtank18@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:53:17 -0400 Subject: [PATCH] To slop SSL away (#191) * windows 98/nt4, slopped openssl away * one (1) roadblock that it can't figure out * fixed maybe * fix dos? * fix dos? (2) * ts is gonna be slow * No need to compress anymore * No need to compress anymore (2) * deslop The main problem with AI models, even today, is that they're very shortsighted. They're too focused on the thing working now than thinking about how maintainable it will be later. As such, you get duplicated code, as well as comments that are geared towards what it's doing right then and there (as if it's an advertisement of it's services) rather than improving the readability of the code. Always check your diffs. * Update workflows * Update freebsd.yml * Update README.md --- .github/workflows/dos-djgpp.yml | 177 +-- .github/workflows/freebsd.yml | 18 +- .github/workflows/linux.yml | 21 +- .github/workflows/macos.yml | 35 +- .github/workflows/windows-arm.yml | 60 +- .github/workflows/windows-x86-x64.yml | 184 +-- CMakeLists.txt | 307 +++-- Dockerfile | 4 +- Dockerfile.djgpp | 41 +- Dockerfile.emscripten | 27 +- Dockerfile.windows | 11 - README.md | 19 +- cmake/CPM.cmake | 33 - cmake/embed_keys.cmake | 10 + src/cli.cpp | 1506 +++++++++++++++---------- src/cli.h | 110 -- src/crypto/mini_bn.cpp | 1273 +++++++++++++++++++++ src/crypto/mini_ec.cpp | 618 ++++++++++ src/crypto/mini_openssl.h | 85 ++ src/crypto/mini_rand.cpp | 183 +++ src/header.h | 41 - src/keys_embed.h | 8 + src/libumskt/confid/confid.cpp | 72 +- src/libumskt/confid/confid.h | 2 +- src/libumskt/debugoutput.cpp | 47 +- src/libumskt/libumskt.cpp | 77 +- src/libumskt/libumskt.h | 39 +- src/libumskt/pidgen2/PIDGEN2.cpp | 27 +- src/libumskt/pidgen3/BINK1998.cpp | 63 +- src/libumskt/pidgen3/BINK1998.h | 4 +- src/libumskt/pidgen3/BINK2002.cpp | 94 +- src/libumskt/pidgen3/BINK2002.h | 4 +- src/libumskt/pidgen3/PIDGEN3.h | 14 +- src/libumskt/pidgen3/key.cpp | 4 +- src/libumskt/pidgen3/util.cpp | 28 +- src/main.cpp | 63 -- src/windows/crt_win32.cpp | 729 ++++++++++++ src/windows/json.cpp | 236 ++++ src/windows/json.h | 29 + src/windows/win32_min.h | 30 + tools/check-win-baseline.ps1 | 168 +++ 41 files changed, 4758 insertions(+), 1743 deletions(-) delete mode 100644 cmake/CPM.cmake create mode 100644 cmake/embed_keys.cmake delete mode 100644 src/cli.h create mode 100644 src/crypto/mini_bn.cpp create mode 100644 src/crypto/mini_ec.cpp create mode 100644 src/crypto/mini_openssl.h create mode 100644 src/crypto/mini_rand.cpp delete mode 100644 src/header.h create mode 100644 src/keys_embed.h delete mode 100644 src/main.cpp create mode 100644 src/windows/crt_win32.cpp create mode 100644 src/windows/json.cpp create mode 100644 src/windows/json.h create mode 100644 src/windows/win32_min.h create mode 100644 tools/check-win-baseline.ps1 diff --git a/.github/workflows/dos-djgpp.yml b/.github/workflows/dos-djgpp.yml index c7705fb..ad3d8d9 100644 --- a/.github/workflows/dos-djgpp.yml +++ b/.github/workflows/dos-djgpp.yml @@ -33,19 +33,18 @@ 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: build: runs-on: ubuntu-latest steps: - name: Checkout Source Tree - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup build environment run: | sudo apt -y update - sudo apt -y install build-essential cmake wget 7zip git flex libfl-dev nasm libslang2-dev pkg-config libslang2-modules gcc-multilib dosbox tree + sudo apt -y install build-essential cmake wget 7zip git gcc-multilib dosbox tree - name: Download and Setup DJGPP Toolchain run: | @@ -54,41 +53,7 @@ jobs: tar xjf djgpp-linux64-gcc1220.tar.bz2 cd ${{ github.workspace }}/djgpp tree . -S - git clone https://github.com/UMSKT/Watt-32.git watt32 - 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: Cache OpenSSL 3.1.2 - uses: actions/cache/restore@v4 - id: cache-openssl - with: - path: | - ${{ github.workspace }}/djgpp - key: openssl-3.1.2-${{ hashFiles('**/CMakeLists.txt') }} - - - name: Checkout and Cross Compile OpenSSL 3.1.2 - if: steps.cache-openssl.outputs.cache-hit != 'true' - run: | - git clone https://github.com/UMSKT/openssl.git openssl - pushd openssl - source ${{ github.workspace }}/djgpp/setenv - ./Configure 386 no-threads no-tests no-sse2 no-asm -DOPENSSL_DEV_NO_ATOMICS -mno-mmx -mno-sse -mno-sse2 -march=i386 --prefix=${{ github.workspace }}/djgpp DJGPP - make && make install_sw - popd - - - name: Save OpenSSL 3.1.2 - if: steps.cache-openssl.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 - with: - path: | - ${{ github.workspace }}/djgpp - key: openssl-3.1.2-${{ hashFiles('**/CMakeLists.txt') }} - - name: Download stub run: | wget https://web.archive.org/web/20250308092209/https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/util/system/cwsdpmi/csdpmi7b.zip -O CSDPMI7B.ZIP @@ -99,7 +64,7 @@ jobs: 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} -D CWSDSTUB_LOCATION=/home/runner/dpmibin/CWSDSTUB.EXE -D DJGPP_BIN_LOCATION=/home/runner/work/UMSKT/UMSKT/djgpp/i586-pc-msdosdjgpp/bin + cmake ../ -D DJGPP=ON -D CMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} -D CWSDSTUB_LOCATION=/home/runner/dpmibin/CWSDSTUB.EXE -D DJGPP_BIN_LOCATION=/home/runner/work/UMSKT/UMSKT/djgpp/i586-pc-msdosdjgpp/bin make - name: Setup DOSBox test environment @@ -145,7 +110,7 @@ jobs: - name: Run tests in DOSBox run: | cd dosbox_test - timeout 30s dosbox -conf dosbox.conf -nogui -exit + timeout 2m dosbox -conf dosbox.conf -nogui -exit # Check if the test completed successfully if [ ! -f DONE.TXT ]; then echo "Tests did not complete successfully" @@ -199,137 +164,7 @@ jobs: mv build/umskt.exe build/actions_upload/ - name: Upload build artifact - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v7 with: name: UMSKT-DOS-x86 - path: build/actions_upload - compress: - needs: build - if: success() - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - name: Setup test environment - run: | - sudo apt -y update - sudo apt -y install dosbox - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: UMSKT-DOS-x86 - path: . - - - name: Install UPX - uses: crazy-max/ghaction-upx@v3 - with: - install-only: true - - - name: Compress binary - shell: pwsh - run: | - upx umskt.exe --best --ultra-brute -v - - - name: Setup DOSBox test environment - run: | - mkdir -p dosbox_test - cp umskt.exe dosbox_test/ - # Download DPMI server directly - # wget https://github.com/UMSKT/winactiontest/raw/refs/heads/main/CWSDPMI.EXE -O dosbox_test/CWSDPMI.EXE - # Create test batch file - cat > dosbox_test/test.bat << EOL - @echo off - echo Running test 1... - umskt.exe -b 2C -c 365 -s 069420,999999 > TEST1.TXT - if errorlevel 1 goto error - echo Running test 2... - umskt.exe -b 5A -c 640 -s 069420,999999 > TEST2.TXT - if errorlevel 1 goto error - echo Running test 3... - umskt.exe -i 253286028742154311079061239762245184619981623171292574 > TEST3.TXT - if errorlevel 1 goto error - echo Tests completed > DONE.TXT - goto end - :error - echo Test failed > ERROR.TXT - :end - exit - EOL - # Create DOSBox configuration - cat > dosbox_test/dosbox.conf << EOL - [sdl] - nosound=true - [cpu] - cputype=386 - core=dynamic - cycles=max - [autoexec] - mount c . - c: - test.bat - exit - EOL - - - name: Run tests in DOSBox - - run: | - cd dosbox_test - timeout 30s dosbox -conf dosbox.conf -nogui -exit - # Check if the test completed successfully - if [ ! -f DONE.TXT ]; then - echo "Tests did not complete successfully" - if [ -f ERROR.TXT ]; then - echo "Test execution failed" - fi - if [ -f TEST1.TXT ]; then - echo "Test 1 output:" - cat TEST1.TXT - fi - if [ -f TEST2.TXT ]; then - echo "Test 2 output:" - cat TEST2.TXT - fi - if [ -f TEST3.TXT ]; then - echo "Test 3 output:" - cat TEST3.TXT - fi - exit 1 - fi - # Verify test outputs - if [ ! -f TEST1.TXT ] || [ ! -f TEST2.TXT ] || [ ! -f TEST3.TXT ]; then - echo "Test output files missing" - exit 1 - fi - # Check test results - looking for key format patterns - if ! grep -qE '[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}' TEST1.TXT || \ - ! grep -qE '[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}' TEST2.TXT || \ - ! grep -qE '[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}' TEST3.TXT; then - echo "Tests failed - unexpected output format" - echo "Test 1 output:" - cat TEST1.TXT - echo "Test 2 output:" - cat TEST2.TXT - echo "Test 3 output:" - cat TEST3.txt - exit 1 - else - echo "All tests passed successfully" - echo "Test 1 output:" - cat TEST1.TXT - echo "Test 2 output:" - cat TEST2.TXT - echo "Test 3 output:" - cat TEST3.TXT - fi - - - name: Move executable to upload directory - run: | - mkdir actions_upload - mv umskt.exe actions_upload/ - - - name: Upload build artifact - uses: actions/upload-artifact@v4.6.2 - with: - name: UMSKT-DOS-x86-Compressed - path: actions_upload + path: build/actions_upload \ No newline at end of file diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 7a3fa9e..ee34dc2 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -21,12 +21,12 @@ name: FreeBSD on: - #push: - # branches: [ "*" ] - # 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 + push: + branches: [ "*" ] + 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: @@ -35,7 +35,7 @@ runs-on: ubuntu-latest name: build-x86_64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Build & Test in FreeBSD id: test @@ -43,7 +43,7 @@ with: usesh: true prepare: | - pkg install -y cmake openssl git bash + pkg install -y cmake git bash run: | mkdir -p build @@ -63,7 +63,7 @@ mv build/umskt build/actions_upload/umskt - name: Upload build artifact - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v7 with: name: UMSKT-FreeBSD path: build/actions_upload diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a3fda65..dffe5c0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -46,7 +46,7 @@ jobs: runs-on: ${{ matrix.runner }} steps: - name: Checkout Source Tree - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup latest Alpine Linux if: ${{ matrix.use_alpine }} @@ -58,9 +58,6 @@ jobs: cmake git musl-dev - openssl-dev - openssl-libs-static - zlib-dev arch: ${{ matrix.arch }} shell-name: alpine-target.sh @@ -68,11 +65,11 @@ jobs: if: ${{ !matrix.use_alpine }} run: | sudo apt-get update - sudo apt-get install -y build-essential cmake git libssl-dev zlib1g-dev + sudo apt-get install -y build-essential cmake git - name: Configure and build UMSKT (Alpine) if: ${{ matrix.use_alpine }} - uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + uses: threeal/cmake-action@v1 with: options: MUSL_STATIC=ON CMAKE_POLICY_VERSION_MINIMUM=3.5 run-build: true @@ -80,7 +77,7 @@ jobs: - name: Configure and build UMSKT (Ubuntu) if: ${{ !matrix.use_alpine }} - uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + uses: threeal/cmake-action@v1 with: options: MUSL_STATIC=ON CMAKE_POLICY_VERSION_MINIMUM=3.5 run-build: true @@ -114,14 +111,14 @@ jobs: mv build/umskt build/actions_upload/umskt - name: Upload build artifact - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v7 with: name: UMSKT-Linux-${{ matrix.arch }}-static path: build/actions_upload - name: Configure and build static internal deps UMSKT (Alpine) if: ${{ matrix.use_alpine }} - uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + uses: threeal/cmake-action@v1 with: options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=OFF CMAKE_POLICY_VERSION_MINIMUM=3.5 run-build: true @@ -129,7 +126,7 @@ jobs: - name: Configure and build static internal deps UMSKT (Ubuntu) if: ${{ !matrix.use_alpine }} - uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + uses: threeal/cmake-action@v1 with: options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=OFF CMAKE_POLICY_VERSION_MINIMUM=3.5 run-build: true @@ -159,7 +156,7 @@ jobs: - name: Configure and build shared deps UMSKT (Alpine) if: ${{ matrix.use_alpine }} - uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + uses: threeal/cmake-action@v1 with: options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=ON CMAKE_POLICY_VERSION_MINIMUM=3.5 run-build: true @@ -167,7 +164,7 @@ jobs: - name: Configure and build shared deps UMSKT (Ubuntu) if: ${{ !matrix.use_alpine }} - uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + uses: threeal/cmake-action@v1 with: options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=ON CMAKE_POLICY_VERSION_MINIMUM=3.5 run-build: true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dc25a95..4cb6328 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -39,43 +39,18 @@ jobs: - arch: arm64 steps: - name: Checkout Source Tree - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install Rosetta if: matrix.arch == 'x86_64' run: | sudo softwareupdate --install-rosetta --agree-to-license || true - - name: Install Homebrew for x86_64 - if: matrix.arch == 'x86_64' - env: { NONINTERACTIVE: 1 } - run: | - arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - - - name: Install x86_64 OpenSSL - if: matrix.arch == 'x86_64' - run: | - arch -x86_64 /usr/local/bin/brew install openssl@3 - echo "OPENSSL_ROOT_DIR=/usr/local/opt/openssl@3" >> $GITHUB_ENV - echo "OPENSSL_INCLUDE_DIR=/usr/local/opt/openssl@3/include" >> $GITHUB_ENV - echo "PKG_CONFIG_PATH=/usr/local/opt/openssl@3/lib/pkgconfig" >> $GITHUB_ENV - - - name: Install arm64 OpenSSL - if: matrix.arch == 'arm64' - run: | - brew install openssl@3 - echo "OPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@3" >> $GITHUB_ENV - echo "OPENSSL_INCLUDE_DIR=/opt/homebrew/opt/openssl@3/include" >> $GITHUB_ENV - echo "PKG_CONFIG_PATH=/opt/homebrew/opt/openssl@3/lib/pkgconfig" >> $GITHUB_ENV - - name: Configure and build UMSKT run: | cd build cmake -DCMAKE_BUILD_TYPE=Release .. \ -DCMAKE_OSX_ARCHITECTURES=${{matrix.arch}} \ - -DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR \ - -DOPENSSL_INCLUDE_DIR=$OPENSSL_INCLUDE_DIR \ - -DOPENSSL_USE_STATIC_LIBS=TRUE \ -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 make @@ -97,7 +72,7 @@ jobs: ./umskt -i 253286028742154311079061239762245184619981623171292574 - name: Upload platform-specific build - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v7 with: name: UMSKT-macOS-${{ matrix.arch }} path: build/actions_upload @@ -107,12 +82,12 @@ jobs: runs-on: macos-latest steps: - name: Download x86_64 slice - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UMSKT-macOS-x86_64 path: x86 - name: Download arm64 slice - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UMSKT-macOS-arm64 path: arm @@ -123,7 +98,7 @@ jobs: lipo -create x86/umskt arm/umskt -output fat/umskt lipo -info fat/umskt - name: Upload universal build - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UMSKT-macOS-universal path: fat diff --git a/.github/workflows/windows-arm.yml b/.github/workflows/windows-arm.yml index b181364..9edbb74 100644 --- a/.github/workflows/windows-arm.yml +++ b/.github/workflows/windows-arm.yml @@ -38,11 +38,9 @@ jobs: include: # - arch: arm32 # arch_compilename: amd64_arm - # vcpkg_arch: arm-windows-static # cmake_arch: ARM - arch: arm64 arch_compilename: amd64_arm64 - vcpkg_arch: arm64-windows-static cmake_arch: ARM64 steps: - name: Setup MSVC for ${{ matrix.arch }} @@ -51,57 +49,7 @@ jobs: arch: ${{ matrix.arch_compilename }} - name: Checkout Source Tree - uses: actions/checkout@v4 - - - name: Cache OpenSSL Binaries - uses: actions/cache/restore@v4 - with: - path: | - ${{ github.workspace }}-OpenSSL-MSVC-${{ matrix.arch }} - key: openssl-1.1.1-${{ matrix.arch }}-${{ hashFiles('**/CMakeLists.txt') }} - - - name: Checkout and Compile OpenSSL v1.1.1 - shell: pwsh - run: | - git clone https://github.com/UMSKT/openssl --branch OpenSSL_1_1_1-stable --depth 1 openssl-src - cd openssl-src - - # Install Perl if needed - choco install strawberryperl -y - - # Configure and build OpenSSL for ARM32 - $env:PATH = "C:\Strawberry\perl\bin;$env:PATH" - - # First set up Visual Studio environment - $vsPath = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath - $vcvarsall = Join-Path $vsPath "VC\Auxiliary\Build\vcvarsall.bat" - - $vcTarget = "" - if ("${{ matrix.arch}}" -eq "arm32") { - $vcTarget = "VC-WIN32-ARM" - } else { - $vcTarget = "VC-WIN64-ARM" - } - - # Configure OpenSSL - perl Configure $vcTarget no-shared no-asm no-engine --prefix="$env:GITHUB_WORKSPACE/OpenSSL-MSVC-${{ matrix.arch }}" - - # Build using MSVC ARM32 tools - cmd /c "call `"$vcvarsall`" ${{matrix.arch_compilename}} && nmake && nmake install_sw" - cd .. - - - name: Save OpenSSL Binaries - uses: actions/cache/save@v4 - with: - path: | - ${{ github.workspace }}/OpenSSL-MSVC-${{ matrix.arch }} - key: openssl-1.1.1-${{ matrix.arch }}-${{ hashFiles('**/CMakeLists.txt') }} - - - name: Set OpenSSL Environment - shell: pwsh - run: | - echo "OPENSSL_ROOT=$env:GITHUB_WORKSPACE/OpenSSL-MSVC-${{ matrix.arch }}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "OPENSSL_LIBDIR=lib" | Out-File -FilePath $env:GITHUB_ENV -Append + uses: actions/checkout@v7 - name: Configure UMSKT shell: pwsh @@ -109,8 +57,6 @@ jobs: cmake -G "Visual Studio 18 2026" ` -A ${{ matrix.cmake_arch }} ` -DWINDOWS_ARM=ON ` - -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` - -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_arch }} ` -DCMAKE_POLICY_VERSION_MINIMUM="3.5" ` . @@ -120,7 +66,7 @@ jobs: cmake --build . --config Release - name: Upload build artifact - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v7 with: name: UMSKT-WinNT-${{ matrix.arch }} path: Release/umskt.exe @@ -134,7 +80,7 @@ jobs: arch: [arm64] steps: - name: Download ARM64 artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UMSKT-WinNT-${{ matrix.arch }} path: . diff --git a/.github/workflows/windows-x86-x64.yml b/.github/workflows/windows-x86-x64.yml index 7c38f19..493d00a 100644 --- a/.github/workflows/windows-x86-x64.yml +++ b/.github/workflows/windows-x86-x64.yml @@ -61,85 +61,17 @@ jobs: [Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine) - name: Checkout Source Tree - uses: actions/checkout@v4 - - - name: Cache OpenSSL 1.1.1 - uses: actions/cache/restore@v4 - id: cache-openssl - with: - path: | - ${{ github.workspace }}/OpenSSL-TDM-${{ matrix.arch }} - key: openssl-1.1.1-${{ matrix.arch }}-${{ hashFiles('**/CMakeLists.txt') }} - - - name: Setup MSYS2 - if: steps.cache-openssl.outputs.cache-hit != 'true' - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.arch == 'x86' && 'MINGW32' || 'MINGW64' }} - update: true - install: >- - mingw-w64-${{ matrix.arch == 'x86' && 'i686' || 'x86_64' }}-gcc - mingw-w64-${{ matrix.arch == 'x86' && 'i686' || 'x86_64' }}-make - mingw-w64-x86_64-perl - perl - git - - - name: Checkout and Compile OpenSSL 1.1.1 - if: steps.cache-openssl.outputs.cache-hit != 'true' - shell: msys2 {0} - run: | - # Clone UMSKT's OpenSSL fork - echo "Cloning UMSKT OpenSSL fork..." - git clone https://github.com/UMSKT/openssl.git --branch OpenSSL_1_1_1-stable --depth 1 openssl-1.1.1 - # Set up proper MSYS2 environment - export MSYS=winsymlinks:nativestrict - export MSYS2_PATH_TYPE=strict - export LANG=en_US.UTF-8 - export PATH="/usr/bin:/mingw64/bin:$PATH" - export PATH="/c/TDM-GCC-64/bin:$PATH" - export MSYSTEM=${{ matrix.msystem }} - cd openssl-1.1.1 - - if [[ ${{ matrix.arch }} == "x86" ]]; then - /usr/bin/perl Configure mingw --prefix=$(cygpath -u "$GITHUB_WORKSPACE")/OpenSSL-TDM-${{ matrix.arch }} --openssldir=$(cygpath -u "$GITHUB_WORKSPACE")/OpenSSL-TDM-32 no-tests no-sse2 no-asm no-threads -DOPENSSL_DEV_NO_ATOMICS -mno-mmx -mno-sse -mno-sse2 -march=i686 -mtune=generic -Os -s -fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -ffunction-sections -fdata-sections -fno-stack-protector - else - /usr/bin/perl Configure mingw64 --prefix=$(cygpath -u "$GITHUB_WORKSPACE")/OpenSSL-TDM-${{ matrix.arch }} --openssldir=$(cygpath -u "$GITHUB_WORKSPACE")/OpenSSL-TDM-64 no-tests no-asm -DOPENSSL_DEV_NO_ATOMICS -mno-mmx -Os -s -fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -ffunction-sections -fdata-sections -fno-stack-protector - fi - mingw32-make -j - mingw32-make install_sw - cd .. - - - name: Save OpenSSL 1.1.1 - if: steps.cache-openssl.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 - with: - path: | - ${{ github.workspace }}/OpenSSL-TDM-${{ matrix.arch }} - key: openssl-1.1.1-${{ matrix.arch }}-${{ hashFiles('**/CMakeLists.txt') }} - - - name: Set OpenSSL Environment - shell: pwsh - run: | - echo "OPENSSL_ROOT=$env:GITHUB_WORKSPACE/OpenSSL-TDM-${{ matrix.arch }}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "OPENSSL_LIBDIR=lib" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "CMAKE_FLAGS=${{ matrix.cmake_flag }}" | Out-File -FilePath $env:GITHUB_ENV -Append + uses: actions/checkout@v7 - name: Configure UMSKT shell: pwsh run: | $env:PATH = 'C:\TDM-GCC-64\bin;' + $env:PATH - $OPENSSL_ROOT = "$env:OPENSSL_ROOT" - $OPENSSL_LIB = "$OPENSSL_ROOT/$env:OPENSSL_LIBDIR" - $OPENSSL_INC = "$OPENSSL_ROOT/include" cmake -G "MinGW Makefiles" ` -DCMAKE_C_COMPILER="gcc" ` -DCMAKE_CXX_COMPILER="g++" ` - -DOPENSSL_ROOT_DIR="$OPENSSL_ROOT" ` - -DOPENSSL_INCLUDE_DIR="$OPENSSL_INC" ` - -DOPENSSL_CRYPTO_LIBRARY="$OPENSSL_LIB/libcrypto.a" ` - -DOPENSSL_SSL_LIBRARY="$OPENSSL_LIB/libssl.a" ` - -DCMAKE_C_FLAGS="$env:CMAKE_FLAGS" ` - -DCMAKE_CXX_FLAGS="$env:CMAKE_FLAGS" ` + -DCMAKE_C_FLAGS="${{ matrix.cmake_flag }}" ` + -DCMAKE_CXX_FLAGS="${{ matrix.cmake_flag }}" ` -DCMAKE_POLICY_VERSION_MINIMUM="3.5" ` . @@ -150,92 +82,38 @@ jobs: mingw32-make - name: Run tests + timeout-minutes: 2 shell: pwsh run: | + $PSNativeCommandUseErrorActionPreference = $false Write-Host Test 1 - generating key - .\umskt.exe -b 2C -c 365 -s 069420 -v - Write-Host Test 2 - generatng confid - .\umskt.exe -i 253286028742154311079061239762245184619981623171292574 + $keyRaw = & .\umskt.exe -b 2C -c 365 -s 069420 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { + Write-Host "key generation exited ${LASTEXITCODE}: $keyRaw" + exit 1 + } + $key = $keyRaw.Trim() + if ($key -notmatch '^[BCDFGHJKMPQRTVWXY2346789-]{29}$') { + Write-Host "key generation produced no key on stdout: [$key]" + exit 1 + } + Write-Host Test 2 - validating that key + .\umskt.exe -b 2C -V $key + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + Write-Host Test 3 - confirmation ID + $cidRaw = & .\umskt.exe -i 253286028742154311079061239762245184619981623171292574 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { + Write-Host "confirmation ID exited ${LASTEXITCODE}: $cidRaw" + exit 1 + } + $cid = $cidRaw.Trim() + if ($cid -ne '020052-539422-707952-510766-409626-387731-998883') { + Write-Host "confirmation ID mismatch: [$cid]" + exit 1 + } - name: Upload build artifact - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v7 with: name: UMSKT-WinNT-${{ matrix.arch }} - path: umskt.exe - - compress: - needs: build - if: success() - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - include: - - arch: x86 - - arch: x64 - steps: - - name: Setup TDM-GCC - run: | - Write-Host Downloading TDM-GCC v10.3.0... - Invoke-WebRequest -Uri 'https://github.com/jmeubank/tdm-gcc/releases/download/v10.3.0-tdm64-2/tdm64-gcc-10.3.0-2.exe' -OutFile 'C:\Windows\temp\TDM-GCC-64.exe' - Write-Host Creating directory... - New-Item -ItemType Directory -Path 'C:\TDM-GCC-64' - Write-Host Copying files [Set 1/3]... - Start-Process '7z' -ArgumentList 'e C:\Windows\temp\TDM-GCC-64.exe -oC:\TDM-GCC-64 -y' -Wait - Write-Host Copying files [Set 2/3]... - Start-Process '7z' -ArgumentList 'e C:\TDM-GCC-64\*.tar.xz -oC:\TDM-GCC-64 -y' -Wait - Write-Host Copying files [Set 3/3]... - Start-Process '7z' -ArgumentList 'x C:\TDM-GCC-64\*.tar -oC:\TDM-GCC-64 -y' -Wait - Write-Host Adding environment variables... - $env:PATH = 'C:\TDM-GCC-64\bin;' + $env:PATH - [Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine) - - - name: Setup UPX - run: | - Invoke-WebRequest -Uri 'https://github.com/upx/upx/releases/download/v5.0.2/upx-5.0.2-win64.zip' -OutFile 'C:\Windows\temp\upx.zip' - Write-Host Creating directory... - New-Item -ItemType Directory -Path 'C:\UPX' - Write-Host Copying files... - Expand-Archive -Path 'C:\Windows\temp\upx.zip' -DestinationPath 'C:\UPX' - Write-Host Adding environment variables... - $env:PATH = 'C:\UPX\upx-5.0.2-win64;' + $env:PATH - [Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine) - - - name: Download ${{matrix.arch}} artifact - uses: actions/download-artifact@v4 - with: - name: UMSKT-WinNT-${{ matrix.arch }} - path: . - - name: Compress binaries - shell: pwsh - run: | - # Strip symbols - strip .\umskt.exe - - # Remove resources - llvm-objcopy --remove-section .rsrc umskt.exe umskt_comp.exe - - # Compress with UPX - & "C:\UPX\upx-5.0.2-win64\upx.exe" --best --ultra-brute .\umskt_comp.exe - - # Replace original EXE - Remove-Item .\umskt.exe - Rename-Item -Path .\umskt_comp.exe -NewName umskt.exe - - - - - name: Run tests - shell: pwsh - run: | - Write-Host Test 1 - generating key `(BINK1998`) - .\umskt.exe -b 2C -c 365 -s 069420,999999 -v - Write-Host Test 2 - generating key `(BINK2002`) - .\umskt.exe -b 5A -c 640 -s 069420,999999 -v - Write-Host Test 3 - generatng confid - .\umskt.exe -i 253286028742154311079061239762245184619981623171292574 - - - name: Upload build artifact - uses: actions/upload-artifact@v4.6.2 - with: - name: UMSKT-WinNT-${{ matrix.arch }}-Compressed - path: umskt.exe + path: umskt.exe \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bcc8bb..5ff7e80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,6 @@ if (WIN32 AND NOT MSVC) # Match resource architecture with target architecture if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -F pe-i386 --target=pe-i386") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=i686 -mtune=generic -mno-sse -mno-sse2 -mno-mmx -mno-3dnow") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=i686 -mtune=generic -mno-sse -mno-sse2 -mno-mmx -mno-3dnow") else() set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -F pe-x86-64 --target=pe-x86-64") endif() @@ -57,9 +55,8 @@ SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_OSX_SYSROOT "macosx" CACHE PATH "macOS SDK path") OPTION(BUILD_SHARED_LIBS "Build internal libraries as dynamic" OFF) -OPTION(UMSKT_USE_SHARED_OPENSSL "Force linking against the system-wide OpenSSL library" 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(DJGPP "Build the MS-DOS executable with DJGPP" OFF) OPTION(MSVC_MSDOS_STUB "Specify a custom MS-DOS stub for a 32-bit MSVC compilation" OFF) OPTION(WINDOWS_ARM "Enable compilation for Windows on ARM (requires appropriate toolchain)" OFF) @@ -71,21 +68,6 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip") endif() -# neither does dos idk i'm trying random stuff -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() - # Configure ARM-specific settings if enabled if (WINDOWS_ARM) if (MSVC) @@ -103,18 +85,17 @@ if (WINDOWS_ARM) message(STATUS "[UMSKT] Configuring for Windows on ARM") endif() -IF(DJGPP_WATT32) +IF(DJGPP) SET(CMAKE_SYSTEM_NAME MSDOS) SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS} ${DJGPP_WATT32}) - SET(UMSKT_LINK_DIRS ${UMSKT_LINK_DIRS} ${WATT_ROOT}/lib) - - # Maximum compatibility flags for DOS/NTVDM - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=i386 -mtune=i386 -mno-sse -mno-sse2 -mno-mmx -mno-3dnow -mno-ssse3 -mno-sse3 -mno-sse4 -mno-sse4.1 -mno-sse4.2 -mno-avx -mno-avx2 -mno-fma -mno-fma4") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=i386 -mtune=i386 -mno-sse -mno-sse2 -mno-mmx -mno-3dnow -mno-ssse3 -mno-sse3 -mno-sse4 -mno-sse4.1 -mno-sse4.2 -mno-avx -mno-avx2 -mno-fma -mno-fma4") + # The DOS workflow has no CMAKE_BUILD_TYPE, so without an explicit -O the + # i386 binary is unoptimized and DOSBox 0.74 cannot finish key generation + # inside its 30 second timeout. + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -march=i386 -mtune=i386 -mno-sse -mno-sse2 -mno-mmx -mno-3dnow -mno-ssse3 -mno-sse3 -mno-sse4 -mno-sse4.1 -mno-sse4.2 -mno-avx -mno-avx2 -mno-fma -mno-fma4") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -march=i386 -mtune=i386 -mno-sse -mno-sse2 -mno-mmx -mno-3dnow -mno-ssse3 -mno-sse3 -mno-sse4 -mno-sse4.1 -mno-sse4.2 -mno-avx -mno-avx2 -mno-fma -mno-fma4") MESSAGE(STATUS "[UMSKT] Using i386 target for DOS DGJPP with stub: ${CWSDSTUB_LOCATION}") ENDIF() @@ -136,14 +117,11 @@ IF(MSVC) ENDIF() SET(CMAKE_EXE_LINKER_FLAGS "/INCREMENTAL:NO /NODEFAULTLIB:MSVCRT /OPT:REF /OPT:ICF") SET(CMAKE_ENABLE_EXPORTS ON) - SET(UMSKT_EXE_WINDOWS_EXTRA src/windows/umskt.rc) - SET(UMSKT_EXE_WINDOWS_DLL src/windows/dllmain.cpp) ENDIF() IF(MUSL_STATIC) MESSAGE(STATUS "[UMSKT] Performing a fully static build using muslc") 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++") @@ -152,150 +130,167 @@ IF(MUSL_STATIC) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") ENDIF() -# 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() +# MinGW on Windows is one freestanding pipeline, compiled -m32 and -m64. +# x86 is the Windows 98 / NT 4.0 PE. x64 uses the same kernel32-only link. +# Every other target uses the same sources with the platform C library. +if (WIN32 AND NOT MSVC AND NOT DJGPP) + SET(UMSKT_FREESTANDING ON) +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() - -# initalize cpm.CMake -INCLUDE(cmake/CPM.cmake) - -# fetch cpm.CMake dependencies -# Include JSON development library -CPMAddPackage( - NAME nlohmann_json - GITHUB_REPOSITORY nlohmann/json - VERSION 3.11.2 +set(UMSKT_KEYS_EMBED "${CMAKE_BINARY_DIR}/keys_embed.cpp") +add_custom_command( + OUTPUT "${UMSKT_KEYS_EMBED}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${CMAKE_SOURCE_DIR}/keys.json -DOUTPUT=${UMSKT_KEYS_EMBED} -P ${CMAKE_SOURCE_DIR}/cmake/embed_keys.cmake + DEPENDS "${CMAKE_SOURCE_DIR}/keys.json" "${CMAKE_SOURCE_DIR}/cmake/embed_keys.cmake" + VERBATIM ) -# Include fmt development library -CPMAddPackage( - NAME fmt - GITHUB_REPOSITORY fmtlib/fmt - GIT_TAG 10.0.0 - VERSION 10.0.0 - #OPTIONS "FMT_INSTALL YES" "BUILD_SHARED_LIBS OFF" +set(UMSKT_SOURCES + src/cli.cpp + src/windows/json.cpp + ${UMSKT_KEYS_EMBED} + src/crypto/mini_bn.cpp + src/crypto/mini_ec.cpp + src/crypto/mini_rand.cpp + src/libumskt/libumskt.cpp + src/libumskt/pidgen3/BINK1998.cpp + src/libumskt/pidgen3/BINK2002.cpp + src/libumskt/pidgen3/key.cpp + src/libumskt/pidgen3/util.cpp + src/libumskt/confid/confid.cpp + src/libumskt/pidgen2/PIDGEN2.cpp + src/libumskt/debugoutput.cpp ) -# Include cmrc resource compiler -CPMAddPackage( - NAME cmrc - GITHUB_REPOSITORY vector-of-bool/cmrc - GIT_TAG 2.0.1 - VERSION 2.0.1 -) +if (UMSKT_FREESTANDING) + list(APPEND UMSKT_SOURCES src/windows/crt_win32.cpp src/windows/umskt.rc) +elseif (WIN32) + list(APPEND UMSKT_SOURCES src/windows/umskt.rc) +endif() -# Include Crypto++ development library -#CPMAddPackage( -# NAME cryptopp-cmake -# GITHUB_REPOSITORY abdes/cryptopp-cmake -# GIT_TAG CRYPTOPP_8_8_0 -# VERSION 8.8.0 -# OPTIONS "CRYPTOPP_BUILD_TESTING OFF" -#) +add_executable(umskt ${UMSKT_SOURCES}) -#include googletest unit testing library -#CPMAddPackage( -# NAME googletest -# GITHUB_REPOSITORY google/googletest -# VERSION 1.13.0 -# OPTIONS "INSTALL_GTEST OFF" "gtest_force_shared_crt" -#) +if (UMSKT_FREESTANDING) + set(CMAKE_C_STANDARD_LIBRARIES "") + set(CMAKE_CXX_STANDARD_LIBRARIES "") -### Resource compilation -CMRC_ADD_RESOURCE_LIBRARY(umskt-rc ALIAS umskt::rc NAMESPACE umskt keys.json) - -SET(LIBUMSKT_SRC src/libumskt/libumskt.cpp src/libumskt/pidgen3/BINK1998.cpp src/libumskt/pidgen3/BINK2002.cpp src/libumskt/pidgen3/key.cpp src/libumskt/pidgen3/util.cpp src/libumskt/confid/confid.cpp src/libumskt/pidgen2/PIDGEN2.cpp src/libumskt/debugoutput.cpp) - -#### Separate Build Path for emscripten -IF (EMSCRIPTEN) - ADD_EXECUTABLE(umskt ${LIBUMSKT_SRC}) - TARGET_INCLUDE_DIRECTORIES(umskt PUBLIC ${OPENSSL_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(umskt -static OpenSSL::Crypto cryptopp::cryptopp fmt) - SET(CMAKE_EXECUTABLE_SUFFIX ".html") - - SET_TARGET_PROPERTIES(umskt PROPERTIES COMPILE_FLAGS "-Os -sEXPORTED_RUNTIME_METHODS=ccall,cwrap") - SET_TARGET_PROPERTIES(umskt PROPERTIES LINK_FLAGS "-Os -sWASM=1 -sEXPORT_ALL=1 -sEXPORTED_RUNTIME_METHODS=ccall,cwrap --no-entry") -ELSE() - ADD_LIBRARY(_umskt ${LIBUMSKT_SRC} ${UMSKT_EXE_WINDOWS_EXTRA} ${UMSKT_EXE_WINDOWS_DLL}) - TARGET_INCLUDE_DIRECTORIES(_umskt PUBLIC ${OPENSSL_INCLUDE_DIR}) - TARGET_LINK_DIRECTORIES(_umskt PUBLIC ${UMSKT_LINK_DIRS}) - TARGET_LINK_LIBRARIES(_umskt ${OPENSSL_CRYPTO_LIBRARIES} fmt ${UMSKT_LINK_LIBS}) - - ### UMSKT executable compilation - ADD_EXECUTABLE(umskt src/main.cpp src/cli.cpp ${UMSKT_EXE_WINDOWS_EXTRA}) - TARGET_INCLUDE_DIRECTORIES(umskt PUBLIC ${OPENSSL_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(umskt _umskt ${OPENSSL_CRYPTO_LIBRARIES} ${ZLIB_LIBRARIES} fmt nlohmann_json::nlohmann_json umskt::rc ${UMSKT_LINK_LIBS}) - TARGET_LINK_DIRECTORIES(umskt PUBLIC ${UMSKT_LINK_DIRS}) - - # Link required Windows system libraries for OpenSSL - if (WIN32) - target_link_libraries(umskt crypt32 ws2_32) + set(UMSKT_WIN_X86 OFF) + if (CMAKE_SIZEOF_VOID_P EQUAL 4 OR CMAKE_C_FLAGS MATCHES "(^| )-m32( |$)" OR CMAKE_CXX_FLAGS MATCHES "(^| )-m32( |$)") + set(UMSKT_WIN_X86 ON) endif() - IF(MSVC AND MSVC_MSDOS_STUB) - SET_PROPERTY(TARGET umskt APPEND PROPERTY LINK_FLAGS /STUB:${MSVC_MSDOS_STUB}) - ENDIF() + set(UMSKT_FREE_FLAGS + -ffreestanding + -fno-exceptions + -fno-rtti + -fno-threadsafe-statics + -fno-use-cxa-atexit + -fno-stack-protector + -fno-unwind-tables + -fno-asynchronous-unwind-tables + -fno-tree-vectorize + -fno-tree-loop-distribute-patterns + ) + if (UMSKT_WIN_X86) + list(APPEND UMSKT_FREE_FLAGS + -m32 + -march=pentium + -mtune=pentium + -mfpmath=387 + -mno-mmx + -mno-sse + -mno-sse2 + -mno-3dnow + ) + set(UMSKT_WIN_ARCH x86) + else() + list(APPEND UMSKT_FREE_FLAGS -m64) + set(UMSKT_WIN_ARCH x64) + endif() - IF (${CMAKE_SYSTEM_NAME} MATCHES "Linux") - install(TARGETS umskt DESTINATION bin) - ENDIF() + target_compile_options(umskt PRIVATE ${UMSKT_FREE_FLAGS}) + target_compile_definitions(umskt PRIVATE + UMSKT_FREESTANDING + WINVER=0x0400 + _WIN32_WINNT=0x0400 + NTDDI_VERSION=0x04000000 + NDEBUG + ) + if (UMSKT_WIN_X86) + target_link_options(umskt PRIVATE + -m32 + -nostdlib + -nostartfiles + -nodefaultlibs + -Wl,--disable-auto-import + -Wl,--gc-sections + -Wl,--subsystem,console:4.0 + -Wl,--major-os-version,4 + -Wl,--minor-os-version,0 + -Wl,--major-subsystem-version,4 + -Wl,--minor-subsystem-version,0 + "SHELL:-Xlinker --stack -Xlinker 1048576,4096" + -Wl,-e,_mainCRTStartup + -Wl,--defsym,__init_array_start=0 + -Wl,--defsym,__init_array_end=0 + ) + else() + target_link_options(umskt PRIVATE + -m64 + -nostdlib + -nostartfiles + -nodefaultlibs + -Wl,--disable-auto-import + -Wl,--gc-sections + -Wl,--subsystem,console:5.2 + -Wl,--major-os-version,5 + -Wl,--minor-os-version,2 + -Wl,--major-subsystem-version,5 + -Wl,--minor-subsystem-version,2 + "SHELL:-Xlinker --stack -Xlinker 1048576,4096" + -Wl,-e,mainCRTStartup + -Wl,--defsym,__init_array_start=0 + -Wl,--defsym,__init_array_end=0 + ) + endif() + target_link_libraries(umskt PRIVATE kernel32) - ### Copy Shared Libraries and dependency files - IF (OPENSSL_CRYPTO_SHARED) - GET_FILENAME_COMPONENT(OPENSSL_CRYPTO_LIBRARY_FILENAME ${OPENSSL_CRYPTO_LIBRARY} NAME) - CONFIGURE_FILE(${OPENSSL_CRYPTO_LIBRARY} "${CMAKE_CURRENT_BINARY_DIR}/${OPENSSL_CRYPTO_LIBRARY_FILENAME}" COPYONLY) - ENDIF() - - IF (DJGPP_WATT32) + get_filename_component(UMSKT_GCC_BIN "${CMAKE_CXX_COMPILER}" DIRECTORY) + find_program(UMSKT_OBJDUMP NAMES objdump HINTS "${UMSKT_GCC_BIN}") + if (UMSKT_OBJDUMP) + add_custom_command(TARGET umskt POST_BUILD + COMMAND powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${CMAKE_SOURCE_DIR}/tools/check-win-baseline.ps1" -Exe "$" -Objdump "${UMSKT_OBJDUMP}" -Arch "${UMSKT_WIN_ARCH}" + COMMENT "Checking umskt for a kernel32-only import table" + VERBATIM + ) + endif() + message(STATUS "[UMSKT] Freestanding Windows ${UMSKT_WIN_ARCH} (kernel32 only)") +else() + if (DJGPP) message(STATUS "[UMSKT] Configuring DJGPP post-build commands") - # Set .exe suffix for DJGPP builds set_target_properties(umskt PROPERTIES SUFFIX ".exe") - add_custom_command(TARGET umskt POST_BUILD - # Convert exe to coff, removing the default stub + add_custom_command(TARGET umskt POST_BUILD COMMAND ${DJGPP_BIN_LOCATION}/exe2coff $ - # Concatenate CWSDSTUB with the COFF file COMMAND ${CMAKE_COMMAND} -E cat ${CWSDSTUB_LOCATION} $/umskt > $ - # Clean up the COFF file COMMAND ${CMAKE_COMMAND} -E rm $/umskt ) - message(STATUS "[UMSKT] STUBIFY_LOCATION set to: ${STUBIFY_LOCATION}") message(STATUS "[UMSKT] CWSDSTUB_LOCATION set to: ${CWSDSTUB_LOCATION}") message(STATUS "[UMSKT] DJGPP_BIN_LOCATION set to: ${DJGPP_BIN_LOCATION}") - ENDIF() -ENDIF() + endif() + + if (MSVC AND MSVC_MSDOS_STUB) + set_property(TARGET umskt APPEND PROPERTY LINK_FLAGS /STUB:${MSVC_MSDOS_STUB}) + endif() + + if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + install(TARGETS umskt DESTINATION bin) + endif() + + if (EMSCRIPTEN) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + set_target_properties(umskt PROPERTIES COMPILE_FLAGS "-Os") + set_target_properties(umskt PROPERTIES LINK_FLAGS "-Os -sWASM=1") + endif() + + message(STATUS "[UMSKT] Hosted build using the bundled curve code") +endif() diff --git a/Dockerfile b/Dockerfile index 81cea46..3079cd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,9 +27,7 @@ RUN apk add --no-cache \ build-base \ cmake \ git \ - musl-dev \ - openssl-dev \ - openssl-libs-static + musl-dev # Stage 2: Build diff --git a/Dockerfile.djgpp b/Dockerfile.djgpp index 4f0784a..2f277f6 100644 --- a/Dockerfile.djgpp +++ b/Dockerfile.djgpp @@ -67,54 +67,23 @@ RUN git clone https://github.com/andrewwutw/build-djgpp.git djgpp \ && ./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"] -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 -FROM watt32 as openssl -WORKDIR /tmp -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 +# Stage 3: compile UMSKT +FROM djgpp as build WORKDIR /src COPY . /src -ENV CC=/djgpp/bin/i386-pc-msdosdjgpp-gcc CXX=/djgpp/bin/i386-pc-msdosdjgpp-g++ PKG_CONFIG_PATH=/djgpp/lib/pkgconfig VERBOSE=1 +ENV CC=/djgpp/bin/i386-pc-msdosdjgpp-gcc CXX=/djgpp/bin/i386-pc-msdosdjgpp-g++ VERBOSE=1 SHELL ["/bin/bash", "-c"] -# Build UMSKT from the local directory RUN mkdir /src/build \ && cd /src/build \ && source /djgpp/setenv \ - && cmake -DDJGPP_WATT32=${WATT_ROOT}/lib/libwatt.a .. \ + && cmake -DDJGPP=ON .. \ && make CMD ["bash"] -# Stage 6: Output +# Stage 4: Output FROM scratch as output COPY --from=build /src/build/umskt.exe /umskt.exe diff --git a/Dockerfile.emscripten b/Dockerfile.emscripten index e7e6bad..af0bacf 100644 --- a/Dockerfile.emscripten +++ b/Dockerfile.emscripten @@ -18,31 +18,8 @@ # @FileCreated by Neo on 06/25/2023 # @Maintainer Neo -# Stage 1: Install Prerequisites -FROM emscripten/emsdk:latest as prerequisites -RUN apt update \ - && apt -y install pkg-config \ - && apt -y clean autoclean autoremove - -# Stage 2: compile OpenSSL for emscripten -FROM prerequisites as openssl -WORKDIR /tmp -ENV EMSCRIPTEN=/emsdk/upstream/emscripten VERBOSE=1 -RUN git clone https://github.com/openssl/openssl.git openssl \ - && cd openssl \ - && git checkout openssl-3.1.1 \ - && emconfigure ./Configure gcc -no-tests -no-asm -static -fPIC -no-sock -no-afalgeng -DOPENSSL_SYS_NETWARE -DSIG_DFL=0 -DSIG_IGN=0 -DHAVE_FORK=0 -DOPENSSL_NO_AFALGENG=1 --with-rand-seed=getrandom --prefix=$EMSCRIPTEN/cache/sysroot|| exit $? \ - && sed -i 's|^CROSS_COMPILE.*$|CROSS_COMPILE=|g' Makefile \ - && emmake make -j 12 build_generated libssl.a libcrypto.a build_libs_nodep \ - && rm -rf $EMSCRIPTEN/cache/sysroot/include/openssl \ - && cp -R include/openssl $EMSCRIPTEN/cache/sysroot/include \ - && cp libcrypto.a libssl.a $EMSCRIPTEN/cache/sysroot/lib \ - && cp libcrypto.pc libssl.pc openssl.pc $EMSCRIPTEN/cache/sysroot/lib/pkgconfig/ \ - && cd .. \ - && rm -rf openssl - -# Stage 3: compile UMSKT -FROM openssl as build +# Stage 1: compile UMSKT +FROM emscripten/emsdk:latest as build WORKDIR /src COPY . /src diff --git a/Dockerfile.windows b/Dockerfile.windows index 30711aa..6e94d63 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -40,16 +40,6 @@ 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 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 FROM prereqisites as Build32 WORKDIR C:\umskt\ @@ -67,7 +57,6 @@ FROM prereqisites as Build64 WORKDIR C:\umskt\ COPY . C:\umskt\ -ENV OPENSSL_ROOT_DIR="C:\Program Files\OpenSSL-Win64" RUN C:\BuildTools\Common7\Tools\VsDevCmd.bat && ` mkdir C:\umskt\build && ` cd C:\umskt\build && ` diff --git a/README.md b/README.md index 42f3ca1..4af3d1a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ | [![Windows ARM](https://github.com/UMSKT/UMSKT/actions/workflows/windows-arm.yml/badge.svg)](../../actions/workflows/windows-arm.yml) | | [![macOS](https://github.com/UMSKT/UMSKT/actions/workflows/macos.yml/badge.svg)](../../actions/workflows/macos.yml) | | [![Linux](https://github.com/UMSKT/UMSKT/actions/workflows/linux.yml/badge.svg)](../../actions/workflows/linux.yml) | -| [![FreeBSD](https://img.shields.io/badge/FreeBSD-unmaintained-D3D3D3?logo=github)](../../actions/workflows/freebsd.yml) | +| [![FreeBSD](https://github.com/UMSKT/UMSKT/actions/workflows/freebsd.yml/badge.svg)](../../actions/workflows/freebsd.yml) | | [![DOS DJGPP](https://github.com/UMSKT/UMSKT/actions/workflows/dos-djgpp.yml/badge.svg)](../../actions/workflows/dos-djgpp.yml) | ------ @@ -54,18 +54,17 @@ In light of the recent exponential interest in this project I've decided to put * MS-DOS 6.22 or later * Any DOS-based version of Windows * Windows NT 4.0 or later (via NTVDM) -#### Windows (x86/x64) -* i686 processor or better -* Windows XP or later - -> [!WARNING] -> Processors barely meeting the minimum system requirements for XP may not work. Use the MS-DOS version via NTVDM in that case. - +#### Windows (x86) +* Pentium processor or better +* Windows 98 First Edition or later +* Windows NT 4.0 SP6a or later +#### Windows (x64) +* Windows MT 5.2 (Windows XP x64, Windows Server 2003) or later #### Windows (ARM64) -* Windows 11 21H2 or later +* Winodws 11 21H2 or later #### macOS * Apple Silicon or x86_64 processor -* Latest version of macOS +* macOS 26 Tahoe or later #### Linux * modern ARM, x86, or x86_64 processor * Latest version of your Linux distro diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake deleted file mode 100644 index a3086b7..0000000 --- a/cmake/CPM.cmake +++ /dev/null @@ -1,33 +0,0 @@ -set(CPM_DOWNLOAD_VERSION 0.38.1) - -if(CPM_SOURCE_CACHE) - set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -elseif(DEFINED ENV{CPM_SOURCE_CACHE}) - set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -else() - set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -endif() - -# Expand relative path. This is important if the provided path contains a tilde (~) -get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) - -function(download_cpm) - message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") - file(DOWNLOAD - https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake - ${CPM_DOWNLOAD_LOCATION} - ) -endfunction() - -if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) - download_cpm() -else() - # resume download if it previously failed - file(READ ${CPM_DOWNLOAD_LOCATION} check) - if("${check}" STREQUAL "") - download_cpm() - endif() - unset(check) -endif() - -include(${CPM_DOWNLOAD_LOCATION}) diff --git a/cmake/embed_keys.cmake b/cmake/embed_keys.cmake new file mode 100644 index 0000000..e6d48ae --- /dev/null +++ b/cmake/embed_keys.cmake @@ -0,0 +1,10 @@ +if(NOT DEFINED INPUT OR NOT DEFINED OUTPUT) + message(FATAL_ERROR "embed_keys.cmake requires -DINPUT= and -DOUTPUT=") +endif() + +file(READ "${INPUT}" KEYS) +file(WRITE "${OUTPUT}" [=[extern const char umskt_keys_json[] = R"UMSKTJSON( +]=] "${KEYS}" [=[ +)UMSKTJSON"; +extern const int umskt_keys_json_size = (int)(sizeof(umskt_keys_json) - 1); +]=]) diff --git a/src/cli.cpp b/src/cli.cpp index 49cde08..9728b84 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -1,656 +1,958 @@ -/** - * This file is a part of the UMSKT Project +/* This file is a part of the UMSKT Project. * - * Copyleft (C) 2019-2023 UMSKT Contributors (et.al.) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * @FileCreated by Andrew on 01/06/2023 - * @Maintainer Neo + * Copyleft (C) 2019-2026 UMSKT Contributors (et.al.) + * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include "cli.h" +#include "libumskt/libumskt.h" +#include "libumskt/pidgen2/PIDGEN2.h" +#include "libumskt/pidgen3/PIDGEN3.h" +#include "libumskt/pidgen3/BINK1998.h" +#include "libumskt/pidgen3/BINK2002.h" +#include "libumskt/confid/confid.h" +#include "windows/json.h" +#include "keys_embed.h" +#ifdef UMSKT_FREESTANDING +#include "windows/win32_min.h" +#endif -CLI::~CLI() -{ - EC_GROUP_free(eCurve); - EC_POINT_free(genPoint); - EC_POINT_free(pubPoint); - BN_free(privateKey); - BN_free(genOrder); +#include +#include +#include +#include + +enum ACTIVATION_ALGORITHM { + WINDOWS = 0, + OFFICE_XP = 1, + OFFICE_2K3 = 2, + OFFICE_2K7 = 3, + PLUS_DME = 4, + OFFICE_ACC = 5 +}; + +enum MODE { + MODE_BINK1998_GENERATE = 0, + MODE_BINK2002_GENERATE = 1, + MODE_CONFIRMATION_ID = 2, + MODE_BINK1998_VALIDATE = 3, + MODE_BINK2002_VALIDATE = 4 +}; + +struct Options { + char binkid[8]; + char keysFilename[260]; + char instid[256]; + char keyToCheck[128]; + char productid[64]; + int channelID; + int serialMin; + int serialMax; + int numKeys; + int upgrade; + int serialSet; + int verbose; + int help; + int error; + int list; + int nonewlines; + int overrideVersion; + int nodashes; + int instidFromStdin; + MODE applicationMode; + ACTIVATION_ALGORITHM activationMode; +}; + +static void out_text(const char *text) { + if (!text) { + return; + } +#ifdef UMSKT_FREESTANDING + uint32_t wrote = 0; + void *handle = GetStdHandle(UMSKT_STD_OUTPUT_HANDLE); + if (handle == NULL || handle == UMSKT_INVALID_HANDLE) { + return; + } + WriteFile(handle, text, (uint32_t)strlen(text), &wrote, NULL); +#else + fputs(text, stdout); + fflush(stdout); +#endif } -bool CLI::loadJSON(const fs::path& filename, json *output) { - if (!filename.empty() && !fs::exists(filename)) { - fmt::print("ERROR: File {} does not exist\n", filename.string()); - return false; - } - else if (fs::exists(filename)) { - std::ifstream f(filename); - *output = json::parse(f, nullptr, false, false); - } - else if (filename.empty()) { - cmrc::embedded_filesystem fs = cmrc::umskt::get_filesystem(); - cmrc::file keys = fs.open("keys.json"); - *output = json::parse(keys, nullptr, false, false); - } - - if (output->is_discarded()) { - fmt::print("ERROR: Unable to parse keys from {}\n", filename.string()); - return false; - } - - return true; +static void outf(const char *fmt, ...) { + char buffer[2048]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + out_text(buffer); } - -void CLI::showHelp(char *argv[]) { - fmt::print("usage: {} \n", argv[0]); - fmt::print("\t-h --help\tshow this message\n"); - fmt::print("\t-v --verbose\tenable verbose output\n"); - fmt::print("\t-n --number\tnumber of keys to generate (defaults to 1)\n"); - fmt::print("\t-f --file\tspecify which keys file to load\n"); - fmt::print("\t-i --instid\tinstallation ID used to generate confirmation ID (reads from stdin if no argument provided)\n"); - fmt::print("\t-m --mode\tproduct family to activate.\n\t\t\tvalid options are \"WINDOWS\", \"OFFICEXP\", \"OFFICE2K3\", \"OFFICE2K7\", \"PLUSDME\", or \"OFFICEACC\"\n\t\t\t(defaults to \"WINDOWS\")\n"); - fmt::print("\t-p --productid\tthe product ID of the Program to activate. only required for Office 2K3 and Office 2K7 programs\n"); - fmt::print("\t-b --binkid\tspecify which BINK identifier to load (defaults to 2E)\n"); - fmt::print("\t-l --list\tshow which products/binks can be loaded\n"); - fmt::print("\t-c --channelid\tspecify which Channel Identifier to use (defaults to 640)\n"); - fmt::print("\t-s --serial\tspecifies a serial (eg. 123456) or comma-separated serial range\n\t\t\t(recommended for BINK2002, eg. 1234,5678) to use in the product ID (defaults to 0,999999)\n"); - fmt::print("\t-u --upgrade\tspecifies the Product Key will be an \"Upgrade\" version\n"); - fmt::print("\t-V --validate\tproduct key to validate signature\n"); - fmt::print("\t-N --nonewlines\tdisables newlines (for easier embedding in other apps)\n"); - fmt::print("\t-o --override\tDisables version check for confirmation IDs, if you need this send an issue on GitHub\n"); - fmt::print("\t-D --nodashes\tDisables dashes in product keys and confirmation IDs (for easier copy-pasting)"); - fmt::print("\n"); +static int streq(const char *a, const char *b) { + return strcmp(a, b) == 0; } -int CLI::parseCommandLine(int argc, char* argv[], Options* options) { - // set default options - *options = Options { - "2E", - "", - "", - "", - "", - 640, - 0, - 999999, - 1, - false, - false, - false, - false, - false, - false, - false, - false, - false, - MODE_BINK1998_GENERATE, - WINDOWS - }; +static void upper_copy(char *dst, const char *src, int cap) { + int i = 0; + if (!src) { + dst[0] = 0; + return; + } + for (; src[i] && i + 1 < cap; i++) { + char ch = src[i]; + if (ch >= 'a' && ch <= 'z') { + ch = (char)(ch - 32); + } + dst[i] = ch; + } + dst[i] = 0; +} - for (int i = 1; i < argc; i++) { - std::string arg = argv[i]; - if (arg == "-v" || arg == "--verbose") { - options->verbose = true; - UMSKT::setDebugOutput(stderr); - } else if (arg == "-h" || arg == "--help") { - options->help = true; - } else if (arg == "-n" || arg == "--number") { - if (i == argc - 1) { - options->error = true; +static int parse_int(const char *text, int *out) { + int value = 0; + int any = 0; + int neg = 0; + if (!text || !*text) { + return 0; + } + if (*text == '-') { + neg = 1; + text++; + } + while (*text >= '0' && *text <= '9') { + any = 1; + value = value * 10 + (*text - '0'); + text++; + } + if (!any) { + return 0; + } + *out = neg ? -value : value; + return 1; +} + +static int parse_hex(const char *text, int *out) { + int value = 0; + int any = 0; + if (!text) { + return 0; + } + while (*text) { + char ch = *text++; + int digit; + if (ch >= '0' && ch <= '9') { + digit = ch - '0'; + } else if (ch >= 'a' && ch <= 'f') { + digit = ch - 'a' + 10; + } else if (ch >= 'A' && ch <= 'F') { + digit = ch - 'A' + 10; + } else { + break; + } + any = 1; + value = (value << 4) | digit; + } + if (!any) { + return 0; + } + *out = value; + return 1; +} + +static char *span_dup(const char *text, int length) { + char *copy = (char *)malloc((size_t)length + 1); + if (!copy) { + return NULL; + } + memcpy(copy, text, (size_t)length); + copy[length] = 0; + return copy; +} + +static char *json_string_dup(const JsonSpan *value) { + const char *text; + int length; + if (!json_unquote(value, &text, &length)) { + return NULL; + } + return span_dup(text, length); +} + +static char *read_all(const char *path, int *length) { + char *data; + size_t size; +#ifdef UMSKT_FREESTANDING + void *file; + uint32_t read = 0; + uint32_t file_size; + file = CreateFileA(path, UMSKT_GENERIC_READ, UMSKT_FILE_SHARE_READ, NULL, UMSKT_OPEN_EXISTING, UMSKT_FILE_ATTRIBUTE_NORMAL, NULL); + if (file == UMSKT_INVALID_HANDLE) { + return NULL; + } + file_size = GetFileSize(file, NULL); + if (file_size == UMSKT_INVALID_FILE_SIZE || file_size > 16 * 1024 * 1024) { + CloseHandle(file); + return NULL; + } + size = file_size; + data = (char *)malloc(size + 1); + if (!data) { + CloseHandle(file); + return NULL; + } + if (!ReadFile(file, data, (uint32_t)size, &read, NULL) || read != (uint32_t)size) { + free(data); + CloseHandle(file); + return NULL; + } + CloseHandle(file); +#else + FILE *file = fopen(path, "rb"); + long file_size; + if (!file) { + return NULL; + } + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return NULL; + } + file_size = ftell(file); + if (file_size < 0 || file_size > 16 * 1024 * 1024) { + fclose(file); + return NULL; + } + size = (size_t)file_size; + if (fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return NULL; + } + data = (char *)malloc(size + 1); + if (!data) { + fclose(file); + return NULL; + } + if (fread(data, 1, size, file) != size) { + free(data); + fclose(file); + return NULL; + } + fclose(file); +#endif + data[size] = 0; + *length = (int)size; + return data; +} + +static char *read_embedded(int *length) { + char *data; + if (umskt_keys_json_size <= 0) { + return NULL; + } + data = (char *)malloc((size_t)umskt_keys_json_size + 1); + if (!data) { + return NULL; + } + memcpy(data, umskt_keys_json, (size_t)umskt_keys_json_size); + data[umskt_keys_json_size] = 0; + *length = umskt_keys_json_size; + return data; +} + +static int read_stdin(char *dst, int cap) { + int used = 0; + if (cap < 2) { + dst[0] = 0; + return 0; + } +#ifdef UMSKT_FREESTANDING + void *input = GetStdHandle(UMSKT_STD_INPUT_HANDLE); + if (input == NULL || input == UMSKT_INVALID_HANDLE) { + dst[0] = 0; + return 0; + } + while (used + 1 < cap) { + char ch; + uint32_t read = 0; + if (!ReadFile(input, &ch, 1, &read, NULL) || read == 0) { + break; + } + if (ch == '\n') { + break; + } + if (ch != '\r') { + dst[used++] = ch; + } + } +#else + if (!fgets(dst, cap, stdin)) { + dst[0] = 0; + return 0; + } + used = (int)strlen(dst); + while (used > 0 && (dst[used - 1] == '\n' || dst[used - 1] == '\r')) { + dst[--used] = 0; + } + return 1; +#endif + dst[used] = 0; + return 1; +} + +static void show_help(char *argv[]) { + outf("usage: %s \n", argv[0]); + out_text("\t-h --help\tshow this message\n"); + out_text("\t-v --verbose\tenable verbose output\n"); + out_text("\t-n --number\tnumber of keys to generate (defaults to 1)\n"); + out_text("\t-f --file\tspecify which keys file to load\n"); + out_text("\t-i --instid\tinstallation ID used to generate confirmation ID (reads from stdin if no argument provided)\n"); + out_text("\t-m --mode\tproduct family to activate.\n\t\t\tvalid options are \"WINDOWS\", \"OFFICEXP\", \"OFFICE2K3\", \"OFFICE2K7\", \"PLUSDME\", or \"OFFICEACC\"\n\t\t\t(defaults to \"WINDOWS\")\n"); + out_text("\t-p --productid\tthe product ID of the Program to activate. only required for Office 2K3 and Office 2K7 programs\n"); + out_text("\t-b --binkid\tspecify which BINK identifier to load (defaults to 2E)\n"); + out_text("\t-l --list\tshow which products/binks can be loaded\n"); + out_text("\t-c --channelid\tspecify which Channel Identifier to use (defaults to 640)\n"); + out_text("\t-s --serial\tspecifies a serial (eg. 123456) or comma-separated serial range\n\t\t\t(recommended for BINK2002, eg. 1234,5678) to use in the product ID (defaults to 0,999999)\n"); + out_text("\t-u --upgrade\tspecifies the Product Key will be an \"Upgrade\" version\n"); + out_text("\t-V --validate\tproduct key to validate signature\n"); + out_text("\t-N --nonewlines\tdisables newlines (for easier embedding in other apps)\n"); + out_text("\t-o --override\tDisables version check for confirmation IDs, if you need this send an issue on GitHub\n"); + out_text("\t-D --nodashes\tDisables dashes in product keys and confirmation IDs (for easier copy-pasting)"); + out_text("\n"); +} + +static void options_init(Options *options) { + memset(options, 0, sizeof(*options)); + strcpy(options->binkid, "2E"); + options->channelID = 640; + options->serialMax = 999999; + options->numKeys = 1; + options->applicationMode = MODE_BINK1998_GENERATE; + options->activationMode = WINDOWS; +} + +static int parse_command_line(int argc, char *argv[], Options *options) { + int i; + options_init(options); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (streq(arg, "-v") || streq(arg, "--verbose")) { + options->verbose = 1; + UMSKT::setDebug(1); + } else if (streq(arg, "-h") || streq(arg, "--help")) { + options->help = 1; + } else if (streq(arg, "-n") || streq(arg, "--number")) { + int count; + if (i == argc - 1 || !parse_int(argv[++i], &count)) { + options->error = 1; break; } - - int nKeys; - if (!sscanf(argv[i+1], "%d", &nKeys)) { - options->error = true; + options->numKeys = count; + } else if (streq(arg, "-b") || streq(arg, "--bink")) { + if (i == argc - 1) { + options->error = 1; + break; + } + upper_copy(options->binkid, argv[++i], (int)sizeof(options->binkid)); + } else if (streq(arg, "-l") || streq(arg, "--list")) { + options->list = 1; + } else if (streq(arg, "-c") || streq(arg, "--channelid")) { + int channel; + if (i == argc - 1 || !parse_int(argv[++i], &channel)) { + options->error = 1; + break; + } + options->channelID = channel; + } else if (streq(arg, "-s") || streq(arg, "--serial")) { + char *comma; + if (i == argc - 1) { + options->error = 1; + break; + } + i++; + comma = strchr(argv[i], ','); + if (comma) { + int serial_min; + int serial_max; + *comma = 0; + if (!parse_int(argv[i], &serial_min) || !parse_int(comma + 1, &serial_max)) { + options->error = 1; + } else { + options->serialSet = 1; + options->serialMin = serial_min; + options->serialMax = serial_max; + } + *comma = ','; } else { - options->numKeys = nKeys; + int serial; + if (!parse_int(argv[i], &serial)) { + options->error = 1; + } else { + options->serialSet = 1; + options->serialMin = serial; + options->serialMax = serial; + } } - i++; - } else if (arg == "-b" || arg == "--bink") { + } else if (streq(arg, "-u") || streq(arg, "--upgrade")) { + options->upgrade = 1; + } else if (streq(arg, "-f") || streq(arg, "--file")) { if (i == argc - 1) { - options->error = true; + options->error = 1; break; } - - options->binkid = argv[i+1]; - i++; - } else if (arg == "-l" || arg == "--list") { - options->list = true; - } else if (arg == "-c" || arg == "--channelid") { - if (i == argc - 1) { - options->error = true; - break; - } - - int siteID; - if (!sscanf(argv[i+1], "%d", &siteID)) { - options->error = true; - } else { - options->channelID = siteID; - } - i++; - } else if (arg == "-s" || arg == "--serial") { - if (i == argc - 1) { - options->error = true; - break; - } - - int serial_min; - int serial_max; - if (strchr(argv[i+1], ',') && sscanf(argv[i + 1], "%d,%d", &serial_min, &serial_max)) { - options->serialSet = true; - options->serialMin = serial_min; - options->serialMax = serial_max; - } - else if (sscanf(argv[i + 1], "%d", &serial_min)) { - options->serialSet = true; - options->serialMin = serial_min; - options->serialMax = serial_min; - } - else { - options->error = true; - } - i++; - } else if (arg == "-u" || arg == "--upgrade") { - options->upgrade = true; - } else if (arg == "-f" || arg == "--file") { - if (i == argc - 1) { - options->error = true; - break; - } - - options->keysFilename = argv[i+1]; - i++; - } else if (arg == "-i" || arg == "--instid") { + strncpy(options->keysFilename, argv[++i], sizeof(options->keysFilename) - 1); + } else if (streq(arg, "-i") || streq(arg, "--instid")) { options->applicationMode = MODE_CONFIRMATION_ID; - if (i == argc - 1 || argv[i+1][0] == '-') { - // No argument provided, will read from stdin later - options->instid = ""; + if (i == argc - 1 || argv[i + 1][0] == '-') { + options->instidFromStdin = 1; } else { - options->instid = argv[i+1]; - i++; + strncpy(options->instid, argv[++i], sizeof(options->instid) - 1); } - } else if (arg == "-m" || arg == "--mode") { - std::string mode = argv[i+1]; - char *p = &mode[0]; - for (; *p; p++) { - *p = toupper((unsigned char)*p); - } - p = &mode[0]; - if (strcmp(p, "WINDOWS") == 0) { - options->activationMode = WINDOWS; - } else if (strcmp(p, "OFFICEXP") == 0) { - options->activationMode = OFFICE_XP; - } else if (strcmp(p, "OFFICE2K3") == 0) { - options->activationMode = OFFICE_2K3; - } else if (strcmp(p, "OFFICE2K7") == 0) { - options->activationMode = OFFICE_2K7; - } else if (strcmp(p, "PLUSDME") == 0) { - options->activationMode = PLUS_DME; - } else if (strcmp(p, "OFFICEACC") == 0) { - options->activationMode = OFFICE_ACC; - } - i++; - } else if (arg == "-p" || arg == "--productid") { - if (i == argc -1) { - options->error = true; - break; - } - options->productid = argv[i+1]; - i++; - } else if (arg == "-V" || arg == "--validate") { + } else if (streq(arg, "-m") || streq(arg, "--mode")) { + char mode[32]; if (i == argc - 1) { - options->error = true; + options->error = 1; break; } - - options->keyToCheck = argv[i+1]; + upper_copy(mode, argv[++i], (int)sizeof(mode)); + if (streq(mode, "WINDOWS")) { + options->activationMode = WINDOWS; + } else if (streq(mode, "OFFICEXP")) { + options->activationMode = OFFICE_XP; + } else if (streq(mode, "OFFICE2K3")) { + options->activationMode = OFFICE_2K3; + } else if (streq(mode, "OFFICE2K7")) { + options->activationMode = OFFICE_2K7; + } else if (streq(mode, "PLUSDME")) { + options->activationMode = PLUS_DME; + } else if (streq(mode, "OFFICEACC")) { + options->activationMode = OFFICE_ACC; + } + } else if (streq(arg, "-p") || streq(arg, "--productid")) { + if (i == argc - 1) { + options->error = 1; + break; + } + strncpy(options->productid, argv[++i], sizeof(options->productid) - 1); + } else if (streq(arg, "-V") || streq(arg, "--validate")) { + if (i == argc - 1) { + options->error = 1; + break; + } + strncpy(options->keyToCheck, argv[++i], sizeof(options->keyToCheck) - 1); options->applicationMode = MODE_BINK1998_VALIDATE; - i++; - - } else if (arg == "-N" || arg == "--nonewlines") { - options->nonewlines = true; - } else if (arg == "-o" || arg == "--override") { - options->overrideVersion = true; - } else if (arg == "-D" || arg == "--nodashes") { - options->nodashes = true; - } else { - options->error = true; + } else if (streq(arg, "-N") || streq(arg, "--nonewlines")) { + options->nonewlines = 1; + } else if (streq(arg, "-o") || streq(arg, "--override")) { + options->overrideVersion = 1; + } else if (streq(arg, "-D") || streq(arg, "--nodashes")) { + options->nodashes = 1; + } else { + options->error = 1; } } - - // make sure that a product id is entered for OFFICE_2K3 or OFFICE_2K7 IIDs - if ((options->activationMode == OFFICE_2K3 || options->activationMode == OFFICE_2K7) && (options->productid.empty() || options->instid.empty()) ) { - return options->error = true; + if ((options->activationMode == OFFICE_2K3 || options->activationMode == OFFICE_2K7) && + (options->productid[0] == 0 || options->instid[0] == 0)) { + options->error = 1; } - return !options->error; } -int CLI::validateCommandLine(Options* options, char *argv[], json *keys) { - if (options->help || options->error) { - if (options->error) { - fmt::print("error parsing command line options\n"); +static int bink_field(const char *json, int length, const char *bink, const char *field, const char *sub, char **out) { + JsonSpan root; + JsonSpan curve; + JsonSpan value; + JsonSpan nested; + if (!json_object_find(json, length, "BINK", &root)) { + return 0; + } + if (!json_object_find(root.p, root.n, bink, &curve)) { + return 0; + } + if (!json_object_find(curve.p, curve.n, field, &value)) { + return 0; + } + if (sub) { + if (!json_object_find(value.p, value.n, sub, &nested)) { + return 0; } - showHelp(argv); - return 1; + value = nested; } - - if (options->verbose) { - if(options->keysFilename.empty()) { - fmt::print("Loading internal keys file\n"); - } else { - fmt::print("Loading keys file {}\n", options->keysFilename); - } - } - - if (!loadJSON(options->keysFilename, keys)) { - return 2; - } - - if (options->verbose) { - if(options->keysFilename.empty()) { - fmt::print("Loaded internal keys file successfully\n"); - } else { - fmt::print("Loaded keys from {} successfully\n",options->keysFilename); - } - } - - if (options->list) { - for (auto el : (*keys)["Products"].items()) { - int id; - sscanf((el.value()["BINK"][0]).get().c_str(), "%x", &id); - std::cout << el.key() << ": " << el.value()["BINK"] << std::endl; - } - - fmt::print("\n\n"); - fmt::print("** Please note: any BINK ID other than 2E is considered experimental at this time **\n"); - fmt::print("\n"); - return 1; - } - - int intBinkID; - sscanf(options->binkid.c_str(), "%x", &intBinkID); - - // FE and FF are BINK 1998, but do not generate valid keys, so we throw an error - if (intBinkID >= 0xFE) { - fmt::print("ERROR: Terminal Services BINKs (FE and FF) are unsupported at this time\n"); - return 1; - } - - if (intBinkID >= 0x40) { - // set bink2002 validate mode if in bink1998 validate mode, else set bink2002 generate mode - options->applicationMode = (options->applicationMode == MODE_BINK1998_VALIDATE) ? MODE_BINK2002_VALIDATE : MODE_BINK2002_GENERATE; - } - - if (options->channelID > 999) { - fmt::print("ERROR: refusing to create a key with a Channel ID greater than 999\n"); - return 1; - } - - // don't allow any serial not between 0 and 999999 - if (options->serialMin > 999999 || options->serialMin < 0 || options->serialMax < 0 || options->serialMax > 999999) { - fmt::print("ERROR: refusing to create a key with a Serial not between 000000 and 999999\n"); - return 1; - } - - if (options->serialMin > options->serialMax) { - fmt::print("ERROR: serial minimum cannot be greater than serial maximum\n"); - return 1; - } - - return 0; + *out = json_string_dup(&value); + return *out != NULL; } -void CLI::printID(DWORD *pid) { +static void print_id(DWORD *pid) { char raw[12]; - char b[6], c[8]; - int i, digit = 0; - - // Convert PID to ascii-number (=raw) + char b[6]; + char c[8]; + int i; + int digit = 0; snprintf(raw, sizeof(raw), "%09u", pid[0]); - - // Make b-part {640-....} strncpy(b, raw, 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 - - while (digit < 0) - digit += 7; - c[6] = digit + '0'; - c[7] = 0; - - fmt::print("> Product ID: PPPPP-{}-{}-23xxx\n", b, c); -} - -void CLI::printKey(char *pk) { - assert(strlen(pk) >= PK_LENGTH); - std::string keyFormat = "{}-{}-{}-{}-{}"; - - if (this->options.nodashes == true) { - keyFormat = "{}{}{}{}{}"; + for (i = 0; i < 6; i++) { + digit -= c[i] - '0'; } - - std::string spk = pk; - fmt::print(keyFormat, - spk.substr(0,5), - spk.substr(5,5), - spk.substr(10,5), - spk.substr(15,5), - spk.substr(20,5)); + while (digit < 0) { + digit += 7; + } + c[6] = (char)(digit + '0'); + c[7] = 0; + outf("> Product ID: PPPPP-%s-%s-23xxx\n", b, c); } -bool CLI::stripKey(const char *in_key, char out_key[PK_LENGTH]) { - // copy out the product key stripping out extraneous characters +static void print_key(const char *pk, int nodashes) { + char groups[5][6]; + int i; + for (i = 0; i < 5; i++) { + memcpy(groups[i], pk + i * 5, 5); + groups[i][5] = 0; + } + if (nodashes) { + outf("%s%s%s%s%s", groups[0], groups[1], groups[2], groups[3], groups[4]); + } else { + outf("%s-%s-%s-%s-%s", groups[0], groups[1], groups[2], groups[3], groups[4]); + } +} + +static int strip_key(const char *in_key, char out_key[PK_LENGTH]) { const char *p = in_key; size_t i = 0; for (; *p; p++) { - // strip out space or dash - if (*p == ' ' || *p == '-') - continue; - // check if we've passed the product key length to avoid overflow - if (i >= PK_LENGTH) - return false; - // convert to uppercase - if character allowed, copy into array - for (int j = 0; j < strlen(PIDGEN3::pKeyCharset); j++) { - if (toupper(*p) == PIDGEN3::pKeyCharset[j]) { - out_key[i++] = toupper(*p); - continue; - } + int j; + if (*p == ' ' || *p == '-') { + continue; } - } - // only return true if we've handled exactly PK_LENGTH chars - return (i == PK_LENGTH); -} - -std::string CLI::readFromStdin() { - std::string input; - std::getline(std::cin, input); - return input; -} - -CLI::CLI(Options options, json keys) { - this->options = options; - this->keys = keys; - - this->BINKID = options.binkid.c_str(); - - // 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). - this->privateKey = BN_new(); - - // 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. - this->genOrder = BN_new(); - - /* Computed data */ - BN_dec2bn(&this->genOrder, this->keys["BINK"][this->BINKID]["n"]. get().c_str()); - BN_dec2bn(&this->privateKey, this->keys["BINK"][this->BINKID]["priv"].get().c_str()); - - if (options.verbose) { - fmt::print("----------------------------------------------------------- \n"); - fmt::print("Loaded the following elliptic curve parameters: BINK[{}]\n", this->BINKID); - fmt::print("----------------------------------------------------------- \n"); - fmt::print(" P: {}\n", this->keys["BINK"][this->BINKID]["p"].get()); - fmt::print(" a: {}\n", this->keys["BINK"][this->BINKID]["a"].get()); - fmt::print(" b: {}\n", this->keys["BINK"][this->BINKID]["b"].get()); - fmt::print("Gx: {}\n", this->keys["BINK"][this->BINKID]["g"]["x"].get()); - fmt::print("Gy: {}\n", this->keys["BINK"][this->BINKID]["g"]["y"].get()); - fmt::print("Kx: {}\n", this->keys["BINK"][this->BINKID]["pub"]["x"].get()); - fmt::print("Ky: {}\n", this->keys["BINK"][this->BINKID]["pub"]["y"].get()); - fmt::print(" n: {}\n", this->keys["BINK"][this->BINKID]["n"].get()); - fmt::print(" k: {}\n", this->keys["BINK"][this->BINKID]["priv"].get()); - fmt::print("\n"); - } - - - eCurve = PIDGEN3::initializeEllipticCurve( - this->keys["BINK"][this->BINKID]["p"].get(), - this->keys["BINK"][this->BINKID]["a"].get(), - this->keys["BINK"][this->BINKID]["b"].get(), - this->keys["BINK"][this->BINKID]["g"]["x"].get(), - this->keys["BINK"][this->BINKID]["g"]["y"].get(), - this->keys["BINK"][this->BINKID]["pub"]["x"].get(), - this->keys["BINK"][this->BINKID]["pub"]["y"].get(), - this->genPoint, - this->pubPoint - ); - - this->count = 0; - this->total = this->options.numKeys; -} - -int CLI::BINK1998Generate() { - // raw PID/serial value - DWORD nRaw = this->options.channelID * 1'000'000 ; /* <- change */ - DWORD serMin = this->options.serialMin; - DWORD serMax = this->options.serialMax; - - // using user-provided serial - if ((serMin == serMax) && this->options.serialSet) { - // just in case, make sure it's less than 1000000 - int serialRnd = (this->options.serialMin % 1000000); - nRaw += serialRnd; - } else { - // generate a random number to use as a serial - BIGNUM *bnrand = BN_new(); - UMSKT::umskt_bn_rand(bnrand, 19, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); - - int oRaw; - char *cRaw = BN_bn2dec(bnrand); - - sscanf(cRaw, "%d", &oRaw); - nRaw += serMin + (oRaw % (serMax - serMin + 1)); // ensure our serial is within specified range - BN_free(bnrand); - } - - if (this->options.verbose) { - // print the resulting Product ID - // PID value is printed in BINK1998::Generate - printID(&nRaw); - } - - // generate a key - BN_sub(this->privateKey, this->genOrder, this->privateKey); - - for (int i = 0; i < this->total; i++) { - PIDGEN3::BINK1998::Generate(this->eCurve, this->genPoint, this->genOrder, this->privateKey, nRaw, options.upgrade, this->pKey); - - bool isValid = PIDGEN3::BINK1998::Verify(this->eCurve, this->genPoint, this->pubPoint, this->pKey); - if (isValid) { - CLI::printKey(this->pKey); - if (i < this->total - 1 || this->options.verbose) { - fmt::print("\n"); - } - this->count += isValid; - } - else { - if (this->options.verbose) { - CLI::printKey(this->pKey); - fmt::print(" [Invalid]"); - if (i < this->total - 1) { - fmt::print("\n"); - } - } - this->total++; // queue a redo, basically - } - } - - if (this->options.verbose) { - fmt::print("\nSuccess count: {}/{}", this->count, this->total); - } - if (this->options.nonewlines == false) { - fmt::print("\n"); - } - return 0; -} - -int CLI::BINK2002Generate() { - DWORD pChannelID = this->options.channelID; - - if (this->options.verbose) { - fmt::print("> Channel ID: {:03d}\n", this->options.channelID); - } - - // generate a key - for (int i = 0; i < this->total; i++) { - DWORD pAuthInfo; - UMSKT::umskt_rand_bytes((BYTE *)&pAuthInfo, 4); - pAuthInfo &= BITMASK(10); - - if (this->options.verbose) { - fmt::print("> AuthInfo: {}\n", pAuthInfo); - } - - PIDGEN3::BINK2002::Generate(this->eCurve, this->genPoint, this->genOrder, this->privateKey, pChannelID, pAuthInfo, options.upgrade, this->options.serialMin, this->options.serialMax, this->pKey); - - bool isValid = PIDGEN3::BINK2002::Verify(this->eCurve, this->genPoint, this->pubPoint, NULL, this->pKey); - if (isValid) { - CLI::printKey(this->pKey); - if (i < this->total - 1 || this->options.verbose) { // check if end of list or verbose - fmt::print("\n"); - } - this->count += isValid; // add to count - } - else { - if (this->options.verbose) { - CLI::printKey(this->pKey); // print the key - fmt::print(" [Invalid]"); // and add " [Invalid]" to the key - if (i < this->total - 1) { // check if end of list - fmt::print("\n"); - } - } - this->total++; // queue a redo, basically - } - } - - if (this->options.verbose) { - fmt::print("\nSuccess count: {}/{}", this->count, this->total); - } - if (this->options.nonewlines == false) { - fmt::print("\n"); - } - - return 0; -} - -int CLI::BINK1998Validate() { - char product_key[PK_LENGTH]{}; - - if (!CLI::stripKey(this->options.keyToCheck.c_str(), product_key)) { - fmt::print("ERROR: Product key is in an incorrect format!\n"); - return 1; - } - - CLI::printKey(product_key); - fmt::print("\n"); - if (!PIDGEN3::BINK1998::Verify(this->eCurve, this->genPoint, this->pubPoint, product_key)) { - fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n"); - return 1; - } - - fmt::print("Key validated successfully!\n"); - return 0; -} - -int CLI::BINK2002Validate() { - char product_key[PK_LENGTH]{}; - - if (!CLI::stripKey(this->options.keyToCheck.c_str(), product_key)) { - fmt::print("ERROR: Product key is in an incorrect format!\n"); - return 1; - } - - CLI::printKey(product_key); - fmt::print("\n"); - if (!PIDGEN3::BINK2002::Verify(this->eCurve, this->genPoint, this->pubPoint, nullptr, product_key)) { - fmt::print("ERROR: Product key is invalid! Wrong BINK ID?\n"); - return 1; - } - - fmt::print("Key validated successfully!\n"); - return 0; -} - -int CLI::ConfirmationID() { - char confirmation_id[49]; - std::string instid = this->options.instid; - - // If instid is empty, read from stdin - if (instid.empty()) { - instid = readFromStdin(); - } - - int err = ConfirmationID::Generate(instid.c_str(), confirmation_id, options.activationMode, options.productid, options.overrideVersion); - - switch (err) { - case ERR_TOO_SHORT: - fmt::print("ERROR: Installation ID is too short.\n"); - return 1; - - case ERR_TOO_LARGE: - fmt::print("ERROR: Installation ID is too long.\n"); - return 1; - - case ERR_INVALID_CHARACTER: - fmt::print("ERROR: Invalid character in installation ID.\n"); - return 1; - - case ERR_INVALID_CHECK_DIGIT: - fmt::print("ERROR: Installation ID checksum failed. Please check that it is typed correctly.\n"); - return 1; - - case ERR_UNKNOWN_VERSION: - fmt::print("ERROR: Unknown installation ID version.\n"); - return 1; - - case ERR_UNLUCKY: - fmt::print("ERROR: Unable to generate valid confirmation ID.\n"); - return 1; - - case SUCCESS: - if (this->options.nodashes == true) { - int j = 0; - for (int i = 0; confirmation_id[i] != '\0'; ++i) { - if (confirmation_id[i] != '-') { - confirmation_id[j++] = confirmation_id[i]; - } - } - confirmation_id[j] = '\0'; - } - fmt::print(confirmation_id); - - if (this->options.nonewlines == false) { - fmt::print("\n"); - } + if (i >= PK_LENGTH) { return 0; + } + for (j = 0; PIDGEN3::pKeyCharset[j]; j++) { + char ch = *p; + if (ch >= 'a' && ch <= 'z') { + ch = (char)(ch - 32); + } + if (ch == PIDGEN3::pKeyCharset[j]) { + out_key[i++] = ch; + break; + } + } + } + return i == PK_LENGTH; +} - default: - fmt::print("Unknown error occurred during Confirmation ID generation: {}\n", err); +static int list_products(const char *json, int length) { + JsonSpan products; + int cursor = 0; + const char *key; + int key_length; + JsonSpan value; + if (!json_object_find(json, length, "Products", &products)) { + out_text("ERROR: Unable to parse keys from \n"); + return 2; + } + while (json_object_next(products.p, products.n, &cursor, &key, &key_length, &value)) { + JsonSpan binks; + int bink_cursor = 0; + JsonSpan item; + int first = 1; + char name[160]; + int copy = key_length < (int)sizeof(name) - 1 ? key_length : (int)sizeof(name) - 1; + memcpy(name, key, (size_t)copy); + name[copy] = 0; + outf("%s: [", name); + if (json_object_find(value.p, value.n, "BINK", &binks)) { + while (json_array_next(binks.p, binks.n, &bink_cursor, &item)) { + const char *text; + int text_len; + char item_text[16]; + int copy_len; + if (!json_unquote(&item, &text, &text_len)) { + continue; + } + copy_len = text_len < (int)sizeof(item_text) - 1 ? text_len : (int)sizeof(item_text) - 1; + memcpy(item_text, text, (size_t)copy_len); + item_text[copy_len] = 0; + if (!first) { + out_text(","); + } + first = 0; + out_text("\""); + out_text(item_text); + out_text("\""); + } + } + out_text("]\n"); + } + out_text("\n\n"); + out_text("** Please note: any BINK ID other than 2E is considered experimental at this time **\n"); + out_text("\n"); + return 1; +} + +struct Curve { + char *p; + char *a; + char *b; + char *gx; + char *gy; + char *kx; + char *ky; + char *order; + char *priv; +}; + +static void curve_free(Curve *curve) { + free(curve->p); + free(curve->a); + free(curve->b); + free(curve->gx); + free(curve->gy); + free(curve->kx); + free(curve->ky); + free(curve->order); + free(curve->priv); +} + +static int curve_load(const char *json, int length, const char *bink, Curve *curve) { + memset(curve, 0, sizeof(*curve)); + if (!bink_field(json, length, bink, "p", NULL, &curve->p) || + !bink_field(json, length, bink, "a", NULL, &curve->a) || + !bink_field(json, length, bink, "b", NULL, &curve->b) || + !bink_field(json, length, bink, "g", "x", &curve->gx) || + !bink_field(json, length, bink, "g", "y", &curve->gy) || + !bink_field(json, length, bink, "pub", "x", &curve->kx) || + !bink_field(json, length, bink, "pub", "y", &curve->ky) || + !bink_field(json, length, bink, "n", NULL, &curve->order) || + !bink_field(json, length, bink, "priv", NULL, &curve->priv)) { + curve_free(curve); + return 0; } return 1; } +static int run_keys(Options *options, const char *json, int length) { + Curve curve; + BIGNUM *privateKey = NULL; + BIGNUM *genOrder = NULL; + EC_POINT *genPoint = NULL; + EC_POINT *pubPoint = NULL; + EC_GROUP *eCurve = NULL; + char pKey[PK_LENGTH + NULL_TERMINATOR]; + int count = 0; + int total = options->numKeys; + int gaveUp = 0; + int i; + int bink_id = 0; + memset(pKey, 0, sizeof(pKey)); + if (!curve_load(json, length, options->binkid, &curve)) { + outf("ERROR: Unable to parse keys from %s\n", options->keysFilename); + return 2; + } + if (options->verbose) { + out_text("----------------------------------------------------------- \n"); + outf("Loaded the following elliptic curve parameters: BINK[%s]\n", options->binkid); + out_text("----------------------------------------------------------- \n"); + outf(" P: %s\n", curve.p); + outf(" a: %s\n", curve.a); + outf(" b: %s\n", curve.b); + outf("Gx: %s\n", curve.gx); + outf("Gy: %s\n", curve.gy); + outf("Kx: %s\n", curve.kx); + outf("Ky: %s\n", curve.ky); + outf(" n: %s\n", curve.order); + outf(" k: %s\n", curve.priv); + out_text("\n"); + } + privateKey = BN_new(); + genOrder = BN_new(); + BN_dec2bn(&genOrder, curve.order); + BN_dec2bn(&privateKey, curve.priv); + eCurve = PIDGEN3::initializeEllipticCurve(curve.p, curve.a, curve.b, curve.gx, curve.gy, curve.kx, curve.ky, genPoint, pubPoint); + parse_hex(options->binkid, &bink_id); + if (options->applicationMode == MODE_BINK1998_GENERATE) { + DWORD nRaw = (DWORD)options->channelID * 1000000u; + if ((options->serialMin == options->serialMax) && options->serialSet) { + nRaw += (DWORD)(options->serialMin % 1000000); + } else { + BIGNUM *bnrand = BN_new(); + char *cRaw; + int oRaw = 0; + UMSKT::umskt_bn_rand(bnrand, 19, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); + cRaw = BN_bn2dec(bnrand); + if (cRaw) { + parse_int(cRaw, &oRaw); + free(cRaw); + } + nRaw += (DWORD)(options->serialMin + (oRaw % (options->serialMax - options->serialMin + 1))); + BN_free(bnrand); + } + if (options->verbose) { + print_id(&nRaw); + } + BN_sub(privateKey, genOrder, privateKey); + for (i = 0; i < total; i++) { + int valid; + PIDGEN3::BINK1998::Generate(eCurve, genPoint, genOrder, privateKey, nRaw, options->upgrade, pKey); + valid = PIDGEN3::BINK1998::Verify(eCurve, genPoint, pubPoint, pKey); + if (valid) { + print_key(pKey, options->nodashes); + if (i < total - 1 || options->verbose) { + out_text("\n"); + } + count += valid; + } else { + if (options->verbose) { + print_key(pKey, options->nodashes); + out_text(" [Invalid]"); + if (i < total - 1) { + out_text("\n"); + } + } + total++; + if (total > options->numKeys + 64) { + gaveUp = 1; + break; + } + } + } + } else if (options->applicationMode == MODE_BINK2002_GENERATE) { + if (options->verbose) { + outf("> Channel ID: %03d\n", options->channelID); + } + for (i = 0; i < total; i++) { + DWORD pAuthInfo = 0; + int valid; + UMSKT::umskt_rand_bytes((BYTE *)&pAuthInfo, 4); + pAuthInfo &= BITMASK(10); + if (options->verbose) { + outf("> AuthInfo: %u\n", pAuthInfo); + } + PIDGEN3::BINK2002::Generate(eCurve, genPoint, genOrder, privateKey, (DWORD)options->channelID, pAuthInfo, options->upgrade, (DWORD)options->serialMin, (DWORD)options->serialMax, pKey); + valid = PIDGEN3::BINK2002::Verify(eCurve, genPoint, pubPoint, NULL, pKey); + if (valid) { + print_key(pKey, options->nodashes); + if (i < total - 1 || options->verbose) { + out_text("\n"); + } + count += valid; + } else { + if (options->verbose) { + print_key(pKey, options->nodashes); + out_text(" [Invalid]"); + if (i < total - 1) { + out_text("\n"); + } + } + total++; + if (total > options->numKeys + 64) { + gaveUp = 1; + break; + } + } + } + } else if (options->applicationMode == MODE_BINK1998_VALIDATE || options->applicationMode == MODE_BINK2002_VALIDATE) { + char product_key[PK_LENGTH + NULL_TERMINATOR]; + memset(product_key, 0, sizeof(product_key)); + if (!strip_key(options->keyToCheck, product_key)) { + out_text("ERROR: Product key is in an incorrect format!\n"); + curve_free(&curve); + BN_free(privateKey); + BN_free(genOrder); + EC_GROUP_free(eCurve); + EC_POINT_free(genPoint); + EC_POINT_free(pubPoint); + return 1; + } + print_key(product_key, 0); + out_text("\n"); + if (options->applicationMode == MODE_BINK1998_VALIDATE) { + if (!PIDGEN3::BINK1998::Verify(eCurve, genPoint, pubPoint, product_key)) { + out_text("ERROR: Product key is invalid! Wrong BINK ID?\n"); + curve_free(&curve); + BN_free(privateKey); + BN_free(genOrder); + EC_GROUP_free(eCurve); + EC_POINT_free(genPoint); + EC_POINT_free(pubPoint); + return 1; + } + } else if (!PIDGEN3::BINK2002::Verify(eCurve, genPoint, pubPoint, NULL, product_key)) { + out_text("ERROR: Product key is invalid! Wrong BINK ID?\n"); + curve_free(&curve); + BN_free(privateKey); + BN_free(genOrder); + EC_GROUP_free(eCurve); + EC_POINT_free(genPoint); + EC_POINT_free(pubPoint); + return 1; + } + out_text("Key validated successfully!\n"); + curve_free(&curve); + BN_free(privateKey); + BN_free(genOrder); + EC_GROUP_free(eCurve); + EC_POINT_free(genPoint); + EC_POINT_free(pubPoint); + return 0; + } + + if (gaveUp || count < options->numKeys) { + out_text("ERROR: failed to generate a valid product key\n"); + curve_free(&curve); + BN_free(privateKey); + BN_free(genOrder); + EC_GROUP_free(eCurve); + EC_POINT_free(genPoint); + EC_POINT_free(pubPoint); + return 1; + } + if (options->verbose) { + outf("\nSuccess count: %d/%d", count, total); + } + if (!options->nonewlines) { + out_text("\n"); + } + curve_free(&curve); + BN_free(privateKey); + BN_free(genOrder); + EC_GROUP_free(eCurve); + EC_POINT_free(genPoint); + EC_POINT_free(pubPoint); + (void)bink_id; + return 0; +} + +static int run_confirmation(Options *options) { + char confirmation_id[49]; + char instid[256]; + int err; + int i; + int j; + if (options->instidFromStdin || options->instid[0] == 0) { + read_stdin(instid, (int)sizeof(instid)); + } else { + strncpy(instid, options->instid, sizeof(instid) - 1); + instid[sizeof(instid) - 1] = 0; + } + err = ConfirmationID::Generate(instid, confirmation_id, options->activationMode, options->productid, options->overrideVersion != 0); + switch (err) { + case ERR_TOO_SHORT: + out_text("ERROR: Installation ID is too short.\n"); + return 1; + case ERR_TOO_LARGE: + out_text("ERROR: Installation ID is too long.\n"); + return 1; + case ERR_INVALID_CHARACTER: + out_text("ERROR: Invalid character in installation ID.\n"); + return 1; + case ERR_INVALID_CHECK_DIGIT: + out_text("ERROR: Installation ID checksum failed. Please check that it is typed correctly.\n"); + return 1; + case ERR_UNKNOWN_VERSION: + out_text("ERROR: Unknown installation ID version.\n"); + return 1; + case ERR_UNLUCKY: + out_text("ERROR: Unable to generate valid confirmation ID.\n"); + return 1; + case SUCCESS: + if (options->nodashes) { + j = 0; + for (i = 0; confirmation_id[i]; i++) { + if (confirmation_id[i] != '-') { + confirmation_id[j++] = confirmation_id[i]; + } + } + confirmation_id[j] = 0; + } + out_text(confirmation_id); + if (!options->nonewlines) { + out_text("\n"); + } + return 0; + default: + outf("Unknown error occurred during Confirmation ID generation: %d\n", err); + return 1; + } +} + +int main(int argc, char *argv[]) { + Options options; + char *json = NULL; + int length = 0; + int status; + int bink_id = 0; + if (!parse_command_line(argc, argv, &options)) { + out_text("error parsing command line options\n"); + show_help(argv); + return options.error ? 1 : 0; + } + if (options.help) { + show_help(argv); + return 1; + } + if (options.verbose) { + if (options.keysFilename[0] == 0) { + out_text("Loading internal keys file\n"); + } else { + outf("Loading keys file %s\n", options.keysFilename); + } + } + if (options.keysFilename[0] == 0) { + json = read_embedded(&length); + } else { + json = read_all(options.keysFilename, &length); + if (!json) { + outf("ERROR: File %s does not exist\n", options.keysFilename); + return 2; + } + } + if (!json) { + outf("ERROR: Unable to parse keys from %s\n", options.keysFilename); + return 2; + } + if (options.verbose) { + if (options.keysFilename[0] == 0) { + out_text("Loaded internal keys file successfully\n"); + } else { + outf("Loaded keys from %s successfully\n", options.keysFilename); + } + } + if (options.list) { + status = list_products(json, length); + free(json); + return status; + } + parse_hex(options.binkid, &bink_id); + if (bink_id >= 0xFE) { + out_text("ERROR: Terminal Services BINKs (FE and FF) are unsupported at this time\n"); + free(json); + return 1; + } + if (bink_id >= 0x40 && options.applicationMode != MODE_CONFIRMATION_ID) { + options.applicationMode = (options.applicationMode == MODE_BINK1998_VALIDATE) ? MODE_BINK2002_VALIDATE : MODE_BINK2002_GENERATE; + } + if (options.channelID > 999) { + out_text("ERROR: refusing to create a key with a Channel ID greater than 999\n"); + free(json); + return 1; + } + if (options.serialMin > 999999 || options.serialMin < 0 || options.serialMax < 0 || options.serialMax > 999999) { + out_text("ERROR: refusing to create a key with a Serial not between 000000 and 999999\n"); + free(json); + return 1; + } + if (options.serialMin > options.serialMax) { + out_text("ERROR: serial minimum cannot be greater than serial maximum\n"); + free(json); + return 1; + } + if (options.applicationMode == MODE_CONFIRMATION_ID) { + status = run_confirmation(&options); + free(json); + return status; + } + status = run_keys(&options, json, length); + free(json); + return status; +} diff --git a/src/cli.h b/src/cli.h deleted file mode 100644 index 4de5d87..0000000 --- a/src/cli.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * This file is a part of the UMSKT Project - * - * Copyleft (C) 2019-2023 UMSKT Contributors (et.al.) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * @FileCreated by Neo on 6/6/2023 - * @Maintainer Neo - */ - -#ifndef UMSKT_CLI_H -#define UMSKT_CLI_H - -#include "header.h" - -#include - -#include "libumskt/libumskt.h" -#include "libumskt/pidgen2/PIDGEN2.h" -#include "libumskt/pidgen3/PIDGEN3.h" -#include "libumskt/pidgen3/BINK1998.h" -#include "libumskt/pidgen3/BINK2002.h" -#include "libumskt/confid/confid.h" - -CMRC_DECLARE(umskt); - -enum ACTIVATION_ALGORITHM { - WINDOWS = 0, - OFFICE_XP = 1, - OFFICE_2K3 = 2, - OFFICE_2K7 = 3, - PLUS_DME = 4, - OFFICE_ACC = 5, -}; - -enum MODE { - MODE_BINK1998_GENERATE = 0, - MODE_BINK2002_GENERATE = 1, - MODE_CONFIRMATION_ID = 2, - MODE_BINK1998_VALIDATE = 3, - MODE_BINK2002_VALIDATE = 4, -}; - -struct Options { - std::string binkid; - std::string keysFilename; - std::string instid; - std::string keyToCheck; - std::string productid; - int channelID; - int serialMin; - int serialMax; - int numKeys; - bool upgrade; - bool serialSet; - bool verbose; - bool help; - bool error; - bool list; - bool nonewlines; - bool overrideVersion; - bool nodashes; - - MODE applicationMode; - ACTIVATION_ALGORITHM activationMode; -}; - -class CLI { - Options options; - json keys; - const char* BINKID; - BIGNUM *privateKey, *genOrder; - EC_POINT *genPoint, *pubPoint; - EC_GROUP *eCurve; - char pKey[25]; - int count, total; - -public: - CLI(Options options, json keys); - ~CLI(); - - static bool loadJSON(const fs::path& filename, json *output); - static void showHelp(char *argv[]); - static int parseCommandLine(int argc, char* argv[], Options *options); - static int validateCommandLine(Options* options, char *argv[], json *keys); - static void printID(DWORD *pid); - void printKey(char *pk); - static bool stripKey(const char *in_key, char out_key[PK_LENGTH]); - static std::string readFromStdin(); - - int BINK1998Generate(); - int BINK2002Generate(); - int BINK1998Validate(); - int BINK2002Validate(); - int ConfirmationID(); -}; - -#endif //UMSKT_CLI_H diff --git a/src/crypto/mini_bn.cpp b/src/crypto/mini_bn.cpp new file mode 100644 index 0000000..c8e4d6f --- /dev/null +++ b/src/crypto/mini_bn.cpp @@ -0,0 +1,1273 @@ +/* This file is a part of the UMSKT Project. + * + * Copyleft (C) 2019-2026 UMSKT Contributors (et.al.) + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * For whatever reason, AI models haven't figured out that code + * comments are meant for the entire project, not just the task + * they were told to do. + */ + +#include "mini_openssl.h" + +#include +#include + +struct bignum_st { + uint32_t *d; + int top; + int dmax; + int neg; +}; + +struct bn_ctx_st { + int unused; +}; + +static int bn_wexpand(BIGNUM *a, int words) { + int next; + uint32_t *grown; + if (words <= a->dmax) { + return 1; + } + next = a->dmax > 0 ? a->dmax : 4; + while (next < words) { + next *= 2; + } + grown = (uint32_t *)realloc(a->d, (size_t)next * sizeof(uint32_t)); + if (!grown) { + return 0; + } + memset(grown + a->dmax, 0, (size_t)(next - a->dmax) * sizeof(uint32_t)); + a->d = grown; + a->dmax = next; + return 1; +} + +static void bn_fix_top(BIGNUM *a) { + while (a->top > 0 && a->d[a->top - 1] == 0) { + a->top--; + } + if (a->top == 0) { + a->neg = 0; + } +} + +BIGNUM *BN_new(void) { + BIGNUM *a = (BIGNUM *)malloc(sizeof(BIGNUM)); + if (!a) { + return NULL; + } + a->d = (uint32_t *)calloc(4, sizeof(uint32_t)); + if (!a->d) { + free(a); + return NULL; + } + a->dmax = 4; + a->top = 0; + a->neg = 0; + return a; +} + +void BN_free(BIGNUM *a) { + if (!a) { + return; + } + free(a->d); + free(a); +} + +BIGNUM *BN_dup(const BIGNUM *a) { + BIGNUM *r = BN_new(); + if (!r) { + return NULL; + } + if (!BN_copy(r, a)) { + BN_free(r); + return NULL; + } + return r; +} + +int BN_zero(BIGNUM *a) { + a->top = 0; + a->neg = 0; + return 1; +} + +int BN_is_zero(const BIGNUM *a) { + return a->top == 0; +} + +int BN_set_word(BIGNUM *a, BN_ULONG w) { + if (w == 0) { + return BN_zero(a); + } + if (!bn_wexpand(a, 1)) { + return 0; + } + a->d[0] = w; + a->top = 1; + a->neg = 0; + return 1; +} + +int BN_copy(BIGNUM *to, const BIGNUM *from) { + int i; + if (to == from) { + return 1; + } + if (!bn_wexpand(to, from->top > 0 ? from->top : 1)) { + return 0; + } + for (i = 0; i < from->top; i++) { + to->d[i] = from->d[i]; + } + to->top = from->top; + to->neg = from->neg; + return 1; +} + +int BN_ucmp(const BIGNUM *a, const BIGNUM *b) { + int i; + if (a->top != b->top) { + return a->top > b->top ? 1 : -1; + } + for (i = a->top - 1; i >= 0; i--) { + if (a->d[i] != b->d[i]) { + return a->d[i] > b->d[i] ? 1 : -1; + } + } + return 0; +} + +int BN_is_odd(const BIGNUM *a) { + return a->top > 0 && (a->d[0] & 1u); +} + +int BN_num_bits(const BIGNUM *a) { + uint32_t word; + int bits; + if (a->top == 0) { + return 0; + } + word = a->d[a->top - 1]; + bits = (a->top - 1) * 32; + while (word) { + bits++; + word >>= 1; + } + return bits; +} + +int BN_num_bytes(const BIGNUM *a) { + return (BN_num_bits(a) + 7) / 8; +} + +int BN_set_bit(BIGNUM *a, int n) { + int word = n / 32; + int bit = n % 32; + int i; + if (n < 0) { + return 0; + } + if (!bn_wexpand(a, word + 1)) { + return 0; + } + for (i = a->top; i <= word; i++) { + a->d[i] = 0; + } + if (a->top <= word) { + a->top = word + 1; + } + a->d[word] |= 1u << bit; + return 1; +} + +int BN_is_bit_set(const BIGNUM *a, int n) { + int word = n / 32; + if (n < 0 || word >= a->top) { + return 0; + } + return (a->d[word] >> (n % 32)) & 1; +} + +int BN_mask_bits(BIGNUM *a, int n) { + int words; + int bits; + if (n < 0) { + return 0; + } + if (n == 0) { + return BN_zero(a); + } + words = n / 32; + bits = n % 32; + if (words >= a->top) { + return 1; + } + if (bits == 0) { + a->top = words; + } else { + a->d[words] &= (1u << bits) - 1u; + a->top = words + 1; + } + bn_fix_top(a); + return 1; +} + +static int bn_lshift_raw(BIGNUM *r, const BIGNUM *a, int n) { + int words; + int bits; + int i; + uint32_t carry; + if (a->top == 0 || n == 0) { + return BN_copy(r, a); + } + words = n / 32; + bits = n % 32; + if (!bn_wexpand(r, a->top + words + 1)) { + return 0; + } + for (i = 0; i < a->top + words + 1; i++) { + r->d[i] = 0; + } + if (bits == 0) { + for (i = 0; i < a->top; i++) { + r->d[i + words] = a->d[i]; + } + r->top = a->top + words; + } else { + carry = 0; + for (i = 0; i < a->top; i++) { + uint32_t value = a->d[i]; + r->d[i + words] = (value << bits) | carry; + carry = value >> (32 - bits); + } + r->d[a->top + words] = carry; + r->top = a->top + words + (carry ? 1 : 0); + } + r->neg = a->neg; + bn_fix_top(r); + return 1; +} + +int BN_lshift(BIGNUM *r, const BIGNUM *a, int n) { + BIGNUM *tmp; + int ok; + if (n < 0) { + return 0; + } + if (r != a) { + return bn_lshift_raw(r, a, n); + } + tmp = BN_dup(a); + if (!tmp) { + return 0; + } + ok = bn_lshift_raw(r, tmp, n); + BN_free(tmp); + return ok; +} + +int BN_rshift1(BIGNUM *r, const BIGNUM *a) { + int i; + uint32_t carry = 0; + if (r != a && !BN_copy(r, a)) { + return 0; + } + for (i = r->top - 1; i >= 0; i--) { + uint32_t next = r->d[i] & 1u; + r->d[i] = (r->d[i] >> 1) | (carry << 31); + carry = next; + } + bn_fix_top(r); + return 1; +} + +static int bn_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + const BIGNUM *longer = a; + const BIGNUM *shorter = b; + int i; + uint32_t carry = 0; + if (longer->top < shorter->top) { + longer = b; + shorter = a; + } + if (!bn_wexpand(r, longer->top + 1)) { + return 0; + } + for (i = 0; i < shorter->top; i++) { + uint64_t sum = (uint64_t)longer->d[i] + shorter->d[i] + carry; + r->d[i] = (uint32_t)sum; + carry = (uint32_t)(sum >> 32); + } + for (; i < longer->top; i++) { + uint64_t sum = (uint64_t)longer->d[i] + carry; + r->d[i] = (uint32_t)sum; + carry = (uint32_t)(sum >> 32); + } + r->d[i] = carry; + r->top = longer->top + (carry ? 1 : 0); + r->neg = 0; + bn_fix_top(r); + return 1; +} + +static int bn_usub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + BIGNUM *tmp; + int i; + uint32_t borrow = 0; + if (r == a || r == b) { + int ok; + tmp = BN_new(); + if (!tmp) { + return 0; + } + ok = bn_usub(tmp, a, b); + if (ok) { + ok = BN_copy(r, tmp); + } + BN_free(tmp); + return ok; + } + if (!bn_wexpand(r, a->top > 0 ? a->top : 1)) { + return 0; + } + for (i = 0; i < b->top; i++) { + uint32_t av = a->d[i]; + uint32_t diff = av - b->d[i]; + uint32_t borrow1 = av < b->d[i]; + uint32_t diff2 = diff - borrow; + uint32_t borrow2 = diff < borrow; + r->d[i] = diff2; + borrow = borrow1 | borrow2; + } + for (; i < a->top; i++) { + uint32_t av = a->d[i]; + r->d[i] = av - borrow; + borrow = av < borrow; + } + r->top = a->top; + r->neg = 0; + bn_fix_top(r); + return 1; +} + +int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + BIGNUM *left; + BIGNUM *right; + int ok; + if (r == a || r == b) { + left = BN_dup(a); + right = BN_dup(b); + ok = left && right && BN_add(r, left, right); + BN_free(left); + BN_free(right); + return ok; + } + if (a->neg == b->neg) { + ok = bn_uadd(r, a, b); + r->neg = a->neg && r->top != 0; + return ok; + } + if (BN_ucmp(a, b) >= 0) { + ok = bn_usub(r, a, b); + r->neg = a->neg && r->top != 0; + } else { + ok = bn_usub(r, b, a); + r->neg = b->neg && r->top != 0; + } + return ok; +} + +int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + BIGNUM *flipped; + int ok; + if (r == a || r == b) { + BIGNUM *left = BN_dup(a); + BIGNUM *right = BN_dup(b); + ok = left && right && BN_sub(r, left, right); + BN_free(left); + BN_free(right); + return ok; + } + flipped = BN_dup(b); + if (!flipped) { + return 0; + } + flipped->neg = b->top != 0 ? !b->neg : 0; + ok = BN_add(r, a, flipped); + BN_free(flipped); + return ok; +} + +int BN_mul_word(BIGNUM *a, BN_ULONG w) { + int i; + uint32_t carry = 0; + if (w == 0 || a->top == 0) { + return BN_zero(a); + } + for (i = 0; i < a->top; i++) { + uint64_t prod = (uint64_t)a->d[i] * w + carry; + a->d[i] = (uint32_t)prod; + carry = (uint32_t)(prod >> 32); + } + if (carry) { + if (!bn_wexpand(a, a->top + 1)) { + return 0; + } + a->d[a->top++] = carry; + } + return 1; +} + +int BN_add_word(BIGNUM *a, BN_ULONG w) { + int i; + uint32_t carry; + if (w == 0) { + return 1; + } + if (a->neg) { + BIGNUM *word = BN_new(); + int ok; + if (!word) { + return 0; + } + BN_set_word(word, w); + ok = BN_sub(a, a, word); + BN_free(word); + return ok; + } + if (a->top == 0) { + return BN_set_word(a, w); + } + carry = w; + for (i = 0; i < a->top && carry; i++) { + uint64_t sum = (uint64_t)a->d[i] + carry; + a->d[i] = (uint32_t)sum; + carry = (uint32_t)(sum >> 32); + } + if (carry) { + if (!bn_wexpand(a, a->top + 1)) { + return 0; + } + a->d[a->top++] = carry; + } + return 1; +} + +int BN_sub_word(BIGNUM *a, BN_ULONG w) { + int i; + uint32_t borrow; + if (w == 0) { + return 1; + } + if (a->top == 0 || a->neg) { + BIGNUM *word = BN_new(); + int ok; + if (!word) { + return 0; + } + BN_set_word(word, w); + ok = BN_sub(a, a, word); + BN_free(word); + return ok; + } + if (a->top == 1 && a->d[0] < w) { + a->d[0] = w - a->d[0]; + a->neg = 1; + return 1; + } + borrow = w; + for (i = 0; i < a->top && borrow; i++) { + uint32_t value = a->d[i]; + a->d[i] = value - borrow; + borrow = value < borrow; + } + bn_fix_top(a); + return 1; +} + +BN_ULONG BN_div_word(BIGNUM *a, BN_ULONG w) { + int i; + uint32_t rem = 0; + if (w == 0) { + return (BN_ULONG)-1; + } + for (i = a->top - 1; i >= 0; i--) { + uint64_t cur = ((uint64_t)rem << 32) | a->d[i]; + a->d[i] = (uint32_t)(cur / w); + rem = (uint32_t)(cur % w); + } + bn_fix_top(a); + return rem; +} + +static int bn_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + int i; + int j; + int top = a->top + b->top; + if (a->top == 0 || b->top == 0) { + return BN_zero(r); + } + if (!bn_wexpand(r, top + 1)) { + return 0; + } + for (i = 0; i < top + 1; i++) { + r->d[i] = 0; + } + for (i = 0; i < a->top; i++) { + uint32_t carry = 0; + for (j = 0; j < b->top; j++) { + union { + uint64_t v; + uint32_t p[2]; + } acc; + acc.v = (uint64_t)a->d[i] * b->d[j] + r->d[i + j] + carry; + r->d[i + j] = acc.p[0]; + carry = acc.p[1]; + } + r->d[i + b->top] = carry; + } + r->top = top; + r->neg = (a->neg ^ b->neg) && 1; + bn_fix_top(r); + return 1; +} + +static int bn_mod(BIGNUM *r, const BIGNUM *a, const BIGNUM *m) { + BIGNUM *shifted; + int max_shift; + int shift; + if (BN_is_zero(m)) { + return 0; + } + if (r != a && !BN_copy(r, a)) { + return 0; + } + r->neg = 0; + if (BN_ucmp(r, m) < 0) { + return 1; + } + shifted = BN_new(); + if (!shifted) { + return 0; + } + max_shift = BN_num_bits(r) - BN_num_bits(m); + if (!BN_lshift(shifted, m, max_shift)) { + BN_free(shifted); + return 0; + } + for (shift = max_shift; shift >= 0; shift--) { + if (BN_ucmp(r, shifted) >= 0) { + if (!bn_usub(r, r, shifted)) { + BN_free(shifted); + return 0; + } + } + if (shift > 0 && !BN_rshift1(shifted, shifted)) { + BN_free(shifted); + return 0; + } + } + BN_free(shifted); + return 1; +} + +static int bn_mod_abs(BIGNUM *r, const BIGNUM *a, const BIGNUM *m) { + BIGNUM *tmp; + int ok; + if (!a->neg) { + return bn_mod(r, a, m); + } + tmp = BN_dup(a); + if (!tmp) { + return 0; + } + tmp->neg = 0; + ok = bn_mod(tmp, tmp, m); + if (ok && !BN_is_zero(tmp)) { + ok = bn_usub(r, m, tmp); + } else if (ok) { + ok = BN_zero(r); + } + BN_free(tmp); + return ok; +} + +int BN_mod_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx) { + BIGNUM *sum; + int ok; + (void)ctx; + sum = BN_new(); + if (!sum) { + return 0; + } + ok = BN_add(sum, a, b) && bn_mod_abs(r, sum, m); + BN_free(sum); + return ok; +} + +int BN_mod_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx) { + BIGNUM *diff; + int ok; + (void)ctx; + diff = BN_new(); + if (!diff) { + return 0; + } + ok = BN_sub(diff, a, b) && bn_mod_abs(r, diff, m); + BN_free(diff); + return ok; +} + +int BN_mod_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx) { + BIGNUM *prod; + int ok; + (void)ctx; + prod = BN_new(); + if (!prod) { + return 0; + } + ok = bn_mul(prod, a, b) && bn_mod_abs(r, prod, m); + BN_free(prod); + return ok; +} + +int BN_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx) { + return BN_mod_mul(r, a, a, m, ctx); +} + +int BN_mod_inverse(BIGNUM *r, const BIGNUM *a, const BIGNUM *m) { + BIGNUM *u; + BIGNUM *v; + BIGNUM *x; + BIGNUM *y; + BIGNUM *tmp; + int ok; + int guard; + if (!r || !a || !m || BN_is_zero(m) || (m->d[0] & 1u) == 0) { + return 0; + } + u = BN_new(); + v = BN_dup(m); + x = BN_new(); + y = BN_new(); + tmp = BN_new(); + ok = u && v && x && y && tmp && BN_set_word(x, 1) && BN_zero(y) && bn_mod_abs(u, a, m); + if (ok && BN_is_zero(u)) { + ok = 0; + } + guard = BN_num_bits(m) * 4 + 8; + while (ok && !BN_is_zero(u) && guard-- > 0) { + while (ok && !BN_is_zero(u) && !BN_is_odd(u)) { + ok = BN_rshift1(u, u); + if (ok && BN_is_odd(x)) { + ok = bn_uadd(tmp, x, m) && BN_rshift1(x, tmp); + } else if (ok) { + ok = BN_rshift1(x, x); + } + } + while (ok && !BN_is_odd(v)) { + ok = BN_rshift1(v, v); + if (ok && BN_is_odd(y)) { + ok = bn_uadd(tmp, y, m) && BN_rshift1(y, tmp); + } else if (ok) { + ok = BN_rshift1(y, y); + } + } + if (!ok || BN_is_zero(u)) { + break; + } + if (BN_ucmp(u, v) >= 0) { + ok = bn_usub(tmp, u, v) && BN_copy(u, tmp); + if (ok && BN_ucmp(x, y) >= 0) { + ok = bn_usub(tmp, x, y) && BN_copy(x, tmp); + } else if (ok) { + ok = bn_usub(tmp, y, x) && bn_usub(x, m, tmp); + } + } else { + ok = bn_usub(tmp, v, u) && BN_copy(v, tmp); + if (ok && BN_ucmp(y, x) >= 0) { + ok = bn_usub(tmp, y, x) && BN_copy(y, tmp); + } else if (ok) { + ok = bn_usub(tmp, x, y) && bn_usub(y, m, tmp); + } + } + } + if (ok && (BN_is_zero(u) == 0 || v->top != 1 || v->d[0] != 1)) { + ok = 0; + } + if (ok) { + ok = BN_copy(r, y); + } + BN_free(u); + BN_free(v); + BN_free(x); + BN_free(y); + BN_free(tmp); + return ok; +} + +static uint32_t mont_n0(uint32_t n0) { + uint32_t inv = n0; + inv *= 2u - n0 * inv; + inv *= 2u - n0 * inv; + inv *= 2u - n0 * inv; + inv *= 2u - n0 * inv; + inv *= 2u - n0 * inv; + return 0u - inv; +} + +static void mont_load(uint32_t *dst, const BIGNUM *src, int nw) { + int i; + for (i = 0; i < nw; i++) { + dst[i] = (src && i < src->top) ? src->d[i] : 0; + } +} + +static void mont_mul(uint32_t *r, const uint32_t *a, const uint32_t *b, const uint32_t *n, uint32_t n0, int nw) { + uint32_t tmp[34]; + int i; + int j; + int ge; + for (i = 0; i < nw + 2; i++) { + tmp[i] = 0; + } + for (i = 0; i < nw; i++) { + uint64_t carry = 0; + uint32_t m; + for (j = 0; j < nw; j++) { + carry += (uint64_t)tmp[j] + (uint64_t)a[j] * b[i]; + tmp[j] = (uint32_t)carry; + carry >>= 32; + } + carry += tmp[nw]; + tmp[nw] = (uint32_t)carry; + tmp[nw + 1] = (uint32_t)(carry >> 32); + m = tmp[0] * n0; + carry = 0; + for (j = 0; j < nw; j++) { + carry += (uint64_t)tmp[j] + (uint64_t)m * n[j]; + tmp[j] = (uint32_t)carry; + carry >>= 32; + } + carry += tmp[nw]; + tmp[nw] = (uint32_t)carry; + tmp[nw + 1] += (uint32_t)(carry >> 32); + for (j = 0; j <= nw; j++) { + tmp[j] = tmp[j + 1]; + } + tmp[nw + 1] = 0; + } + ge = tmp[nw] != 0; + if (!ge) { + for (i = nw - 1; i >= 0; i--) { + if (tmp[i] != n[i]) { + ge = tmp[i] > n[i]; + break; + } + if (i == 0) { + ge = 1; + } + } + } + if (ge) { + uint32_t borrow = 0; + for (i = 0; i < nw; i++) { + uint64_t sub = (uint64_t)n[i] + borrow; + r[i] = (uint32_t)((uint64_t)tmp[i] - sub); + borrow = (uint64_t)tmp[i] < sub; + } + } else { + for (i = 0; i < nw; i++) { + r[i] = tmp[i]; + } + } +} + +static int bn_to_mont_rr(BIGNUM *rr, const BIGNUM *m, int nw) { + return BN_set_word(rr, 1) && BN_lshift(rr, rr, nw * 64) && bn_mod(rr, rr, m); +} + +static int bn_mod_exp(BIGNUM *r, const BIGNUM *base, const BIGNUM *exp, const BIGNUM *m) { + BIGNUM *reduced; + BIGNUM *rr; + uint32_t n[32]; + uint32_t base_m[32]; + uint32_t acc[32]; + uint32_t cur[32]; + uint32_t rr_w[32]; + uint32_t n0; + int nw; + int bits; + int i; + int ok; + if (!m || m->top == 0 || m->top > 32 || (m->d[0] & 1u) == 0) { + return 0; + } + if (BN_is_zero(exp)) { + return BN_set_word(r, 1); + } + nw = m->top; + reduced = BN_new(); + rr = BN_new(); + ok = reduced && rr && bn_mod(reduced, base, m) && bn_to_mont_rr(rr, m, nw); + if (!ok) { + BN_free(reduced); + BN_free(rr); + return 0; + } + mont_load(n, m, nw); + mont_load(rr_w, rr, nw); + mont_load(base_m, reduced, nw); + n0 = mont_n0(n[0]); + mont_mul(base_m, base_m, rr_w, n, n0, nw); + mont_mul(acc, rr_w, rr_w, n, n0, nw); + /* acc was R^2 * R^2 * R^{-1} = R^3. Montgomery form of 1 is R, from 1 * R^2 * R^{-1}. */ + { + uint32_t one[32]; + for (i = 0; i < nw; i++) { + one[i] = 0; + } + one[0] = 1; + mont_mul(acc, one, rr_w, n, n0, nw); + } + bits = BN_num_bits(exp); + for (i = bits - 1; i >= 0; i--) { + mont_mul(cur, acc, acc, n, n0, nw); + if (BN_is_bit_set(exp, i)) { + mont_mul(acc, cur, base_m, n, n0, nw); + } else { + for (int w = 0; w < nw; w++) { + acc[w] = cur[w]; + } + } + } + { + uint32_t one[32]; + for (i = 0; i < nw; i++) { + one[i] = i == 0 ? 1u : 0u; + } + mont_mul(cur, acc, one, n, n0, nw); + } + if (!bn_wexpand(r, nw)) { + BN_free(reduced); + BN_free(rr); + return 0; + } + for (i = 0; i < nw; i++) { + r->d[i] = cur[i]; + } + r->top = nw; + r->neg = 0; + bn_fix_top(r); + BN_free(reduced); + BN_free(rr); + return 1; +} + +static int bn_legendre_is_one(const BIGNUM *a, const BIGNUM *p) { + BIGNUM *exp = BN_dup(p); + BIGNUM *result = BN_new(); + int status = -1; + if (!exp || !result || !BN_sub_word(exp, 1) || !BN_rshift1(exp, exp)) { + BN_free(exp); + BN_free(result); + return -1; + } + if (!bn_mod_exp(result, a, exp, p)) { + BN_free(exp); + BN_free(result); + return -1; + } + if (BN_is_zero(result)) { + status = 0; + } else if (result->top == 1 && result->d[0] == 1 && !result->neg) { + status = 1; + } else { + status = -1; + } + BN_free(exp); + BN_free(result); + return status; +} + +BIGNUM *BN_mod_sqrt(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx) { + BIGNUM *aa; + BIGNUM *out; + int legendre; + (void)ctx; + if (!r) { + r = BN_new(); + if (!r) { + return NULL; + } + } + aa = BN_new(); + out = BN_new(); + if (!aa || !out || !bn_mod(aa, a, p)) { + BN_free(aa); + BN_free(out); + return NULL; + } + if (BN_is_zero(aa)) { + BN_zero(r); + BN_free(aa); + BN_free(out); + return r; + } + legendre = bn_legendre_is_one(aa, p); + if (legendre != 1) { + BN_free(aa); + BN_free(out); + return NULL; + } + if ((p->d[0] & 3u) == 3u) { + BIGNUM *exp = BN_dup(p); + int ok = exp && BN_add_word(exp, 1) && BN_rshift1(exp, exp) && BN_rshift1(exp, exp) && bn_mod_exp(out, aa, exp, p); + BN_free(exp); + if (!ok) { + BN_free(aa); + BN_free(out); + return NULL; + } + } else { + /* Tonelli-Shanks. p - 1 = q * 2^s, q odd. */ + BIGNUM *q = BN_dup(p); + BIGNUM *z = BN_new(); + BIGNUM *c = BN_new(); + BIGNUM *t = BN_new(); + BIGNUM *root = BN_new(); + BIGNUM *b = BN_new(); + BIGNUM *one = BN_new(); + int s = 0; + int m; + int ok = q && z && c && t && root && b && one; + if (!ok) { + BN_free(aa); + BN_free(out); + BN_free(q); + BN_free(z); + BN_free(c); + BN_free(t); + BN_free(root); + BN_free(b); + BN_free(one); + return NULL; + } + BN_sub_word(q, 1); + while (!BN_is_odd(q)) { + BN_rshift1(q, q); + s++; + } + BN_set_word(one, 1); + BN_set_word(z, 2); + while (bn_legendre_is_one(z, p) != -1) { + BN_add_word(z, 1); + } + m = s; + ok = bn_mod_exp(c, z, q, p); + if (ok) { + BIGNUM *half = BN_dup(q); + ok = half && BN_add_word(half, 1) && BN_rshift1(half, half) && bn_mod_exp(root, aa, half, p) && bn_mod_exp(t, aa, q, p); + BN_free(half); + } + while (ok) { + int i = 0; + BIGNUM *ti = BN_dup(t); + if (!ti) { + ok = 0; + break; + } + while (ok && BN_ucmp(ti, one) != 0 && i < m) { + ok = BN_mod_sqr(ti, ti, p, NULL); + i++; + } + BN_free(ti); + if (!ok || i == m) { + ok = 0; + break; + } + if (i == 0) { + BN_copy(out, root); + break; + } + { + BIGNUM *shift = BN_new(); + if (!shift) { + ok = 0; + break; + } + BN_set_word(shift, 1); + if (m - i - 1 > 0) { + ok = BN_lshift(shift, shift, m - i - 1); + } + /* b = c^(2^(m-i-1)) */ + if (ok) { + ok = bn_mod_exp(b, c, shift, p); + } + BN_free(shift); + } + if (!ok) { + break; + } + ok = BN_mod_mul(root, root, b, p, NULL) && BN_mod_sqr(b, b, p, NULL) && BN_mod_mul(t, t, b, p, NULL); + BN_copy(c, b); + m = i; + } + BN_free(q); + BN_free(z); + BN_free(c); + BN_free(t); + BN_free(root); + BN_free(b); + BN_free(one); + if (!ok || BN_is_zero(out)) { + BN_free(aa); + BN_free(out); + return NULL; + } + } + { + BIGNUM *check = BN_new(); + int ok = check && BN_mod_sqr(check, out, p, NULL) && BN_ucmp(check, aa) == 0; + BN_free(check); + BN_free(aa); + if (!ok) { + BN_free(out); + return NULL; + } + } + BN_copy(r, out); + BN_free(out); + return r; +} + +int BN_dec2bn(BIGNUM **a, const char *str) { + int digits = 0; + if (!a) { + return 0; + } + if (!*a) { + *a = BN_new(); + if (!*a) { + return 0; + } + } + BN_zero(*a); + if (!str) { + return 0; + } + if (*str == '+') { + str++; + } + while (*str == '0') { + str++; + digits++; + } + if (*str < '0' || *str > '9') { + return digits; + } + while (*str >= '0' && *str <= '9') { + if (!BN_mul_word(*a, 10) || !BN_add_word(*a, (BN_ULONG)(*str - '0'))) { + return 0; + } + str++; + digits++; + } + return digits; +} + +char *BN_bn2dec(const BIGNUM *a) { + BIGNUM *tmp; + int cap; + int count = 0; + int neg; + int i; + char *buf; + if (BN_is_zero(a)) { + buf = (char *)malloc(2); + if (buf) { + buf[0] = '0'; + buf[1] = 0; + } + return buf; + } + tmp = BN_dup(a); + if (!tmp) { + return NULL; + } + neg = tmp->neg; + tmp->neg = 0; + cap = BN_num_bits(tmp) / 3 + 4 + neg; + buf = (char *)malloc((size_t)cap); + if (!buf) { + BN_free(tmp); + return NULL; + } + while (!BN_is_zero(tmp)) { + BN_ULONG rem = BN_div_word(tmp, 10); + buf[count++] = (char)('0' + rem); + } + if (neg) { + buf[count++] = '-'; + } + for (i = 0; i < count / 2; i++) { + char swap = buf[i]; + buf[i] = buf[count - 1 - i]; + buf[count - 1 - i] = swap; + } + buf[count] = 0; + BN_free(tmp); + return buf; +} + +int BN_bn2bin(const BIGNUM *a, unsigned char *to) { + int n = BN_num_bytes(a); + int i; + int b; + memset(to, 0, (size_t)n); + for (i = 0; i < a->top; i++) { + uint32_t value = a->d[i]; + for (b = 0; b < 4; b++) { + int index = n - 1 - (i * 4 + b); + if (index >= 0 && index < n) { + to[index] = (unsigned char)((value >> (8 * b)) & 0xFF); + } + } + } + return n; +} + +BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret) { + int words; + int i; + if (!ret) { + ret = BN_new(); + if (!ret) { + return NULL; + } + } + BN_zero(ret); + if (!s || len <= 0) { + return ret; + } + words = (len + 3) / 4; + if (!bn_wexpand(ret, words)) { + return NULL; + } + for (i = 0; i < words; i++) { + ret->d[i] = 0; + } + ret->top = words; + for (i = 0; i < len; i++) { + int from_right = len - 1 - i; + ret->d[from_right / 4] |= ((uint32_t)s[i]) << ((from_right % 4) * 8); + } + bn_fix_top(ret); + return ret; +} + +BIGNUM *BN_lebin2bn(const unsigned char *s, int len, BIGNUM *ret) { + int words; + int i; + if (!ret) { + ret = BN_new(); + if (!ret) { + return NULL; + } + } + BN_zero(ret); + if (!s || len <= 0) { + return ret; + } + words = (len + 3) / 4; + if (!bn_wexpand(ret, words)) { + return NULL; + } + for (i = 0; i < words; i++) { + ret->d[i] = 0; + } + ret->top = words; + for (i = 0; i < len; i++) { + ret->d[i / 4] |= ((uint32_t)s[i]) << ((i % 4) * 8); + } + bn_fix_top(ret); + return ret; +} + +int BN_bn2lebinpad(const BIGNUM *a, unsigned char *to, int tolen) { + int i; + int b; + if (tolen < 0 || BN_num_bytes(a) > tolen) { + return -1; + } + memset(to, 0, (size_t)tolen); + for (i = 0; i < a->top; i++) { + uint32_t value = a->d[i]; + for (b = 0; b < 4; b++) { + int index = i * 4 + b; + if (index < tolen) { + to[index] = (unsigned char)((value >> (8 * b)) & 0xFF); + } + } + } + return tolen; +} + +int BN_rand(BIGNUM *rnd, int bits, int top, int bottom) { + int bytes; + int excess; + unsigned char *buf; + if (!rnd || bits < 0 || (bits == 1 && top > 0)) { + return 0; + } + if (bits == 0) { + return BN_zero(rnd); + } + bytes = (bits + 7) / 8; + excess = bytes * 8 - bits; + buf = (unsigned char *)malloc((size_t)bytes); + if (!buf) { + return 0; + } + if (RAND_bytes(buf, bytes) != 1) { + free(buf); + return 0; + } + buf[0] = (unsigned char)(buf[0] & (0xFFu >> excess)); + if (top == BN_RAND_TOP_ONE || top == BN_RAND_TOP_TWO) { + int bit = (bits - 1) % 8; + buf[0] = (unsigned char)(buf[0] | (1u << bit)); + if (top == BN_RAND_TOP_TWO && bits > 1) { + if (bit == 0) { + buf[1] = (unsigned char)(buf[1] | 0x80); + } else { + buf[0] = (unsigned char)(buf[0] | (1u << (bit - 1))); + } + } + } + if (bottom == BN_RAND_BOTTOM_ODD) { + buf[bytes - 1] = (unsigned char)(buf[bytes - 1] | 1u); + } + if (!BN_bin2bn(buf, bytes, rnd)) { + free(buf); + return 0; + } + free(buf); + return 1; +} + +BN_CTX *BN_CTX_new(void) { + BN_CTX *ctx = (BN_CTX *)malloc(sizeof(BN_CTX)); + if (ctx) { + ctx->unused = 0; + } + return ctx; +} + +void BN_CTX_free(BN_CTX *ctx) { + free(ctx); +} diff --git a/src/crypto/mini_ec.cpp b/src/crypto/mini_ec.cpp new file mode 100644 index 0000000..aa8ec72 --- /dev/null +++ b/src/crypto/mini_ec.cpp @@ -0,0 +1,618 @@ +/* This file is a part of the UMSKT Project. + * + * Copyleft (C) 2019-2026 UMSKT Contributors (et.al.) + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * Short Weierstrass curves over a prime field. Scalar multiplication uses + * Jacobian coordinates so a 384/512-bit multiply does one inversion. + */ + +#include "mini_openssl.h" + +#include +#include + +struct ec_group_st { + BIGNUM *p; + BIGNUM *a; + BIGNUM *b; +}; + +struct ec_point_st { + const EC_GROUP *group; + BIGNUM *x; + BIGNUM *y; + int infinity; +}; + +struct jpoint { + BIGNUM *X; + BIGNUM *Y; + BIGNUM *Z; + int infinity; +}; + +static int field_mul(const EC_GROUP *g, BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + return BN_mod_mul(r, a, b, g->p, NULL); +} + +static int field_sqr(const EC_GROUP *g, BIGNUM *r, const BIGNUM *a) { + return BN_mod_sqr(r, a, g->p, NULL); +} + +static int field_add(const EC_GROUP *g, BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + return BN_mod_add(r, a, b, g->p, NULL); +} + +static int field_sub(const EC_GROUP *g, BIGNUM *r, const BIGNUM *a, const BIGNUM *b) { + return BN_mod_sub(r, a, b, g->p, NULL); +} + +static int field_reduce(const EC_GROUP *g, BIGNUM *r, const BIGNUM *a) { + BIGNUM *zero = BN_new(); + int ok; + if (!zero) { + return 0; + } + BN_zero(zero); + ok = BN_mod_add(r, a, zero, g->p, NULL); + BN_free(zero); + return ok; +} + +static int field_mul_small(const EC_GROUP *g, BIGNUM *r, const BIGNUM *a, BN_ULONG w) { + BIGNUM *tmp = BN_dup(a); + int ok = tmp && BN_mul_word(tmp, w) && field_reduce(g, r, tmp); + BN_free(tmp); + return ok; +} + +static int field_inv(const EC_GROUP *g, BIGNUM *r, const BIGNUM *a) { + return BN_mod_inverse(r, a, g->p); +} + +static void jfree(struct jpoint *p) { + if (!p) { + return; + } + BN_free(p->X); + BN_free(p->Y); + BN_free(p->Z); +} + +static int jinit(struct jpoint *p) { + p->X = BN_new(); + p->Y = BN_new(); + p->Z = BN_new(); + p->infinity = 1; + if (!p->X || !p->Y || !p->Z) { + jfree(p); + return 0; + } + return 1; +} + +static int jset_affine(const EC_GROUP *g, struct jpoint *p, const EC_POINT *src) { + if (src->infinity) { + p->infinity = 1; + return 1; + } + p->infinity = 0; + return field_reduce(g, p->X, src->x) && field_reduce(g, p->Y, src->y) && BN_set_word(p->Z, 1); +} + +static int jdouble(const EC_GROUP *g, struct jpoint *p) { + BIGNUM *YY; + BIGNUM *YYYY; + BIGNUM *XX; + BIGNUM *S; + BIGNUM *M; + BIGNUM *Z2; + BIGNUM *Z4; + BIGNUM *X3; + BIGNUM *Y3; + BIGNUM *Z3; + BIGNUM *tmp; + int ok; + if (p->infinity) { + return 1; + } + if (BN_is_zero(p->Y)) { + p->infinity = 1; + return 1; + } + YY = BN_new(); + YYYY = BN_new(); + XX = BN_new(); + S = BN_new(); + M = BN_new(); + Z2 = BN_new(); + Z4 = BN_new(); + X3 = BN_new(); + Y3 = BN_new(); + Z3 = BN_new(); + tmp = BN_new(); + ok = YY && YYYY && XX && S && M && Z2 && Z4 && X3 && Y3 && Z3 && tmp; + if (ok) { + ok = field_sqr(g, YY, p->Y) && field_sqr(g, YYYY, YY) && field_sqr(g, XX, p->X); + } + if (ok) { + ok = field_mul(g, S, p->X, YY) && field_mul_small(g, S, S, 4); + } + if (ok) { + ok = field_mul_small(g, M, XX, 3) && field_sqr(g, Z2, p->Z) && field_sqr(g, Z4, Z2) && field_mul(g, tmp, g->a, Z4) && field_add(g, M, M, tmp); + } + if (ok) { + ok = field_sqr(g, X3, M) && field_mul_small(g, tmp, S, 2) && field_sub(g, X3, X3, tmp); + } + if (ok) { + ok = field_mul(g, Z3, p->Y, p->Z) && field_mul_small(g, Z3, Z3, 2); + } + if (ok) { + ok = field_sub(g, tmp, S, X3) && field_mul(g, tmp, M, tmp) && field_mul_small(g, YYYY, YYYY, 8) && field_sub(g, Y3, tmp, YYYY); + } + if (ok) { + ok = BN_copy(p->X, X3) && BN_copy(p->Y, Y3) && BN_copy(p->Z, Z3); + } + BN_free(YY); + BN_free(YYYY); + BN_free(XX); + BN_free(S); + BN_free(M); + BN_free(Z2); + BN_free(Z4); + BN_free(X3); + BN_free(Y3); + BN_free(Z3); + BN_free(tmp); + return ok; +} + +static int jadd_mixed(const EC_GROUP *g, struct jpoint *p, const EC_POINT *q) { + BIGNUM *Z2; + BIGNUM *Z3; + BIGNUM *U2; + BIGNUM *S2; + BIGNUM *H; + BIGNUM *R; + BIGNUM *HH; + BIGNUM *HHH; + BIGNUM *U1HH; + BIGNUM *X3; + BIGNUM *Y3; + BIGNUM *Zout; + BIGNUM *tmp; + int ok; + if (q->infinity) { + return 1; + } + if (p->infinity) { + return jset_affine(g, p, q); + } + Z2 = BN_new(); + Z3 = BN_new(); + U2 = BN_new(); + S2 = BN_new(); + H = BN_new(); + R = BN_new(); + HH = BN_new(); + HHH = BN_new(); + U1HH = BN_new(); + X3 = BN_new(); + Y3 = BN_new(); + Zout = BN_new(); + tmp = BN_new(); + ok = Z2 && Z3 && U2 && S2 && H && R && HH && HHH && U1HH && X3 && Y3 && Zout && tmp; + if (ok) { + ok = field_sqr(g, Z2, p->Z) && field_mul(g, Z3, Z2, p->Z) && field_mul(g, U2, q->x, Z2) && field_mul(g, S2, q->y, Z3); + } + if (ok) { + ok = field_sub(g, H, U2, p->X) && field_sub(g, R, S2, p->Y); + } + if (ok && BN_is_zero(H)) { + int double_it = BN_is_zero(R); + BN_free(Z2); + BN_free(Z3); + BN_free(U2); + BN_free(S2); + BN_free(H); + BN_free(R); + BN_free(HH); + BN_free(HHH); + BN_free(U1HH); + BN_free(X3); + BN_free(Y3); + BN_free(Zout); + BN_free(tmp); + if (double_it) { + return jdouble(g, p); + } + p->infinity = 1; + return 1; + } + if (ok) { + ok = field_sqr(g, HH, H) && field_mul(g, HHH, H, HH) && field_mul(g, U1HH, p->X, HH); + } + if (ok) { + ok = field_sqr(g, X3, R) && field_sub(g, X3, X3, HHH) && field_mul_small(g, tmp, U1HH, 2) && field_sub(g, X3, X3, tmp); + } + if (ok) { + ok = field_sub(g, tmp, U1HH, X3) && field_mul(g, tmp, R, tmp) && field_mul(g, Y3, p->Y, HHH) && field_sub(g, Y3, tmp, Y3); + } + if (ok) { + ok = field_mul(g, Zout, p->Z, H) && BN_copy(p->X, X3) && BN_copy(p->Y, Y3) && BN_copy(p->Z, Zout); + } + BN_free(Z2); + BN_free(Z3); + BN_free(U2); + BN_free(S2); + BN_free(H); + BN_free(R); + BN_free(HH); + BN_free(HHH); + BN_free(U1HH); + BN_free(X3); + BN_free(Y3); + BN_free(Zout); + BN_free(tmp); + return ok; +} + +static int jto_affine(const EC_GROUP *g, EC_POINT *out, const struct jpoint *p) { + BIGNUM *inv = BN_new(); + BIGNUM *z2 = BN_new(); + BIGNUM *z3 = BN_new(); + int ok = inv && z2 && z3; + if (p->infinity || BN_is_zero(p->Z)) { + out->infinity = 1; + BN_free(inv); + BN_free(z2); + BN_free(z3); + return 1; + } + if (ok) { + ok = field_inv(g, inv, p->Z) && field_sqr(g, z2, inv) && field_mul(g, z3, z2, inv) && field_mul(g, out->x, p->X, z2) && field_mul(g, out->y, p->Y, z3); + } + if (ok) { + out->infinity = 0; + } + BN_free(inv); + BN_free(z2); + BN_free(z3); + return ok; +} + +EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx) { + EC_GROUP *group; + (void)ctx; + if (!p || BN_is_zero(p)) { + return NULL; + } + group = (EC_GROUP *)malloc(sizeof(EC_GROUP)); + if (!group) { + return NULL; + } + group->p = BN_dup(p); + group->a = BN_dup(a); + group->b = BN_dup(b); + if (!group->p || !group->a || !group->b) { + EC_GROUP_free(group); + return NULL; + } + return group; +} + +void EC_GROUP_free(EC_GROUP *group) { + if (!group) { + return; + } + BN_free(group->p); + BN_free(group->a); + BN_free(group->b); + free(group); +} + +EC_POINT *EC_POINT_new(const EC_GROUP *group) { + EC_POINT *point = (EC_POINT *)malloc(sizeof(EC_POINT)); + if (!point) { + return NULL; + } + point->group = group; + point->x = BN_new(); + point->y = BN_new(); + point->infinity = 1; + if (!point->x || !point->y) { + EC_POINT_free(point); + return NULL; + } + return point; +} + +void EC_POINT_free(EC_POINT *point) { + if (!point) { + return; + } + BN_free(point->x); + BN_free(point->y); + free(point); +} + +int EC_POINT_set_affine_coordinates(const EC_GROUP *group, EC_POINT *point, const BIGNUM *x, const BIGNUM *y, BN_CTX *ctx) { + (void)ctx; + if (!group || !point || !x || !y) { + return 0; + } + point->group = group; + point->infinity = 0; + return field_reduce(group, point->x, x) && field_reduce(group, point->y, y); +} + +int EC_POINT_get_affine_coordinates(const EC_GROUP *group, const EC_POINT *point, BIGNUM *x, BIGNUM *y, BN_CTX *ctx) { + (void)group; + (void)ctx; + if (!point || point->infinity) { + return 0; + } + return BN_copy(x, point->x) && BN_copy(y, point->y); +} + +int EC_POINT_is_on_curve(const EC_GROUP *group, const EC_POINT *point, BN_CTX *ctx) { + BIGNUM *y2 = BN_new(); + BIGNUM *x3 = BN_new(); + BIGNUM *ax = BN_new(); + BIGNUM *rhs = BN_new(); + int ok; + (void)ctx; + if (!point || point->infinity) { + BN_free(y2); + BN_free(x3); + BN_free(ax); + BN_free(rhs); + return 0; + } + ok = y2 && x3 && ax && rhs && field_sqr(group, y2, point->y) && field_sqr(group, x3, point->x) && field_mul(group, x3, x3, point->x) && field_mul(group, ax, group->a, point->x) && field_add(group, rhs, x3, ax) && field_add(group, rhs, rhs, group->b); + if (ok) { + ok = BN_ucmp(y2, rhs) == 0; + } + BN_free(y2); + BN_free(x3); + BN_free(ax); + BN_free(rhs); + return ok; +} + +int EC_POINT_add(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, const EC_POINT *b, BN_CTX *ctx) { + struct jpoint sum; + int ok; + (void)ctx; + if (!jinit(&sum)) { + return 0; + } + ok = jset_affine(group, &sum, a) && jadd_mixed(group, &sum, b) && jto_affine(group, r, &sum); + jfree(&sum); + return ok; +} + +int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx) { + struct jpoint acc; + int bits; + int i; + int ok; + (void)ctx; + if (n) { + return 0; + } + if (!q || !m) { + return 0; + } + if (q->infinity || BN_is_zero(m)) { + r->infinity = 1; + return 1; + } + if (!jinit(&acc)) { + return 0; + } + bits = BN_num_bits(m); + ok = 1; + for (i = bits - 1; ok && i >= 0; i--) { + if (!acc.infinity) { + ok = jdouble(group, &acc); + } + if (ok && BN_is_bit_set(m, i)) { + ok = jadd_mixed(group, &acc, q); + } + } + if (ok) { + ok = jto_affine(group, r, &acc); + } + jfree(&acc); + return ok; +} + +unsigned char *SHA1(const unsigned char *d, size_t n, unsigned char *md) { + uint32_t h0 = 0x67452301u; + uint32_t h1 = 0xEFCDAB89u; + uint32_t h2 = 0x98BADCFEu; + uint32_t h3 = 0x10325476u; + uint32_t h4 = 0xC3D2E1F0u; + size_t bit_len_hi; + size_t bit_len_lo; + size_t total; + size_t offset = 0; + unsigned char block[64]; + size_t i; + if (!md) { + return NULL; + } + bit_len_lo = (n << 3) & 0xFFFFFFFFu; + bit_len_hi = (n >> 29); + while (offset + 64 <= n) { + uint32_t w[80]; + uint32_t a, b, c, e, f, temp; + uint32_t k; + for (i = 0; i < 16; i++) { + w[i] = ((uint32_t)d[offset + i * 4] << 24) | ((uint32_t)d[offset + i * 4 + 1] << 16) | ((uint32_t)d[offset + i * 4 + 2] << 8) | (uint32_t)d[offset + i * 4 + 3]; + } + for (i = 16; i < 80; i++) { + uint32_t x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]; + w[i] = (x << 1) | (x >> 31); + } + a = h0; + b = h1; + c = h2; + e = h3; + f = h4; + for (i = 0; i < 80; i++) { + uint32_t fmix; + if (i < 20) { + fmix = (b & c) | ((~b) & e); + k = 0x5A827999u; + } else if (i < 40) { + fmix = b ^ c ^ e; + k = 0x6ED9EBA1u; + } else if (i < 60) { + fmix = (b & c) | (b & e) | (c & e); + k = 0x8F1BBCDCu; + } else { + fmix = b ^ c ^ e; + k = 0xCA62C1D6u; + } + temp = ((a << 5) | (a >> 27)) + fmix + f + k + w[i]; + f = e; + e = c; + c = (b << 30) | (b >> 2); + b = a; + a = temp; + } + h0 += a; + h1 += b; + h2 += c; + h3 += e; + h4 += f; + offset += 64; + } + total = n - offset; + memset(block, 0, sizeof(block)); + if (total) { + memcpy(block, d + offset, total); + } + block[total] = 0x80; + if (total >= 56) { + uint32_t w[80]; + uint32_t a, b, c, e, f, temp; + for (i = 0; i < 16; i++) { + w[i] = ((uint32_t)block[i * 4] << 24) | ((uint32_t)block[i * 4 + 1] << 16) | ((uint32_t)block[i * 4 + 2] << 8) | (uint32_t)block[i * 4 + 3]; + } + for (i = 16; i < 80; i++) { + uint32_t x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]; + w[i] = (x << 1) | (x >> 31); + } + a = h0; + b = h1; + c = h2; + e = h3; + f = h4; + for (i = 0; i < 80; i++) { + uint32_t fmix; + uint32_t k; + if (i < 20) { + fmix = (b & c) | ((~b) & e); + k = 0x5A827999u; + } else if (i < 40) { + fmix = b ^ c ^ e; + k = 0x6ED9EBA1u; + } else if (i < 60) { + fmix = (b & c) | (b & e) | (c & e); + k = 0x8F1BBCDCu; + } else { + fmix = b ^ c ^ e; + k = 0xCA62C1D6u; + } + temp = ((a << 5) | (a >> 27)) + fmix + f + k + w[i]; + f = e; + e = c; + c = (b << 30) | (b >> 2); + b = a; + a = temp; + } + h0 += a; + h1 += b; + h2 += c; + h3 += e; + h4 += f; + memset(block, 0, sizeof(block)); + } + block[56] = (unsigned char)((bit_len_hi >> 24) & 0xFF); + block[57] = (unsigned char)((bit_len_hi >> 16) & 0xFF); + block[58] = (unsigned char)((bit_len_hi >> 8) & 0xFF); + block[59] = (unsigned char)(bit_len_hi & 0xFF); + block[60] = (unsigned char)((bit_len_lo >> 24) & 0xFF); + block[61] = (unsigned char)((bit_len_lo >> 16) & 0xFF); + block[62] = (unsigned char)((bit_len_lo >> 8) & 0xFF); + block[63] = (unsigned char)(bit_len_lo & 0xFF); + { + uint32_t w[80]; + uint32_t a, b, c, e, f, temp; + for (i = 0; i < 16; i++) { + w[i] = ((uint32_t)block[i * 4] << 24) | ((uint32_t)block[i * 4 + 1] << 16) | ((uint32_t)block[i * 4 + 2] << 8) | (uint32_t)block[i * 4 + 3]; + } + for (i = 16; i < 80; i++) { + uint32_t x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]; + w[i] = (x << 1) | (x >> 31); + } + a = h0; + b = h1; + c = h2; + e = h3; + f = h4; + for (i = 0; i < 80; i++) { + uint32_t fmix; + uint32_t k; + if (i < 20) { + fmix = (b & c) | ((~b) & e); + k = 0x5A827999u; + } else if (i < 40) { + fmix = b ^ c ^ e; + k = 0x6ED9EBA1u; + } else if (i < 60) { + fmix = (b & c) | (b & e) | (c & e); + k = 0x8F1BBCDCu; + } else { + fmix = b ^ c ^ e; + k = 0xCA62C1D6u; + } + temp = ((a << 5) | (a >> 27)) + fmix + f + k + w[i]; + f = e; + e = c; + c = (b << 30) | (b >> 2); + b = a; + a = temp; + } + h0 += a; + h1 += b; + h2 += c; + h3 += e; + h4 += f; + } + md[0] = (unsigned char)(h0 >> 24); + md[1] = (unsigned char)(h0 >> 16); + md[2] = (unsigned char)(h0 >> 8); + md[3] = (unsigned char)h0; + md[4] = (unsigned char)(h1 >> 24); + md[5] = (unsigned char)(h1 >> 16); + md[6] = (unsigned char)(h1 >> 8); + md[7] = (unsigned char)h1; + md[8] = (unsigned char)(h2 >> 24); + md[9] = (unsigned char)(h2 >> 16); + md[10] = (unsigned char)(h2 >> 8); + md[11] = (unsigned char)h2; + md[12] = (unsigned char)(h3 >> 24); + md[13] = (unsigned char)(h3 >> 16); + md[14] = (unsigned char)(h3 >> 8); + md[15] = (unsigned char)h3; + md[16] = (unsigned char)(h4 >> 24); + md[17] = (unsigned char)(h4 >> 16); + md[18] = (unsigned char)(h4 >> 8); + md[19] = (unsigned char)h4; + return md; +} diff --git a/src/crypto/mini_openssl.h b/src/crypto/mini_openssl.h new file mode 100644 index 0000000..40ebcf2 --- /dev/null +++ b/src/crypto/mini_openssl.h @@ -0,0 +1,85 @@ +/* This file is a part of the UMSKT Project. + * + * Copyleft (C) 2019-2026 UMSKT Contributors (et.al.) + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef UMSKT_MINI_OPENSSL_H +#define UMSKT_MINI_OPENSSL_H + +#include +#include + +#define BN_RAND_TOP_ANY (-1) +#define BN_RAND_TOP_ONE 0 +#define BN_RAND_TOP_TWO 1 +#define BN_RAND_BOTTOM_ANY 0 +#define BN_RAND_BOTTOM_ODD 1 + +#define SHA_DIGEST_LENGTH 20 + +typedef uint32_t BN_ULONG; + +struct bignum_st; +struct bn_ctx_st; +struct ec_group_st; +struct ec_point_st; + +typedef struct bignum_st BIGNUM; +typedef struct bn_ctx_st BN_CTX; +typedef struct ec_group_st EC_GROUP; +typedef struct ec_point_st EC_POINT; + +BIGNUM *BN_new(void); +BIGNUM *BN_dup(const BIGNUM *a); +void BN_free(BIGNUM *a); +int BN_zero(BIGNUM *a); +int BN_set_word(BIGNUM *a, BN_ULONG w); +int BN_copy(BIGNUM *to, const BIGNUM *from); +int BN_ucmp(const BIGNUM *a, const BIGNUM *b); +int BN_is_zero(const BIGNUM *a); +int BN_is_odd(const BIGNUM *a); +int BN_num_bits(const BIGNUM *a); +int BN_num_bytes(const BIGNUM *a); +int BN_set_bit(BIGNUM *a, int n); +int BN_is_bit_set(const BIGNUM *a, int n); +int BN_mask_bits(BIGNUM *a, int n); +int BN_lshift(BIGNUM *r, const BIGNUM *a, int n); +int BN_rshift1(BIGNUM *r, const BIGNUM *a); +int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +int BN_mul_word(BIGNUM *a, BN_ULONG w); +int BN_add_word(BIGNUM *a, BN_ULONG w); +int BN_sub_word(BIGNUM *a, BN_ULONG w); +BN_ULONG BN_div_word(BIGNUM *a, BN_ULONG w); +int BN_dec2bn(BIGNUM **a, const char *str); +char *BN_bn2dec(const BIGNUM *a); +int BN_bn2bin(const BIGNUM *a, unsigned char *to); +BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); +BIGNUM *BN_lebin2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2lebinpad(const BIGNUM *a, unsigned char *to, int tolen); +int BN_mod_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx); +int BN_mod_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx); +int BN_mod_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx); +int BN_mod_inverse(BIGNUM *r, const BIGNUM *a, const BIGNUM *m); +int BN_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); +BIGNUM *BN_mod_sqrt(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); +int BN_rand(BIGNUM *rnd, int bits, int top, int bottom); + +BN_CTX *BN_CTX_new(void); +void BN_CTX_free(BN_CTX *ctx); + +EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); +void EC_GROUP_free(EC_GROUP *group); +EC_POINT *EC_POINT_new(const EC_GROUP *group); +void EC_POINT_free(EC_POINT *point); +int EC_POINT_set_affine_coordinates(const EC_GROUP *group, EC_POINT *point, const BIGNUM *x, const BIGNUM *y, BN_CTX *ctx); +int EC_POINT_get_affine_coordinates(const EC_GROUP *group, const EC_POINT *point, BIGNUM *x, BIGNUM *y, BN_CTX *ctx); +int EC_POINT_is_on_curve(const EC_GROUP *group, const EC_POINT *point, BN_CTX *ctx); +int EC_POINT_add(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, const EC_POINT *b, BN_CTX *ctx); +int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx); + +unsigned char *SHA1(const unsigned char *d, size_t n, unsigned char *md); +int RAND_bytes(unsigned char *buf, int num); + +#endif diff --git a/src/crypto/mini_rand.cpp b/src/crypto/mini_rand.cpp new file mode 100644 index 0000000..1744523 --- /dev/null +++ b/src/crypto/mini_rand.cpp @@ -0,0 +1,183 @@ +/* This file is a part of the UMSKT Project. + * + * Copyleft (C) 2019-2026 UMSKT Contributors (et.al.) + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include "mini_openssl.h" + +#include +#include + +#if defined(_WIN32) +#ifndef WINVER +#define WINVER 0x0400 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 +#endif +#ifndef NTDDI_VERSION +#define NTDDI_VERSION 0x04000000 +#endif +#include +#elif defined(__DJGPP__) +#include +#include +#include +extern "C" long int random(void); +extern "C" int srandom(int seed); +#else +#include +#include +#endif + +static uint32_t rng_state[4] = {0xA3C59AC3u, 0x6F372C1Du, 0x19E8B2A5u, 0xC0FFEE11u}; +static int rng_ready = 0; + +static uint32_t rng_step(void) { + uint32_t t = rng_state[3]; + uint32_t s = rng_state[0]; + rng_state[3] = rng_state[2]; + rng_state[2] = rng_state[1]; + rng_state[1] = s; + t ^= t << 11; + t ^= t >> 8; + rng_state[0] = t ^ s ^ (s >> 19); + return rng_state[0]; +} + +static void rng_mix(uint32_t extra) { + rng_state[0] ^= extra; + rng_state[1] ^= extra * 0x9E3779B9u; + rng_step(); + rng_step(); +} + +static void rng_fill(unsigned char *buf, int num) { + int filled = 0; + int i; + for (i = 0; i < num; i++) { + if ((i & 3) == 0) { + filled = (int)rng_step(); + } + buf[i] = (unsigned char)(filled & 0xFF); + filled >>= 8; + } +} + +#if defined(_WIN32) +typedef int (WINAPI *CryptAcquireContextA_fn)(uintptr_t *prov, const char *container, const char *provider, unsigned long type, unsigned long flags); +typedef int (WINAPI *CryptGenRandom_fn)(uintptr_t prov, unsigned long len, unsigned char *buf); +typedef int (WINAPI *CryptReleaseContext_fn)(uintptr_t prov, unsigned long flags); + +static void rng_stir(void) { + LARGE_INTEGER counter; + SYSTEMTIME time; + counter.QuadPart = 0; + QueryPerformanceCounter(&counter); + GetSystemTime(&time); + rng_mix(GetTickCount()); + rng_mix((uint32_t)counter.LowPart); + rng_mix((uint32_t)counter.HighPart); + rng_mix(((uint32_t)time.wMilliseconds << 16) ^ ((uint32_t)time.wSecond << 8) ^ time.wMinute); + rng_mix(GetCurrentProcessId() ^ ((uint32_t)GetCurrentThreadId() << 16)); +} + +static int rng_capi(unsigned char *buf, int num) { + HMODULE advapi; + CryptAcquireContextA_fn acquire; + CryptGenRandom_fn generate; + CryptReleaseContext_fn release; + uintptr_t prov = 0; + int ok; + advapi = LoadLibraryA("advapi32.dll"); + if (!advapi) { + return 0; + } + acquire = (CryptAcquireContextA_fn)GetProcAddress(advapi, "CryptAcquireContextA"); + generate = (CryptGenRandom_fn)GetProcAddress(advapi, "CryptGenRandom"); + release = (CryptReleaseContext_fn)GetProcAddress(advapi, "CryptReleaseContext"); + if (!acquire || !generate || !release) { + FreeLibrary(advapi); + return 0; + } + if (!acquire(&prov, NULL, NULL, 1, 0xF0000000u)) { + FreeLibrary(advapi); + return 0; + } + ok = generate(prov, (unsigned long)num, buf); + release(prov, 0); + FreeLibrary(advapi); + return ok ? 1 : 0; +} + +int RAND_bytes(unsigned char *buf, int num) { + unsigned char extra[256]; + int i; + if (!buf || num < 0) { + return 0; + } + if (!rng_ready) { + rng_stir(); + rng_ready = 1; + } + rng_stir(); + if (num <= (int)sizeof(extra) && rng_capi(extra, num)) { + for (i = 0; i < num; i++) { + buf[i] = (unsigned char)(extra[i] ^ (rng_step() & 0xFF)); + } + return 1; + } + rng_fill(buf, num); + return 1; +} +#elif defined(__DJGPP__) +int RAND_bytes(unsigned char *buf, int num) { + int i; + if (!buf || num < 0) { + return 0; + } + if (!rng_ready) { + struct timeval tv; + int seed; + gettimeofday(&tv, NULL); + seed = (int)(tv.tv_sec ^ tv.tv_usec); + srandom(seed); + srand(seed ^ 0x1234ABCD); + rng_mix((uint32_t)seed); + rng_ready = 1; + } + for (i = 0; i < num; i++) { + buf[i] = (unsigned char)((random() ^ rand()) & 0xFF); + } + return 1; +} +#else +static void rng_stir(void) { + rng_mix((uint32_t)time(NULL)); + rng_mix((uint32_t)clock()); +} + +int RAND_bytes(unsigned char *buf, int num) { + FILE *random_file; + size_t got = 0; + if (!buf || num < 0) { + return 0; + } + random_file = fopen("/dev/urandom", "rb"); + if (random_file) { + got = fread(buf, 1, (size_t)num, random_file); + fclose(random_file); + if (got == (size_t)num) { + return 1; + } + } + if (!rng_ready) { + rng_stir(); + rng_ready = 1; + } + rng_stir(); + rng_fill(buf, num); + return 1; +} +#endif diff --git a/src/header.h b/src/header.h deleted file mode 100644 index 9600584..0000000 --- a/src/header.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * This file is a part of the UMSKT Project - * - * Copyleft (C) 2019-2023 UMSKT Contributors (et.al.) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * @FileCreated by Neo on 5/26/2023 - * @Maintainer Neo - */ - -#ifndef UMSKT_HEADER_H -#define UMSKT_HEADER_H - -#include "typedefs.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -using json = nlohmann::json; -namespace fs = std::filesystem; - -#endif //UMSKT_HEADER_H diff --git a/src/keys_embed.h b/src/keys_embed.h new file mode 100644 index 0000000..03e7fb7 --- /dev/null +++ b/src/keys_embed.h @@ -0,0 +1,8 @@ +#ifndef UMSKT_KEYS_EMBED_H +#define UMSKT_KEYS_EMBED_H + +/* Generated from keys.json. The bytes are not NUL-terminated; use the size. */ +extern const char umskt_keys_json[]; +extern const int umskt_keys_json_size; + +#endif diff --git a/src/libumskt/confid/confid.cpp b/src/libumskt/confid/confid.cpp index 3885f83..7f148e2 100644 --- a/src/libumskt/confid/confid.cpp +++ b/src/libumskt/confid/confid.cpp @@ -789,7 +789,48 @@ void ConfirmationID::Unmix(unsigned char* buffer, size_t bufSize, const unsigned } } -int ConfirmationID::Generate(const char* installation_id_str, char confirmation_id[49], int mode, std::string productid, bool overrideVersion) +static int dec_at(const char *text, int offset, int count) { + int value = 0; + int index; + if (!text) { + return 0; + } + for (index = 0; index < offset; index++) { + if (!text[index]) { + return 0; + } + } + for (index = 0; index < count; index++) { + char ch = text[offset + index]; + if (ch < '0' || ch > '9') { + break; + } + value = value * 10 + (ch - '0'); + } + return value; +} + +static void take_upper(char *dst, const char *text, int offset, int count) { + int index = 0; + int seen = 0; + if (text) { + while (text[seen] && seen < offset) { + seen++; + } + if (seen == offset) { + for (; index < count && text[offset + index]; index++) { + char ch = text[offset + index]; + if (ch >= 'a' && ch <= 'z') { + ch = (char)(ch - 32); + } + dst[index] = ch; + } + } + } + dst[index] = 0; +} + +int ConfirmationID::Generate(const char* installation_id_str, char confirmation_id[49], int mode, const char *productid, bool overrideVersion) { int version; unsigned char hardwareID[8]; @@ -951,21 +992,20 @@ int ConfirmationID::Generate(const char* installation_id_str, char confirmation_ } } memcpy(&parsed, hardwareID, 8); - productID[0] = stoi(productid.substr(0,5)); - std::string channelid = productid.substr(6,3); - char *p = &channelid[0]; - for (; *p; p++) { - *p = toupper((unsigned char)*p); - } - if (strcmp(&channelid[0], "OEM") == 0) { - productID[1] = stoi(productid.substr(12,3)); - productID[2] = (stoi(productid.substr(15,1)) * 100000) + stoi(productid.substr(18,5)); - productID[2] = calculateCheckDigit(productID[2]); - productID[3] = ((stoi(productid.substr(10,2))) * 1000) + productID[3]; - } else { - productID[1] = stoi(productid.substr(6,3)); - productID[2] = stoi(productid.substr(10,7)); - productID[3] = stoi(productid.substr(18,5)); + { + char channelid[8]; + productID[0] = dec_at(productid, 0, 5); + take_upper(channelid, productid, 6, 3); + if (strcmp(channelid, "OEM") == 0) { + productID[1] = dec_at(productid, 12, 3); + productID[2] = (dec_at(productid, 15, 1) * 100000) + dec_at(productid, 18, 5); + productID[2] = calculateCheckDigit(productID[2]); + productID[3] = (dec_at(productid, 10, 2) * 1000) + productID[3]; + } else { + productID[1] = dec_at(productid, 6, 3); + productID[2] = dec_at(productid, 10, 7); + productID[3] = dec_at(productid, 18, 5); + } } } // fmt::print("ProductID: {}-{}-{}-{} \n", productID[0], productID[1], productID[2], productID[3]); diff --git a/src/libumskt/confid/confid.h b/src/libumskt/confid/confid.h index aef1577..5542a4a 100644 --- a/src/libumskt/confid/confid.h +++ b/src/libumskt/confid/confid.h @@ -66,7 +66,7 @@ EXPORT class ConfirmationID { static void Unmix(unsigned char* buffer, size_t bufSize, const unsigned char* key, size_t keySize); public: - static int Generate(const char* installation_id_str, char confirmation_id[49], int mode, std::string productid, bool overrideVersion); + static int Generate(const char* installation_id_str, char confirmation_id[49], int mode, const char *productid, bool overrideVersion); //EXPORT static int CLIRun(); }; diff --git a/src/libumskt/debugoutput.cpp b/src/libumskt/debugoutput.cpp index 3d0b95a..53ca864 100644 --- a/src/libumskt/debugoutput.cpp +++ b/src/libumskt/debugoutput.cpp @@ -7,7 +7,7 @@ * 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 @@ -22,14 +22,47 @@ #include "libumskt.h" +#ifdef UMSKT_FREESTANDING +#include "../windows/win32_min.h" -#ifdef _WIN32 -std::FILE* UMSKT::debug = std::fopen("NUL:", "w"); +int UMSKT::debug_on = 0; + +void UMSKT::setDebug(int on) { + debug_on = on; +} + +void UMSKT::debugf(const char *fmt, ...) { + char buffer[1024]; + va_list ap; + uint32_t wrote = 0; + if (!debug_on || !fmt) { + return; + } + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + WriteFile(GetStdHandle(UMSKT_STD_ERROR_HANDLE), buffer, (uint32_t)strlen(buffer), &wrote, 0); +} #else -std::FILE* UMSKT::debug = std::fopen("/dev/null", "w"); +#ifdef _WIN32 +FILE *UMSKT::debug = fopen("NUL:", "w"); +#else +FILE *UMSKT::debug = fopen("/dev/null", "w"); #endif - -void UMSKT::setDebugOutput(std::FILE* input) { - debug = input; +void UMSKT::setDebug(int on) { + if (on) { + debug = stderr; + } } + +void UMSKT::debugf(const char *fmt, ...) { + va_list ap; + if (!debug || !fmt) { + return; + } + va_start(ap, fmt); + vfprintf(debug, fmt, ap); + va_end(ap); +} +#endif diff --git a/src/libumskt/libumskt.cpp b/src/libumskt/libumskt.cpp index 442e6ff..9f68481 100644 --- a/src/libumskt/libumskt.cpp +++ b/src/libumskt/libumskt.cpp @@ -27,7 +27,7 @@ #include "pidgen3/BINK2002.h" #include "pidgen2/PIDGEN2.h" -FNEXPORT int ConfirmationID_Generate(const char* installation_id_str, char confirmation_id[49], int mode, std::string productid, bool bypassVersion) { +FNEXPORT int ConfirmationID_Generate(const char* installation_id_str, char confirmation_id[49], int mode, const char *productid, bool bypassVersion) { return ConfirmationID::Generate(installation_id_str, confirmation_id, mode, productid, bypassVersion); } @@ -35,19 +35,19 @@ FNEXPORT EC_GROUP* PIDGEN3_initializeEllipticCurve(char* pSel, char* aSel, char* return PIDGEN3::initializeEllipticCurve(pSel, aSel, bSel, generatorXSel, generatorYSel, publicKeyXSel, publicKeyYSel, genPoint, pubPoint); } -FNEXPORT bool PIDGEN3_BINK1998_Verify(EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, char (&pKey)[25]) { +FNEXPORT bool PIDGEN3_BINK1998_Verify(EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, char (&pKey)[PK_LENGTH + NULL_TERMINATOR]) { return PIDGEN3::BINK1998::Verify(eCurve, basePoint, publicKey, pKey); } -FNEXPORT void PIDGEN3_BINK1998_Generate(EC_GROUP *eCurve, EC_POINT *basePoint, BIGNUM *genOrder, BIGNUM *privateKey, DWORD pSerial, BOOL pUpgrade,char (&pKey)[25]) { +FNEXPORT void PIDGEN3_BINK1998_Generate(EC_GROUP *eCurve, EC_POINT *basePoint, BIGNUM *genOrder, BIGNUM *privateKey, DWORD pSerial, BOOL pUpgrade,char (&pKey)[PK_LENGTH + NULL_TERMINATOR]) { return PIDGEN3::BINK1998::Generate(eCurve, basePoint, genOrder, privateKey, pSerial, pUpgrade, pKey); } -FNEXPORT bool PIDGEN3_BINK2002_Verify(EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, DWORD *pSerial, char (&cdKey)[25]) { +FNEXPORT bool PIDGEN3_BINK2002_Verify(EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, DWORD *pSerial, char (&cdKey)[PK_LENGTH + NULL_TERMINATOR]) { return PIDGEN3::BINK2002::Verify(eCurve, basePoint, publicKey, pSerial, cdKey); } -FNEXPORT void PIDGEN3_BINK2002_Generate(EC_GROUP *eCurve, EC_POINT *basePoint, BIGNUM *genOrder, BIGNUM *privateKey, DWORD pChannelID, DWORD pAuthInfo, BOOL pUpgrade, DWORD serMin, DWORD serMax, char (&pKey)[25]) { +FNEXPORT void PIDGEN3_BINK2002_Generate(EC_GROUP *eCurve, EC_POINT *basePoint, BIGNUM *genOrder, BIGNUM *privateKey, DWORD pChannelID, DWORD pAuthInfo, BOOL pUpgrade, DWORD serMin, DWORD serMax, char (&pKey)[PK_LENGTH + NULL_TERMINATOR]) { return PIDGEN3::BINK2002::Generate(eCurve, basePoint, genOrder, privateKey, pChannelID, pAuthInfo, pUpgrade, serMin, serMax, pKey); } @@ -59,77 +59,10 @@ FNEXPORT int PIDGEN2_GenerateOEM(char* year, char* day, char* oem, char* keyout) return PIDGEN2::GenerateOEM(year, day, oem, keyout); } -// RNG utility functions int UMSKT::umskt_rand_bytes(unsigned char *buf, int num) { -#if UMSKT_RNG_DJGPP - // DOS-compatible RNG using DJGPP's random() function - static bool initialized = false; - if (!initialized) { - // Get initial seed from multiple sources for better entropy - struct timeval tv; - gettimeofday(&tv, NULL); - - // Combine microseconds with BIOS timer ticks - unsigned long ticks = *(volatile unsigned long *)0x0040001CL; - int seed = (int)((tv.tv_sec ^ tv.tv_usec) ^ (ticks * 100000)); - - // Initialize both random() and rand() with different seeds - srandom(seed); - srand(seed ^ 0x1234ABCD); // Use a different seed for rand - - initialized = true; - } - - for (int i = 0; i < num; i++) { - // Use random() for better randomness, especially in lower bits - buf[i] = (unsigned char)(random() & 0xFF); - - // Mix in rand() as an additional source - buf[i] ^= (unsigned char)(rand() & 0xFF); - } - return 1; -#else - // Use OpenSSL's RAND_bytes for non-DOS systems return RAND_bytes(buf, num); -#endif } int UMSKT::umskt_bn_rand(BIGNUM *rnd, int bits, int top, int bottom) { -#if UMSKT_RNG_DJGPP - // DOS-compatible RNG implementation for BIGNUMs - unsigned char *buf = (unsigned char *)malloc((bits + 7) / 8); - if (!buf) return 0; - - // Generate random bytes - umskt_rand_bytes(buf, (bits + 7) / 8); - - // Convert to BIGNUM - if (!BN_bin2bn(buf, (bits + 7) / 8, rnd)) { - free(buf); - return 0; - } - - free(buf); - - // Apply top/bottom constraints like BN_rand does - if (top != -1) { - if (top) { - if (bits == 0) { - BN_zero(rnd); - return 1; - } - BN_set_bit(rnd, bits - 1); - } - BN_mask_bits(rnd, bits); - } - - if (bottom) { - BN_set_bit(rnd, 0); - } - - return 1; -#else - // Use OpenSSL's BN_rand for non-DOS systems return BN_rand(rnd, bits, top, bottom); -#endif } diff --git a/src/libumskt/libumskt.h b/src/libumskt/libumskt.h index 293ca0a..b35536f 100644 --- a/src/libumskt/libumskt.h +++ b/src/libumskt/libumskt.h @@ -25,22 +25,11 @@ #include "../typedefs.h" -#include -#include -#include +#include +#include +#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef __DJGPP__ -#include -#endif +#include "../crypto/mini_openssl.h" // Algorithm macros #define PK_LENGTH 25 @@ -63,25 +52,19 @@ #define BYDWORD(n) (DWORD)(*((n) + 0) | *((n) + 1) << 8 | *((n) + 2) << 16 | *((n) + 3) << 24) #define BITMASK(n) ((1ULL << (n)) - 1) -// RNG utility functions -#ifdef __DJGPP__ -#define UMSKT_RNG_DJGPP 1 -extern "C" { - long int random(void); - int srandom(int seed); -} -#else -#define UMSKT_RNG_DJGPP 0 -#endif - class UMSKT { public: - static std::FILE* debug; class PIDGEN2; class PIDGEN3; class ConfigurationID; - static void setDebugOutput(std::FILE* input); +#ifdef UMSKT_FREESTANDING + static int debug_on; +#else + static FILE *debug; +#endif + static void setDebug(int on); + static void debugf(const char *fmt, ...); // RNG utility functions static int umskt_rand_bytes(unsigned char *buf, int num); diff --git a/src/libumskt/pidgen2/PIDGEN2.cpp b/src/libumskt/pidgen2/PIDGEN2.cpp index 6a3b59e..7461343 100644 --- a/src/libumskt/pidgen2/PIDGEN2.cpp +++ b/src/libumskt/pidgen2/PIDGEN2.cpp @@ -22,6 +22,18 @@ #include "PIDGEN2.h" +static int parse_i(const char *text) { + int value = 0; + if (!text) { + return 0; + } + while (*text >= '0' && *text <= '9') { + value = value * 10 + (*text - '0'); + text++; + } + return value; +} + const char* channelIDBlacklist [7] = {"333", "444", "555", "666", "777", "888", "999"}; const char* validYears[8] = { "95", "96", "97", "98", "99", "00", "01", "02"}; @@ -93,7 +105,7 @@ bool PIDGEN2::isValidDay(char* day) { return false; } - int iDay = std::stoi(day); + int iDay = parse_i(day); if (iDay == 0 || iDay >= 365) { return false; } @@ -117,7 +129,9 @@ int PIDGEN2::GenerateOEM(char* year, char* day, char* oem, char* &keyout) { int mod = addDigits(oem); mod += mod % 21; - strcpy(oem, fmt::format("{:07d}", mod).c_str()); + char formatted[16]; + snprintf(formatted, sizeof(formatted), "%07d", mod); + strcpy(oem, formatted); } if (!isValidYear(year)) { @@ -125,11 +139,16 @@ int PIDGEN2::GenerateOEM(char* year, char* day, char* oem, char* &keyout) { } if (!isValidDay(day)) { - int iday = std::stoi(day); + int iday = parse_i(day); iday = (iday + 1) % 365; + (void)iday; } - strcpy(keyout, fmt::format("{}{}-OEM-{}-{}", year, day, oem, oem).c_str()); + { + char formatted[64]; + snprintf(formatted, sizeof(formatted), "%s%s-OEM-%s-%s", year, day, oem, oem); + strcpy(keyout, formatted); + } return 0; } \ No newline at end of file diff --git a/src/libumskt/pidgen3/BINK1998.cpp b/src/libumskt/pidgen3/BINK1998.cpp index 81c1736..e17b54d 100644 --- a/src/libumskt/pidgen3/BINK1998.cpp +++ b/src/libumskt/pidgen3/BINK1998.cpp @@ -75,7 +75,7 @@ bool PIDGEN3::BINK1998::Verify( EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, - char (&pKey)[25] + char (&pKey)[PK_LENGTH + NULL_TERMINATOR] ) { BN_CTX *numContext = BN_CTX_new(); @@ -94,12 +94,12 @@ bool PIDGEN3::BINK1998::Verify( // Extract RPK, hash and signature from bytecode. Unpack(pRaw, pUpgrade, pSerial, pHash, pSignature); - fmt::print(UMSKT::debug, "Validation results:\n"); - fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade); - fmt::print(UMSKT::debug, " Serial: 0x{:08x}\n", pSerial); - fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash); - fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature); - fmt::print(UMSKT::debug, "\n"); + UMSKT::debugf("Validation results:\n"); + UMSKT::debugf(" Upgrade: 0x%08x\n", (unsigned)pUpgrade); + UMSKT::debugf(" Serial: 0x%08x\n", (unsigned)pSerial); + UMSKT::debugf(" Hash: 0x%08x\n", (unsigned)pHash); + UMSKT::debugf(" Signature: 0x%08llx\n", (unsigned long long)pSignature); + UMSKT::debugf("\n"); pData = pSerial << 1 | pUpgrade; @@ -182,7 +182,7 @@ void PIDGEN3::BINK1998::Generate( BIGNUM *privateKey, DWORD pSerial, BOOL pUpgrade, - char (&pKey)[25] + char (&pKey)[PK_LENGTH + NULL_TERMINATOR] ) { BN_CTX *numContext = BN_CTX_new(); @@ -196,18 +196,16 @@ void PIDGEN3::BINK1998::Generate( // Data segment of the RPK. DWORD pData = pSerial << 1 | pUpgrade; + int tries = 0; + + // One random scalar. Further candidates are c+1, c+2, ... so each extra + // attempt adds G instead of multiplying the curve again. About 1 in 512 + // signatures fit in 55 bits, and a 386 cannot afford hundreds of multiplications. + EC_POINT *r = EC_POINT_new(eCurve); + UMSKT::umskt_bn_rand(c, FIELD_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); + EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext); do { - EC_POINT *r = EC_POINT_new(eCurve); - - // Generate a random number c consisting of 384 bits without any constraints. - UMSKT::umskt_bn_rand(c, FIELD_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); - - // Pick a random derivative of the base point on the elliptic curve. - // R = cG; - EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext); - - // Acquire its coordinates. // x = R.x; y = R.y; EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext); @@ -258,25 +256,34 @@ void PIDGEN3::BINK1998::Generate( // s += c (mod n) BN_mod_add(s, s, c, genOrder, numContext); - // Translate resulting scalar into a 64-bit integer (the byte order is little-endian). - BN_bn2lebinpad(s, (BYTE *)&pSignature, BN_num_bytes(s)); + // Low 64 bits only. The scalar is wider than this field, and the + // destination is an 8-byte QWORD. + BN_mask_bits(s, 64); + pSignature = 0; + BN_bn2lebinpad(s, (BYTE *)&pSignature, (int)sizeof(pSignature)); + tries++; // Pack product key. Pack(pRaw, pUpgrade, pSerial, pHash, pSignature); - fmt::print(UMSKT::debug, "Generation results:\n"); - fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade); - fmt::print(UMSKT::debug, " Serial: 0x{:08x}\n", pSerial); - fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash); - fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature); - fmt::print(UMSKT::debug, "\n"); + UMSKT::debugf("Generation results:\n"); + UMSKT::debugf(" Upgrade: 0x%08x\n", (unsigned)pUpgrade); + UMSKT::debugf(" Serial: 0x%08x\n", (unsigned)pSerial); + UMSKT::debugf(" Hash: 0x%08x\n", (unsigned)pHash); + UMSKT::debugf(" Signature: 0x%08llx\n", (unsigned long long)pSignature); + UMSKT::debugf("\n"); - EC_POINT_free(r); - } while (pSignature > BITMASK(55)); + if (tries < 4096 && pSignature > BITMASK(55)) { + EC_POINT_add(eCurve, r, r, basePoint, numContext); + BN_add_word(c, 1); + } + } while (tries < 4096 && pSignature > BITMASK(55)); // ↑ ↑ ↑ // The signature can't be longer than 55 bits, else it will // make the CD-key longer than 25 characters. + EC_POINT_free(r); + // Convert bytecode to Base24 CD-key. base24(pKey, (BYTE *)pRaw); diff --git a/src/libumskt/pidgen3/BINK1998.h b/src/libumskt/pidgen3/BINK1998.h index 0260f60..ae44635 100644 --- a/src/libumskt/pidgen3/BINK1998.h +++ b/src/libumskt/pidgen3/BINK1998.h @@ -47,7 +47,7 @@ public: EC_GROUP *eCurve, EC_POINT *basePoint, EC_POINT *publicKey, - char (&pKey)[25] + char (&pKey)[PK_LENGTH + NULL_TERMINATOR] ); static void Generate( @@ -57,7 +57,7 @@ public: BIGNUM *privateKey, DWORD pSerial, BOOL pUpgrade, - char (&pKey)[25] + char (&pKey)[PK_LENGTH + NULL_TERMINATOR] ); }; diff --git a/src/libumskt/pidgen3/BINK2002.cpp b/src/libumskt/pidgen3/BINK2002.cpp index 2ead4eb..126f40b 100644 --- a/src/libumskt/pidgen3/BINK2002.cpp +++ b/src/libumskt/pidgen3/BINK2002.cpp @@ -79,7 +79,7 @@ bool PIDGEN3::BINK2002::Verify( EC_POINT *basePoint, EC_POINT *publicKey, DWORD *pSerial, - char (&cdKey)[25] + char (&cdKey)[PK_LENGTH + NULL_TERMINATOR] ) { BN_CTX *context = BN_CTX_new(); @@ -183,14 +183,14 @@ bool PIDGEN3::BINK2002::Verify( DWORD serial = (((BYDWORD(msgDigest + 4) >> 13) << 1) | (BYDWORD(msgDigest) >> 31)) & BITMASK(20); if (pSerial != nullptr) *pSerial = serial; - fmt::print(UMSKT::debug, "Validation results:\n"); - fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade); - fmt::print(UMSKT::debug, "Channel ID: 0x{:08x}\n", pChannelID); - fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash); - fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature); - fmt::print(UMSKT::debug, " AuthInfo: 0x{:08x}\n", pAuthInfo); - fmt::print(UMSKT::debug, " Serial: {:06d}\n", serial); - fmt::print(UMSKT::debug, "\n"); + UMSKT::debugf("Validation results:\n"); + UMSKT::debugf(" Upgrade: 0x%08x\n", (unsigned)pUpgrade); + UMSKT::debugf("Channel ID: 0x%08x\n", (unsigned)pChannelID); + UMSKT::debugf(" Hash: 0x%08x\n", (unsigned)pHash); + UMSKT::debugf(" Signature: 0x%08llx\n", (unsigned long long)pSignature); + UMSKT::debugf(" AuthInfo: 0x%08x\n", (unsigned)pAuthInfo); + UMSKT::debugf(" Serial: %06u\n", (unsigned)serial); + UMSKT::debugf("\n"); // Translate the byte digest into a 32-bit integer - this is our computed hash. // Truncate the hash to 31 bits. @@ -221,11 +221,12 @@ void PIDGEN3::BINK2002::Generate( BOOL pUpgrade, DWORD serMin, DWORD serMax, - char (&pKey)[25] + char (&pKey)[PK_LENGTH + NULL_TERMINATOR] ) { BN_CTX *numContext = BN_CTX_new(); BIGNUM *c = BN_new(), + *c4 = BN_new(), *e = BN_new(), *s = BN_new(), *x = BN_new(), @@ -233,23 +234,21 @@ void PIDGEN3::BINK2002::Generate( QWORD pRaw[2]{}, pSignature = 0; + int tries = 0; // Data segment of the RPK. DWORD pData = pChannelID << 1 | pUpgrade; - BOOL noSquare; - BOOL serialInRange; + BOOL noSquare = true; + BOOL serialInRange = false; + + // One random scalar. Further candidates are c+1, c+2, ... so each extra + // attempt adds G instead of multiplying the curve again. + EC_POINT *r = EC_POINT_new(eCurve); + UMSKT::umskt_bn_rand(c, FIELD_BITS_2003, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); + EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext); do { - EC_POINT *r = EC_POINT_new(eCurve); - - // Generate a random number c consisting of 512 bits without any constraints. - UMSKT::umskt_bn_rand(c, FIELD_BITS_2003, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); - - // R = cG - EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext); - - // Acquire its coordinates. // x = R.x; y = R.y; EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext); @@ -278,7 +277,15 @@ void PIDGEN3::BINK2002::Generate( DWORD serial = (((BYDWORD(msgDigest + 4) >> 13) << 1) | (BYDWORD(msgDigest) >> 31)) & BITMASK(20); serialInRange = (serial >= serMin) && (serial <= serMax); - if (!serialInRange) continue; + if (!serialInRange) { + noSquare = true; + tries++; + if (tries < 4096) { + EC_POINT_add(eCurve, r, r, basePoint, numContext); + BN_add_word(c, 1); + } + continue; + } // Translate the byte digest into a 32-bit integer - this is our computed hash. // Truncate the hash to 31 bits. @@ -345,11 +352,12 @@ void PIDGEN3::BINK2002::Generate( // s = (ek (mod n))² BN_mod_sqr(s, s, genOrder, numContext); - // c *= 4 (c <<= 2) - BN_lshift(c, c, 2); + // 4c is a copy. c itself stays the scalar of R for the next step. + BN_copy(c4, c); + BN_lshift(c4, c4, 2); - // s += c - BN_add(s, s, c); + // s += 4c + BN_add(s, s, c4); // 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. @@ -369,31 +377,41 @@ void PIDGEN3::BINK2002::Generate( // s /= 2 (s >>= 1) BN_rshift1(s, s); - // Translate resulting scalar into a 64-bit integer (the byte order is little-endian). - BN_bn2lebinpad(s, (BYTE *)&pSignature, BN_num_bytes(s)); + // Low 64 bits only. The scalar is wider than this field, and the + // destination is an 8-byte QWORD. + BN_mask_bits(s, 64); + pSignature = 0; + BN_bn2lebinpad(s, (BYTE *)&pSignature, (int)sizeof(pSignature)); + tries++; // Pack product key. Pack(pRaw, pUpgrade, pChannelID, pHash, pSignature, pAuthInfo); - fmt::print(UMSKT::debug, "Generation results:\n"); - fmt::print(UMSKT::debug, " Upgrade: 0x{:08x}\n", pUpgrade); - fmt::print(UMSKT::debug, "Channel ID: 0x{:08x}\n", pChannelID); - fmt::print(UMSKT::debug, " Hash: 0x{:08x}\n", pHash); - fmt::print(UMSKT::debug, " Signature: 0x{:08x}\n", pSignature); - fmt::print(UMSKT::debug, " AuthInfo: 0x{:08x}\n", pAuthInfo); - fmt::print(UMSKT::debug, " Serial: {:06d}\n", serial); - fmt::print(UMSKT::debug, "\n"); + UMSKT::debugf("Generation results:\n"); + UMSKT::debugf(" Upgrade: 0x%08x\n", (unsigned)pUpgrade); + UMSKT::debugf("Channel ID: 0x%08x\n", (unsigned)pChannelID); + UMSKT::debugf(" Hash: 0x%08x\n", (unsigned)pHash); + UMSKT::debugf(" Signature: 0x%08llx\n", (unsigned long long)pSignature); + UMSKT::debugf(" AuthInfo: 0x%08x\n", (unsigned)pAuthInfo); + UMSKT::debugf(" Serial: %06u\n", (unsigned)serial); + UMSKT::debugf("\n"); - EC_POINT_free(r); - } while (pSignature > BITMASK(62) || noSquare || !serialInRange); + if (tries < 4096 && (pSignature > BITMASK(62) || noSquare || !serialInRange)) { + EC_POINT_add(eCurve, r, r, basePoint, numContext); + BN_add_word(c, 1); + } + } while (tries < 4096 && (pSignature > BITMASK(62) || noSquare || !serialInRange)); // ↑ ↑ ↑ // The signature can't be longer than 62 bits, else it will // overlap with the AuthInfo segment next to it. + EC_POINT_free(r); + // Convert bytecode to Base24 CD-key. base24(pKey, (BYTE *)pRaw); BN_free(c); + BN_free(c4); BN_free(s); BN_free(x); BN_free(y); diff --git a/src/libumskt/pidgen3/BINK2002.h b/src/libumskt/pidgen3/BINK2002.h index 921ff65..0e2c676 100644 --- a/src/libumskt/pidgen3/BINK2002.h +++ b/src/libumskt/pidgen3/BINK2002.h @@ -50,7 +50,7 @@ public: EC_POINT *basePoint, EC_POINT *publicKey, DWORD *pSerial, - char (&cdKey)[25] + char (&cdKey)[PK_LENGTH + NULL_TERMINATOR] ); static void Generate( @@ -63,7 +63,7 @@ public: BOOL pUpgrade, DWORD serMin, DWORD serMax, - char (&pKey)[25] + char (&pKey)[PK_LENGTH + NULL_TERMINATOR] ); }; diff --git a/src/libumskt/pidgen3/PIDGEN3.h b/src/libumskt/pidgen3/PIDGEN3.h index 6f77de1..24851c6 100644 --- a/src/libumskt/pidgen3/PIDGEN3.h +++ b/src/libumskt/pidgen3/PIDGEN3.h @@ -34,13 +34,13 @@ public: static int BN_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen); // Hello OpenSSL developers, please tell me, where is this function at? static void endian(BYTE *data, int length); static EC_GROUP* initializeEllipticCurve( - std::string pSel, - std::string aSel, - std::string bSel, - std::string generatorXSel, - std::string generatorYSel, - std::string publicKeyXSel, - std::string publicKeyYSel, + const char *pSel, + const char *aSel, + const char *bSel, + const char *generatorXSel, + const char *generatorYSel, + const char *publicKeyXSel, + const char *publicKeyYSel, EC_POINT *&genPoint, EC_POINT *&pubPoint ); diff --git a/src/libumskt/pidgen3/key.cpp b/src/libumskt/pidgen3/key.cpp index 636669c..a59661f 100644 --- a/src/libumskt/pidgen3/key.cpp +++ b/src/libumskt/pidgen3/key.cpp @@ -76,8 +76,8 @@ void PIDGEN3::base24(char *cdKey, BYTE *byteSeq) { // Convert reversed byte sequence to BigNum z. z = BN_bin2bn(rbyteSeq, length, nullptr); - // Divide z by 24 and convert the remainder to a CD-key char. - cdKey[25] = 0; + // 25 key characters plus the terminator. Callers pass PK_LENGTH + NULL_TERMINATOR. + cdKey[PK_LENGTH] = 0; for (int i = 24; i >= 0; i--) cdKey[i] = pKeyCharset[BN_div_word(z, 24)]; diff --git a/src/libumskt/pidgen3/util.cpp b/src/libumskt/pidgen3/util.cpp index 2a878e5..be16582 100644 --- a/src/libumskt/pidgen3/util.cpp +++ b/src/libumskt/pidgen3/util.cpp @@ -38,13 +38,13 @@ void PIDGEN3::endian(BYTE *data, int length) { /* Initializes the elliptic curve. */ EC_GROUP* PIDGEN3::initializeEllipticCurve( - const std::string pSel, - const std::string aSel, - const std::string bSel, - const std::string generatorXSel, - const std::string generatorYSel, - const std::string publicKeyXSel, - const std::string publicKeyYSel, + const char *pSel, + const char *aSel, + const char *bSel, + const char *generatorXSel, + const char *generatorYSel, + const char *publicKeyXSel, + const char *publicKeyYSel, EC_POINT *&genPoint, EC_POINT *&pubPoint ) { @@ -72,14 +72,14 @@ EC_GROUP* PIDGEN3::initializeEllipticCurve( context = BN_CTX_new(); /* Public data */ - BN_dec2bn(&p, pSel.c_str()); - BN_dec2bn(&a, aSel.c_str()); - BN_dec2bn(&b, bSel.c_str()); - BN_dec2bn(&generatorX, generatorXSel.c_str()); - BN_dec2bn(&generatorY, generatorYSel.c_str()); + BN_dec2bn(&p, pSel); + BN_dec2bn(&a, aSel); + BN_dec2bn(&b, bSel); + BN_dec2bn(&generatorX, generatorXSel); + BN_dec2bn(&generatorY, generatorYSel); - BN_dec2bn(&publicKeyX, publicKeyXSel.c_str()); - BN_dec2bn(&publicKeyY, publicKeyYSel.c_str()); + BN_dec2bn(&publicKeyX, publicKeyXSel); + BN_dec2bn(&publicKeyY, publicKeyYSel); /* Elliptic Curve calculations. */ // The group is defined via Fp = all integers [0; p - 1], where p is prime. diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 31234b6..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/** - * This file is a part of the UMSKT Project - * - * Copyleft (C) 2019-2023 UMSKT Contributors (et.al.) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * @FileCreated by Andrew on 01/06/2023 - * @Maintainer Neo - */ - -#include "header.h" -#include "cli.h" - -Options options; - -int main(int argc, char *argv[]) { - if (!CLI::parseCommandLine(argc, argv, &options)) { - fmt::print("error parsing command line options\n"); - CLI::showHelp(argv); - return !options.error ? 0 : 1; - } - - json keys; - - int status = CLI::validateCommandLine(&options, argv, &keys); - if (status > 0) { - return status; - } - - CLI run(options, keys); - - switch(options.applicationMode) { - case MODE_BINK1998_GENERATE: - return run.BINK1998Generate(); - - case MODE_BINK2002_GENERATE: - return run.BINK2002Generate(); - - case MODE_BINK1998_VALIDATE: - return run.BINK1998Validate(); - - case MODE_BINK2002_VALIDATE: - return run.BINK2002Validate(); - - case MODE_CONFIRMATION_ID: - return run.ConfirmationID(); - - default: - return 1; - } -} diff --git a/src/windows/crt_win32.cpp b/src/windows/crt_win32.cpp new file mode 100644 index 0000000..ded6843 --- /dev/null +++ b/src/windows/crt_win32.cpp @@ -0,0 +1,729 @@ +#ifndef WINVER +#define WINVER 0x0400 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 +#endif +#ifndef NTDDI_VERSION +#define NTDDI_VERSION 0x04000000 +#endif + +#include +#include +#include +#include + +extern int main(int argc, char **argv); + +typedef void (*umskt_ctor)(void); + +extern "C" umskt_ctor __init_array_start[] __attribute__((weak)); +extern "C" umskt_ctor __init_array_end[] __attribute__((weak)); + +extern "C" void *memset(void *dst, int value, size_t count) { + unsigned char *bytes = (unsigned char *)dst; + unsigned char fill = (unsigned char)value; + while (count--) { + *bytes++ = fill; + } + return dst; +} + +extern "C" void *memcpy(void *dst, const void *src, size_t count) { + unsigned char *out = (unsigned char *)dst; + const unsigned char *in = (const unsigned char *)src; + while (count--) { + *out++ = *in++; + } + return dst; +} + +extern "C" void *memmove(void *dst, const void *src, size_t count) { + unsigned char *out = (unsigned char *)dst; + const unsigned char *in = (const unsigned char *)src; + size_t index; + if (out == in || count == 0) { + return dst; + } + if (out < in) { + for (index = 0; index < count; index++) { + out[index] = in[index]; + } + } else { + index = count; + while (index > 0) { + index--; + out[index] = in[index]; + } + } + return dst; +} + +extern "C" int memcmp(const void *left, const void *right, size_t count) { + const unsigned char *a = (const unsigned char *)left; + const unsigned char *b = (const unsigned char *)right; + while (count--) { + if (*a != *b) { + return (int)*a - (int)*b; + } + a++; + b++; + } + return 0; +} + +extern "C" size_t strlen(const char *text) { + size_t n = 0; + if (!text) { + return 0; + } + while (text[n]) { + n++; + } + return n; +} + +extern "C" int strcmp(const char *a, const char *b) { + if (!a || !b) { + return a == b ? 0 : (a ? 1 : -1); + } + while (*a && *a == *b) { + a++; + b++; + } + return (unsigned char)*a - (unsigned char)*b; +} + +extern "C" int strncmp(const char *a, const char *b, size_t n) { + while (n && *a && *a == *b) { + a++; + b++; + n--; + } + if (!n) { + return 0; + } + return (unsigned char)*a - (unsigned char)*b; +} + +extern "C" char *strcpy(char *dst, const char *src) { + char *out = dst; + while ((*dst++ = *src++)) { + } + return out; +} + +extern "C" char *strncpy(char *dst, const char *src, size_t n) { + size_t i = 0; + for (; i < n && src[i]; i++) { + dst[i] = src[i]; + } + for (; i < n; i++) { + dst[i] = 0; + } + return dst; +} + +extern "C" char *strchr(const char *text, int ch) { + if (!text) { + return NULL; + } + while (*text && *text != (char)ch) { + text++; + } + if (*text == (char)ch) { + return (char *)text; + } + return NULL; +} + +extern "C" void *malloc(size_t size) { + if (size == 0) { + size = 1; + } + return HeapAlloc(GetProcessHeap(), 0, size); +} + +extern "C" void *calloc(size_t count, size_t size) { + size_t bytes = count * size; + if (count != 0 && bytes / count != size) { + return NULL; + } + if (bytes == 0) { + bytes = 1; + } + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bytes); +} + +extern "C" void free(void *block) { + if (block) { + HeapFree(GetProcessHeap(), 0, block); + } +} + +extern "C" void *realloc(void *block, size_t size) { + if (!block) { + return malloc(size); + } + if (size == 0) { + free(block); + return NULL; + } + return HeapReAlloc(GetProcessHeap(), 0, block, size); +} + +extern "C" void abort(void) { + ExitProcess(3); +} + +extern "C" int atexit(void (*function)(void)) { + (void)function; + return 0; +} + +extern "C" int __cxa_atexit(void (*function)(void *), void *arg, void *dso) { + (void)function; + (void)arg; + (void)dso; + return 0; +} + +extern "C" void __cxa_pure_virtual(void) { + ExitProcess(1); +} + +void *operator new(size_t count) { + return malloc(count ? count : 1); +} + +void *operator new[](size_t count) { + return malloc(count ? count : 1); +} + +void operator delete(void *ptr) noexcept { + free(ptr); +} + +void operator delete[](void *ptr) noexcept { + free(ptr); +} + +void operator delete(void *ptr, size_t) noexcept { + free(ptr); +} + +void operator delete[](void *ptr, size_t) noexcept { + free(ptr); +} + +#if defined(__i386__) +extern "C" __attribute__((naked)) void __chkstk_ms(void) { + __asm__ __volatile__( + "pushl %ecx\n\t" + "pushl %eax\n\t" + "cmpl $0x1000, %eax\n\t" + "leal 12(%esp), %ecx\n\t" + "jb 1f\n\t" + "2:\n\t" + "subl $0x1000, %ecx\n\t" + "testl %eax, (%ecx)\n\t" + "subl $0x1000, %eax\n\t" + "cmpl $0x1000, %eax\n\t" + "ja 2b\n\t" + "1:\n\t" + "subl %eax, %ecx\n\t" + "testl %eax, (%ecx)\n\t" + "popl %eax\n\t" + "popl %ecx\n\t" + "ret\n\t"); +} + +static void udivmod64(uint32_t nlo, uint32_t nhi, uint32_t dlo, uint32_t dhi, uint32_t *qlo, uint32_t *qhi, uint32_t *rlo, uint32_t *rhi) { + uint32_t ql = 0; + uint32_t qh = 0; + uint32_t rl = 0; + uint32_t rh = 0; + int bit; + if (dlo == 0 && dhi == 0) { + *qlo = 0; + *qhi = 0; + *rlo = 0; + *rhi = 0; + return; + } + for (bit = 63; bit >= 0; bit--) { + uint32_t src = bit >= 32 ? nhi : nlo; + uint32_t src_bit = (src >> (bit >= 32 ? bit - 32 : bit)) & 1u; + rh = (rh << 1) | (rl >> 31); + rl = (rl << 1) | src_bit; + if (rh > dhi || (rh == dhi && rl >= dlo)) { + uint32_t new_rl = rl - dlo; + uint32_t borrow = rl < dlo; + rh = rh - dhi - borrow; + rl = new_rl; + if (bit >= 32) { + qh |= 1u << (bit - 32); + } else { + ql |= 1u << bit; + } + } + } + *qlo = ql; + *qhi = qh; + *rlo = rl; + *rhi = rh; +} + +extern "C" unsigned long long __udivdi3(unsigned long long n, unsigned long long d) { + union { + unsigned long long v; + uint32_t p[2]; + } N, D, Q; + uint32_t rlo, rhi; + N.v = n; + D.v = d; + udivmod64(N.p[0], N.p[1], D.p[0], D.p[1], &Q.p[0], &Q.p[1], &rlo, &rhi); + return Q.v; +} + +extern "C" unsigned long long __umoddi3(unsigned long long n, unsigned long long d) { + union { + unsigned long long v; + uint32_t p[2]; + } N, D, R; + uint32_t qlo, qhi; + N.v = n; + D.v = d; + udivmod64(N.p[0], N.p[1], D.p[0], D.p[1], &qlo, &qhi, &R.p[0], &R.p[1]); + return R.v; +} + +extern "C" unsigned long long __udivmoddi4(unsigned long long n, unsigned long long d, unsigned long long *rem) { + unsigned long long quot = __udivdi3(n, d); + if (rem) { + *rem = __umoddi3(n, d); + } + return quot; +} + +extern "C" long long __divdi3(long long a, long long b) { + int neg = 0; + unsigned long long ua; + unsigned long long ub; + unsigned long long uq; + if (a < 0) { + neg ^= 1; + ua = 0u - (unsigned long long)a; + } else { + ua = (unsigned long long)a; + } + if (b < 0) { + neg ^= 1; + ub = 0u - (unsigned long long)b; + } else { + ub = (unsigned long long)b; + } + uq = __udivdi3(ua, ub); + if (neg) { + uq = 0u - uq; + } + return (long long)uq; +} + +extern "C" long long __moddi3(long long a, long long b) { + int neg = a < 0; + unsigned long long ua = a < 0 ? (0u - (unsigned long long)a) : (unsigned long long)a; + unsigned long long ub = b < 0 ? (0u - (unsigned long long)b) : (unsigned long long)b; + unsigned long long ur = __umoddi3(ua, ub); + if (neg) { + ur = 0u - ur; + } + return (long long)ur; +} + +extern "C" long long __muldi3(long long a, long long b) { + union { + unsigned long long v; + uint32_t p[2]; + } A, B, R; + uint64_t low; + uint64_t mid; + A.v = (unsigned long long)a; + B.v = (unsigned long long)b; + low = (uint64_t)A.p[0] * B.p[0]; + mid = (low >> 32) + (uint64_t)A.p[0] * B.p[1] + (uint64_t)A.p[1] * B.p[0]; + R.p[0] = (uint32_t)low; + R.p[1] = (uint32_t)mid; + return (long long)R.v; +} + +extern "C" unsigned long long __lshrdi3(unsigned long long value, int amount) { + union { + unsigned long long v; + uint32_t p[2]; + } u; + u.v = value; + if (amount <= 0) { + return value; + } + if (amount >= 64) { + u.p[0] = 0; + u.p[1] = 0; + return u.v; + } + if (amount >= 32) { + u.p[0] = u.p[1] >> (amount - 32); + u.p[1] = 0; + return u.v; + } + u.p[0] = (u.p[0] >> amount) | (u.p[1] << (32 - amount)); + u.p[1] >>= amount; + return u.v; +} + +extern "C" unsigned long long __ashldi3(unsigned long long value, int amount) { + union { + unsigned long long v; + uint32_t p[2]; + } u; + u.v = value; + if (amount <= 0) { + return value; + } + if (amount >= 64) { + u.p[0] = 0; + u.p[1] = 0; + return u.v; + } + if (amount >= 32) { + u.p[1] = u.p[0] << (amount - 32); + u.p[0] = 0; + return u.v; + } + u.p[1] = (u.p[1] << amount) | (u.p[0] >> (32 - amount)); + u.p[0] <<= amount; + return u.v; +} + +extern "C" long long __ashrdi3(long long value, int amount) { + union { + unsigned long long v; + uint32_t p[2]; + } u; + uint32_t lo; + uint32_t hi; + uint32_t sign; + u.v = (unsigned long long)value; + lo = u.p[0]; + hi = u.p[1]; + sign = hi & 0x80000000u; + if (amount <= 0) { + return value; + } + if (amount >= 64) { + u.p[0] = sign ? 0xFFFFFFFFu : 0; + u.p[1] = sign ? 0xFFFFFFFFu : 0; + return (long long)u.v; + } + if (amount >= 32) { + u.p[0] = (uint32_t)((int32_t)hi >> (amount - 32)); + u.p[1] = sign ? 0xFFFFFFFFu : 0; + return (long long)u.v; + } + u.p[0] = (lo >> amount) | (hi << (32 - amount)); + u.p[1] = (uint32_t)((int32_t)hi >> amount); + return (long long)u.v; +} +#elif defined(__x86_64__) +extern "C" __attribute__((naked)) void __chkstk_ms(void) { + __asm__ __volatile__( + "pushq %rcx\n\t" + "pushq %rax\n\t" + "cmpq $0x1000, %rax\n\t" + "leaq 24(%rsp), %rcx\n\t" + "jb 1f\n\t" + "2:\n\t" + "subq $0x1000, %rcx\n\t" + "testq %rax, (%rcx)\n\t" + "subq $0x1000, %rax\n\t" + "cmpq $0x1000, %rax\n\t" + "ja 2b\n\t" + "1:\n\t" + "subq %rax, %rcx\n\t" + "testq %rax, (%rcx)\n\t" + "popq %rax\n\t" + "popq %rcx\n\t" + "ret\n\t"); +} +#endif + +static int main_ctors_done = 0; + +extern "C" void __main(void) { + umskt_ctor *ctor; + if (main_ctors_done) { + return; + } + main_ctors_done = 1; + if (!__init_array_start || !__init_array_end) { + return; + } + for (ctor = __init_array_start; ctor < __init_array_end; ctor++) { + if (*ctor) { + (*ctor)(); + } + } +} + +static void fmt_putc(char *dst, size_t cap, size_t *used, char ch) { + if (*used + 1 < cap) { + dst[*used] = ch; + } + (*used)++; +} + +static void fmt_puts(char *dst, size_t cap, size_t *used, const char *text) { + if (!text) { + text = "(null)"; + } + while (*text) { + fmt_putc(dst, cap, used, *text++); + } +} + +static void fmt_pad(char *dst, size_t cap, size_t *used, const char *digits, int count, int width, int zero) { + int pad = width - count; + if (digits[0] == '-' && zero) { + fmt_putc(dst, cap, used, '-'); + digits++; + count--; + pad = width - count - 1; + } + while (pad-- > 0) { + fmt_putc(dst, cap, used, zero ? '0' : ' '); + } + while (count-- > 0) { + fmt_putc(dst, cap, used, *digits++); + } +} + +static void fmt_u64(char *dst, size_t cap, size_t *used, unsigned long long value, unsigned base, int upper, int width, int zero) { + char rev[72]; + int count = 0; + const char *alphabet = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + if (value == 0) { + rev[count++] = '0'; + } + while (value) { + rev[count++] = alphabet[value % base]; + value /= base; + } + { + char forward[72]; + int i; + for (i = 0; i < count; i++) { + forward[i] = rev[count - 1 - i]; + } + forward[count] = 0; + fmt_pad(dst, cap, used, forward, count, width, zero); + } +} + +static int umskt_vsnprintf(char *dst, size_t cap, const char *fmt, va_list ap) { + size_t used = 0; + if (!fmt) { + fmt = ""; + } + while (*fmt) { + if (*fmt != '%') { + fmt_putc(dst, cap, &used, *fmt++); + continue; + } + fmt++; + if (*fmt == '%') { + fmt_putc(dst, cap, &used, '%'); + fmt++; + continue; + } + int zero = 0; + int width = 0; + int is_long = 0; + if (*fmt == '0') { + zero = 1; + fmt++; + } + while (*fmt >= '0' && *fmt <= '9') { + width = width * 10 + (*fmt - '0'); + fmt++; + } + if (*fmt == 'l') { + is_long = 1; + fmt++; + if (*fmt == 'l') { + fmt++; + } + } + if (*fmt == 's') { + fmt_puts(dst, cap, &used, va_arg(ap, const char *)); + } else if (*fmt == 'c') { + fmt_putc(dst, cap, &used, (char)va_arg(ap, int)); + } else if (*fmt == 'd' || *fmt == 'i') { + if (is_long) { + long long value = va_arg(ap, long long); + if (value < 0) { + unsigned long long mag = 0u - (unsigned long long)value; + char rev[72]; + int count = 0; + char forward[72]; + int i; + if (mag == 0) { + rev[count++] = '0'; + } + while (mag) { + rev[count++] = (char)('0' + (mag % 10)); + mag /= 10; + } + forward[0] = '-'; + for (i = 0; i < count; i++) { + forward[i + 1] = rev[count - 1 - i]; + } + fmt_pad(dst, cap, &used, forward, count + 1, width, zero); + } else { + fmt_u64(dst, cap, &used, (unsigned long long)value, 10, 0, width, zero); + } + } else { + int value = va_arg(ap, int); + if (value < 0) { + unsigned int mag = 0u - (unsigned int)value; + char rev[16]; + char forward[16]; + int count = 0; + int i; + while (mag) { + rev[count++] = (char)('0' + (mag % 10)); + mag /= 10; + } + if (count == 0) { + rev[count++] = '0'; + } + forward[0] = '-'; + for (i = 0; i < count; i++) { + forward[i + 1] = rev[count - 1 - i]; + } + fmt_pad(dst, cap, &used, forward, count + 1, width, zero); + } else { + fmt_u64(dst, cap, &used, (unsigned)value, 10, 0, width, zero); + } + } + } else if (*fmt == 'u') { + if (is_long) { + fmt_u64(dst, cap, &used, va_arg(ap, unsigned long long), 10, 0, width, zero); + } else { + fmt_u64(dst, cap, &used, va_arg(ap, unsigned int), 10, 0, width, zero); + } + } else if (*fmt == 'x' || *fmt == 'X') { + if (is_long) { + fmt_u64(dst, cap, &used, va_arg(ap, unsigned long long), 16, *fmt == 'X', width, zero); + } else { + fmt_u64(dst, cap, &used, va_arg(ap, unsigned int), 16, *fmt == 'X', width, zero); + } + } else if (*fmt) { + fmt_putc(dst, cap, &used, *fmt); + } + if (*fmt) { + fmt++; + } + } + if (cap > 0) { + size_t end = used < cap ? used : cap - 1; + dst[end] = 0; + } + return (int)used; +} + +/* MinGW's stdio.h turns snprintf/vsnprintf into an inline that calls these. */ +#ifdef snprintf +#undef snprintf +#endif +#ifdef vsnprintf +#undef vsnprintf +#endif + +extern "C" int __mingw_vsnprintf(char *dst, size_t cap, const char *fmt, va_list ap) { + return umskt_vsnprintf(dst, cap, fmt, ap); +} + +extern "C" int vsnprintf(char *dst, size_t cap, const char *fmt, va_list ap) { + return umskt_vsnprintf(dst, cap, fmt, ap); +} + +extern "C" int snprintf(char *dst, size_t cap, const char *fmt, ...) { + va_list ap; + int count; + va_start(ap, fmt); + count = umskt_vsnprintf(dst, cap, fmt, ap); + va_end(ap); + return count; +} + +static int is_space(char ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; +} + +extern "C" void mainCRTStartup(void) { + char *command = GetCommandLineA(); + char *buffer; + char **argv; + int length = 0; + int argc = 0; + char *cursor; + HANDLE heap = GetProcessHeap(); + + __main(); + if (!command) { + command = (char *)""; + } + while (command[length]) { + length++; + } + buffer = (char *)HeapAlloc(heap, 0, (SIZE_T)length + 1); + argv = (char **)HeapAlloc(heap, HEAP_ZERO_MEMORY, (SIZE_T)(length + 2) * sizeof(char *)); + if (!buffer || !argv) { + ExitProcess(1); + } + for (int index = 0; index <= length; index++) { + buffer[index] = command[index]; + } + cursor = buffer; + while (*cursor) { + char *out; + int quoted = 0; + int more; + while (is_space(*cursor)) { + cursor++; + } + if (!*cursor) { + break; + } + argv[argc++] = cursor; + out = cursor; + while (*cursor) { + if (*cursor == '"') { + quoted = !quoted; + cursor++; + continue; + } + if (!quoted && is_space(*cursor)) { + break; + } + *out++ = *cursor++; + } + more = *cursor != 0; + *out = 0; + if (more) { + cursor++; + } + } + argv[argc] = NULL; + ExitProcess((UINT)main(argc, argv)); +} diff --git a/src/windows/json.cpp b/src/windows/json.cpp new file mode 100644 index 0000000..b0b3c69 --- /dev/null +++ b/src/windows/json.cpp @@ -0,0 +1,236 @@ +/* This file is a part of the UMSKT Project. + * + * Copyleft (C) 2019-2026 UMSKT Contributors (et.al.) + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include "json.h" + +struct JsonCursor { + const char *begin; + const char *p; + const char *end; +}; + +static void skip_ws(JsonCursor *j) { + while (j->p < j->end) { + char ch = *j->p; + if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r') { + break; + } + j->p++; + } +} + +static int skip_string(JsonCursor *j) { + if (j->p >= j->end || *j->p != '"') { + return 0; + } + j->p++; + while (j->p < j->end) { + char ch = *j->p++; + if (ch == '\\') { + if (j->p < j->end) { + j->p++; + } + continue; + } + if (ch == '"') { + return 1; + } + } + return 0; +} + +static int skip_value(JsonCursor *j) { + skip_ws(j); + if (j->p >= j->end) { + return 0; + } + if (*j->p == '"') { + return skip_string(j); + } + if (*j->p == '{' || *j->p == '[') { + char open = *j->p; + char close = open == '{' ? '}' : ']'; + int depth = 0; + while (j->p < j->end) { + if (*j->p == '"') { + if (!skip_string(j)) { + return 0; + } + continue; + } + if (*j->p == open) { + depth++; + } else if (*j->p == close) { + depth--; + j->p++; + if (depth == 0) { + return 1; + } + continue; + } + j->p++; + } + return 0; + } + while (j->p < j->end) { + char ch = *j->p; + if (ch == ',' || ch == '}' || ch == ']' || ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') { + break; + } + j->p++; + } + return 1; +} + +static int parse_key(JsonCursor *j, const char **key, int *key_length) { + const char *start; + skip_ws(j); + if (j->p >= j->end || *j->p != '"') { + return 0; + } + j->p++; + start = j->p; + while (j->p < j->end && *j->p != '"') { + if (*j->p == '\\') { + return 0; + } + j->p++; + } + if (j->p >= j->end) { + return 0; + } + *key = start; + *key_length = (int)(j->p - start); + j->p++; + return 1; +} + +static int key_eq(const char *key, int key_length, const char *wanted) { + int i = 0; + while (wanted[i] && i < key_length) { + if (key[i] != wanted[i]) { + return 0; + } + i++; + } + return wanted[i] == 0 && i == key_length; +} + +int json_object_find(const char *object, int length, const char *key, JsonSpan *value) { + JsonCursor j; + j.begin = object; + j.p = object; + j.end = object + length; + skip_ws(&j); + if (j.p >= j.end || *j.p != '{') { + return 0; + } + j.p++; + while (j.p < j.end) { + const char *found; + int found_len; + skip_ws(&j); + if (j.p < j.end && *j.p == '}') { + return 0; + } + if (!parse_key(&j, &found, &found_len)) { + return 0; + } + skip_ws(&j); + if (j.p >= j.end || *j.p != ':') { + return 0; + } + j.p++; + skip_ws(&j); + if (key_eq(found, found_len, key)) { + const char *start = j.p; + if (!skip_value(&j)) { + return 0; + } + value->p = start; + value->n = (int)(j.p - start); + return 1; + } + if (!skip_value(&j)) { + return 0; + } + skip_ws(&j); + if (j.p < j.end && *j.p == ',') { + j.p++; + } + } + return 0; +} + +int json_unquote(const JsonSpan *value, const char **text, int *length) { + const char *p; + const char *end; + if (!value || value->n < 2 || value->p[0] != '"') { + return 0; + } + p = value->p + 1; + end = value->p + value->n; + if (end[-1] != '"') { + return 0; + } + *text = p; + *length = (int)(end - p - 1); + return 1; +} + +static int next_member(const char *text, int length, int *cursor, int array, const char **key, int *key_length, JsonSpan *value) { + JsonCursor j; + const char *open; + j.begin = text; + j.end = text + length; + if (*cursor == 0) { + j.p = text; + skip_ws(&j); + if (j.p >= j.end || *j.p != (array ? '[' : '{')) { + return 0; + } + j.p++; + *cursor = (int)(j.p - text); + } + j.p = text + *cursor; + skip_ws(&j); + if (j.p >= j.end || *j.p == (array ? ']' : '}')) { + return 0; + } + if (!array) { + if (!parse_key(&j, key, key_length)) { + return 0; + } + skip_ws(&j); + if (j.p >= j.end || *j.p != ':') { + return 0; + } + j.p++; + skip_ws(&j); + } + open = j.p; + if (!skip_value(&j)) { + return 0; + } + value->p = open; + value->n = (int)(j.p - open); + skip_ws(&j); + if (j.p < j.end && *j.p == ',') { + j.p++; + } + *cursor = (int)(j.p - text); + return 1; +} + +int json_object_next(const char *object, int length, int *cursor, const char **key, int *key_length, JsonSpan *value) { + return next_member(object, length, cursor, 0, key, key_length, value); +} + +int json_array_next(const char *array, int length, int *cursor, JsonSpan *value) { + const char *ignored = 0; + int ignored_len = 0; + return next_member(array, length, cursor, 1, &ignored, &ignored_len, value); +} diff --git a/src/windows/json.h b/src/windows/json.h new file mode 100644 index 0000000..262f3ce --- /dev/null +++ b/src/windows/json.h @@ -0,0 +1,29 @@ +/* This file is a part of the UMSKT Project. + * + * Copyleft (C) 2019-2026 UMSKT Contributors (et.al.) + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * Walks the keys.json object shape. Strings are spans into the source buffer. + */ + +#ifndef UMSKT_WIN_JSON_H +#define UMSKT_WIN_JSON_H + +struct JsonSpan { + const char *p; + int n; +}; + +/* Find a direct child of an object. `object` includes the surrounding braces. */ +int json_object_find(const char *object, int length, const char *key, JsonSpan *value); + +/* If value is a JSON string, point at the unquoted bytes. They are not NUL-terminated. */ +int json_unquote(const JsonSpan *value, const char **text, int *length); + +/* Iterate direct children. *cursor starts at 0 and is updated. Returns 0 when finished. */ +int json_object_next(const char *object, int length, int *cursor, const char **key, int *key_length, JsonSpan *value); + +/* Iterate array elements. *cursor starts at 0. */ +int json_array_next(const char *array, int length, int *cursor, JsonSpan *value); + +#endif diff --git a/src/windows/win32_min.h b/src/windows/win32_min.h new file mode 100644 index 0000000..14fdfe9 --- /dev/null +++ b/src/windows/win32_min.h @@ -0,0 +1,30 @@ +#ifndef UMSKT_WIN32_MIN_H +#define UMSKT_WIN32_MIN_H + +#include + +#define UMSKT_STD_INPUT_HANDLE ((uint32_t)-10) +#define UMSKT_STD_OUTPUT_HANDLE ((uint32_t)-11) +#define UMSKT_STD_ERROR_HANDLE ((uint32_t)-12) +#define UMSKT_INVALID_HANDLE ((void *)(intptr_t)-1) +#define UMSKT_GENERIC_READ 0x80000000u +#define UMSKT_FILE_SHARE_READ 1u +#define UMSKT_OPEN_EXISTING 3u +#define UMSKT_FILE_ATTRIBUTE_NORMAL 0x80u +#define UMSKT_INVALID_FILE_SIZE 0xFFFFFFFFu +#define UMSKT_RT_RCDATA ((const char *)(uintptr_t)10) + +extern "C" { +__declspec(dllimport) void *__stdcall GetStdHandle(uint32_t); +__declspec(dllimport) int __stdcall WriteFile(void *, const void *, uint32_t, uint32_t *, void *); +__declspec(dllimport) int __stdcall ReadFile(void *, void *, uint32_t, uint32_t *, void *); +__declspec(dllimport) void *__stdcall CreateFileA(const char *, uint32_t, uint32_t, void *, uint32_t, uint32_t, void *); +__declspec(dllimport) int __stdcall CloseHandle(void *); +__declspec(dllimport) uint32_t __stdcall GetFileSize(void *, uint32_t *); +__declspec(dllimport) void *__stdcall FindResourceA(void *, const char *, const char *); +__declspec(dllimport) uint32_t __stdcall SizeofResource(void *, void *); +__declspec(dllimport) void *__stdcall LoadResource(void *, void *); +__declspec(dllimport) void *__stdcall LockResource(void *); +} + +#endif diff --git a/tools/check-win-baseline.ps1 b/tools/check-win-baseline.ps1 new file mode 100644 index 0000000..ed55224 --- /dev/null +++ b/tools/check-win-baseline.ps1 @@ -0,0 +1,168 @@ +param( + [Parameter(Mandatory = $true)][string]$Exe, + [Parameter(Mandatory = $true)][string]$Objdump, + [Parameter(Mandatory = $true)][ValidateSet("x86", "x64")][string]$Arch +) + +$ErrorActionPreference = "Stop" + +function Fail([string]$Message) { + Write-Host "Windows baseline check failed: $Message" + exit 1 +} + +if (-not (Test-Path -LiteralPath $Exe)) { + Fail "missing binary: $Exe" +} +if (-not (Test-Path -LiteralPath $Objdump)) { + Fail "missing objdump: $Objdump" +} + +$format = & $Objdump -f -- $Exe 2>&1 | Out-String +if ($LASTEXITCODE -ne 0) { + Fail "objdump -f failed: $format" +} + +$expectFormat = $(if ($Arch -eq "x86") { "pei-i386" } else { "pei-x86-64" }) +if ($format -notmatch [regex]::Escape($expectFormat)) { + Fail "expected a $expectFormat binary" +} + +$pe = & $Objdump -p -- $Exe 2>&1 | Out-String +if ($LASTEXITCODE -ne 0) { + Fail "objdump -p failed: $pe" +} + +function PeField([string]$Text, [string]$Name) { + if ($Text -match ("(?m)^\s*" + [regex]::Escape($Name) + "\s+(\d+)")) { + return [int]$Matches[1] + } + return $null +} + +$osMajor = PeField $pe "MajorOSystemVersion" +$osMinor = PeField $pe "MinorOSystemVersion" +$subMajor = PeField $pe "MajorSubsystemVersion" +$subMinor = PeField $pe "MinorSubsystemVersion" +if ($Arch -eq "x86") { + if ($osMajor -ne 4 -or $osMinor -ne 0 -or $subMajor -ne 4 -or $subMinor -ne 0) { + Fail "expected Windows 98 / NT 4 subsystem 4.0, got OS $osMajor.$osMinor subsystem $subMajor.$subMinor" + } +} else { + if ($osMajor -ne 5 -or $osMinor -ne 2 -or $subMajor -ne 5 -or $subMinor -ne 2) { + Fail "expected XP x64 subsystem 5.2, got OS $osMajor.$osMinor subsystem $subMajor.$subMinor" + } +} + +$allowedDlls = @{ + "kernel32.dll" = $true +} +$badImports = @{ + "encodepointer" = $true + "decodepointer" = $true + "encodesystempointer" = $true + "decodesystempointer" = $true + "gettickcount64" = $true + "initializecriticalsectionex" = $true + "flsalloc" = $true + "flsfree" = $true + "flsgetvalue" = $true + "flssetvalue" = $true + "comparestringex" = $true + "lcmapstringex" = $true + "getlocaleinfoex" = $true + "lcidtolocalename" = $true + "localenametolcid" = $true + "initializeconditionvariable" = $true + "sleepconditionvariablecs" = $true + "sleepconditionvariablesrw" = $true + "wakeconditionvariable" = $true + "wakeallconditionvariable" = $true + "initializesrwlock" = $true + "acquiresrwlockexclusive" = $true + "releasesrwlockexclusive" = $true + "tryacquiresrwlockexclusive" = $true + "getfileinformationbyhandleex" = $true + "setfileinformationbyhandle" = $true + "getfinalpathnamebyhandlea" = $true + "getfinalpathnamebyhandlew" = $true + "createsymboliclinka" = $true + "createsymboliclinkw" = $true + "getmodulehandleexa" = $true + "getmodulehandleexw" = $true + "iswow64process" = $true + "interlockedcompareexchange64" = $true + "heapsetinformation" = $true + "__getmainargs" = $true + "__wgetmainargs" = $true + "__set_app_type" = $true + "_amsg_exit" = $true + "__p__fmode" = $true + "__p__commode" = $true + "__p__environ" = $true + "getnativesysteminfo" = $true + "createtoolhelp32snapshot" = $true + "process32first" = $true + "process32firsta" = $true + "process32firstw" = $true + "process32next" = $true + "process32nexta" = $true + "process32nextw" = $true + "loadlibraryw" = $true +} + +$importHits = New-Object System.Collections.Generic.List[string] +foreach ($line in ($pe -split "`r?`n")) { + if ($line -match "DLL Name:\s*(\S+)") { + $dll = $Matches[1] + if (-not $allowedDlls.ContainsKey($dll.ToLowerInvariant())) { + $importHits.Add("DLL $dll") + } + } + if ($line -match "^\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+(\S+)") { + $name = $Matches[1] + if ($name -match "^(.*)@\d+$") { + $name = $Matches[1] + } + if ($badImports.ContainsKey($name.ToLowerInvariant())) { + $importHits.Add($name) + } + } +} +if ($importHits.Count -gt 0) { + $shown = ($importHits | Select-Object -First 20) -join ", " + Fail "imports outside kernel32, or APIs missing on Windows 98 / NT 4: $shown" +} + +if ($Arch -eq "x86") { + $badMnemonic = "(?i)^(cmov[a-z]*|fcmov[a-z]*|emms|nopl|nopw|fcomi|fcomip|fucomi|fucomip|movaps|movups|movapd|movupd|movdqa|movdqu|movss|movhps|movlps|movlhps|movhlps|xorps|xorpd|andps|andpd|orps|orpd|addps|addpd|addss|addsd|subps|subpd|subss|subsd|mulps|mulpd|mulss|mulsd|divps|divpd|divss|divsd|cvtsi2ss|cvtsi2sd|cvttss2si|cvttsd2si|cvtss2si|cvtsd2si|pshufd|pshuflw|pshufhw|pshufw|punpck[a-z]+|packss[a-z]+|packus[a-z]+|pcmp[a-z]+|padd[bwdq]|psub[bwdq]|pmullw|pmulhw|pmulhuw|pmaddwd|pavgb|pavgw|pextrw|pinsrw|pmovmskb|prefetch[a-z]*|sfence|mfence|lfence|maskmovq|movntq|movntps|lddqu|pause)\b" + $badRegister = "(?i)%(mm|xmm|ymm|zmm)[0-9]" + $insnHits = New-Object System.Collections.Generic.List[string] + $disasm = & $Objdump -d -M att -- $Exe 2>&1 + if ($LASTEXITCODE -ne 0) { + Fail "objdump -d failed" + } + foreach ($line in $disasm) { + $text = [string]$line + $insn = $null + if ($text.Contains("`t")) { + $parts = $text.Split("`t") + $insn = $parts[$parts.Length - 1].Trim() + } + if (-not $insn) { + continue + } + if ($insn -match $badMnemonic -or $insn -match $badRegister) { + $insnHits.Add($text.Trim()) + if ($insnHits.Count -ge 20) { + break + } + } + } + if ($insnHits.Count -gt 0) { + Fail ("forbidden instructions:`n" + ($insnHits -join "`n")) + } +} + +Write-Host "Windows baseline check passed ($Arch, kernel32 only): $Exe" +exit 0