I am trying to upgrade my PHP WebSocket library to SSL, using the wss://-protocol on the client-side. However, I find myself at a loss trying to figure out a) how to set up the server socket, and b) how to decode the incoming messages. Here is my current attempt at the former:
$context = stream_context_create();
// local_cert must be in PEM format
stream_context_set_option($context, 'ssl', 'local_cert', $pemFilePath);
// Pass Phrase (password) of private key
stream_context_set_option($context, 'ssl', 'passphrase', $pemPassPhrase);
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);
$serverSocket = stream_socket_server('ssl://'.$host.':'.$port, $errNo, $errStr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
However, when using SSL as the server protocol, the client does not seem to be sending any messages, and when using tcp, I get weird, encrypted data, although it was my guess that it would be magically decrypted due to my providing the options to the stream context.
What am I doing wrong?
I ran into the same problem with our implementation, and found a good workaround. Put your websocket server on any firewalled port, and tunnel connections from a secure port port. You can use stunnel with a configuration like this:
[websockets]
accept = 8443
connect = 8080
This way, stunnel does all the security, and your websocket server does all the websockets. Win!
Related
I'm having deep trouble with implementing a PHP server websocket. It works completely fine without SSL.
However, when I switch client to "wss://" and add SSL context to the server socket, I get random binary garbage after client connects. I searched everything on the web but cannot figure it out.
Client code does basically nothing:
var ws = new WebSocket('ws://127.0.0.1:8888');
ws.onopen = function() {
console.log('ws open');
}
Here is the minimal PHP code for SSL-less connection which works fine:
<?php
$socket = stream_socket_server(
'tcp://127.0.0.1:8888',
$errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN
);
$cn = stream_socket_accept($socket, 99999, $name);
$text = fread($cn, 1024);
echo $text;
?>
With it, and client using "ws://", I receive normal WebSocket upgrade request which I can process. Everything is fine.
Now, I change client call to "wss://" and add SSL to server:
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'local_cert', '../ssl/cert.pem');
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);
$socket = stream_socket_server(
'tcp://127.0.0.1:8888',
$errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
$context
);
and this time I receive some binary encoded data which I'm unable to process:
▬♥☺☻☺☺♥♥{8T▬↕↑/Б{Ҭh#_W l0w2♣9x(j‹~Y{A∟MTT
::‼☺‼☻‼♥+/,0̨̩‼¶/5☺☺↨☺☺
↕►♦♦♦☺♣♣♣♠♠☺↕3+)**☺↔ JtD~[}▼$▲ɒNt◄vCyw-☻☺☺+♂
**♥♦♥♥♥☻♥☺♥☻☻ ☺§
I also tried all kinds of keys and certificates for SSL. I used Apache and Nginx certs, I created them manually with OpenSSL, I created them with PHP code that I found on web, nothing helps. I reproduced this on Windows and Ubuntu systems with PHP7.4 and Apache and Nginx web servers, used localhost and external IP addresses, does not differ.
In the context of the IHE Connectathon, I want to make a raw socket server responsing to the ATNA profile, which requires TLS sockets with both ends certificates.
My issue if summed up in this message :
https://groups.google.com/d/msg/eu_connectathon/O-VGI_3cltw/ARsElA65ZkkJ
Edit: Sorry, the Google Groups isn't public, here is the message :
Hi Florian,
What exactly does the error message "Server asked for a certificate,
but list of issuers doesn't contain valid certificate authority."
mean and did the implementation of the TLS Tools Client change during
the last years, or am I using the wrong certificates?
The message means that the server has sent the CertificateRequest
message to the clienmt with no values in the certificate_authorities
field.
I ran into this problem last year and discussed this with a developer
of the TLS Tools. He claimed that if a server didn't include this
field, the client wouldn't have a clue what kind of certificate to
return, assuming a scenario where you would connect top multiple
affinity domains, each with their own CA.
It appears that you can instruct OpenSSL to return this value by
calling SSL_CTX_set_client_CA_list, e.g. in
DcmTLSTransportLayer::addTrustedCertificateFile. I haven't tested this
with the TLS tools yet, but I hope to accomplish that before the
connectathon starts.
But my implementation, in PHP, is not the same as their's.
It looks like PHP is missing the "SSL CTX set client CA list" possibility, to tell the client which certificate authority it should use.
$context = stream_context_create();
if ($certificate) {
// Server certificate + private key
stream_context_set_option($context, 'ssl', 'local_cert', "/path/to/server.pem");
stream_context_set_option($context, 'ssl', 'passphrase', $passphrase);
// Client public certificates
stream_context_set_option($context, 'ssl', 'cafile', "/path/to/ca.pem");
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', 'peer_name', "TlsTools2");
stream_context_set_option($context, 'ssl', 'capture_peer_cert', true);
stream_context_set_option($context, 'ssl', 'capture_peer_cert_chain', true);
}
$this->__socket = #stream_socket_server("tcp://$address:$port", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
The IHE Gazelle TLS client tells me "Server asked for a certificate, but list of issuers doesn't contain valid certificate authority."
The messages between client and server pass, but the test is not OK, as "not enough secure", as tells the message.
Do you see a problem, and does PHP have more options I didn't see ?
Thanks for your help.
Edit: As #rdlowrey suggested me, I just created a bug report : https://bugs.php.net/bug.php?id=69215
As I mentioned in the initial comment:
PHP's stream server implementation has never actually used SSL_CTX_set_client_CA_list() for encrypted streams.
This has been corrected upstream as referenced in the associated bug report:
https://bugs.php.net/bug.php?id=69215
This change will be reflected once the PHP 5.6.8 binary is released (or you can build PHP manually against the current source prior to that release).
Implementation
Using an updated binary the OP's example code will work as expected without modification. A simple example of a crypto context to use in servers:
<?php
$serverCtx = stream_context_create(['ssl' => [
'local_cert' => '/path/to/my-server-cert.pem',
'passphrase' => 'elephpant',
'cafile' => '/path/to/my-ca-certs.pem',
'verify_peer' => true
]]);
In the above example PHP automatically uses the names from certs found in the my-ca-certs.pem file referenced above when sending the client CA list as part of the TLS handshake.
Notes
When enabling peer verification in encrypted server streams via "verify_peer" => true PHP will not automatically enable peer name verification. Unless you only wish to allow a single certificate holder (with a specific known name) access to your server this is exactly what you want. This default behavior supports the more common use-case of allowing any client whose certificate was signed by a trusted CA to establish connections to your server. However, if you wish to enforce name verification in an encrypted server modify the above context example as shown here:
<?php
$serverCtx = stream_context_create(['ssl' => [
'local_cert' => '/path/to/my-server-cert.pem',
'passphrase' => 'elephpant',
'cafile' => '/path/to/my-ca-certs.pem',
'verify_peer' => true,
'verify_peer_name' => true, // verify the name on the cert
'peer_name' => 'zanzibar' // ensure the cert's name matches this
]]);
$certFile = 'ful_path/yourcert.pem';
$context = stream_context_create();
$result = stream_context_set_option($context, 'ssl', 'local_cert',
$certFile);
$result = stream_context_set_option($context, 'ssl', 'verify_peer',
false);
$result = stream_context_set_option($context, 'ssl', 'verify_host',
false);
$result = stream_context_set_option($context, 'ssl',
'allow_self_signed', true);
$sock = stream_socket_client('tls://www.somewhere.com:9999',
$errno,$errstr, 30, STREAM_CLIENT_CONNECT, $context);
have added: www.somewhere.com ca to server certs
have removed &$
have checked yourcert.pem expiry
short of migrating the entire code to curl, i am stuck. it has to be something so obvious as the nose on my face... but what...
First, let me say this:
PHP5.6 defaults to using CA certificate stores managed by your operating system. Unless you're connecting to a DNS name that exposes a self-signed certificate or something you probably don't need an SSL context at all.
Before you do anything else, try connecting without any ssl context. If the remote site's certificate is valid and was signed by any standard certificate authority it should "just work" automagically in PHP 5.6.
That said ... there are several very questionable things in your code snippet. It's really impossible to know which is the real problem without knowing more about what you're trying to do, so I'll just iterate over all of it.
$result = stream_context_set_option($context, 'ssl', 'local_cert',
$certFile);
^ Are you connecting to a site that requires YOU to provide a certificate that the remote server verifies? If not (and this is almost certainly the case), you should not be specifying the "local_cert" option.
$result = stream_context_set_option($context, 'ssl', 'verify_peer',
false);
^ This is a terrible idea as it exposes you to Man-in-the-Middle attacks. You should never do this unless you're testing something in a one-off scenario. DO NOT DO THIS.
$result = stream_context_set_option($context, 'ssl', 'verify_host',
false);
^ This is not even a thing. There's no "verify_host" option in PHP.
$result = stream_context_set_option($context, 'ssl',
'allow_self_signed', true);
So ... in summary there's no good way to answer this with the information you've provided. But I've pointed out the obvious issues ...
I'm having issues reopening a TCP socket connection after its remotely closed. Specifically this is for Apple Push Notification service - when Apple encounters a device token from you that pairs with a device that deleted your application they close the secure TCP socket to their push gateway. I need to immediately reopen this connection (or start a new one) when this issue occurs so I can continue sending notification to other users who still do have the app installed. I have some code using which I try to sort this out but it's freezing my entire Apache server and I have to restart manually to get things running again and I have no idea why. Below is the code with which I attempt to reopen a socket with Apple:
if($error_response == "InvalidToken"){
$results .= "\ntried to restart";
fclose($fp);
usleep(20000);
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck_real.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', '****');
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
}
I have to send push notification to iOS devices. My connection has to be enabled through a proxy. I tried everything but without success. I have an error 110 Connection Timed Out. It's working with cURL if I just try to connect to Apple push's address. I don't know where the problem is. Proxy config ? PHP stream_context wrong implementation ?
Here's my code :
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'certificate.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', 'my_passphrase');
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
stream_context_set_option($ctx, 'http', 'proxy', 'tcp://my-proxy.net:8080');
stream_context_set_option($ctx, 'http', 'request_fulluri', true);
$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err,$errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
var_dump($fp);
var_dump($err);
var_dump($errstr);
exit;
Do you have an idea ?
EDIT:
Can it be directly linked to Squid ? I just figured out the proxy is running with Squid.
I also try with fopen() function instead of stream_socket_client() but it seems it doesn't allow ssl protocol.
Here's my var_dump outputs : bool(false) int(110) string(20) "Connection timed out"
I also have this warning : Warning: stream_socket_client(): unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Connection timed out) in /share/www/website/test.php on line 22
It could simply be your proxy does not allow port 2195 to be opened.
iPhone Push Notification Unable to Connect to the SSL Server
I guess you either:
Need to talk to the people who run the proxy to see if port 2195 is open or not.
or
Setup a test server listening on port 2195 and then try to do a test connection to it through the proxy. That should allow you to test if it is the proxy that is blocking the connection requests.
or
Test whether Curl can open the connection using a proxy.
Which is done by setting the options:
// sets the proxy to go through
curl_setopt($ch, CURLOPT_PROXY, $proxy);
// sets to use a tunnel proxy which most http proxies are
curl_setopt($ch, CURLOPT_HTTPTUNNELPROXY, $proxy);
Full testing code here.
create the SSL context
open a tcp socket to the proxy
send request to the proxy to connect to APNs
once connexion is accepted enable SSL
Have a look at my reply in this post: Send Push Notification to APNS through proxy