Jump to content

websocket upgrade request fails over tls. unsure how to implement ssl into php based socket server..


r3wt

Recommended Posts

I am in development stages of a website. this website is primarily in php. today, i decided i reached a point in development where it was time to move everything to TLS, and not suprisingly the websocket connection is being refused and i'm not sure why.

 

The only error i get is

 

 

 

 
Notice: Undefined index: Sec-WebSocket-Key in /usr/share/nginx/html/models/funcs.php on line 933
 

 

Here is the handshaking function:

function perform_handshaking($receved_header,$client_conn, $host, $port)
{
	$headers = array();
	$lines = preg_split("/\r\n/", $receved_header);
	foreach($lines as $line)
	{
		$line = chop($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')));
	//hand shaking header
	$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/socket/server.php\r\n".
	"Sec-WebSocket-Accept:$secAccept\r\n\r\n";
	socket_write($client_conn,$upgrade,strlen($upgrade));
}

Here is the server

//
//socket server
//
require_once __DIR__ . '/../models/config.php';

$host = 'www.example';
$port = 8888;
$admin = array("admin", "superadmin");
$null = NULL;
$socket_key = fetchkey('0x33');
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($socket, 0, $port);
socket_listen($socket);
$clients = array($socket);

while (true) {
	$changed = $clients;
	socket_select($changed, $null, $null, 0, 10);
	if (in_array($socket, $changed)) {
		$socket_new = socket_accept($socket);
		$clients[] = $socket_new;
		$header = socket_read($socket_new, 1024);
		perform_handshaking($header, $socket_new, $host, $port);
		socket_getpeername($socket_new, $ip);
		$found_socket = array_search($socket, $changed);
		sendOldMessages($socket_new);		
		unset($changed[$found_socket]);
	}
	foreach ($changed as $changed_socket) {	
		while(socket_recv($changed_socket, $buf, 1024, 0) >= 1)
		{	
			$received_text = unmask($buf);
			$msg = json_decode($received_text);
			$msg_type    =  @$msg->type;
			$write_key    = @$msg->writekey;
			if($msg_type == 'chatmsg' && $write_key == $socket_key) {
				$user_name    = @$msg->name;
				$user_message = @$msg->message;
				$user_color   = @$msg->color;
				if(in_array($user_name,$admin)) {
					if (strpos($user_message,'./ban') !== false) {
						newBan($user_message);
						break 2; 
					}
					if (strpos($user_message,'./unban') !== false) {
						unBan($user_message);
						break 2; 
					}	
				}
				if(usernameExists($user_name)) {
					if($user_name == $null || $user_message == $null) {
						break 2;
					}else{
						if(!isBant($user_name)) {
							newMessage($user_name,$user_message,$user_color);					
							break 2;
						}else{
							cooldown($user_name);
						}
					}
				}
			}
		}
		if($msg_type == 'alert' && $write_key == $socket_key) {
			$recipient   = @$msg->user;
			$alert		 = @$msg->alert;
			$response_text = mask(json_encode(array('type'=>'alert', 'user'=>security($recipient), 'alert'=>security($alert))));
			send_message($response_text);
		}
		
		if($msg_type == 'mktdata' && $write_key == $socket_key) {
			$pair   = @$msg->pair;
			$price	= @$msg->price;
			$response_text = mask(json_encode(array('type'=>'mktdata', 'pair'=>$pair, 'price'=>$price)));
			send_message($response_text);
		}
		
		$buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ);
		if ($buf === false) { 
			$found_socket = array_search($changed_socket, $clients);
			@socket_getpeername($changed_socket, $ip);
			unset($clients[$found_socket]);
		}
	}
}
socket_close($sock);

From what i can gather, i somehow need to give this server script access to my ssl certificate so it can decode the post request(this is my basic understanding of how tls works, could be wrong, correct me if i am please.) . Now, i am at a total loss as to what i should try next, and there is quiet a shortage of information on this subject related to php atleast.

 

 

Some other information to note:

 

chat messages are posted via ajax to a file, sendmsg.php that also must establish a connection to this socket server and broadcast the posted message.

 

likewise, a cronjob runs to get new alert messages and market price statistics and connects and broadcasts the information to the server in basically the same way as sendmsg.php does.

 

Any questions are welcome, and any advice is appreciated. i may drop some btc to anyone who is able to help me fix this and move on to the next challenge :P

 

thanks,

          Garrett

Link to comment
Share on other sites

Hi,

 

the server needs to perform a full TLS handshake. Implementing this on your own is practically impossible and simply not a good idea.

 

So there are basically three options:

  • You use a WebSocket library which can do TLS (looks like Wrench is a good choice, but I've never tried it myself).
  • You rewrite the code from scratch using the high-level Streams. This is what Wrench does under the hood.
  • You use nginx as a reverse WebSocket proxy to convert the WSS traffic to plain WS.

 

Link to comment
Share on other sites

well, i converted it all to the stream functions as you suggested, still no magic. finally get a response in the debug console 

 

 

 

WebSocket connection to 'wss://openex.info:8888/socket/server2.php' failed: Connection closed before receiving a handshake response

 

 

here is the new code including all functions. it was hard to find replacements for all the socket functions, and i'm sure that i made a billion errors:

//
//php websocket server with tls
//

require_once __DIR__ . '/../models/config.php';

$host = 'openex.info';
$port = '8888';
$admin = array("admin", "superadmin");
$null = NULL;
$socket_key = fetchkey('0x33');
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'local_cert', $ssl_cert);
stream_context_set_option($context, 'ssl', 'passphrase', $ssl_pass);
stream_context_set_option($context, 'ssl', 'allow_self_signed', false);
stream_context_set_option($context, 'ssl', 'verify_peer', true);
$socket = stream_socket_server('ssl://'.$host.':'.$port, $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
$clients = array($socket);
while(true) {
	$changed = $clients;
	stream_select($changed, $null, $null, 0, 10);
	if (in_array($socket, $changed)) {
		$socket_new = stream_socket_accept($socket);
		$clients[] = $socket_new;
		$header = fread($socket_new, 1024);
		perform_handshaking2($header, $socket_new, $host, $port);
		$peer = stream_socket_get_name($socket_new, true);
		$found_socket = array_search($socket, $changed);
		sendOldMessages2($peer);		
		unset($changed[$found_socket]);
	}
	foreach ($changed as $changed_socket) {	
		while(fread($changed_socket, $buf, 1024, 0) >= 1)
		{	
			$received_text = unmask($buf);
			$msg = json_decode($received_text);
			$msg_type    =  @$msg->type;
			$write_key    = @$msg->writekey;
			if($msg_type == 'chatmsg' && $write_key == $socket_key) {
				$user_name    = @$msg->name;
				$user_message = @$msg->message;
				$user_color   = @$msg->color;
				if(in_array($user_name,$admin)) {
					if (strpos($user_message,'./ban') !== false) {
						newBan2($user_message);
						break 2; 
					}
					if (strpos($user_message,'./unban') !== false) {
						unBan2($user_message);
						break 2; 
					}	
				}
				if(usernameExists($user_name)) {
					if($user_name == $null || $user_message == $null) {
						break 2;
					}else{
						if(!isBant2($user_name)) {
							newMessage2($user_name,$user_message,$user_color);					
							break 2;
						}else{
							cooldown2($user_name);
						}
					}
				}
			}
		}
		if($msg_type == 'alert' && $write_key == $socket_key) {
			$recipient   = @$msg->user;
			$alert		 = @$msg->alert;
			$response_text = mask(json_encode(array('type'=>'alert', 'user'=>security($recipient), 'alert'=>security($alert))));
			send_message2($response_text);
		}
		
		if($msg_type == 'mktdata' && $write_key == $socket_key) {
			$pair   = @$msg->pair;
			$price	= @$msg->price;
			$response_text = mask(json_encode(array('type'=>'mktdata', 'pair'=>$pair, 'price'=>$price)));
			send_message2($response_text);
		}
		
		$buf = @fread($changed_socket, 1024, PHP_NORMAL_READ);
		if ($buf === false) { 
			$found_socket = array_search($changed_socket, $clients);
			@stream_socket_get_name($changed_socket, true);
			unset($clients[$found_socket]);
		}
	}
}
fclose($socket);


function perform_handshaking2($receved_header,$client_conn, $host, $port)
{
	$headers = array();
	$lines = preg_split("/\r\n/", $receved_header);
	foreach($lines as $line)
	{
		$line = chop($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')));
	//hand shaking header
	$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/socket/server2.php\r\n".
	"Sec-WebSocket-Accept:$secAccept\r\n\r\n";
	fwrite($client_conn,$upgrade,strlen($upgrade));
}

function send_message2($msg)
{
	global $clients;
	foreach($clients as $changed_socket)
	{
		@fwrite($changed_socket,$msg);
	}
	return true;
}
function send_message_single_client2($msg,$client) 
{
	@fwrite($client,$msg);
	return true;
}



function newBan2($user_message) 
{
	global $mysqli;
	$command = explode(" ", $user_message);
	if(count($command) >= 2) {
		$toban   = $command[1];
		if(!empty($toban)) {
			if(usernameExists($toban)) {
				$begin = time();
				if(isset($command[2])) {
					switch ($command[2]) {
						case '10m' : $length = 10 * 60; $duration = '10 minutes'; break;
						case '30m' : $length = 30 * 60; $duration = '30 minutes'; break;
						case '1h'  : $length = 60 * 60; $duration = '1 hour'; break;
						case '1d'  : $length = 24 * 60 * 60; $duration = '1 day'; break;
						case '1w'  : $length = 7 * 24 * 60 * 60; $duration = '1 week'; break;
						default    : return;
					}
				}else{
					$length   = 5 * 60; 
					$duration = '5 minutes';
				}
				$end  = $begin + $length;
				$stmt = $mysqli->prepare("INSERT INTO uc_chat_bans (username,starttime,endtime,duration) VALUES (?,?,?,?)");
				$stmt->bind_param('siis',$toban,$begin,$end,$duration);
				$stmt->execute();
				$stmt->close();
				$ban_nme = 'System';
				$ban_msg = '** '.security($toban).' has been banned from chat for '.security($duration).'**';
				$ban_col = '000000';
				$ban_now = time();
				$stmt = $mysqli->prepare("INSERT INTO uc_chat_msg (username,message,color,timestamp) VALUES (?,?,?,?);");
				$stmt->bind_param('sssi',$ban_nme,$ban_msg,$ban_col,$ban_now);
				$stmt->execute();
				$stmt->close();
				$response_text = mask(json_encode(array('type'=>'userban', 'toban'=>$toban, 'bantime'=>$duration)));
				send_message2($response_text); //send data
			}
		}
	}
}

function unBan2($user_message)
{
	global $mysqli;
	$command = explode(" ", $user_message);
	$tounban = $command[1];
	$stmt = $mysqli->prepare("DELETE FROM uc_chat_bans WHERE username = ?");
	$stmt->bind_param('s',$tounban);
	$stmt->execute();
	$stmt->close();
}
function isBant2($name) 
{
	$user111 = $user000 = null;
	global $mysqli;
	$time = time();
	$stmt = $mysqli->prepare("SELECT username FROM uc_chat_bans WHERE endtime > ? AND username = ?");
	$stmt->bind_param('is',$time,$name);
	$stmt->execute();
	$stmt->bind_result($user000);
	while($stmt->fetch())
	{
		$user111 = $user000;
	}
	if($user111 == $name)
	{
		$stmt->close();
		return true;
	}else{
		$stmt->close();
		return false;
	}
}

function sendOldMessages2($socket_new)
{
	global $mysqli;
	$query = $mysqli->query("SELECT * FROM (SELECT * FROM uc_chat_msg WHERE `hidden`='0' ORDER BY `id` DESC LIMIT 100) as last100 ORDER BY id");
	while($row = $query->fetch_assoc()) {
		$response_text = mask(json_encode(array('type'=>'usermsg', 'name'=>security($row["username"]), 'message'=>security($row["message"]), 'color'=>security($row["color"]),'timestamp'=>security($row["timestamp"]))));
		send_message_single_client2($response_text, $socket_new);//send old messages to the new client
	}
	$query->close();
}

function newMessage2($user_name,$user_message,$user_color)
{
	global $mysqli;
	$now = time();
	$stmt = $mysqli->prepare("INSERT INTO uc_chat_msg (username,message,color,timestamp) VALUES (?,?,?,?);");
	$stmt->bind_param('sssi',$user_name,$user_message,$user_color,$now);
	$stmt->execute();
	$stmt->close();
	$response_text = mask(json_encode(array('type'=>'usermsg', 'name'=>security($user_name), 'message'=>security($user_message), 'color'=>security($user_color), 'timestamp'=>$now)));
	send_message($response_text);
}

function cooldown2($user_name)
{
	$response_text = mask(json_encode(array('type'=>'cooldown', 'user'=>security($user_name))));
	send_message($response_text); //send data
}
Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.