Using self signed CA certificate for WebSocket (ws) in Node JS
I need to connect to a separate WebSocket server using a ws client in Node JS .
Since I have a "Self-Signed Root CA" installed in my computer's "Trusted Root Certification Authorities" store, I can connect using the sample program in Chrome.
I know Node JS uses a hardcoded list of root CAs (stupid), but I was hoping there would be some way to import my own root CAs.
I tried:
export NODE_EXTRA_CA_CERTS=C:\\Users\\IT1\\Documents\\security\\rootCA.pem
- Using the ssl-root-cas library suggested in many forum posts , but I don't think it might work here because it doesn't change the https of the ws client I'm using. (I think, I'm spitting now)
- Use the ca, cert and key options available to the WebSocket constructor.
// Using just ca
var test = new WebSocket(uri, {
ca: fs.readFileSync("C:\\Users\\IT1\\Documents\\security\\rootCA.pem")
});
// Using cert and key
var test = new WebSocket(uri, {
cert: fs.readFileSync("C:\\Users\\IT1\\Documents\\security\\rootCA.crt"),
key: fs.readFileSync("C:\\Users\\IT1\\Documents\\security\\rootCA.key")
});
// Using ca, cert and key
var test = new WebSocket(uri, {
ca: fs.readFileSync("C:\\Users\\IT1\\Documents\\security\\rootCA.pem"),
cert: fs.readFileSync("C:\\Users\\IT1\\Documents\\security\\rootCA.crt"),
key: fs.readFileSync("C:\\Users\\IT1\\Documents\\security\\rootCA.key")
});
And no matter what, I always get the following error message:
events.js:200
throw er; // Unhandled 'error' event
^
Error: unable to verify the first certificate
at TLSSocket.onConnectSecure (_tls_wrap.js:1321:34)
at TLSSocket.emit (events.js:223:5)
at TLSSocket._finishInit (_tls_wrap.js:794:8)
at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:608:12)
Emitted 'error' event on WebSocket instance at:
at ClientRequest.<anonymous> (C:\Users\IT1\source\repos\WebSocketTest\WebSocketTest\node_modules\ws\lib\websocket.js:554:15)
at ClientRequest.emit (events.js:223:5)
at TLSSocket.socketErrorListener (_http_client.js:406:9)
at TLSSocket.emit (events.js:223:5)
at emitErrorNT (internal/streams/destroy.js:92:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
at processTicksAndRejections (internal/process/task_queues.js:81:21) {
code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
}
Also, I can't use rejectUnauthorized: true
. I need to authorize it, so please don't suggest this.
Please help, I really want to catch this.
After struggling for almost a day or two, I decided to validate the certificate in OpenSSL. It turns out that the certificate uses a different encryption algorithm (SHA 256 vs DES3 or similar). The reason it works fine in the browser is because I have previously installed other certificates for the domain.
The moral of this story:
- Before trying to use SSL certificates in your code, be sure to test them with OpenSSL. OpenSSL will always give a lot more information about why your certificate is not working than NodeJS, Java or any runtime environment.
- Before testing root certificates, delete all certificates for this domain from the trusted root store.
- I followed tutorials online about generating a self-signed root CA in Windows, and the tutorials I found didn't give much information about what I was doing, more "this is how you do it". This is where I am going wrong as I am following some tutorials and combining information to find a solution. This means that I used one tutorial to generate the root certificate and private key, and then used another tutorial to sign other certificates (specifying a different algorithm, but not saying that the algorithm must match the root CA and signing certificate).
- Before diving into making a self-signed root CA, it helps to have a decent understanding of how SSL and root certificates work. Not from a technical point of view, but from the point of view of why there are root CAs, how to use them in the real world and how to apply them to your application as a developer. I found this article very helpful.