Author Topic: websockets and woe - lugnut's mission to create html5 websockets - still broked  (Read 3273 times)

some of you may know i've been running truce's torque webserver.
i've modified it a bit to allow support for various things, and my latest goal is to add support for html5 websockets.

following this guide and numerous other resources
http://www.altdevblogaday.com/2012/01/23/writing-your-own-websocket-server/
and
https://developer.mozilla.org/en-US/docs/WebSockets
i've made this addition to the Webclient::finish method in the webserver at line 253

Code: [Select]
if($_SERVERHTTP_Connection $= "Upgrade")
{
%server.debug("> Upgrading connection to websocket.");

if($WebSocketNum $= "")
$WebSocketNum = 0;

%spec = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
%clkey = $_SERVERHTTP_Sec_WebSocket_Key;

%catkey = %clkey @ %spec;
%server.debug("> CatKey: " @ %catkey);

%rkey = base64Encode(sha1(%catkey));
%server.debug("> key: " @ %rkey);

%this.send("HTTP/1.1 101 Switching Protocols\r\n");
%this.send("Upgrade: websocket\r\n");
%this.send("Connection: Upgrade\r\n");
%this.send("Sec-WebSocket-Accept: " @ %rkey @ "\r\n");
%this.send("\r\n");
// %this.send("Num = " @ $WebSocketNum @ "\r\n");

$WebSocketArray[$WebSocketNum] = %this;
$WebSocketNum++;
return;
}

the problem occurs when the system is calculating either the SHA1 sum or the base64 encoding at the end. here's a sample request utilizing javascript console:
Code: (client) [Select]
var s = new WebSocket("69.64.43.11:33580");
Code: (server) [Select]
% [Webserver] > Client timeout in 500 milliseconds.
% [Webserver] Packet terminated from client 1845687.
% [Webserver] Parsing client 1845687's header: Upgrade: websocket
Connection: Upgrade
Host: 69.64.43.11:33580
Origin: http://69.64.43.11:33580
Sec-WebSocket-Key: aGf7b7cHpm/HtRf16dG3Xg==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
Cookie: a=streamline; n=Lugnut

% [Webserver] > Assigning websocket to HTTP_Upgrade.
% [Webserver] > Assigning Upgrade to HTTP_Connection.
% [Webserver] > Assigning 69.64.43.11:33580 to HTTP_Host.
% [Webserver] > Assigning http://69.64.43.11:33580 to HTTP_Origin.
% [Webserver] > Assigning aGf7b7cHpm/HtRf16dG3Xg== to HTTP_Sec_WebSocket_Key.
% [Webserver] > Assigning 13 to HTTP_Sec_WebSocket_Version.
% [Webserver] > Assigning x-webkit-deflate-frame to HTTP_Sec_WebSocket_Extensions.
% [Webserver] > Assigning a=streamline; n=Lugnut to HTTP_Cookie.
% [Webserver] Parsing client 1845687's POST args:
% [Webserver] > No POST args found to parse!
% [Webserver] > Upgrading connection to websocket.
% [Webserver] > clKey = aGf7b7cHpm/HtRf16dG3Xg==
% [Webserver] > recdKey = aGf7b7cHpm/HtRf16dG3Xg==
% [Webserver] > CatKey: aGf7b7cHpm/HtRf16dG3Xg==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
% [Webserver] > key: ZDdkN2FlMGM5MzFmN2RhY2Y0NDRlOWJmNzVmZTJjNzBkNTQyNzIwOQ==
Code: (client) [Select]
Error during WebSocket handshake: Sec-WebSocket-Accept mismatch
as you can see, the specification magic key is appended to the sent key successfully, and the end result is pooped out.

using default programs in my linux distro, i ran the following commands in my terminal
Code: [Select]
lugnut@LugBook:~$ sha1sum
aGf7b7cHpm/HtRf16dG3Xg==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
bf416c18230328f3113518ed03f8fbcee351d7b4  -
lugnut@LugBook:~$ base64
bf416c18230328f3113518ed03f8fbcee351d7b4
YmY0MTZjMTgyMzAzMjhmMzExMzUxOGVkMDNmOGZiY2VlMzUxZDdiNAo=
firstly i input the whole key (sent + spec) into sha1sum
then i took that and put it into the base64 converter

obviously the webservers result is drastically different than the one i manually created.

however, i don't know who to blame :(

it appears that torque is miscalculating either base64 or sha1...

What can I do?
EDIT: after posting this i realized there was two spaces and a - after my sha1 encoding i had ignored
Code: [Select]
lugnut@LugBook:~$ base64
bf416c18230328f3113518ed03f8fbcee351d7b4  -
YmY0MTZjMTgyMzAzMjhmMzExMzUxOGVkMDNmOGZiY2VlMzUxZDdiNCAgLQo=
this is a match to my earlier result without the whitespace.
« Last Edit: August 31, 2012, 12:20:44 AM by Lugnut »

Here's the relevant part of that blog post
Quote from: article, code is in C
Connecting
Creating a WebSocket connection is initiated by the client sending the following upgrade request:

Code: [Select]
GET /servicename HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
The server responds with:

Code: [Select]
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Most of these HTTP fields are self explanatory but not Sec-WebSocket-Key and Sec-WebSocket-Accept. Sec-WebSocket-Key is a string sent by the client as a challenge to the server. This leads to the question- how does the server calculate the value of Sec-WebSocket-Accept and complete the challenge? It is quite simple. The server first takes Sec-WebSocket-Key and concatenates it with a GUID string from the WebSocket specification. Then the SHA-1 hash of the resulting string is computed and, finally, Sec-WebSocket-Accept is the base64 encoding of the hash value. Let’s work through an example:

Code: [Select]
SpecifcationGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
FullWebSocketKey = concatenate(Sec-WebSocket-Key, SpecifcationGUID);
  // dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
KeyHash = SHA-1(FullWebSocketKey);
  // 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea
Sec-Websocket-Accept = base64(KeyHash);
  // s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Additionally, I ran the things in the blog through both my webserver and the default programs:
Code: [Select]
lugnut@LugBook:~$ sha1sum 
dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
05082898ab78f6da9c1ad2587b8012cd0cf52172  -
lugnut@LugBook:~$ base64
05082898ab78f6da9c1ad2587b8012cd0cf52172
MDUwODI4OThhYjc4ZjZkYTljMWFkMjU4N2I4MDEyY2QwY2Y1MjE3Mgo=
Code: [Select]
==> echo(base64encode(sha1("dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11")));
YjM3YTRmMmNjMDYyNGYxNjkwZjY0NjA2Y2YzODU5NDViMmJlYzRlYQ==

what the forget.

here's truce's base64 pack and dependancy FWIW
Code: [Select]
////////////////////////////////////////
//  Base64 Pack             by Truce  //
////////////////////////////////////////

function convertBase(%val,%atype,%btype)
{
%vlen = strLen(%val);
%alen = strLen(%atype);
%blen = strLen(%btype);

for(%i = 0; %i < %vlen; %i++)
%sum += striPos(%atype,getSubStr(%val,%i,1)) * mPow(%alen,%vlen - %i - 1);

while(1)
{
%rem = %sum % %blen;
%new = getSubStr(%btype,%rem,1) @ %new;
%sum = mFloor(%sum / %blen);

if(!%sum)
break;
}

return %new;
}

function base64Encode(%str)
{
%base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
%asciimap  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMN" @
             "OPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

%len = strLen(%str);

for(%i = 0; %i < %len; %i++)
{
%chr   = getSubStr(%str,%i,1);
%ascii = strPos(%asciimap,%chr) + 32;
%bin   = convertBase(%ascii,"0123456789","01");

while(strLen(%bin) < 8)
%bin = "0" @ %bin;

%all = %all @ %bin;
}

%len = strLen(%all);

for(%i = 0; %i < %len; %i += 6)
{
%pack = getSubStr(%all,%i,6);

while(strLen(%pack) < 6)
%pack = %pack @ "0";

%dec = convertBase(%pack,"01","0123456789");
%new = %new @ getSubStr(%base64map,%dec,1);
}

while(strLen(%new) % 4 > 0)
%new = %new @ "=";

return %new;
}
« Last Edit: August 28, 2012, 02:17:50 AM by Lugnut »

