还有一个 crypto_type 上下文。在旧版本中,这是 crypto_method。这在 https://php.net/manual/en/function.stream-socket-enable-crypto.php 上有所提及。
SSL 上下文选项 — SSL 上下文选项列表
用于 ssl://
和 tls://
传输的上下文选项。
版本 | 描述 |
---|---|
7.2.0 | 添加了 security_level 。需要 OpenSSL >= 1.1.0。 |
注意: 因为
ssl://
是https://
和ftps://
包装器的底层传输,所以适用于ssl://
的任何上下文选项也适用于https://
和ftps://
。
注意: 为了使 SNI (服务器名称指示) 可用,PHP 必须使用 OpenSSL 0.9.8j 或更高版本编译。使用 **
OPENSSL_TLSEXT_SERVER_NAME
** 来确定是否支持 SNI。
还有一个 crypto_type 上下文。在旧版本中,这是 crypto_method。这在 https://php.net/manual/en/function.stream-socket-enable-crypto.php 上有所提及。
启用 SNI (服务器名称指示)
PEM 必须包含证书和私钥。
<?php
$context = stream_context_create([
'ssl' => [
'SNI_enabled' => true,
'SNI_server_certs' => [
'host1.com' => '/path/host1.com.pem',
'host2.com' => '/path/host2.com.pem',
],
]
]);
?>
我通常从 https://curl.haxx.se/docs/caextract.html 下载根 CA 证书,然后将其作为 'cafile' 放置,它几乎总是有效的。
我遇到的唯一问题是,当服务器没有正确发送中间 CA 证书时,您必须将其手动添加到文件中。
我无法加载使用 stunnel 工具生成的 PEM。但是,我能够使用 PHP 调用来生成 stunnel 和 php 都识别的 PEM,如这里所述
http://www.devdungeon.com/content/how-use-ssl-sockets-php
此代码片段现在对我有用,并且使用 stunnel verify=4,双方都确认了指纹。奇怪的是,如果在下面设置了 "tls://",则强制使用 TLSv1,但使用 "ssl://" 允许 TLSv1.2
$stream_context = stream_context_create([ 'ssl' => [
'local_cert' => '/path/to/key.pem',
'peer_fingerprint' => openssl_x509_fingerprint(file_get_contents('/path/to/key.crt')),
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
'verify_depth' => 0 ]]);
$fp = stream_socket_client('ssl://ssl.server.com:12345',
$errno, $errstr, 30, STREAM_CLIENT_CONNECT, $stream_context);
fwrite($fp, "foo bar\n");
while($line = fgets($fp, 8192)) echo $line;
建议使用 "ssl://" 传输。
在 php 5.5 ~ 7.1 中
ssl:// 传输 = ssl_v2|ssl_v3|tls_v1.0|tls_v1.1|tls_v1.2
tls:// 传输 = tls_v1.0
在 7.2 之后 ssl:// 和 tls:// 传输相同
php 7.2 ~ 7.3 = tls_v1.0|tls_v1.1|tls_v1.2
php 7.4 ~ 8.1 = tls_v1.0|tls_v1.1|tls_v1.2|tls_v1.3
CN_match 的工作方式与直觉相反。我在开发 PHP 实现的 SSL 服务器时遇到了这个问题。我在代码中声明了
- 不允许自签名证书(有效)
- 使用 CA 证书验证对等证书(有效)
- 使用 CN_match 验证客户端的 CN(无效),例如
stream_context_set_option($context, 'ssl', 'CN_match', '*.example.org');
我假设这将匹配任何 CN 在 .example.org 域之下的客户端。
不幸的是,情况并非如此。上面的选项不能做到这一点。
它实际做的是
- 获取客户端的 CN 并将其与 CN_match 进行比较
- 如果客户端的 CN 包含星号,例如 *.example.org,则会以通配符匹配的方式将其与 CN_match 进行比较
示例说明行为
(CNM = 服务器的 CN_match)
(CCN = 客户端的 CN)
- CNM=host.example.org,CCN=host.example.org ---> OK
- CNM=host.example.org,CCN=*.example.org ---> OK
- CNM=.example.org,CCN=*.example.org ---> OK
- CNM=example.org,CCN=*.example.org ---> 错误
- CNM=*.example.org,CCN=host.example.org ---> 错误
- CNM=*.example.org,CCN=*.example.org ---> OK
根据 PHP 源代码,我相信如果您尝试充当客户端,并且服务器包含通配符证书,则情况也是如此。如果您将 CN_match 设置为 myserver.example.org,并且服务器使用 *.example.org 进行自我介绍,则允许连接。
以上所有内容适用于 PHP 5.2.12 版本。
我将提供一个补丁来支持以星号开头的 CN_match。
我将其用于 Apple 推送通知服务。
通过 local_cert 选项传递本地证书文件名 `cert.pem`。
直接调用脚本时,效果很好。
但是,当我从其他位置包含/加载该脚本时,它停止工作,没有任何明确的错误消息。
通过传递文件的完整路径 `<FullPathTo>cert.pem` 来解决。
如果您想根据已保存的本地证书验证服务器,以进一步验证目标服务器,则必须使用 fullchain.pem。然后 verify_peer 选项将起作用。因此,只需获取服务器证书,然后搜索根 CA 的 pem 文件,并将所有内容复制到一个文件中。例如
我的证书在链中包含 "GeoTrust TLS RSA CA G1" 证书,因此您可以在 Google 上搜索该字符串。转到官方的 DigiCert GeoTrust 页面,下载 "GeoTrustTLSRSACAG1.crt" 证书。然后,您可以使用以下命令将其转换为 pem 格式
openssl x509 -inform DER -in GeoTrustTLSRSACAG1.crt -out GeoTrustTLSRSACAG1.crt.pem -outform PEM