本文目录导读:

- 开启详细的错误报告
- 使用 cURL 进行详细调试
- 使用 stream context 调试
- 使用 OpenSSL 命令行工具对比调试
- 使用 PHP OpenSSL 扩展直接调试
- 常见 SSL/TLS 问题及解决方法
- 最佳实践建议
在 PHP 中进行 SSL/TLS 调试,主要涉及连接 HTTPS 服务时的证书验证、密码套件选择、握手过程等问题,以下是系统的调试方法和工具:
开启详细的错误报告
<?php
// 开启所有错误显示
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// 开启 OpenSSL 详细错误信息
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
'capture_peer_cert' => true, // 捕获证书
'capture_peer_cert_chain' => true, // 捕获证书链
// 'cafile' => '/path/to/ca-bundle.crt',
// 'local_cert' => '/path/to/client-cert.pem',
// 'local_pk' => '/path/to/client-key.pem',
// 'passphrase' => 'your_passphrase',
// 'ciphers' => 'HIGH:!aNULL:!MD5',
// 'SNI_enabled' => true,
// 'peer_name' => 'example.com',
]
]);
try {
$fp = fopen('https://api.example.com/endpoint', 'r', false, $context);
// 获取连接的参数
$params = stream_context_get_params($fp);
echo "===== TLS 连接信息 =====\n";
echo "协议版本: " . $params['options']['ssl']['protocol'] ?? 'N/A' . "\n";
echo "密码套件: " . $params['options']['ssl']['cipher'] ?? 'N/A' . "\n";
// 检查证书
if (isset($params['options']['ssl']['peer_certificate'])) {
$cert = openssl_x509_parse($params['options']['ssl']['peer_certificate']);
echo "===== 服务器证书信息 =====\n";
echo "主题: " . $cert['subject']['CN'] . "\n";
echo "颁发者: " . $cert['issuer']['CN'] . "\n";
echo "有效从: " . date('Y-m-d', $cert['validFrom_time_t']) . "\n";
echo "有效到: " . date('Y-m-d', $cert['validTo_time_t']) . "\n";
}
// 读取内容
$response = stream_get_contents($fp);
echo "===== 响应内容 =====\n";
echo $response . "\n";
fclose($fp);
} catch (Exception $e) {
echo "===== 错误信息 =====\n";
echo "错误消息: " . $e->getMessage() . "\n";
echo "错误代码: " . $e->getCode() . "\n";
echo "错误文件: " . $e->getFile() . ":" . $e->getLine() . "\n";
// 获取详细错误
if (function_exists('openssl_error_string')) {
echo "===== OpenSSL 错误 =====\n";
while ($msg = openssl_error_string()) {
echo $msg . "\n";
}
}
}
?>
使用 cURL 进行详细调试
<?php
function debugSSLConnection($url, $options = []) {
$ch = curl_init($url);
// 基础配置
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_VERBOSE => true, // 显示详细输出
CURLOPT_SSL_VERIFYPEER => true, // 验证证书
CURLOPT_SSL_VERIFYHOST => 2, // 验证主机名
CURLOPT_CERTINFO => true, // 获取证书详情
// CURLOPT_CAINFO => '/path/to/ca-bundle.crt',
// CURLOPT_SSLCERT => '/path/to/client-cert.pem',
// CURLOPT_SSLKEY => '/path/to/client-key.pem',
// CURLOPT_SSL_CIPHER_LIST => 'TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384',
// CURLOPT_SSL_OPTIONS => CURLSSLOPT_NO_REVOKE,
// CURLOPT_PROXY => '127.0.0.1:8888', // 代理调试
// CURLOPT_STDERR => fopen('curl_debug.log', 'w'),
]);
// 合并自定义选项
if (!empty($options)) {
curl_setopt_array($ch, $options);
}
// 获取详细信息
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) {
// 可以在这里记录响应头
return strlen($header);
});
$response = curl_exec($ch);
$info = curl_getinfo($ch);
$error = curl_error($ch);
$errno = curl_errno($ch);
echo "===== cURL 调试信息 =====\n";
echo "错误号: $errno\n";
echo "错误信息: $error\n";
echo "HTTP 状态码: " . $info['http_code'] . "\n";
echo "SSL 版本: " . $info['ssl_version'] . "\n";
echo "SSL 验证结果: " . $info['ssl_verify_result'] . "\n";
echo "协议: " . $info['protocol'] . "\n";
echo "主 IP: " . $info['primary_ip'] . ":" . $info['primary_port'] . "\n";
if (isset($info['certinfo'])) {
echo "\n===== 证书信息 =====\n";
foreach ($info['certinfo'] as $cert) {
echo "主题: " . $cert['Subject'] . "\n";
echo "颁发者: " . $cert['Issuer'] . "\n";
echo "到期日: " . $cert['Expire date'] . "\n";
echo "---\n";
}
}
// 显示 TLS 握手详情
if ($response === false) {
echo "\n===== TLS 握手失败 =====\n";
if ($errno == CURLE_SSL_CONNECT_ERROR) {
echo "SSL 连接失败,可能原因:\n";
echo "1. 证书无效或过期\n";
echo "2. 证书不匹配域名\n";
echo "3. 服务器不支持当前 TLS 版本\n";
echo "4. 密码套件不匹配\n";
}
}
curl_close($ch);
return [
'success' => $response !== false,
'response' => $response,
'info' => $info
];
}
// 测试连接
$result = debugSSLConnection('https://www.example.com');
?>
使用 stream context 调试
<?php
function debugStreamSSL($url) {
// 定义错误和警告处理
$errors = [];
$warnings = [];
set_error_handler(function($errno, $errstr, $errfile, $errline) use (&$errors) {
$errors[] = "Error $errno: $errstr in $errfile:$errline";
return true;
});
// 创建上下文
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
'capture_peer_cert' => true,
'capture_peer_chain' => true,
'verify_depth' => 5,
'SNI_enabled' => true,
],
'http' => [
'method' => 'GET',
'timeout' => 30,
'follow_location' => true,
'ignore_errors' => true,
]
]);
// 记录 TLS 日志
$tlsLog = [];
stream_context_set_option($context, 'ssl', 'verify_peer', true);
// 尝试连接
$startTime = microtime(true);
$result = @file_get_contents($url, false, $context);
$endTime = microtime(true);
// 恢复错误处理
restore_error_handler();
echo "===== Stream 连接诊断 =====\n";
echo "URL: $url\n";
echo "连接时间: " . round($endTime - $startTime, 4) . " 秒\n";
if ($result === false) {
echo "连接失败!\n";
if (!empty($errors)) {
echo "\n===== 错误列表 =====\n";
foreach ($errors as $error) {
echo $error . "\n";
}
}
// 获取底层 SSL 错误
if (function_exists('openssl_error_string')) {
echo "\n===== OpenSSL 错误流 =====\n";
while ($err = openssl_error_string()) {
echo $err . "\n";
}
}
} else {
echo "连接成功!\n";
// 获取连接参数
$metadata = stream_get_meta_data($result);
if (isset($metadata['crypto'])) {
echo "\n===== 加密参数 =====\n";
print_r($metadata['crypto']);
}
// 获取证书
$params = stream_context_get_params($context);
if (isset($params['options']['ssl']['peer_certificate'])) {
$cert = openssl_x509_parse($params['options']['ssl']['peer_certificate']);
echo "\n===== 服务器证书 =====\n";
echo "CN: " . $cert['subject']['CN'] . "\n";
echo "O: " . ($cert['subject']['O'] ?? 'N/A') . "\n";
echo "Valid from: " . date('Y-m-d', $cert['validFrom_time_t']) . "\n";
echo "Valid to: " . date('Y-m-d', $cert['validTo_time_t']) . "\n";
}
}
}
debugStreamSSL('https://www.example.com');
?>
使用 OpenSSL 命令行工具对比调试
# 查看远程服务器的证书信息 openssl s_client -connect example.com:443 -showcerts # 指定 TLS 版本 openssl s_client -connect example.com:443 -tls1_2 openssl s_client -connect example.com:443 -tls1_3 # 查看支持的密码套件 openssl ciphers -v 'HIGH:!aNULL:!MD5' # 测试特定密码套件 openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384' # 查看证书链 openssl s_client -connect example.com:443 -showcerts -CApath /etc/ssl/certs/
使用 PHP OpenSSL 扩展直接调试
<?php
function debugOpenSSL($host, $port = 443) {
echo "===== 开启 SSL 调试 =====\n";
// 查看支持的协议
$supportedProtocols = stream_get_transports();
if (in_array('tls', $supportedProtocols)) {
echo "支持 TLS 传输\n";
}
if (in_array('ssl', $supportedProtocols)) {
echo "支持 SSL 传输\n";
}
// 检查常用函数
echo "\n检查模块状态:\n";
echo "openssl 扩展: " . (extension_loaded('openssl') ? '已加载' : '未加载') . "\n";
echo "openssl 版本: " . (defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : '未知') . "\n";
// 获取 OpenSSL 配置
echo "\nOpenSSL 配置:\n";
echo "默认 CA 文件: " . ini_get('openssl.cafile') . "\n";
echo "默认 CA 路径: " . ini_get('openssl.capath') . "\n";
// 测试连接
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
]
]);
$sock = @stream_socket_client(
"tls://$host:$port",
$errno,
$errstr,
30,
STREAM_CLIENT_CONNECT,
$context
);
if ($sock) {
echo "\n===== TLS 连接成功 =====\n";
echo "主机: $host:$port\n";
// 获取服务器证书
$params = stream_context_get_params($sock);
if (isset($params['options']['ssl']['peer_certificate'])) {
$cert = openssl_x509_parse($params['options']['ssl']['peer_certificate']);
echo "\n证书详情:\n";
print_r($cert['subject']);
echo "\n签名算法: " . $cert['signatureTypeLN'] . "\n";
}
fclose($sock);
} else {
echo "\n===== 连接失败 =====\n";
echo "错误号: $errno\n";
echo "错误信息: $errstr\n";
// 显示 OpenSSL 错误
while ($sslError = openssl_error_string()) {
echo "SSL 错误: $sslError\n";
}
}
}
debugOpenSSL('www.example.com');
?>
常见 SSL/TLS 问题及解决方法
<?php
function diagnoseSSLIssues($url) {
echo "===== SSL/TLS 问题诊断 =====\n\n";
// 1. 检查证书过期
$cert = @file_get_contents($url, false, stream_context_create([
'ssl' => ['verify_peer' => false]
]));
// 2. 常见问题列表
echo "常见 SSL/TLS 问题及检查方法:\n";
echo "1. 证书过期: 检查证书有效期\n";
echo " - 使用: openssl s_client -connect host:443 查看\n";
echo " - 检查服务器系统时间\n\n";
echo "2. 证书链问题: 确保服务器提供完整证书链\n";
echo " - 使用: openssl s_client -connect host:443 -showcerts\n";
echo " - 中间证书可能缺失\n\n";
echo "3. 域名不匹配: 证书 CN 或 SAN 不包含您的域名\n";
echo " - 检查证书中的域名\n";
echo " - 考虑通配符证书\n\n";
echo "4. TLS 版本不兼容: 服务器或客户端不支持共同的 TLS 版本\n";
echo " - 检查服务器支持的 TLS 版本\n";
echo " - 使用 -tls1_2 或 -tls1_3 参数\n\n";
echo "5. 密码套件问题: 无共同的加密算法支持\n";
echo " - 使用: openssl ciphers -v 'HIGH'\n";
echo " - 检查服务器支持的密码套件\n\n";
echo "6. 客户端证书问题: 双向认证时缺少或不正确\n";
echo " - 检查 SSLCert 和 SSLKey 路径\n";
echo " - 检查证书格式 (PEM/DER)\n\n";
echo "7. 代理问题: 代理拦截 SSL 连接\n";
echo " - 在本地测试无代理情况下是否成功\n";
echo " - 查看代理日志\n\n";
echo "8. SNI 问题: 服务器上的虚拟主机配置\n";
echo " - 确保 SNI_enabled => true\n";
echo " - 检查 peer_name 设置\n";
}
?>
最佳实践建议
<?php
// 生产环境安全配置
$secureContext = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
'verify_depth' => 10,
'SNI_enabled' => true,
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT |
STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT,
'ciphers' => 'HIGH:!aNULL:!MD5:!3DES:!DES:!RC4',
'peer_fingerprint' => null, // 可选的证书指纹验证
]
]);
// 开发环境调试配置(仅用于测试,生产环境禁用!)
$debugContext = stream_context_create([
'ssl' => [
'verify_peer' => false, // 生产环境必须改为 true
'verify_peer_name' => false,
'allow_self_signed' => true,
'crypto_method' => STREAM_CRYPTO_METHOD_TLS_CLIENT,
]
]);
?>
-
第一步:基础检查
- PHP OpenSSL 扩展是否加载
- 系统 CA 证书是否更新
-
第二步:连接测试
- 使用
openssl s_client测试服务器 - 对比验证 PHP 和命令行行为
- 使用
-
第三步:详细错误捕获
- 开启所有错误报告
- 捕获 OpenSSL 错误流
-
第四步:证书检查
- 使用
openssl x509验证证书 - 检查证书链完整性
- 使用
-
第五步:环境优化
- 更新 CA 证书
- 配置正确的密码套件
- 设置合适的 TLS 版本
生产环境永远要开启 verify_peer 和 verify_peer_name,调试用的宽松设置绝不能用于生产!