如何使用PHP读取TLS证书并实现安全WebSocket连接?
解决PHP安全WebSocket证书加载失败的问题
看起来你遇到的核心问题是证书文件格式不匹配PHP的要求,再加上代码里混用了流资源和普通socket函数,导致一系列报错。我来一步步帮你解决:
一、先理清证书文件的问题
你提供的cert-0x8zHR文件里混合了证书请求(CSR)、私钥、域名证书和中间证书,PHP在读取时会被多余的CSR内容干扰,导致无法正确识别私钥。咱们先整理出正确的证书文件:
- 提取私钥:从
cert-0x8zHR里复制从-----BEGIN PRIVATE KEY-----到-----END PRIVATE KEY-----的全部内容(包括首尾行),保存为example.com.key文件。 - 整理证书链:
- 从
cert-0x8zHR里复制第一个-----BEGIN CERTIFICATE-----到-----END CERTIFICATE-----的内容(这是你的域名证书); - 把
cert-Du9H8N里的中间证书内容追加到上面的内容后面; - 保存为
example.com.crt文件(顺序必须是:域名证书在前,中间证书在后)。
- 从
验证证书和私钥是否匹配:用以下命令检查,两个输出的MD5值必须一致
openssl x509 -noout -modulus -in example.com.crt | openssl md5 openssl rsa -noout -modulus -in example.com.key | openssl md5
二、修正PHP代码的两处关键错误
错误1:混用流资源和普通Socket函数
stream_socket_accept()返回的是流资源,不能用socket_read()/socket_write()/socket_close()这些普通socket函数,要换成对应的流操作函数fread()/fwrite()/fclose()。这也是你后续收到socket_read() expects parameter 1 to be resource报错的原因之一。
错误2:SSL配置优化
针对Let's Encrypt证书,调整SSL选项,确保PHP能正确加载证书:
$context = stream_context_create(); // Let's Encrypt是可信CA,verify_peer设为true没问题,测试阶段可以临时设为false排查 stream_context_set_option($context, 'ssl', 'verify_peer', true); stream_context_set_option($context, 'ssl', 'verify_peer_name', true); stream_context_set_option($context, 'ssl', 'allow_self_signed', false); stream_context_set_option($context, 'ssl', 'peer_name', 'example.com'); stream_context_set_option($context, 'ssl', 'CN_match', 'example.com'); stream_context_set_option($context, 'ssl', 'SNI_enabled', true); // 指定整理好的证书链和私钥文件 stream_context_set_option($context, 'ssl', 'local_cert', '/path/to/example.com.crt'); stream_context_set_option($context, 'ssl', 'local_pk', '/path/to/example.com.key'); // 如果私钥设置了密码,需要添加下面这行(Let's Encrypt默认无密码) // stream_context_set_option($context, 'ssl', 'passphrase', 'your_key_password');
三、修正后的完整PHP示例代码
$context = stream_context_create(); stream_context_set_option($context, 'ssl', 'allow_self_signed', false); stream_context_set_option($context, 'ssl', 'verify_peer', true); stream_context_set_option($context, 'ssl', 'verify_peer_name', true); stream_context_set_option($context, 'ssl', 'peer_name', 'example.com'); stream_context_set_option($context, 'ssl', 'CN_match', 'example.com'); stream_context_set_option($context, 'ssl', 'SNI_enabled', true); stream_context_set_option($context, 'ssl', 'local_cert', '/path/to/example.com.crt'); stream_context_set_option($context, 'ssl', 'local_pk', '/path/to/example.com.key'); $serverSocket = stream_socket_server( 'tls://example.com:8090', $errNo, $errStr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context ); if (!$serverSocket) { die("启动服务器失败: $errStr ($errNo)"); } echo "安全WebSocket服务器运行在 wss://example.com:8090\n"; while (true) { $client = stream_socket_accept($serverSocket); if (!$client) { echo "接受客户端连接失败\n"; continue; } // 读取握手请求 $request = fread($client, 5000); if (!$request) { fclose($client); continue; } // 提取WebSocket密钥 if (!preg_match('#Sec-WebSocket-Key: (.*)\r\n#', $request, $matches)) { fclose($client); continue; } $key = base64_encode(pack('H*', sha1($matches[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); $headers = "HTTP/1.1 101 Switching Protocols\r\n"; $headers .= "Upgrade: websocket\r\n"; $headers .= "Connection: Upgrade\r\n"; $headers .= "Sec-WebSocket-Version: 13\r\n"; $headers .= "Sec-WebSocket-Accept: $key\r\n\r\n"; // 发送握手响应 fwrite($client, $headers); // 示例:给客户端发送消息(注意WebSocket需要正确的帧格式) $response = "Hello from secure WebSocket!"; $frame = chr(0x81) . chr(strlen($response)) . $response; fwrite($client, $frame); // 关闭连接 fclose($client); } fclose($serverSocket);
最后检查事项
- 确保证书和私钥文件的权限正确,PHP进程有读取权限(比如设置为
640,属主为www-data或PHP运行的用户); - 客户端连接时,确保
host变量是example.com,端口是8090,并且服务器的8090端口已经开放(防火墙/安全组要放行); - 如果还是报错,可以临时把
verify_peer和verify_peer_name设为false,排除证书验证的问题,再逐步排查。
内容的提问来源于stack exchange,提问作者alpham8
相关产品推荐
相关产品推荐

