Skip to content

Commit

Permalink
test: test TLS client authentication
Browse files Browse the repository at this point in the history
TLS client authentication should be tested, including failure scenarios.

PR-URL: nodejs#24733
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
  • Loading branch information
sam-github authored and refack committed Jan 10, 2019
1 parent c83cf24 commit 8a486db
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 3 deletions.
2 changes: 2 additions & 0 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,8 @@ changes:
certificate can match or chain to.
For self-signed certificates, the certificate is its own CA, and must be
provided.
For PEM encoded certificates, supported types are "X509 CERTIFICATE", and
"CERTIFICATE".
* `cert` {string|string[]|Buffer|Buffer[]} Cert chains in PEM format. One cert
chain should be provided per private key. Each cert chain should consist of
the PEM formatted certificate for a provided private `key`, followed by the
Expand Down
9 changes: 6 additions & 3 deletions test/fixtures/tls-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ const keys = exports.keys = {
agent5: load('agent5', 'ca2'),
agent6: load('agent6', 'ca1'),
agent7: load('agent7', 'fake-cnnic-root'),
agent10: load('agent10', 'ca2'),
ec10: load('ec10', 'ca5'),
ec: load('ec', 'ec'),
};

function load(cert, issuer) {
issuer = issuer || cert; // Assume self-signed if no issuer
// root is the self-signed root of the trust chain, not an intermediate ca.
function load(cert, root) {
root = root || cert; // Assume self-signed if no issuer
const id = {
key: fixtures.readKey(cert + '-key.pem', 'binary'),
cert: fixtures.readKey(cert + '-cert.pem', 'binary'),
ca: fixtures.readKey(issuer + '-cert.pem', 'binary'),
ca: fixtures.readKey(root + '-cert.pem', 'binary'),
};
return id;
}
Expand Down
331 changes: 331 additions & 0 deletions test/parallel/test-tls-client-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
'use strict';

require('../common');
const fixtures = require('../common/fixtures');

const {
assert, connect, keys
} = require(fixtures.path('tls-connect'));

// Use ec10 and agent10, they are the only identities with intermediate CAs.
const client = keys.ec10;
const server = keys.agent10;

// The certificates aren't for "localhost", so override the identity check.
function checkServerIdentity(hostname, cert) {
assert.strictEqual(hostname, 'localhost');
assert.strictEqual(cert.subject.CN, 'agent10.example.com');
}

// Split out the single end-entity cert and the subordinate CA for later use.
split(client.cert, client);
split(server.cert, server);

function split(file, into) {
const certs = /([^]*END CERTIFICATE-----\r?\n)(-----BEGIN[^]*)/.exec(file);
assert.strictEqual(certs.length, 3);
into.single = certs[1];
into.subca = certs[2];
}

// Typical setup, nothing special, complete cert chains sent to peer.
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

// As above, but without requesting client's cert.
connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

// Request cert from client that doesn't have one.
connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
});

// Typical configuration error, incomplete cert chains sent, we have to know the
// peer's subordinate CAs in order to verify the peer.
connect({
client: {
key: client.key,
cert: client.single,
ca: [server.ca, server.subca],
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: [client.ca, client.subca],
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

// Like above, but provide root CA and subordinate CA as multi-PEM.
connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca + '\n' + server.subca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: client.ca + '\n' + client.subca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

// Like above, but provide multi-PEM in an array.
connect({
client: {
key: client.key,
cert: client.single,
ca: [server.ca + '\n' + server.subca],
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: [client.ca + '\n' + client.subca],
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

// Fail to complete server's chain
connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
return cleanup();
});

// Fail to complete client's chain.
connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.ifError(pair.client.error);
assert.ifError(pair.server.error);
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
});

// Fail to find CA for server.
connect({
client: {
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY');
return cleanup();
});

// Server sent their CA, but CA cannot be trusted if it is not locally known.
connect({
client: {
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert + '\n' + server.ca,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'SELF_SIGNED_CERT_IN_CHAIN');
return cleanup();
});

// Server sent their CA, wrongly, but its OK since we know the CA locally.
connect({
client: {
checkServerIdentity,
ca: server.ca,
},
server: {
key: server.key,
cert: server.cert + '\n' + server.ca,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

// Fail to complete client's chain.
connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
});

// Fail to find CA for client.
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
});

// Confirm lack of support for "BEGIN TRUSTED CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca.replace(/CERTIFICATE/g, 'TRUSTED CERTIFICATE'),
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY');
return cleanup();
});

// Confirm lack of support for "BEGIN TRUSTED CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca.replace(/CERTIFICATE/g, 'TRUSTED CERTIFICATE'),
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
});

// Confirm support for "BEGIN X509 CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca.replace(/CERTIFICATE/g, 'X509 CERTIFICATE'),
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

// Confirm support for "BEGIN X509 CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca.replace(/CERTIFICATE/g, 'X509 CERTIFICATE'),
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
return cleanup();
});

0 comments on commit 8a486db

Please sign in to comment.