summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/tls.md2
-rw-r--r--test/fixtures/tls-connect.js9
-rw-r--r--test/parallel/test-tls-client-auth.js331
3 files changed, 339 insertions, 3 deletions
diff --git a/doc/api/tls.md b/doc/api/tls.md
index bd098b98d4..44ad2bdf2d 100644
--- a/doc/api/tls.md
+++ b/doc/api/tls.md
@@ -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
diff --git a/test/fixtures/tls-connect.js b/test/fixtures/tls-connect.js
index 303061adc3..764bea7703 100644
--- a/test/fixtures/tls-connect.js
+++ b/test/fixtures/tls-connect.js
@@ -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;
}
diff --git a/test/parallel/test-tls-client-auth.js b/test/parallel/test-tls-client-auth.js
new file mode 100644
index 0000000000..1701981692
--- /dev/null
+++ b/test/parallel/test-tls-client-auth.js
@@ -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();
+});