Skip to content

fix(legacy): use SSL_CERT_FILE on Windows, as Go does - #260

Merged
pjcdawkins merged 3 commits into
mainfrom
fix/windows-ssl-cert-file
Oct 21, 2026
Merged

pjcdawkins merged 3 commits into
mainfrom
fix/windows-ssl-cert-file

Conversation

@pjcdawkins

@pjcdawkins pjcdawkins commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Since #259, the embedded PHP on Windows ignores SSL_CERT_FILE, while Go (since 1.27) still uses it on Windows. With it set, Go and PHP commands would trust different certificates, which #141 fixed before.

The wrapper now starts PHP on Windows with -d openssl.cafile="${SSL_CERT_FILE}" when the variable is set. PHP expands the variable itself, so the path needs no escaping. PHP gives openssl.cafile to every curl handle as CURLOPT_CAINFO, and Schannel then verifies against that file instead of the Windows certificate store, as v5.15.0 did (through Composer\CaBundle) and as Go does. Without the variable, nothing is passed and the store is used, as in #259. An unusable file makes requests fail, as in Go.

PHP only passes openssl.cafile to curl for servers (CURLOPT_CAINFO), and curl verifies an HTTPS proxy with CURLOPT_PROXY_CAINFO, without falling back. So with Schannel, Config::getGuzzleOptions() also sets CURLOPT_PROXY_CAINFO from openssl.cafile when it is set, as Go uses the file for both.

SSL_CERT_DIR is still not supported on Windows, as Schannel can't use a directory.

Tests (Windows only, run in CI's windows-cert-store job, no administrator rights needed):

  • TestWindowsSSLCertFileSetting: PHP reads paths with ~, backslashes, a UNC prefix, ${...}, $ and quotes back unchanged.
  • TestWindowsSSLCertFile: a request through PHP's curl with the wrapper's settings succeeds with a file holding the server's root, and fails with one without it (error 60) or a missing file (error 77). It fails without this change. Through an HTTPS proxy, the proxy is only verified with the file when CURLOPT_PROXY_CAINFO is set.

Also checked on Windows 11 with a build of this branch, using a fake API token: the token request to the auth server gets an authentication error (so TLS succeeded) with no SSL_CERT_FILE and with the curl.se bundle, and a Schannel untrusted-root error with a file without the right roots. Through a local HTTPS proxy with its own CA, the request succeeds when the proxy's CA is in SSL_CERT_FILE (it failed before the last commit), and fails when it is not, or when SSL_CERT_FILE is unset.

🤖 Generated with Claude Code

pjcdawkins and others added 2 commits October 21, 2026 19:05
Since #259, the embedded PHP on Windows verifies certificates with
Schannel against the Windows certificate store, and ignored
SSL_CERT_FILE. Go (since 1.27) still uses it on Windows, so with it set,
Go and PHP commands would trust different certificates, as before #141.

The wrapper now passes SSL_CERT_FILE to PHP as openssl.cafile, which
PHP gives to every curl handle as CURLOPT_CAINFO. Schannel then verifies
against that file instead of the store, as v5.15.0 did (through
Composer\CaBundle) and as Go does. Without SSL_CERT_FILE, nothing is
passed and the store is used. An unusable file makes requests fail, as
in Go.

SSL_CERT_DIR is still not supported on Windows, as Schannel cannot use a
directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wrapper now sets openssl.cafile to "${SSL_CERT_FILE}", which PHP
expands itself, instead of quoting and escaping the path. A path with a
literal "${...}" was otherwise expanded by PHP's ini parser. The ini
escaping helper is removed, and the test checks paths with "~",
backslashes, "$" and quotes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Changes suggested — 🟡 1 warning

🔍 Full review · 8 files reviewed

Verification
  • settings() returns nil when SSL_CERT_FILE is empty or unset, and Go's crypto/x509 also ignores an empty value, so the two agree there.
  • makeCmd puts the -d settings after -n and before the phar path, so PHP applies them as INI entries rather than handing them to the CLI as arguments.
  • Exec copies os.Environ() into the child's environment, so ${SSL_CERT_FILE} is defined when PHP expands the INI value.
  • The new Windows tests reuse generateTestCA, phpRequestScript and the curl error constants from cert_store_windows_test.go, which builds only on Windows like they do.

The new TestWindowsSSLCertFileSetting and TestWindowsSSLCertFile match -run TestWindows, so CI's windows-cert-store job in ci.yml runs them. They call PHP directly with settings() rather than through makeCmd/Exec, and no test covers HTTPS-proxy verification.

Review details
  • Commit: 5b06fbc
  • Model: claude-opus-5-5

Review 1 of 10 for this pull request · View the full run

Comment thread internal/legacy/php_manager_windows.go
PHP passes openssl.cafile to curl as CURLOPT_CAINFO, which only covers
servers: curl verifies an HTTPS proxy with CURLOPT_PROXY_CAINFO, without
falling back to CAINFO. So with SSL_CERT_FILE set, Schannel still
verified a proxy against the Windows store, while Go uses the file for
both.

With Schannel, getGuzzleOptions() now also sets CURLOPT_PROXY_CAINFO
from openssl.cafile when it is set. The Windows test checks both cases
through an HTTPS proxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@upsun-dispatch

Copy link
Copy Markdown
Contributor

📋 PR Summary

On Windows, the wrapper now starts the embedded PHP with openssl.cafile set to SSL_CERT_FILE when that variable is set. Curl (Schannel) then checks certificates against that file instead of the Windows certificate store, which is what Go does. PHP only passes this file to curl for servers, so the legacy CLI also sets CURLOPT_PROXY_CAINFO from it. That way HTTPS proxies are checked against the same file.

Changes
Layer / File(s) Summary
Wrapper PHP settings
internal/legacy/php_manager.go Adds the PHP settings to the arguments used to start PHP.
internal/legacy/php_manager_windows.go On Windows, adds -d openssl.cafile="${SSL_CERT_FILE}" when the variable is set. PHP expands the variable itself.
internal/legacy/php_manager_unix.go Returns no extra PHP settings on Unix.
internal/legacy/legacy.go Passes the platform's PHP settings when it runs the legacy CLI.
Legacy CLI TLS options
legacy/src/Service/Config.php With Schannel, sets CURLOPT_PROXY_CAINFO from openssl.cafile when that is set, so HTTPS proxies are checked against the same file.
legacy/tests/ConfigTest.php Expects CURLOPT_PROXY_CAINFO in the curl options when openssl.cafile is set.
Windows tests
internal/legacy/php_manager_windows_test.go New tests. One checks that PHP reads unusual paths back unchanged. The other checks curl results with a file holding the server's root, a file without it, a missing file, and an HTTPS proxy with and without the proxy CA setting.
internal/legacy/cert_store_windows_test.go Adds proxy options to the shared PHP request script, including taking the proxy CA file from openssl.cafile.
Docs
CLAUDE.md Explains how SSL_CERT_FILE is handled on Windows, including the proxy CA setting.

@pjcdawkins
pjcdawkins merged commit ac04404 into main Oct 21, 2026
7 checks passed
@pjcdawkins
pjcdawkins deleted the fix/windows-ssl-cert-file branch October 21, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant