【问题标题】:PHP stream wrappers and Windows Certificate Store with ProxyPHP 流包装器和带有代理的 Windows 证书存储
【发布时间】:2023-09-15 08:52:01
【问题描述】:

设置/环境:

在我们的 PHP 应用程序中,我们有时需要从 PHP 向其他服务器发出 HTTPS 请求。有问题的设置如下:

  • 我们正在使用 PHP 流包装器来执行 HTTP 请求(使用 Guzzle HTTP)。我们这样做是因为流包装器支持使用 Windows 证书存储进行证书验证。
  • 服务器在 Windows 上运行。
  • 我们对 HTTPS 请求使用代理。
  • 防火墙配置为允许
    1. 访问我们正在向其发出请求的服务器。
    2. 访问与所用证书相关的所有证书吊销列表。

我们的问题:

有时,我们的 HTTPS 请求会出乎意料地失败,并出现证书验证错误。这个问题一直存在,直到有人打开到服务器的远程桌面会话并请求我们尝试在服务器 Internet Explorer 中查询的相同 URL。之后,我们的 PHP 应用程序就可以按照它应该的方式处理它的请求了。

问题:

这里有什么问题?我们可以做些什么来进一步分析这一点?

【问题讨论】:

  • 你有什么样的证书验证错误?
  • 只有这条消息:PHP Warning: fsockopen(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
  • 根 CA 和所有中间 CA(可能是代理的 CA)是否已导入根存储?你能提供print_r(openssl_get_cert_locations());的PHP版本和输出吗?
  • 所有的CA都是导入的(证书由域管理),openssl_get_cert_locations()中列出的任何路径都没有证书,这是正确的。流包装器函数使用 Windows 证书存储来获取所需的证书,其中包含所有必要的证书,并且运行 PHP 的用户拥有访问它们的所有必要权限(至少我们是这样的)。
  • 您通知 Guzzle 使用 https 代理了吗? DOCS verify 也是一个很好的阅读部分。

标签: php windows https ssl-certificate guzzle


【解决方案1】:

不是应用问题。

我 99% 确定这是路由问题,在某些情况下数据包在路由器中被丢弃。我会查看网络,改变环境,或者如果可能的话,做一些网络嗅探或监控。

如果您拥有良好的网络基础设施,您可以针对请求计数和超时数据收集(从路由器和交换机)执行 SNPM 陷阱,并将其提取到 Elastic APM 中。这将为您提供非常详细的时间序列分析。

【讨论】:

  • 进行网络嗅探等对我们来说很难,因为这发生在我们无权访问的客户本地服务器上。
【解决方案2】:

你可以看到这个https://github.com/guzzle/guzzle/issues/394verify是问题所在。如果您将verify 设置为false,这将使您的系统暴露于安全攻击。

// Use the system's CA bundle (this is the default setting)
$client->request('GET', '/', ['verify' => true]);

// Use a custom SSL certificate on disk.
$client->request('GET', '/', ['verify' => '/path/to/cert.pem']);

// Disable validation entirely (don't do this!).
$client->request('GET', '/', ['verify' => false]);

这些是Request Options,您可以查看如何进行 SSL 证书验证。他们将问题描述如下

并非所有系统的磁盘上都有已知的 CA 捆绑包。例如,Windows 和 OS X 没有 CA 捆绑包的单一公共位置。什么时候 将“验证”设置为 true,Guzzle 将尽最大努力找到最 系统上的适当 CA 捆绑包。使用 cURL 或 PHP 时 PHP 版本 >= 5.6 上的流包装器,默认情况下会发生这种情况。什么时候 在版本

检查是否在您的 php.ini 文件中设置了 openssl.cafile。

检查你的 php.ini 文件中是否设置了 curl.cainfo。

检查 /etc/pki/tls/certs/ca-bundle.crt 是否存在(Red Hat、CentOS、Fedora; 由 ca-certificates 包提供)

检查 /etc/ssl/certs/ca-certificates.crt 是否存在(Ubuntu、Debian;由 ca-certificates 包)

检查 /usr/local/share/certs/ca-root-nss.crt 是否存在(FreeBSD;由 ca_root_nss 包)

检查 /usr/local/etc/openssl/cert.pem(OS X;由 homebrew 提供)

检查 C:\windows\system32\curl-ca-bundle.crt 是否存在(Windows)

检查 C:\windows\curl-ca-bundle.crt 是否存在(Windows)

此查找的结果缓存在内存中,以便后续调用相同的 过程将很快返回。但是,当只发送一个 在 Apache 之类的每个进程中请求,您应该考虑 将 openssl.cafile 环境变量设置为磁盘上的路径 该文件以便跳过整个过程

另见 how to ignore invalid ssl certificate errors in-guzzle 5guzzle-request-fails

【讨论】:

【解决方案3】:

如果这是一个 Guzzle 问题,那么它每次都会发生。

但是,请尝试使用 cURL 发出相同的 HTTPS 调用,以验证是否存在这种情况,并查看 cURL 请求是否也像 Internet Explorer 一样暂时清除问题。

但这看起来像是一个缓存问题 - PHP 服务器请求无法正确访问(启动证书)证书存储,它只能在某人之后使用其服务 else 已获得访问权限,并且只要缓存未过期。为确保确实如此,只需定期发出调用并标记用户登录和使用 IE 之间经过的时间,并且 Guzzle 调用开始失败。如果我是对的,那个时间将永远是一样的。

这可能是一个权限问题(我认为它可能,但是赋予什么权限,我不知所措)。除非该 URL 的新 CRL 可用,并且 PHP 没有得到它们,否则可能不允许调用)。这种情况也可以通过在发生错误时从 PHP 启动的 PowerShell 脚本运行 IE 连接尝试到相同的 URL 来临时修复,或者(更有可能并且希望)尝试运行所述脚本会引发一些信息更丰富的错误消息.

更新

我研究了 Windows 上的 PHP 如何通过 Guzzle 处理 TLS,但没有任何明显的结果。但是我找到了an interesting page about TLS/SSL quirks

更有趣的是,我还发现了一些关于 PHP 如何最终使用 Schannel 进行 TLS 连接的参考资料,以及 Windows 尤其是 Internet Explorer 如何对互操作性持一种傲慢的态度。所以我建议你试试activating the Schannel log on Windows 看看有没有什么结果。

此外,在链接页面上有一个对正在使用的客户端缓存的引用,并且相关页面最终为 here(“ClientCacheTime”)。

【讨论】:

  • 我可以排除我们的 HTTP 请求没有获取 CRL,因为我们必须添加特定的防火墙规则以允许获取 CRL。在此之前,请求根本没有通过。但你是对的 - 这看起来像一个缓存问题,但那可能是什么缓存?从 PHP 填充缓存需要什么权限?我希望有人会遇到同样的问题并且知道:/
  • 您的更新中的引用正是我所希望的——我们将进一步调查并告诉我它是如何工作的——可能在 2-3 周内。谢谢:-)