6

This test program connects to an https server and gets some content. I've checked my server in browsers and with curl and the certificate is working correctly. If I run curl to grab data from the server it correctly complains about the certificate being unknown unless I pass it in with --cacert or turn security off with -k.

So the problem I am having is that although I think my client should be doing certificate authentication and I am telling it where the public certificate is, it just always works. If I remove the ca: option so it has no idea what the certificate is from the server then it silently works. I would like to catch the authentication error but I can't seem to do so.

var https = require('https');
var fs = require('fs');

function main() {

      var data = '';

      var get = https.get({
        path: '/',
        host: 'localhost',
        port: 8000,
        agent: false,
        ca: [ fs.readFileSync('https_simple/cacert.pem') ]

      }, function(x) {

        x.setEncoding('utf8');
        x.on('data', function(c) {data += c});
        x.on('error', function(e) {
          throw e;
        });
        x.on('end', function() {
          console.log('Hai!. Here is the response:');
          console.log(data);
        });

      });

      get.on('error', function(e) {throw e});

      get.end();

    }

main();
justinhj
  • 11,147
  • 11
  • 58
  • 104

4 Answers4

10

In order to make this work I needed to upgrade to v0.7.8 (although any v0.7 should be fine) where the rejectUnauthorized functionality has been added to https.get

This combination of options is needed:

agent: false, // or you can supply your own agent, but if you don't you must set to false
rejectUnauthorized: true, 
ca: [ fs.readFileSync('https_simple/cacert.pem') ]

Now if the authentication fails you will get an 'error' event and the request will not go ahead.

See the https.request documentation for details on making your own Agent

The bug fix was committed in this change: https://github.com/joyent/node/commit/f8c335d0

justinhj
  • 11,147
  • 11
  • 58
  • 104
  • 1
    rejectUnauthorized was added in version 0.7.0 – Old Pro Apr 20 '12 at 05:01
  • 2
    Looking at the docs: http://nodejs.org/api/tls.html, there's no indication that `rejectUnauthorized` was added for client ... for server yes but not client ... can you please set me on the right path here? Am I looking at out of date docs here? – pulkitsinghal Jun 08 '12 at 03:30
  • The change list is in the answer above. The issue was tracked here https://github.com/joyent/node/issues/2247 I've not looked at whether the documentation was updated though, sorry. – justinhj Dec 12 '12 at 21:10
5

As per the documentation for https.request, the ca option of both https.get and https.request is an option from tls.connect. The documentation for the options to the tls.connect module function states:

ca: An array of strings or Buffers of trusted certificates. If this is omitted several well known "root" CAs will be used, like VeriSign. These are used to authorize connections.

Digging into the node.js source, the root certs used can be found here: https://github.com/joyent/node/blob/master/src/node_root_certs.h

So in short, with no authority cert provided as an option to https.get the tls module will attempt to authenticate the connection using the list of root certs anyway.

Pero P.
  • 25,813
  • 9
  • 61
  • 85
  • 1
    That's good info thanks. It doesn't help me here, because I am using my own self signed certificate. I would expect the connection to only work if I pass my certificate in, but it works either way. – justinhj Apr 17 '12 at 23:52
  • @Перо is there some way to combine the server cert and not lose the root CAs? Ex: `ca: [ fs.readFileSync('myServerCert.pem'), node_root_certs.h]` What would be the syntax to access the constant in the header file? – pulkitsinghal Jun 08 '12 at 02:58
2

I do this in npm, using the request module. It goes like this:

var cacert = ... // in npm, this is a config setting
var request = require("request")
request.get({ url: "https://...",
              ca: cacert,
              strictSSL: true })
  .on("response", function (resp) { ... })
  .on("error", function (er) { ... })

The error event will be raised if the ssl isn't valid.

isaacs
  • 16,656
  • 6
  • 41
  • 31
1

In V 0.6.15 you need to explicitly check whether or not the certificate validation passed or failed.

if (x.connection.authorized === false) {    
   console.log('SSL Authentication failed');
} else if (x.connection.authorized === true) {    
   console.log('SSL Authentication succeeded');
} 
Old Pro
  • 24,624
  • 7
  • 58
  • 106
  • That seems promising but x.authorized is undefined – justinhj Apr 19 '12 at 17:49
  • Presumably, this is meant to be placed in the callback function. Unfortunately, this doesn't prevent the request to be made when the certificate isn't trusted, it just tells you once it's done (you might have leaked authentication credentials by then, for example). – Bruno Jun 17 '12 at 21:02
  • @Bruno, this is simply a limitation of version 0.6, to be expected with versions < 1.0. Fixed in version 0.7. – Old Pro Jun 17 '12 at 21:43
  • @ZzZombo the `if-else` is not redundant. It would be better to supply an `else` clause to the second `if`, but I don't know what the OP would want to do in that situation. You probably want to brush up on [the difference between `==` and `===`](http://stackoverflow.com/a/523647/712765). – Old Pro Apr 13 '17 at 18:40