目录
之前在ja3 中尝试过直接使用php 做支持TLS的握手服务器,然而没搞明白。当时使用用的是workerman,而提供的配置ssl在chrome (忘记版本,只是85以后了)中并无支持,今天又做了尝试,发现了点新东西。
源码
这是一个wss 使用TLS作为传输层的例子,在php文档评论发现的,见文末,居然已经过去2年了,惊呆。
<?php
$host = '192.168.1.2';
$port = 1234;
$path = 'C:/Certbot/live/php.net/';
$transport = 'tlsv1.2';
$ssl = ['ssl' => [
'local_cert' => $path . 'cert.pem',
'local_pk' => $path . 'privkey.pem',
'disable_compression' => true,
'verify_peer' => false,
'ssltransport' => $transport,
] ];
$ssl_context = stream_context_create($ssl);
$server = stream_socket_server($transport . '://' . $host . ':' . $port, $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $ssl_context);
if (!$server) { die("$errstr ($errno)"); }
$clients = array($server);
$write = NULL;
$except = NULL;
while (true) {
$changed = $clients;
stream_select($changed, $write, $except, 10);
if (in_array($server, $changed)) {
$client = @stream_socket_accept($server);
if (!$client){ continue; }
$clients[] = $client;
$ip = stream_socket_get_name( $client, true );
echo "New Client connected from $ip\n";
stream_set_blocking($client, true);
$headers = fread($client, 1500);
handshake($client, $headers, $host, $port);
stream_set_blocking($client, false);
send_message($clients, mask($ip . ' connected'));
$found_socket = array_search($server, $changed);
unset($changed[$found_socket]);
}
foreach ($changed as $changed_socket) {
$ip = stream_socket_get_name( $changed_socket, true );
$buffer = stream_get_contents($changed_socket);
if ($buffer == false) {
echo "Client Disconnected from $ip\n";
@fclose($changed_socket);
$found_socket = array_search($changed_socket, $clients);
unset($clients[$found_socket]);
}
$unmasked = unmask($buffer);
if ($unmasked != "") { echo "\nReceived a Message from $ip:\n\"$unmasked\" \n"; }
$response = mask($unmasked);
send_message($clients, $response);
}
}
fclose($server);
function unmask($text) {
$length = @ord($text[1]) & 127;
if($length == 126) { $masks = substr($text, 4, 4); $data = substr($text, 8); }
elseif($length == 127) { $masks = substr($text, 10, 4); $data = substr($text, 14); }
else { $masks = substr($text, 2, 4); $data = substr($text, 6); }
$text = "";
for ($i = 0; $i < strlen($data); ++$i) { $text .= $data[$i] ^ $masks[$i % 4]; }
return $text;
}
function mask($text) {
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if($length <= 125)
$header = pack('CC', $b1, $length);
elseif($length > 125 && $length < 65536)
$header = pack('CCn', $b1, 126, $length);
elseif($length >= 65536)
$header = pack('CCNN', $b1, 127, $length);
return $header.$text;
}
function handshake($client, $rcvd, $host, $port){
$headers = array();
$lines = preg_split("/\r\n/", $rcvd);
foreach($lines as $line)
{
$line = rtrim($line);
if(preg_match('/\A(\S+): (.*)\z/', $line, $matches)){
$headers[$matches[1]] = $matches[2];
}
}
$secKey = $headers['Sec-WebSocket-Key'];
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"WebSocket-Origin: $host\r\n" .
"WebSocket-Location: wss://$host:$port\r\n".
"Sec-WebSocket-Version: 13\r\n" .
"Sec-WebSocket-Accept:$secAccept\r\n\r\n";
fwrite($client, $upgrade);
}
function send_message($clients, $msg){
foreach($clients as $changed_socket){
@fwrite($changed_socket, $msg);
}
}
?>
我遇到的问题
1、 tlsv1.3 not work on PHP 7.2.24-0ubuntu0.18.04.15
提示的错误如下,谷歌上没发现可用信息(2023年3月20日),可能搜索方向不对。猜测方向应该是openssl拓展问题,可能要重新编译以支持。
Unable to find the socket transport "tlsv1.3" - did you forget to enable it when you configured PHP?)
其他信息
-
支持不支持tlsv1.3
可以通过 phpinfo 中Registered Stream Socket Transport查看。
Registered Stream Socket Transports => tcp, udp, unix, udg, ssl, tls, tlsv1.0, tlsv1.1, tlsv1.2
-
transport 各个php 版本支持情况
recommended use “ssl://” transport.
in php 5.5 ~ 7.1
ssl:// transport = ssl_v2|ssl_v3|tls_v1.0|tls_v1.1|tls_v1.2
tls:// transport = tls_v1.0
after 7.2 ssl:// and tls:// transports is same
php 7.2 ~ 7.3 = tls_v1.0|tls_v1.1|tls_v1.2
php 7.4 ~ 8.1 = tls_v1.0|tls_v1.1|tls_v1.2|tls_v1.3
来源
魔改下workerman
修改TcpConnection.php
这里改的是server部分
// ./vendor/workerman/workerman/Connection/TcpConnection.php line 745
if(defined('STREAM_CRYPTO_METHOD_SERVER')){
$type = \STREAM_CRYPTO_METHOD_SERVER;
}else{
$type = \STREAM_CRYPTO_METHOD_SSLv2_SERVER | \STREAM_CRYPTO_METHOD_SSLv23_SERVER;
}
运行前的小修改
以下为官方的示例,增加STREAM_CRYPTO_METHOD_SERVER定义。参数参考stream-socket-enable-crypt函数的参数crypto_method
<?php
define('STREAM_CRYPTO_METHOD_SERVER',STREAM_CRYPTO_METHOD_ANY_SERVER);
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
$context = array(
'ssl' => array(
'local_cert' => '磁盘路径/server.pem',
'local_pk' => '磁盘路径/server.key',
'verify_peer' => false,
'allow_self_signed' => true,
)
);
$worker = new Worker('websocket://0.0.0.0:8282', $context);
$worker->transport = 'ssl';
$worker->onMessage = function(TcpConnection $con, $msg) {
$con->send('ok');
};
Worker::runAll();
参考
his is an example of how to set up stream_socket_server to connect with multiple Secure Websockets on WSS (wss://) that uses SSL / TLS as a Transport