First, replace the base64Encode function with this version of it:
(Not sure what I was thinking by not including all characters...)

Quote
function base64Encode(%str)
{
   %base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabc defghijklmnopqrstuvwxyz012345 6789+/";
   %asciimap  =     "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" @
                "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" @
                "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F" @
                "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F" @
                "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F" @
                "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F" @
                "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F" @
                "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F" @
                "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F" @
                "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F" @
                "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF" @
                "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF" @
                "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF" @
                "\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF" @
                "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF" @
                "\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF";
   
   %len = strLen(%str);
   
   for(%i = 0; %i < %len; %i++)
   {
      %chr   = getSubStr(%str,%i,1);
      %ascii = strPos(%asciimap,%chr) + 1;
      %bin   = convertBase(%ascii,"0123456789","01");
      
      while(strLen(%bin) < 8)
         %bin = "0" @ %bin;
      
      %all = %all @ %bin;
   }
   
   %len = strLen(%all);
   
   for(%i = 0; %i < %len; %i += 6)
   {
      %pack = getSubStr(%all,%i,6);
      
      while(strLen(%pack) < 6)
         %pack = %pack @ "0";
      
      %dec = convertBase(%pack,"01","0123456789");
      %new = %new @ getSubStr(%base64map,%dec,1);
   }
   
   while(strLen(%new) % 4 > 0)
      %new = %new @ "=";
   
   return %new;
}

Then in Torque, I skipped to line 5 of this code:

Code: [Select]
SpecifcationGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
FullWebSocketKey = concatenate(Sec-WebSocket-Key, SpecifcationGUID);
  // dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
KeyHash = SHA-1(FullWebSocketKey);
  // 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea
Sec-Websocket-Accept = base64(KeyHash);
  // s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

and tested it as such:

Code: [Select]
$testsha = "\xB3\x7A\x4F\x2C\xC0\x62\x4F\x16\x90\xF6\x46\x06\xCF\x38\x59\x45\xB2\xBE\xC4\xEA";
echo(base64Encode($testsha));
// s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

So now it encodes the sha1 result properly. But what is Blockland's sha1 result?

Code: [Select]
$testkey = "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
echo(sha1($testkey));
// b37a4f2cc0624f1690f64606cf385945b2bec4ea

It looks different than line 5 of the first code block in this post, but it's not.

Code: [Select]
$testkey  = "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
$testsha  = sha1($testkey);
$testsha2 = "";

for(%i = 0; %i < strLen($testsha); %i += 2)
eval("$testsha2 = $testsha2 @ \"\\x" @ getSubStr($testsha,%i,2) @ "\";");

$result = base64Encode($testsha2);
echo($result);
// s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Basically, it's using every two characters in the sha1 result as one ascii character.
So with the fixed version of base64Encode + this method, it seems to work fine.

thanks mate!
i hear eval uses local variables from the context it's called in. this is valid, right?
Code: [Select]
function trueSha1(%input)
{
%sourceSha = sha1(%input);
%output = "";

for(%i = 0; %i < strLen(%sourceSha); %i += 2)
eval("%output = %output @ \"\\x" @ getSubStr(%sourceSha,%input,2) @ "\";");

%return = base64Encode(%output);
return %return;
}

