diff --git a/.github/workflows/build-php.yml b/.github/workflows/build-php.yml index b5f6cf8..d86a30e 100644 --- a/.github/workflows/build-php.yml +++ b/.github/workflows/build-php.yml @@ -220,8 +220,14 @@ jobs: build-windows-amd64: name: Windows x64 needs: resolve-version - runs-on: windows-latest + # Not windows-latest or windows-2025: those images now ship Visual Studio + # 2026, and static-php-cli only looks for 2022 or 2019, so its doctor check + # fails before anything is built. + runs-on: windows-2022 steps: + - name: Check out repository code + uses: actions/checkout@v7 + - name: Clone static-php-cli run: | git clone --depth 1 --branch ${{ env.SPC_VERSION }} https://github.com/crazywhalecc/static-php-cli.git spc @@ -238,6 +244,12 @@ jobs: cd spc composer install --no-dev --classmap-authoritative + # Schannel cannot use a CA file and the Windows certificate store + # together, which the CLI needs. This must run before the download step, + # which reads the dependencies it changes. + - name: Build curl against OpenSSL, not Schannel + run: php scripts/patch-spc-windows-curl.php spc + - name: Setup build environment run: | cd spc @@ -259,6 +271,18 @@ jobs: $extensions = $extensions.Trim(',') ./bin/spc build "$extensions" --build-cli ${{ inputs.debug && '--debug' || '' }} + # The CLI depends on the SSL backend: only OpenSSL can use a CA file and + # the Windows certificate store together. + - name: Check the SSL backend + run: | + $curl = & ./spc/buildroot/bin/php.exe -n -r "echo curl_version()['ssl_version'];" + $openssl = & ./spc/buildroot/bin/php.exe -n -r "echo OPENSSL_VERSION_TEXT;" + Write-Host "curl: $curl" + Write-Host "openssl extension: $openssl" + if ($curl -notlike 'OpenSSL/*') { + throw "expected curl to be built against OpenSSL, got '$curl'" + } + - name: Upload artifact uses: actions/upload-artifact@v7 with: diff --git a/README.md b/README.md index dc9ab35..f1dd270 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,22 @@ The workflow will: - **Linux/macOS**: static-php-cli automatically uses the system CA bundle - **Windows**: Requires explicit `cacert.pem` configuration (handled by the CLI) +### Windows SSL backend + +static-php-cli builds curl with Schannel on Windows. These builds change that to +OpenSSL, which is already built for the `openssl` extension, using +`scripts/patch-spc-windows-curl.php`. + +Schannel verifies against a CA file *instead of* the Windows certificate store, +and refuses a file larger than 1 MiB. The CLI has to pass a CA file, because the +`openssl` extension cannot read the store at all, so with Schannel a root +certificate installed by an organization is never trusted. curl built against +OpenSSL loads a CA file and the Windows stores together, and has no size limit. + +The patch fails the build if static-php-cli changes in a way it does not expect, +rather than quietly producing a Schannel build. See +[upsun/cli#110](https://github.com/upsun/cli/issues/110). + ## License The build scripts in this repository are MIT licensed. PHP binaries are subject to the [PHP License](https://www.php.net/license/). diff --git a/scripts/patch-spc-windows-curl.php b/scripts/patch-spc-windows-curl.php new file mode 100644 index 0000000..b2d8b65 --- /dev/null +++ b/scripts/patch-spc-windows-curl.php @@ -0,0 +1,107 @@ + "'-DUSE_WINDOWS_SSPI=OFF '", + "'-DCURL_USE_SCHANNEL=ON '" => "'-DCURL_USE_SCHANNEL=OFF '", + "'-DCURL_USE_OPENSSL=OFF '" => "'-DCURL_USE_OPENSSL=ON '", +]; +foreach ($options as $from => $to) { + if (substr_count($source, $to) === 1 && !str_contains($source, $from)) { + echo "already set: $to\n"; + continue; + } + if (substr_count($source, $from) !== 1) { + fail("expected exactly one $from in $curlFile: check the static-php-cli version"); + } + $source = str_replace($from, $to, $source); + echo "set $to\n"; +} + +// Use the Windows certificate store when no CA file is set, which is what +// Schannel did. Only then: given a CA file, curl uses that alone unless asked +// for both. This keeps verification working if the CLI cannot write its bundle. +$nativeCA = "'-DCURL_CA_NATIVE=ON '"; +$anchor = "'-DCURL_ENABLE_SSL=ON ' ."; +if (str_contains($source, $nativeCA)) { + echo "already set: $nativeCA\n"; +} else { + if (substr_count($source, $anchor) !== 1) { + fail("expected exactly one $anchor in $curlFile: check the static-php-cli version"); + } + $source = str_replace($anchor, $nativeCA . " .\n " . $anchor, $source); + echo "set $nativeCA\n"; +} + +writeFileOrFail($curlFile, $source);