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
相关产品推荐
相关产品推荐