i got worried when no one was responding, as if i had asked a question that was too complex :(

That is valid.

But, you don't need to do that.

You can just use collapseEscape("\\x" @ %a) where %a is the hex number.

Code: [Select]
function trueSha1(%input)
{
%sourceSha = sha1(%input);
%output = "";

for(%i = 0; %i < strLen(%sourceSha); %i += 2)
eval("%output = %output @ " @ collapseEscape("\\x", getSubStr(%sourceSha,%input,2)) @ "\";");

%return = base64Encode(%output);
return %return;
}
???

Code: [Select]
function trueSha1(%input)
{
%sourceSha = sha1(%input);
%output = "";

for(%i = 0; %i < strLen(%sourceSha); %i += 2)
%output = %output @ "\\x" @ getSubStr(%sourceSha,%input,2);

%return = base64Encode(%output);
return %return;
}

Why are you using eval unnecessarily 

???

I didn't know about collapseEscape, but if it works like I think it does, use it in place of eval.

Why are you using eval unnecessarily 

You need eval (or collapseEscape apparently) for this to work - actually try what you just posted.

I didn't know about collapseEscape, but if it works like I think it does, use it in place of eval.

You need eval (or collapseEscape apparently) for this to work - actually try what you just posted.
oh. ok

Code: [Select]
function trueSha1(%input)
{
%sourceSha = sha1(%input);
%output = "";

for(%i = 0; %i < strLen(%sourceSha); %i += 2)
%output = %output @ collapseEscape("\\x" @ getSubStr(%sourceSha,%input,2));

return base64Encode(%output);
}

lovey
thanks for the help guys, i'ma test it now

mmm that didn't work out so swell

> Upgrading connection to websocket.
> CatKey: h/D1cwIGMUTXf8khfOOFLw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
> ShaKey: z7PTz7PTz7PTz7PTz7PTz7PTz7M= //this is the key calculated using the trueSha1 function that truce made and ip modified
> key: di QVHn2UFR5N0BUdi QVHn2UFR5N0BUdi NPQ==

... spaces?

using ip's code

Replace %input in the for loop with %i
I don't know why you did that

Replace %input in the for loop with %i
I don't know why you did that
misinterpreted the code, was too excited to see it finally working T.T

Code: [Select]
> CatKey: 27ebx99X70eajZ2+toSlog==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
> ShaKey: pBmGM6MRWiwhVBJD85M0vIZ8nQj=
> key: cEJsRzz1TVJXaX nVjJKRDf0TTB1SVn3bkFpPQ==
Code: [Select]
function trueSha1(%input)
{
%sourceSha = sha1(%input);

for(%i = 0; %i < strLen(%sourceSha); %i += 2)
%output = %output @ collapseEscape("\\x" @ getSubStr(%sourceSha,%i,2));

return base64Encode(%output);
}

wat do
it appears to be throwing random spaces in
should i try just removing them?
Code: [Select]
lugnut@LugBook:~$ base64
pBmGM6MRWiwhVBJD85M0vIZ8nQj=
cEJtR002TVJXaXdoVkJKRDg1TTB2SVo4blFqPQo=
Code: [Select]
cEJtR002TVJXaX [space in torque version is here] doVkJKRDg1TTB2SVo4blFqPQo= // note difference



it appears the code is misinterpreting one of the characters as a space OR outputting some of it as a space

which i guess doesn't work

Quote from: me
wat do

ran it through my stuff
Code: [Select]
lugnut@LugBook:~$ base64 -d
cEJsRzz1TVJXaX nVjJKRDf0TTB1SVn3bkFpPQ==
pBlG<�MRWibase64: invalid input
lugnut@LugBook:~$ base64
pBmGM6MRWiwhVBJD85M0vIZ8nQj=
cEJtR002TVJXaXdoVkJKRDg1TTB2SVo4blFqPQo=
lugnut@LugBook:~$ base64 -d
cEJtR002TVJXaXdoVkJKRDg1TTB2SVo4blFqPQo=
pBmGM6MRWiwhVBJD85M0vIZ8nQj=
« Last Edit: August 28, 2012, 02:36:50 PM by Lugnut »

Check your syntax, or post the whole code? Using both my code and the function you posted, I got:

Quote from: input
27ebx99X70eajZ2+toSlog==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
Quote from: sha1
a819c633c3115a3c62541243fba335c0867ea109
Quote from: sha1 ascii
¨\x19Æ3Ã\x11Z<bT\x12Cû£5À†~¡\t
Quote from: base64
qBnGM8MRWjxiVBJD+6M1wIZ+oQk=

(note the escaped characters in sha1 ascii, don't just paste it into a converter)