Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

SSL Client Certificates Mandatory/Flakey #1494

Closed
gasteve opened this issue Aug 11, 2011 · 10 comments
Closed

SSL Client Certificates Mandatory/Flakey #1494

gasteve opened this issue Aug 11, 2011 · 10 comments

Comments

@gasteve
Copy link

gasteve commented Aug 11, 2011

When I set requestCert to true and rejectUnauthorized to false, Safari reports that the server requires a client SSL certificate. And, Chrome appears to behave poorly (it doesn't require the user to provide a certificate, but for some connections it reports "Error 107 (net::ERR_SSL_PROTOCOL_ERROR): SSL protocol error."). If I don't set requestCert to true, then even if I connect with a client where a cert has been used, the cert isn't present on the server side (getPeerCertificate() returns an empty object). I'm not sure if the certificate is inaccessible because the session isn't being signed with the certificate, or if it is, but node is just ignoring it and not exposing it in JavaScript.

The behavior I am seeking is to allow clients without a certificate to connect and use a website, while at the same time providing API access to clients that authenticate using a client certificate. As a workaround, I'll run a second https server on a separate port for API access, but this is not ideal and it seems there should be a way to make this work.

@bnoordhuis
Copy link
Member

Can you post a standalone (pure node, no third-party libs) test case that showcases that behaviour? You can find test certs in test/fixtures.

@gasteve
Copy link
Author

gasteve commented Aug 11, 2011

Below is test code to illustrate the problem (or at least what I think is a problem). On the client side, just use whatever key and cert you have handy...on the server, I used a test key and cert from the node distribution. If requestCert is set to true, then it appears browsers will always ask the user for a client cert (that seems like the correct behavior). But, if requestCert is set to false, the browser doesn't request a certificate from the user, but getPeerCertificate() will return an empty object even if the client provided a certificate (as this example client does). Now, I think I've found another issue in the latest master...if I run the client on the latest master, geetPeerCertificate() on the server side returns an empty object even if requestCert is set to true. If I run the client with node 0.4.9 however, getPeerCertificate() yields the client certificate info (server was running the latest master in both cases).

It seems to me like the correct behavior should be that getPeerCertificate() should return a peer certificate whenever one is provided regardless of whether requestCert was set to true or false.

The server side:

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

var options = {
  key: fs.readFileSync('../test/fixtures/keys/agent2-key.pem'),
  cert: fs.readFileSync('../test/fixtures/keys/agent2-cert.pem'),
  requestCert: true,
  rejectUnauthorized: false,
};

https.createServer(options, function (req, res) {
  console.log(req.connection.getPeerCertificate());
  res.writeHead(200);
  res.end("hello world\n");
}).listen(8000);

The client side:

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

var key = fs.readFileSync('./client.key');
var cert = fs.readFileSync('./client.cert');

var options = {
    host: '127.0.0.1',
    port: 8000,
    path: '/',
    key: key,
    cert: cert,
};

var req = https.request(options, function(res) {
  console.log("statusCode: ", res.statusCode);
  console.log("headers: ", res.headers);

  res.on('data', function(d) {
    process.stdout.write(d);
  });
});
req.end();

req.on('error', function(e) {
  console.error(e);
});

@koichik
Copy link

koichik commented Aug 12, 2011

But, if requestCert is set to false, the browser doesn't request a certificate from the user, but getPeerCertificate() will return an empty object even if the client provided a certificate (as this example client does).

I am no expert on SSL/TLS, but I think it is correct behavior.
Because the server does not send CertificateRequest message to the client if requestCert: false, then the client does not send its certificate.
Please refer to SSL/TLS handshake protocol for more information.

Now, I think I've found another issue in the latest master...if I run the client on the latest master, geetPeerCertificate() on the server side returns an empty object even if requestCert is set to true.

Good catch! This is the problem of http2.
If you use --use-http1 on the client, getPeerCertificate() returns certificate object.
cc: @mikeal

@koichik
Copy link

koichik commented Aug 12, 2011

This is the problem of http2.

No, I was wrong.
In http2, default (global) Agent does not use client certification.
We should set options.agent to custome Agent explicitly.

var options = {
    host: '127.0.0.1',
    port: 8000,
    path: '/',
    key: key,
    cert: cert,
    agent: new https.Agent(options)
};

It is not bad because it cannot share connections with other HTTPS request which does not use client certification.
But we need document it...

@plexel
Copy link

plexel commented Aug 12, 2011

@gasteve "Error 107 (net::ERR_SSL_PROTOCOL_ERROR): SSL protocol error."

This looks like the error I was getting in chrome when doing the tests with node on XP as reported at #1492

It seems that node does not respond to SSL type client hello's, as listed in wireshark network protocol analyser for Windows. The client hello must be type SSLv2 or TLSv1.0 for node to respond. Chrome 12 gives up after two tries and issues the PROTOCOL error message. There is a similar problem with Firefox (tested on FF5) where it tries many times with SSL type client hello's and then issues a "The connection was reset" error. IE8 is ok because after issuing an SSL type it then issues an SSLv2 type and node reponds. Opera 11.5 is ok too.

The problem only happens if a 'requestCert: true' is included in the options object. If not included then I believe no SSL type client hello's are issued by browsers and all is well with node. All of this reported here is just what I saw in the wireshark analyser.

@gasteve
Copy link
Author

gasteve commented Aug 12, 2011

So, how would I alter the client code listed here such that it works in 0.4.9, 0.4.10 and the latest master? I don't really understand the following code...seems like at the point when the new Agent is instantiated, options would be undefined:

var options = {
    host: '127.0.0.1',
    port: 8000,
    path: '/',
    key: key,
    cert: cert,
    agent: new https.Agent(options)
};

@koichik
Copy link

koichik commented Aug 12, 2011

Ooooops, correction:

var options = {
    host: '127.0.0.1',
    port: 8000,
    path: '/',
    key: key,
    cert: cert
};
options.agent = new https.Agent(options);

It works both v0.4.10 and v0.5.4 (both http1 and http2).

@plexel
Copy link

plexel commented Aug 12, 2011

@gasteve Chrome appears to behave poorly ... for some connections it reports "Error 107 (net::ERR_SSL_PROTOCOL_ERROR): SSL protocol error.").

I think you'll find that it is node that is behaving poorly not Chrome. Chrome reports that error because node does not handshake the client hello properly if requestCert is true, as per my above comment.

@mikeal
Copy link

mikeal commented Aug 13, 2011

we should get this documented properly once everyone is happy with a solution.

@plexel
Copy link

plexel commented Aug 29, 2011

Regarding Chrome "Error 107 (net::ERR_SSL_PROTOCOL_ERROR): SSL protocol error)" reported above. This has now been identified as a node crypto problem and there is a workaround by koichik listed at:-

#1516

The workaround is awaiting approval for inclusion in node proper.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants