You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP使用stream_socket_client时出现SSL/TLS握手错误‘bad certificate’

PHP stream_socket_client SSL握手报错 'bad certificate',服务器接收空证书链

我用PHP的stream_socket_client对接要求SSL/TLS加密的外部服务时,遇到SSL握手错误,提示bad certificate。关键现象是:服务器关闭客户端认证时握手成功,开启认证则报错,且服务器显示收到的证书链为空。

相关代码

private function createSocketConnection($host, $port)
{
    // Path to the client certificate and private key
    // $localCertificateFile = '/var/www/sslcert/suezpublic.crt';
    // $privateKeyFile = '/var/www/sslcert/privatekesy.pem';

    // Create a secure SSL/TLS context with client certificates
    $contextOptions = [
        'ssl' => [
            'local_cert' => '/var/www/sslcert/suezcombined.pem',
            // 'local_pk' => '/var/www/sslcert/privatekesy.pem',
            'cafile' => '/var/www/sslcert/bizswitch.pem', // GoDaddy Intermediate certificate or your CA certificate
            'verify_peer' => true, // Enable peer certificate verification
            'verify_peer_name' => true, // Check that the common name exists and matches the host name
            'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, // Use TLSv1.2
            // 'crypto_timeout' => 30, // Increase the handshake timeout to 30 seconds
            'CN_match' => 'www.suezelectric.com', // Ensure the common name matches the server's hostname
            'ciphers' => 'HIGH:!SSLv2:!SSLv3', // Specify allowed ciphers for security
            'disable_compression' => true, // Disable SSL/TLS compression for security
            'capture_peer_cert' => true,
            'capture_peer_chain' => true,
            // 'allow_self_signed' => true,
            'debug' => true, // Enable SSL debugging
        ],
    ];
    $context = stream_context_create($contextOptions);

    // Create a TCP/IP socket connection
    $socket = stream_socket_client("tls://$host:$port", $errno, $errstr, 30);

    if ($socket === false) {
        throw new Exception("Failed to create socket: [$errno] $errstr");
    }

    // Enable SSL/TLS on the stream
    $secured = stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);

    if ($secured === false) {
        throw new Exception("Failed to enable SSL/TLS on the socket");
    }

    return $socket;

    // Now you can use the $socket to send/receive data with the server using SSL/TLS
    // For example, you can use fwrite($socket, $data) to send data to the server.
}

已完成的验证

  • 客户端证书与私钥有效且匹配
  • CA证书包含必要的中间证书
  • TLS版本和加密套件与服务器配置兼容
  • 证书文件路径正确(已通过error_log确认)

问题

我的SSL/TLS配置是否有遗漏?或者使用stream_socket_client时存在导致该错误的潜在问题?


解决方案

1. 核心问题:自定义SSL上下文未关联到socket连接

你创建了SSL上下文,但没有将其传递给stream_socket_client函数,导致PHP使用默认上下文发起连接,根本没有加载你的客户端证书——这就是服务器收到空证书链的直接原因。

修改stream_socket_client调用,添加上下文参数:

$socket = stream_socket_client(
    "tls://$host:$port",
    $errno,
    $errstr,
    30,
    STREAM_CLIENT_CONNECT,
    $context // 传递自定义SSL上下文
);

2. 检查合并证书的格式

suezcombined.pem需要将私钥和证书合并在同一个文件中,且顺序必须是私钥在前,证书在后,格式示例:

-----BEGIN PRIVATE KEY-----
[你的私钥内容]
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
[你的客户端证书内容]
-----END CERTIFICATE-----

如果证书包含中间链,也需要追加到文件末尾(客户端证书之后)。

3. 确认证书文件权限

确保PHP运行进程(通常是www-data或apache用户)拥有证书文件的读取权限:

chown www-data:www-data /var/www/sslcert/*.pem
chmod 600 /var/www/sslcert/*.pem # 严格权限,避免其他用户读取私钥

4. 利用调试信息定位问题

开启debug选项后,查看PHP的错误日志(通常在/var/log/php/error.log或/var/log/apache2/error.log),里面会包含SSL握手的详细调试信息,比如:

  • 是否成功加载客户端证书
  • 证书链是否完整
  • 握手过程中具体的错误步骤

5. 可选:简化SSL启用步骤

因为你已经使用了tls://协议前缀,stream_socket_client会自动尝试启用SSL/TLS,所以可以考虑去掉手动调用stream_socket_enable_crypto的步骤,避免重复配置导致冲突。


内容的提问来源于stack exchange,提问作者Dimgba Kalu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 03:21:05