PHP 和 SSL CA 验证 - 独立于操作系统

2022-01-25 00:00:00 windows ssl linux ssl-certificate php

这是一个简单的 PHP 脚本,它打开一个准备发送 HTTP 请求的 SSL 套接字:

<上一页>$contextOptions = 数组();$socketUrl = 'ssl://google.com:443';$streamContext = stream_context_create($contextOptions);$socket = stream_socket_client($socketUrl, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $streamContext);if (!$socket || $errno !== 0) {var_dump($socket, $errstr);出口;}var_dump($socket);exit('已创建套接字.');

这可行 - 我刚刚测试过 - 但没有针对受信任的 CA 存储的验证.

我们可以修改该脚本以使用 PHP 的 SSL 上下文选项:

<上一页>$contextOptions = 数组('ssl' => 数组('cafile' => 'C:xamppcacerts.pem','CN_match' => '*.google.com',//只有在 'verify_peer' 设置为 TRUE 时才会检查 CN_match.请参阅 https://bugs.php.net/bug.php?id=47030.'verify_peer' => 真,));$socketUrl = 'ssl://google.com:443';$streamContext = stream_context_create($contextOptions);$socket = stream_socket_client($socketUrl, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $streamContext);if (!$socket || $errno !== 0) {var_dump($socket, $errstr);出口;}var_dump($socket);exit('已创建套接字.');

只要 'cafile' 存在并且具有正确的 CA,那么此示例也可以工作...

...但是我们如何在不对 CA 文件名/文件路径进行硬编码的情况下做到这一点呢?我们正在尝试创建一个独立于操作系统验证 SSL 证书的东西,而不需要为运行此脚本的每个服务器单独配置.

我知道 Linux 有一个 CA 目录,我们可以将其作为capath".窗户呢?它在哪里存储其受信任的 CA?我搜索了这些不幸的是似乎在注册表中,所以我们无法从 PHP 访问它们吗?其他操作系统呢?

解决方案

一场失败的战斗......

在 PHP 5.6 之前,如果不手动设置 "cafile""CN_match" 上下文选项,则无法在 PHP 中进行安全加密传输.不幸的是,即使您确实正确设置了这些值,您的传输仍然很可能会失败,因为 5.6 之前的版本在验证主机名时不会参考对等证书中日益流行的 SAN (subjectAltName) 扩展.因此,通过 PHP 的内置流包装器进行的安全"加密在很大程度上是用词不当.使用旧版本 PHP 最安全的选择(字面意思)是 curl 扩展.

关于 Windows 证书...

Windows 使用自己的证书存储,并以与 OpenSSL 不同的格式对其证书进行编码.相比之下,openssl 应用程序使用开放的 .PEM 格式.5.6 之前的 PHP 版本无法以任何方式与 Windows 证书存储区交互.因此,使用内置流包装器功能以跨操作系统方式进行可靠且安全的加密是不可能的.

PHP 5.6 向前迈出了一大步

  • 新的 openssl.cafile 和 openssl.capath php.ini 指令允许您全局分配证书位置,而无需在每个流上下文中设置它们

  • 默认情况下,所有加密流都会验证对等证书和主机名,如果未提供 "CN_match" 上下文选项,则会从 URI 中自动解析主机名.

    李>
  • 现在,流加密操作在验证主机名时会检查对等证书 SAN 条目

  • 如果没有在流上下文中指定 CA 文件/路径或 php.ini 指令,PHP 会自动回退到操作系统的证书存储区(在 Windows 中也是如此!)

举例来说,这是您在 PHP-5.6 中安全连接到 github.com 所需要做的所有事情:

是的.就是这样.无需担心上下文设置和验证参数.在这方面,PHP 现在的功能就像您的浏览器一样.

更多关于该主题的阅读

这些 PHP 5.6 更改只是 SSL/TLS 改进的冰山一角.我们努力使 5.6 成为迄今为止在加密通信方面最安全的 PHP 版本.

如果您想了解有关这些新功能的更多信息,可以通过官方渠道获得大量信息

  • [RFC] 改进的 TLS 默认值
  • [RFC] TLS 对等验证
  • 5.6 新闻文件
  • 5.6 升级文件

关于 5.4/5.5 中 SAN 匹配的说明

我们正在努力将至少与 5.4 和 5.5 分支匹配的 SAN 向后移植,因为如果没有此功能,以任何有意义的方式(作为客户端)使用加密包装器是极其困难的.虽然这项反向移植工作在很大程度上取决于我作为志愿者的空闲时间,但支持这个答案肯定会帮助它更快地实现:)

Here is a simple PHP script that opens an SSL socket ready to send HTTP requests:

$contextOptions = array();

$socketUrl = 'ssl://google.com:443';
$streamContext = stream_context_create($contextOptions);
$socket = stream_socket_client($socketUrl, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $streamContext);

if (!$socket || $errno !== 0) {
    var_dump($socket, $errstr);
    exit;
}

var_dump($socket);
exit('Socket created.');

This works - I've just tested it - but there is no validation against a trusted CA store.

We can modify that script to use PHP's SSL Context options:

$contextOptions = array(
    'ssl' => array(
        'cafile' => 'C:xamppcacerts.pem',
        'CN_match' => '*.google.com',  // CN_match will only be checked if 'verify_peer' is set to TRUE.  See https://bugs.php.net/bug.php?id=47030.
        'verify_peer' => TRUE,
    )
);

$socketUrl = 'ssl://google.com:443';
$streamContext = stream_context_create($contextOptions);
$socket = stream_socket_client($socketUrl, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $streamContext);

if (!$socket || $errno !== 0) {
    var_dump($socket, $errstr);
    exit;
}

var_dump($socket);
exit('Socket created.');

As long as the 'cafile' exists and has the correct CA then this example also works...

...but how can we do this without hard-coding a CA filename/filepath? We're trying to create something that verifies SSL certificates OS-independently without requiring separate configuration for each server that runs this script.

I know Linux has a directory for CAs that we could put as the 'capath'. What about Windows? Where does it store its trusted CAs? I searched and these unfortunately seemed to be in the registry, so is there no way we can access them from PHP? What about other OSs?

解决方案

A losing battle ...

There is no way to conduct a secure encrypted transfer in PHP without manually setting the "cafile" and "CN_match" context options prior to PHP 5.6. And unfortunately, even when you do set these values correctly your transfers are still very likely to fail because pre-5.6 versions do not consult the increasingly popular SAN (subjectAltName) extension present in peer certificates when verifying host names. As a result, "secure" encryption via PHP's built-in stream wrappers is largely a misnomer. Your safest bet (literally) with older versions of PHP is the curl extension.

Regarding windows certs ...

Windows uses its own cert store and encodes its certificates in different format from OpenSSL. By comparison, openssl applications use the open .PEM format. Pre-5.6 versions of PHP are unable to interface with the windows cert store in any way. For this reason it's impossible to have reliable and safe encryption in a cross-OS way using the built-in stream wrapper functionality.

PHP 5.6 is a major step forward

  • New openssl.cafile and openssl.capath php.ini directives allow you to globally assign cert locations without having set them in every stream context

  • All encrypted streams verify peer certs and host names by default and the host name is automatically parsed from the URI if no "CN_match" context option is supplied.

  • Stream crypto operations now check peer cert SAN entries when verifying host names

  • If no CA file/path is specified in the stream context or php.ini directives PHP automatically falls back to the operating system's cert stores (in Windows, too!)

By way of example, this is all you'd need to do to connect securely to github.com in PHP-5.6:

<?php
$socket = stream_socket_client("tls://github.com:443");

Yes. That really is it. No fussing about with context settings and verification parameters. PHP now functions like your browser in this regard.

More reading on the subject

These PHP 5.6 changes are only the tip of the iceberg with regard to SSL/TLS improvements. We've worked hard to make 5.6 the most secure PHP release to date with regard to encrypted communications.

If you'd like to learn more about these new features there is a wealth of information available via the official channels

  • [RFC] Improved TLS Defaults
  • [RFC] TLS Peer Verification
  • 5.6 NEWS file
  • 5.6 UPGRADING file

A note on SAN matching in 5.4/5.5

We are working to backport at least the SAN matching to the 5.4 and 5.5 branches as it's extremely difficult to use the encryption wrappers in any meaningful way (as a client) without this functionality. Though this backporting work is largely dependent on my available free time as a volunteer, upvoting this answer might certainly help it happen sooner :)

相关文章