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