PHP 怎么SSL/TLS调试

wen PHP项目 2

本文目录导读:

PHP 怎么SSL/TLS调试

  1. 开启详细的错误报告
  2. 使用 cURL 进行详细调试
  3. 使用 stream context 调试
  4. 使用 OpenSSL 命令行工具对比调试
  5. 使用 PHP OpenSSL 扩展直接调试
  6. 常见 SSL/TLS 问题及解决方法
  7. 最佳实践建议

在 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,
    ]
]);
?>
  1. 第一步:基础检查

    • PHP OpenSSL 扩展是否加载
    • 系统 CA 证书是否更新
  2. 第二步:连接测试

    • 使用 openssl s_client 测试服务器
    • 对比验证 PHP 和命令行行为
  3. 第三步:详细错误捕获

    • 开启所有错误报告
    • 捕获 OpenSSL 错误流
  4. 第四步:证书检查

    • 使用 openssl x509 验证证书
    • 检查证书链完整性
  5. 第五步:环境优化

    • 更新 CA 证书
    • 配置正确的密码套件
    • 设置合适的 TLS 版本

生产环境永远要开启 verify_peerverify_peer_name,调试用的宽松设置绝不能用于生产!

抱歉,评论功能暂时关闭!