From 1f4dd2be6ed93dda591dd31ed5483a9b452a8d2a Mon Sep 17 00:00:00 2001 From: Cristofer Gonzales Date: Thu, 30 Mar 2017 18:15:28 -0300 Subject: [PATCH] Adds support for certificates with empty Subject Node.js default verification does not consider the possibility of an empty subjects ([issue](https://github.com/nodejs/node/issues/11771)). In somce cases Windows Active Directory will use certificates with empty subjects, see this [article](https://blogs.technet.microsoft.com/askds/2008/09/16/third-party-application-fails-using-ldap-over-ssl/) for details, making necessary that the connector supports them to use `ldaps`. This change adds a new configuration option `SSL_ENABLE_EMPTY_SUBJECT` that enables using a patched verification for the server identity that allows using certificates with empty subject. We want to avoid modifying the default behaviour unless required so this flag defaults to `false` and can be enabled when required. --- lib/initConf.js | 1 + lib/ldap.js | 5 ++++ lib/tls.js | 14 +++++++++++ test/certs.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 lib/tls.js create mode 100644 test/certs.js diff --git a/lib/initConf.js b/lib/initConf.js index c16c1af6..d2047863 100644 --- a/lib/initConf.js +++ b/lib/initConf.js @@ -20,6 +20,7 @@ var defaults = { ALLOW_PASSWORD_CHANGE_REQUIRED: false, OVERRIDE_CONFIG: true, CACHE_FILE: __dirname + '/../cache.db', + SSL_ENABLE_EMPTY_SUBJECT: false, SSL_CA_FILE: '.+.(pem|crt|cer)$', SSL_CA_PATH: false, SSL_OPENSSLDIR_PATTERN: 'OPENSSLDIR\\s*\\:\\s*\\"([^\\"]*)\\"' diff --git a/lib/ldap.js b/lib/ldap.js index 4eecb7db..83157aaf 100644 --- a/lib/ldap.js +++ b/lib/ldap.js @@ -5,11 +5,16 @@ var client, binder; var crypto = require('./crypto'); var cb = require('cb'); var https = require('https'); +var tls = require('./tls'); function createConnection () { var tlsOptions = null; if (nconf.get('LDAP_URL').toLowerCase().substr(0, 5) === 'ldaps') { tlsOptions = { ca: https.globalAgent.options.ca }; + if (nconf.get('SSL_ENABLE_EMPTY_SUBJECT')) { + // When enabled use the connector own verification function that fixes Node.js issue described in https://github.com/nodejs/node/issues/11771 for details + tlsoptions.checkServerIdentity= tls.checkServerIdentity; + } } var connection = ldap.createClient({ diff --git a/lib/tls.js b/lib/tls.js new file mode 100644 index 00000000..812d5377 --- /dev/null +++ b/lib/tls.js @@ -0,0 +1,14 @@ +var tls = require('tls'); + +// Fixes the cert representation when subject is empty and altNames are present +// See https://github.com/nodejs/node/issues/11771 for details +function checkServerIdentity(host, cert) { + // cert subject should be empty if altnames are defined + if (cert && !cert.subject && /(IP|DNS|URL)/.test(cert.subjectaltname)) cert.subject = tls.parseCertString(""); + + return tls.checkServerIdentity(host, cert); +} + +module.exports = { + checkServerIdentity: checkServerIdentity +}; diff --git a/test/certs.js b/test/certs.js new file mode 100644 index 00000000..c6cf273d --- /dev/null +++ b/test/certs.js @@ -0,0 +1,62 @@ +const tls = require('tls'); +const tlsHelper = require('../lib/tls'); +const expect = require('chai').expect; + +describe('Connection to server with empty subject', function () { + + const PORT=19876; + const cert = { + publicKey: '-----BEGIN CERTIFICATE-----\r\nMIIDHjCCAgagAwIBAgIBATANBgkqhkiG9w0BAQUFADB4MQswCQYDVQQGEwJVUzET\r\nMBEGA1UECBMKV2FzaGluZ3RvbjERMA8GA1UEBxMIQmVsbGV2dWUxDjAMBgNVBAoT\r\nBUF1dGgwMR8wHQYDVQQLExZBRCBMREFQIENvbm5lY3RvciBUZXN0MRAwDgYDVQQD\r\nEwdUZXN0IENBMB4XDTE3MDMzMDIwNDgzOVoXDTI3MDMyODIwNDgzOVowADCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIYhS96xvPNDaq9iV5ddYrtlySr\r\nwktrWyCklH2S6gtJLen8IAwzHlg\/Cf6NbnqQIvusU5nehT0lv5jgNES85CifcBlV\r\nZyUClP48S8+xex6m6XU41PHSaDqpmzL3VdEBibqjv3Xnv+h3Np7JQnersHRMmUNZ\r\nFqzZvGg3yCgcKxpdqQRelB6AZDBQKXPxQ22ZZA2bMJlTfmXr65wkIRb5z8K4isi9\r\nyz4b1lKKkIobPiYY\/N8aki3BopfXSPnmuNOQV8pCjrB8+Ttwi3ukzE83KjtpZi+O\r\nJKqeGb0lHa1KJlTTqD12P3efDNrjKBdjjO39bMNRknlmudkMKtxDs93FlTECAwEA\r\nAaMrMCkwCwYDVR0PBAQDAgXgMBoGA1UdEQQTMBGHBH8AAAGCCWxvY2FsaG9zdDAN\r\nBgkqhkiG9w0BAQUFAAOCAQEAKIVqF\/LDMTiJxz9BoRzbpUV3\/z+T3O8IpRFNc+D5\r\n3JKevdzJRY3ShGivrafNfbS+eeYICR7\/3otnkbx0S9L81pc58R+qCHQYVTD+B\/eY\r\nYpBd\/vpiUE9\/RBSYDE\/O1FgC5ecYznugnWVl+y3wlT7g9XkkXJnZb\/SKvJwjWU6L\r\nF5CI4FwjlTbFIjWjDFUMYmfZXq\/ggj8nkQMHsy7NpqfYMZ6+QocL1V45QL+BBe9C\r\n5cdPptr9XMf3oS7Gy\/cBnjkQd0u4zJ8X2CYYkaJlwDQo+vbES\/Y0sGCwDiqTGDcH\r\ncfoOH7SdUHHKSLmUcrVoopY21mFcd3gZ178OET6UsQMOZw==\r\n-----END CERTIFICATE-----', + privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEAwhiFL3rG880Nqr2JXl11iu2XJKvCS2tbIKSUfZLqC0kt6fwg\r\nDDMeWD8J\/o1uepAi+6xTmd6FPSW\/mOA0RLzkKJ9wGVVnJQKU\/jxLz7F7HqbpdTjU\r\n8dJoOqmbMvdV0QGJuqO\/dee\/6Hc2nslCd6uwdEyZQ1kWrNm8aDfIKBwrGl2pBF6U\r\nHoBkMFApc\/FDbZlkDZswmVN+ZevrnCQhFvnPwriKyL3LPhvWUoqQihs+Jhj83xqS\r\nLcGil9dI+ea405BXykKOsHz5O3CLe6TMTzcqO2lmL44kqp4ZvSUdrUomVNOoPXY\/\r\nd58M2uMoF2OM7f1sw1GSeWa52Qwq3EOz3cWVMQIDAQABAoIBABw7rtvqMxhxomRM\r\nr7evRpLP3qVx6pBH7HiCGCtv\/GVp3qjjiNHdebOCb\/S8I+7mGoCbX4nJSX5MiGM3\r\nccLx6wpRrt+wgZFrn7qfkLOEcJFT3C+19Zu7bHfkBfRS8AO4Ao3IlegTruGkvag5\r\nRFbd\/YvdPIoEYn0AKxzJyG61MjviVONIJL7PkMk4q2nq12QjtOHp3TM1oSY6rwBE\r\nR585i3TZeQilhVOlvm+i6by47Tw8ljyCc9rsN\/sajoxjs650296MiRMElbs321Dl\r\n13DIzF9pTy654ZIjKvQgQta2LrK1kp8a4BUj03POf8oCH23ufkTmEB1hDQHapL7I\r\nC3ue05UCgYEA7SKNAm7q3e36QHPw3QZAvSpV51W9zcY7JymvMWCj\/izG0h7g6Th1\r\npI4Gu9wWEvJdZSEnEc2d5E4E6EeJnA\/OQ1cZqmbNqed\/r3xU+FfhpE7sP6rWYr5R\r\nW9TOr3U+bV+ts+iToj0Jlq40OlPFRwxNVCyDr2QSPlTqAzkk6lGGIzcCgYEA0Ylw\r\n5MYNdzMfunkUbMTE349ioninKY0xSOttSTQPw9NqRcBupcipY0a\/drNECQoPXWKu\r\nt+ZT13EmCZxBjuPqMO5a\/BexD+dhf3b4Ksui1PMhX8U4ODAdBQlBRT2GAUdQgl8a\r\ncFQSIRV0sW+svE6AsXV4Kx73V5aC0D4Zz+vXDtcCgYEAxsmvAbovw4mKvtsysGZc\r\ngPdreflDmquxzNvB1JfaAepRZbWi\/39oB2FUPcl667knF+7ZzK\/cy5WnwXyu3BfX\r\n5lWu201A3UyGmnqU1Hb\/XfkXTSwOekpm85+LAEU95vxNJkMy989JKXqxp6+v8iZa\r\n8NQ8NBykuoH+hmMyEgfzdbMCgYA0sYyba5b9T\/T9ru9M\/xrHYcabNx5Km8A2J0Zf\r\nb2E7jNIf4mmw9UprteH2VtSYNVhx0pw\/kQOqnUDEj\/AIoBZH4dktpkOXzUc+h8uW\r\n74juZooRDIa70pWpq48ne3ZUofuEHaiHcQzyFvQ2nu\/glxlUB0eGCI6JD0esWMGj\r\nARsfFwKBgBWeWFjgjotC12QZfV0ZMMw9sOnQQtbjxtzEBnalLfL+GjPJsawd9E+z\r\n0R39qIs0tN9\/\/93fw0WGu9BjraIzbFptEPirjT\/oe+p+GzMPztRcoZZ8kvM16BD5\r\n5D2UYsL+GUyeYTeMbd8bsUnE+Ul+We8tIPWxmrePipde9Uts9Qy4\r\n-----END RSA PRIVATE KEY-----', + ca : '-----BEGIN CERTIFICATE-----\r\nMIIEUTCCAzmgAwIBAgIJAMlqt\/8UuGbMMA0GCSqGSIb3DQEBBQUAMHgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMREwDwYDVQQHEwhCZWxsZXZ1ZTEO\r\nMAwGA1UEChMFQXV0aDAxHzAdBgNVBAsTFkFEIExEQVAgQ29ubmVjdG9yIFRlc3Qx\r\nEDAOBgNVBAMTB1Rlc3QgQ0EwHhcNMTcwMzMwMjAzMjE3WhcNMjcwMzI4MjAzMjE3\r\nWjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjERMA8GA1UEBxMI\r\nQmVsbGV2dWUxDjAMBgNVBAoTBUF1dGgwMR8wHQYDVQQLExZBRCBMREFQIENvbm5l\r\nY3RvciBUZXN0MRAwDgYDVQQDEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\r\nAQ8AMIIBCgKCAQEAvAKizExxRjxEKxkboxsE7ErdiOA3YBOaiRXAxjZelANGuuzo\r\nXPYPiwtf5gaHGRzfiJLzJDmIxbJsiR61qPC1\/mcaEWn54OKhwQa05FKusDm6ej8D\r\nWs6\/QDFwZYOukdPbz\/ZpV0In2YPHfdHucx\/JhKE3V5gYFd04U8PcKwn2eH\/fSjIA\r\n1ghSpHBa9F0jIPrBrNe1zPe33AFaE1SKuT\/JcbNaYx1jh1C1KWM7OX5EQ4M\/HUP3\r\n9VWdXVxt48Yj7OoLPrwz4\/5d\/KqCWoznQkcJ2FyA7LJqN1GBP1EoLLAew7yKMGzI\r\n5GCbS8o3F5baLeWJ3jlvylCFE0LDAj0UoxPSUQIDAQABo4HdMIHaMB0GA1UdDgQW\r\nBBTBFNN+vNPh\/ilPbnXsAQa0kAnUuzCBqgYDVR0jBIGiMIGfgBTBFNN+vNPh\/ilP\r\nbnXsAQa0kAnUu6F8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0\r\nb24xETAPBgNVBAcTCEJlbGxldnVlMQ4wDAYDVQQKEwVBdXRoMDEfMB0GA1UECxMW\r\nQUQgTERBUCBDb25uZWN0b3IgVGVzdDEQMA4GA1UEAxMHVGVzdCBDQYIJAMlqt\/8U\r\nuGbMMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAFQWabxlItAUN+\/3\r\n2gwDgqSVXrPGJ9XyQfVA3RmrVxD+PZqxo9+preS8SVGDLGzt9bL4MFO8+d\/0IVNM\r\nn+j4CCXRd\/BgKHpg6Cj14PPsrJuoCIhFJv9x5XAYqOoTf8KlkZKTfPXuZ0gAStuh\r\nVpKwC7rYrNjRigj0UIIe1ghtzhs2av5BRbvYFQlBgrQI4uMQKhlm8STPsV9vr8iF\r\nWcwmf2jvSmVVcRNPSjxOoqraYxhaIwCAEpQKKPsyoPUwSvCPcvZqwOmFd2FWwST9\r\nIc+EpvwplAizLJmh8M8mEEXfz1RkeYVsiWgcRV22Edd20lvlbnptRkMSEVbE2XMB\r\n9c2aY1k=\r\n-----END CERTIFICATE-----' + }; + + var server; + + before(function(done) { + server=tls.createServer({ + key: cert.privateKey, + cert: cert.publicKey + }); + server.on('connection', function(){}); + server.on('secureConnection', function(){}); + server.listen(PORT, done); + }); + + + after(function(done) { + if (server) { + server.close(done); + } else { + done(); + } + }); + + // This test is mainly to keep track of the original Node.Js issue. When this test fails you can safely remove ../lib/tls.js + it('should fail when using node provided server identity verification', function(done) { + var socket = tls.connect({ + port: PORT, + ca: cert.ca, + servername: 'localhost', + }, function() { + socket.end(); + done(new Error('Certificate should not pass server Identity validation')); + }); + socket.on('error', function(e) { + expect(e).to.have.property('reason','Cert is empty'); + done(); + }); + }); + + it('should connect when using connector\'s server identity verification', function(done) { + var socket = tls.connect(PORT, { + ca: cert.ca, + servername: 'localhost', + checkServerIdentity: tlsHelper.checkServerIdentity + }, function() { + socket.end(); + done(); + }); + socket.on('error', done); + }); +});