WebSocket-Node icon indicating copy to clipboard operation
WebSocket-Node copied to clipboard

WebSocketClient does not respect injecting CAs

Open aeisenberg opened this issue 9 years ago • 3 comments

I have a custom CA that I need to use in order to access a websocket through wss. I would expect that something like this would connect to the websocket:

require('ssl-root-cas/latest').inject(). addFile(__dirname + '/my-awesome-ca.pem');
let WebsocketClient = require('websocket').client;
let client = new WebsocketClient({ });
client.connect(`wss://${url}`);

However, when I run this code, I get the following error:

{ Error: unable to verify the first certificate
    at Error (native)
    at TLSSocket.<anonymous> (_tls_wrap.js:1060:38)
    at emitNone (events.js:86:13)
    at TLSSocket.emit (events.js:185:7)
    at TLSSocket._finishInit (_tls_wrap.js:584:8)
    at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:416:38) code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' }

This implies that the certificate I added is not being used.

I can verify that the certificate is being added properly since I can connect to the websocket using the ws module, like this:

require('ssl-root-cas/latest').inject(). addFile(__dirname + '/my-awesome-ca.pem');
var WebSocket = require('ws');
var ws = new WebSocket(`${url}`);
ws.on('open', function open() {
  console.log('OPENED');
});

And this works as expected.

I tried other variants to get this working, like this:

let allCas = require('ssl-root-cas/latest').inject(). addFile(__dirname + '/my-awesome-ca.pem');
let WebsocketClient = require('websocket').client;
let client = new WebsocketClient({ tlsOptions: {ca: allCas } });
client.connect(`wss://${url}`);

But the same error.

When I run this option (turning off security and not something I want to do), things work as expected:

let allCas = require('ssl-root-cas/latest').inject(). addFile(__dirname + '/my-awesome-ca.pem');
let WebsocketClient = require('websocket').client;
let client = new WebsocketClient({ tlsOptions: {rejectUnauthorized: false } });
client.connect(`wss://${url}`);

My guess is that for some reason the WebSocketClient class is not applying the global CAs when constructing the initial http request.

I can provide more information if you like.

aeisenberg avatar Aug 29 '16 16:08 aeisenberg

It looks like the problem comes from the {agent: false} option for creating the http request.

If I change the call to connect to this:

client.connect(`wss://${url}`, undefined, undefined, undefined, {agent: undefined});

I am able to connect correctly. The call above uses the default, global agent instead of constructing a new one.

I am not sure of the proper solution to this, but there are some options I can come up with:

  1. better documentation (ie- no code changes)
  2. change {agent: false} to {agent: undefined} in the WebSocket client constructor
  3. augment the WebSocket client constructor config object to toggle the kind of agent you want to use.

aeisenberg avatar Aug 29 '16 17:08 aeisenberg

I think this is expected.

"agent: false" uses a list of well known CAs. Your injected CAs are ignored. "agent: undefined" uses a global HTTPS agent which ignores the list of well known CAs. Then your injected CAs are used.

Unfortunately this is poorly documented and I inferred this behavior from HTTPS docs at https://nodejs.org/api/https.html

josuegomes avatar Aug 29 '16 21:08 josuegomes

I think this depends on your initial set of expectations. :) It may be that this behavior adheres to the original intention of the author of this module, but nowhere in the docs of this module do I see that a new agent is created for each request (did I miss it?).

But, the fact that I need to resort to such an ugly call to connect implies that this feature has not been anticipated yet.

So, at a minimum, I'd say the documentation needs to be updated, but my personal preference would be to augment the WebSocketClient config to support this as an option directly.

This is something I could do.

aeisenberg avatar Aug 29 '16 21:08 aeisenberg