Open ghedipunk opened 9 years ago
One thing I'm personally big on is encrypting your private key with a passphrase. I think the WS server should be able to load an encrypted private key when it's staring up and prompt the operator for a passphrase.
This would require human intervention in case the server ever needs to be restarted, such as after a power loss to the hardware... Security comes at the cost of convenience. If anyone is not comfortable getting up at 2am to SSH into their machine and enter a password, then they shouldn't encrypt their private key... then again, they also run a greater risk of their private key being compromised in case their server is compromised.
Anyone intending on working on TLS once I get the initial branch set up: Check the OWASP TLS cheat sheet often.
https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet
Both Chrome 43 and Firefox 38 do handshakes for TLS 1.0.
Vulnerabilities of TLS 1.0 are CBC Chaining and Padding Oracle attacks.
TODO: Find out if we can mitigate these attacks in WS.
Anyways, looks like we're implementing TLS 1.0 first, then can work up to 1.1 and 1.2. I wonder if we can get browsers to update their TLS defaults for wss: connections to reflect their https: defaults.
anything new on this? trying to implement https/wss using your library, which btw works great for what i'm using it for!!!
is there anything i can do to implement wws, i'm still new to the websocket but now have a fully working solution on our intranet using your library and exploring future https implementation and big stop on using chrome because the websocket is not using wss while https.
Thanks!!
Currently blocked on other changes.
The end goal of those changes are to make TLS work properly and securely, and make the server easier to use and extend (and a bit faster and more reliable).
Progress is slow, but not completely stopped.
Anything you can point me toward to help?
Using this on a custom trouble ticket system I built to provide real-time updates and other features, haven't had a single issue so far
Thanks!
Hi tylorjefcoat,
I have just got my WSS working. You can use this as an example, however do not copy this directly into Ghedypunks class as I have changed naming conventions etc to suit my own needs.
It is work in progress - not tested to great extends.
Hi, any news on the "wss" thing? I use your great code for the client-server communication of a home automation (mostly blinds) system. And I really would be happy to be able to use secure communication. Thanks!
Been busy with young kids and a new job.
I will make TLS support a priority over the next few weekends.
I guess the easiest way is to switch from socket library to stream library and use the built-in tls protocol.
@ghedipunk: Thanks for your efforts. I appreciate that :-)
@Xaraknid: With PHP, streams seems to be easy. How would the interface in Javascript on the client side look like? Or better, do you know of an simple example? E.g. a chat tool?
@Xaraknid That's what I was just thinking, as well.
I'm really torn on whether to do it in the new development branch first, or in the legacy branch, though. I don't have nearly enough done in the new development branch that I could take it to completion quickly. (At least, not quickly while I've got 1 year old twins in my house.) On the other hand, the new branch is meant to make unit testing easy, which is a feature that I think is almost as important as TLS.
Alright, decision made: I am officially putting unit testing on the ledger as technical debt. Any new development done without getting a good baseline for unit testing will add to that technical debt. And, I am officially adding more technical debt by deciding to implement TLS on the legacy branch first.
@mdrmdrmdr The Javascript and all other client code would stay exactly the same, we'd just be able to use "wss://" properly now. (If the client code doesn't support TLS, then it would have to be updated, but all major browsers use TLS 1.0 when opening a connection with the WSS protocol.)
@mdrmdrmdr as said by @ghedipunk.
@ghedipunk socket and libev library work on the same level and still can be easily done but with some search will require the openssl library to near easy than stream built-in.
socket and stream library have the same restriction as both use select for polling and have a hard system limit of 1024 concurrent connections.
Current way that the server is implemented, we can't have both a secure and insecure connection going at the same time without changing the constructor's signature. (I consider that a soft breaking change: Easy solution is to add more parameters to the constructor, but we can't change the order of arguments without breaking backwards compatibility.)
I'm of the opinion that if a process is handling secure data, it shouldn't handle data insecurely... That is, if you're using TLS, then all traffic must be over TLS. However, this opinion isn't shared with people who are used to the Apache HTTPd way of doing things, where the same process will happily serve up HTTP and HTTPS content at will.
So, either use WS or WSS.
Not sure how the major browsers handle self-signed certs over WSS... Will need to test.
Will also need to set up the cert on my public web server.
Note: Latest push to the legacy branch (d1cfae2bbe1c0f922c2ad18ee57951e77aaf99a0) is broken.
Will not make an attempt to unbreak it until I'm ready to see if the changes work or not.
You are right you can't run both at the same time. But I think you can support both all will depend how you start the server.
ws $test = new phpws("0.0.0.0","8080",'stream');
wss $test = new phpws("tls://0.0.0.0","8080",'stream');
for stream the job is done internally only thing required is to config it $ctx=stream_context_create(array('ssl'=>array( "local_cert"=>"mycertandkey.pem", "passphrase"=>"badpassphrase" )));
Not tested it yet and as said socket and libev all job need to be done by server. By knowing the layer those job need to be done you only need to have a flag turn on when using TLS connection and call private method when the flag is on (encrypt / decrypt).
Self-signed certificate will at least pop-up a warning if not working at all. Let's encrypt is a good solution as long you did not forget to renew it once per 3 month as they are valid for 90 days.
EDIT : even with socket library it's relatively easy you'll need to use openssl_private_decrypt on READ buffer and openssl_private_encrypt on WRITE buffer before sending.
Well on the way to switching to a stream_socket_*
based implementation, rather than the socket_
wrappers around the C BSD Sockets library.
Potential Compatibility Note: Moving the Master socket resource out of the sockets array. (You only need to worry about this if you implement your own run()
method, or otherwise expect the Master socket resource anywhere but $this->master
.)
New implementation will use stream_socket_accept()
with a zero timeout before the one second blocking on stream_socket_select()
, rather than having the master socket (now the master socket stream) be included in &$read
and again tested against $this->master
.
There is minimal difference between stream and socket. I can assure you as I can run both eventloop.
Master socket MUST be in socket array know as $read. socket_select or stream_socket_select. Otherwise the server will never be notify that a client attempt to connect.
I highly against a timeout of 0 that will result in heavy wasted CPU usage. It's better to wait patiently untill there is something on the wire than checking constantly with nothing on the wire.
Legacy branch passes very basic smoke test after switching to stream_socket_*
implementation. (Smoke test on PHP 5.5, OSX)
Have not yet tested the TLS part, don't have any certs handy on this machine.
Great point about the timeout of zero. Matches my observation of the high-CPU busy loop, although stream_socket_accept did work when I tried accepting. Once stream_socket_select had a socket in &$read to check, it respected the 1 second timeout there.
The only good reason I see to use timeout is if you want to implement disconnection process after too long inactivity like sending a ping opcode and if no pong then kill the client. Even there I prefer having timeout blank.
An advice do not use @ they are tempting but you should restraint yourself of using it. First they are slow, secondly and the most important point is that you only hide bug under the carpet. It's better to found and kill the bug on the long run.
Moved the master resource back into the &$read
parameter of select(), along with accept(). (Switched things up to break
out of the foreach, rather than have an if/elseif/else.)
Passes smoke test.
Besides worrying about TLS, I also want to test unusual client disconnects, since I couldn't find the appropriate functions to check TCP error codes under the stream_ functions in the manual. (Note to self: Also verify normal client disconnects.)
Note on testing timeline: The host that manages the server to which ghedipunk.net is pointed has recently changed details about their SSH access on their managed VPS accounts... Basically, they said that it's hard to support people who know what they're doing, so they're taking away our tools, and those who want the tools back will have to pay 10 times what they're paying now for a dedicated server. No SSH to the terminal. No sudo. Just a few point-and-click options on a recently dumbed down admin panel. (alright, I'll stop ranting. Yes, I used to recommend them. Now I don't.)
So, since I'm too lazy to actually switch hosts, and I really don't want to bother spinning up a new host on Amazon's cloud, I'll be setting things up on the linux box at my house and in order to get a certificate through Let's Encrypt, I'll be making a few domain changes, which will take some time to propagate...
In other words, I might not get to testing the actual TLS portion tonight. Will absolutely test the client disconnect scenarios, though.
Progress so far:
Basic smoke test passed on Windows 10, PHP 5.6.
Found an old(ish) cert (still valid for a couple more months) and edited my hosts file to make localhost match.
So far, failing at "stream_socket_accept(): Failed to enable crypto" which is good progress. Probably need to create an openssl.cnf
file.
Warning: stream_socket_accept(): SSL operation failed with code 1. OpenSSL Error messages:
error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number
Definite progress. It's handshaking. Not really in the mood to get OpenSSL working on Windows, though.
Time to move it over to Linux.
Correction: Great success!
(Note: ghedipunk.net is pointing to localhost in this case, due to edits in my hosts file)
Trick was to edit client.html on disk, rather than use Chrome's DOM editor to change the URI scheme from WS to WSS after it was loaded.
Will polish things up then push things so far, then get on to testing the differences between Berkeley sockets and Stream sockets.
3c2d6ca402c827d4ddcc750a98159f6d74b35b5c passes the smoke test.
Some cleanup needed... I'm thinking of having an example TLS echo server, just like the current testwebsock.php
, and probably update client.html
with the option to connect with either WS or WSS.
Will also need to update the readme.
Then I need to really dig into the differences between Berkeley sockets and stream sockets, especially detecting when a client disconnects, whether normally or abnormally... then test large packets, Nagle's Algorithm... Then I'll add the Autobahn|testsuite to the standard testing procedures... THEN merge into master.
Then back to work on the next branch. (Actually, work on the support site first, but that's a whole other mess.)
Sounds great :-) I'd use - once it's stable - the PHP part on a Raspberry Pi 2 and the Client on Windows 7 and iOS 9.x. Thanks for your work!
http://phpsecurity.readthedocs.org/en/latest/Transport-Layer-Security-%28HTTPS-SSL-and-TLS%29.html A very good information about secure connection. It's important to fine tune configure options around ssl/tls connection to prevent many sort of exploit and in the end having false impression of "security".
I know you want to keep backward compatibility and I think a good way to do that for adding TLS feature without having user the need to go in internal file ( websockets.php ) to config it. In the path right now every patch to the core will required the user to modify it to set up the feature.
I suggest moving this step outside of websockets.php to testwebsock.php. Simply change the construct from
__construct($addr, $port, $bufferLength = 2048)
to
__construct($addr, $port, $type= 'ws',$bufferLength = 2048,$ssl_options='')
or similar order
That will be backward compatible, need of only one method to setup connection. With that you can check enforce all options neccessary for secure connection to be present if some missing exit("malformed ssl_options missing X ")
The route that I've already started going down is removing the setupSecureConnection()
method, and will have the example script (testtlssock.php
) override setupConnection()
with a secure implementation.
So far,
class echoServer extends WebSocketServer {
/**
* Overrides the base setupConnection to implement TLS.
*
* @return void
*/
protected function setupConnection() {
$errno = $errstr = null;
$options = array(
'ssl' => array(
'peer_name' => 'YOUR DOMAIN NAME HERE',
'verify_peer' => false,
'local_cert' => 'cert.pem',
'local_pk' => 'pk.key',
),
);
$context = stream_context_create($options);
$this->master = stream_socket_server(
'tls://' . $this->listenAddress . ':' . $this->listenPort,
$errno,
$errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
$context
);
}
... continue on with the echo server as in testwebsock.php
I need to research some secure defaults for the context options still, before I put the example up publicly. My philosophy is that security requires failing closed, so I'm also considering putting in how to encrypt and decrypt the certificate, securing the context options inside of a configuration file (which is really where these details need to live, so that nobody is editing source code to change, say, the filename for the cert), and even encrypting the config file and prompting for a password to decrypt that option file when the WS server starts up.
(Also, yes, I'm fully aware that there is a context option named 'ciphers' that defaults to 'DEFAULT'... and that this default might be able to be downgraded all the way down to no encryption at all. Lots of steps to go through in the checklist to make sure things are secure. I'll also be re-reading through the OWASP docs to see if I've missed anything they've missed, as well as peppering this example with plenty of comments warning people to maintain a minimum level of security, and that authentication, integrity, and non-repeating is as much an essential part of security as encryption is.)
By prompting for password didn't prevent the use of nohup and let's only option screen ?
It's all good to tighten security as long all the chain is on same level. The level of security is equal of the weakest link to keep thing in perspective.
Yes, prompting for a password will prevent the use of nohup, which will require the process itself to become a daemon.
Process to become a daemon:
init
once this process goes away)init
, which automatically handles SIGCHLD
.)Default TLS context options that I intend to use in the example code, posted here for any discussion:
$options = array(
'ssl' => array(
'peer_name' => 'YOUR DOMAIN NAME HERE',
'verify_peer' => false,
'local_cert' => 'cert.pem',
'local_pk' => 'pk.key',
'disable_compression' => true,
'SNI_enabled' => true,
'ciphers' => 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK',
),
);
Implementation detail note: (In other words, don't count on this behavior anywhere, even here, even if you know that it's happening.)
Stream_socket doesn't give many handy ways to know if a connection is closed, like BSD sockets do. Adding in WS pings for periodically testing the connection, in cases where a WS close frame is not possible (i.e., unplugged network cable).
At first I was wondering what you are talking about connection closed. You mean when you reading on the socket. You are right as socket are low-end library you have more control than stream.
Socket can tell you why it fail. But both have the same behavior to know when you lost connection.
socket_recv return false when a error happen. fread return false when a error happen.
In both case most likely because you lost connection and you can safely close your side of the connection.
Unless you want to behave differently depending on "why" error happen. You do not need to go deeper than that.
Edit : normal disconnection socket_recv return 0 fread return not tested yet
As state above as the result is the same ( normal or abnormal disconnection ) you close the connection. A simple == will catch 0 and FALSE.
Hi, any news here?
Push!
Hope my last question was not ignored since it was issued on April 1st ;-)
Ok, no response. Since I did not want to use ratchet, I played around with PHP-Websockets to achive secure communication. What I did:
So far my websocket communication runs stable with https/wss on only one port (443). No need for an extra port to be opended at the router for the websocket communication due to the usage of the mod_proxy module. So at least for me, a TLS version of PHP-Websockets is not required anymore.
:-))
@mdrmdrmdr I will try to implement your instructions. Thanks so much for sharing the basic steps! If you can, please answer these questions
ProxyPass
directive? .htaccess
?ProxyPass
and ProxyPassReverse
rulesrun
function. It's difficult for me to know exactly what you did from your description@PeterElzinga Would you mind sharing the rest of your websocket class? the snippet you posted relies on modifications you've made elsewhere so it's difficult to implement it
@RoxKilly: I think the proxy code could also go to .htaccess, but I placed it into the file named in the instructions for wstunnel, which is "/etc/apache2/mods-enabled/proxy_wstunnel.load". My "run" function is attached. Pls note that there are some globel constants used. E.g. CR=\r and NL=\n. Also "msg" is my logging function. U need to replace this.
Note: remove ".txt" from the filenames.
Good luck! ;-)
@mdrmdrmdr Thanks a ton for posting your function run()
and your directives.
It took me hours to realize that my ProxyPass
rules were not working because they require not just mod_proxy
, but also mod_proxy_wstunnel
My ProxyPass
and ProxyPassReverse
rules are in VirtualHost
config (within the httpd.conf
file):
<IfModule mod_proxy.c>
ProxyPass "/_ws_/" "ws://127.0.0.1:8080/"
ProxyPassReverse "/_ws_/" "ws://127.0.0.1:8080/"
</IfModule>
I then start PHP-Websockets
listening at that IP and port. So the WS server is not directly exposed to the internet.
I still need to figure out how to handle routes, but at least I have the setup working.
I tried your run code but it said CR and NL is undefined
Read the posts carefully then you'll find the answer...
@mdrmdrmdr thanks for your contributions.
A query, when you say: - start the client with "https", how would you do it? PHP CLI? or from a browser?
Thank you
@guillenexo it means load the https
webpage in the browser (the page that will attempt to connect to the websocket via wss://...
)
Couldn't have said it better ;-)
thanks!
My server is using HTTPS, so that I can use a ProxyPass
to link up the web server and 5GE backed websocket server and encrypt data on the fly
Big thanks to @mdrmdrmdr whose 15 Aug 2016 comment worked perfectly for me. I was able to run TLS (wss://) Websockets through the native SSL Apache server and PHP without changing PHP code. I only changed the HTML file with protocol wss and port to 443. Apache's mod_proxy
with ProxyPass
and ProxyPassReverse
in the virtualhost conf was a genius idea!
Thanks @ghedipunk for PHP-Websockets project. We're able to send broadcast messages to multiple users for a chat server for example. This is not the case with websocketd project which isolates each user.
I have decided to implement TLS (
wss://
) in the server using the built-in PHP OpenSSL functions.This will require much testing and much review. I know I'm good, but I also know that hubris is the cause of many security holes. Simply put, I simply refuse to trust myself when it comes to encryption, even if I know I'm doing everything right. (I've had good teachers when it comes to encryption. I don't trust them, either.)
So, this be the discussion thread for implementing TLS.