Can't establish a connection with javascript to a secure websocket server

Viewed 1159

My development environment is this:

  • OS: Microsoft Windows 10
  • PHP framework: Laravel 8.0
  • PHP version 7.4
  • Websocket server: cboden/ratchet 0.4.3
  • WAMP server 3.2.0 (Apache 2.4.41)
  • Firefox 91.0.1 (64-bit) / chrome

I created a new Laravel app to implement a Secure Websocket Server and get connected to it using plain javascript on the client side (Laravel blade file). The websocket server works fine, as far as I can see it running, but the web browser is not able to connect, as seen on this image:

enter image description here

I have tried using different URLs, with and without port number, but to no avail. I created a SSL certificate and private key files, using openssl.exe tool, and put them in the command folder for testing purposes.

This is my handle code for the Secure Websocket Server:

   public function handle()
   {
      $loop   = Factory::create();
      $webSock = new SecureServer(
         new Server('0.0.0.0:8090', $loop),
         $loop,
         array(
            'local_cert'        => 'certificate.crt', 
            'local_pk'          => 'private.key', 
            'allow_self_signed' => TRUE, 
            'verify_peer' => FALSE
         )
      );

      // Ratchet magic
      $webServer = new IoServer(
         new HttpServer(
            new WsServer(
               new WebSocketController()
            )
         ),
         $webSock
      );

      $loop->run();
   }

My virtual host in httpd-ssl.conf file:

<VirtualHost *:443>
  ServerName ssa
  DocumentRoot "d:/web/app/ssa/public"
  SSLEngine on
  
  SSLCertificateFile "${SRVROOT}/conf/certificate.crt"
  SSLCertificateKeyFile "${SRVROOT}/conf/private.key"

  SSLVerifyClient none
  SSLVerifyDepth 10

  <Directory "d:/web/app/ssa/public">
    Options +Indexes +Includes +FollowSymLinks +MultiViews
    AllowOverride All
    Require local
  </Directory>
  
  ProxyRequests Off 
  ProxyPass /wss/  ws://ssa:8090
</VirtualHost>

The Apache modules proxy_module, proxy_http_module and proxy_wstunnel_module are loaded. The web app is running in HTTPS. Before, it was running over HTTP and WS and everything worked perfectly, but I need to secure this app and I am having issues to connect to the secure websocket server.

Am I missing something?

Is there something wrong with my Websocket server or Apache configuration?

3 Answers

You are surely trying to connect to the wrong destination. It says wss:///ssa/wss/, but probably it should be wss://your.site.domain/ssa/wss/ .

So let's look at front end code and find out what's wrong with it.

Ok, as @apokryfos pointed out, I tried to proxy the websocket server through HTTPS but I was doing it in the wrong way.

I changed my websocket server to a non-secure one and did the following change to my virtual host:

<VirtualHost *:443>
  ServerName ssa
  DocumentRoot "d:/web/app/ssa/public"
  SSLEngine on
  
  SSLCertificateFile "${SRVROOT}/conf/certificate.crt"
  SSLCertificateKeyFile "${SRVROOT}/conf/private.key"

  SSLVerifyClient none
  SSLVerifyDepth 10

  <Directory "d:/web/app/ssa/public">
    Options +Indexes +Includes +FollowSymLinks +MultiViews
    AllowOverride All
    Require local
  </Directory>
  
  Redirect /wss /wss/
  ProxyPass /wss/ ws://127.0.0.1:8090/
  ProxyPassReverse /ws/ wss://127.0.0.1:8090/
</VirtualHost>

On the client side, the browser can now contact the backend WS server through the HTTPS port:

// The connection to the WebSocket Server.
var socket = new WebSocket("wss://ssa:443/wss/");

I got this solution from Apache Config: Websockets Proxy WSS request to WS backend

Now I got my non-secure Websocket server sending/receiving through HTTPS. This is, for sure, not the solution I expected to apply to my needs but it certainly works. I still hope to find a formal solution to connecting plain JavaScript client to a Secure Websocket Server (wss://) without using a proxy mechanism.

For not to complicate my first answer with more information, here I provide the answer that really worked for me after all.

I created the Secure Websocket Server as follows:

public function handle() {
      $loop = Factory::create();
      $webSock = new SecureServer(
         new Server('0.0.0.0:8443', $loop),
         $loop,
         array(
            'local_cert' => 'C:/wamp64/bin/apache/apache2.4.41/conf/server.crt',
            'local_pk' => 'C:/wamp64/bin/apache/apache2.4.41/conf/server.key', 
            'allow_self_signed' => TRUE, 
            'verify_peer' => FALSE
         )
      );
      $webServer = new IoServer(
         new HttpServer(
            new WsServer(
               new WebSocketController()
            )
         ),
         $webSock
      );
      $loop->run();
}

Note I changed the port number to 8443 (I don't think this has something to do) and also changed the certificate and key files for the new ones, generated as follows:

openssl req -config config.conf -new -x509 -out server.crt -days 3650

And the config.conf file is:

[req]
default_bits = 2048
encrypt_key = no
default_md = sha256
default_keyfile = server.key
distinguished_name = req_distinguished_name
prompt = no

[req_distinguished_name]
C = KH
ST = Siem Reap
L = SR
O = AHC
OU = IT
CN = localhost

[bs_section]
CA=false

All the difference lies in the last line CA=false to indicate I did not signed or acted as a Certificate Authority (CA). This gets rid of the MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY message.

Then, I got rid of the lines that defined the proxy in my httpd-ssl.conf file:

<VirtualHost *:443>
  ServerName ssa
  DocumentRoot "d:/web/app/ssa/public"
  SSLEngine on
  
  SSLCertificateFile "${SRVROOT}/conf/server.crt"
  SSLCertificateKeyFile "${SRVROOT}/conf/server.key"

  SSLVerifyClient none
  SSLVerifyDepth 10

  <Directory "d:/web/app/ssa/public">
    Options +Indexes +Includes +FollowSymLinks +MultiViews
    AllowOverride All
    Require local
  </Directory>
  
  #Redirect /wss /wss/
  #ProxyPass /wss/ ws://127.0.0.1:8090/
  #ProxyPassReverse /ws/ wss://127.0.0.1:8090/
</VirtualHost>

Please notice that for this virtual host I used the same certificate and key files I used for the Secure Websocket Server.

Ok, that was it for my certificate issue.

Now everything works as expected.

Related