diff --git a/docs/plugins/tls.md b/docs/plugins/tls.md index 556def1aa..4756256cc 100644 --- a/docs/plugins/tls.md +++ b/docs/plugins/tls.md @@ -4,10 +4,9 @@ This plugin enables the use of TLS (via `STARTTLS`) in Haraka. For this plugin to work you must have SSL certificates installed correctly. -## Install Location +## Certificate Files -Key and certificate chain default locations are as follows. The paths -can be overridden in the `config/tls.ini` file using `key` and `cert` options. +Defaults are shown and can be overridden in `config/tls.ini`. key=tls_key.pem cert=tls_cert.pem @@ -45,17 +44,15 @@ The following settings can be specified in `config/tls.ini`. ### key -Specifies an alternative location for the key file. If multiple keys are to be -specified, use `key[]=` assignment for each of them. Non-absolute paths are relative -to the `config/` directory. +Specifies an alternative location for the key file. For multiple keys, use `key[]=` assignment for each. Non-absolute paths are relative to the `config/` directory. -For example, to configure single key and cert chain files, located in the `config/` +To configure a single key and a cert chain, located in the `config/` directory, use the following in `tls.ini`: key=example.com.key.pem cert=example.com.crt-chain.pem -If multiple pairs of key and cert chain files should be used, outside of the haraka +For multiple pairs of key and cert chain files should be used, outside of the haraka `config/` directory, configure instead: key[]=/etc/ssl/private/example.com.rsa.key.pem @@ -65,10 +62,7 @@ If multiple pairs of key and cert chain files should be used, outside of the har ### cert -Specifies an alternative location for the certificate chain file. If multiple -certificate chains are to be used, use `cert[]=` assignment for each of them. -Non-absolute paths are relative to the `config/` directory. See the description of -the `key` parameter for specific use. +Specifies the location(s) for the certificate chain file. For multiple certificate chains, use `cert[]=` assignment for each. Non-absolute paths are relative to the `config/` directory. See the description of the `key` parameter for specific use. ### no_tls_hosts @@ -106,7 +100,7 @@ Only one curve can be specified. The default is `prime256v1` (NIST P-256). Specifies the file containing the diffie-hellman parameters to use for DH or DHE key exchange. Create such a file using `openssl dhparam`. -No DH ciphers can be used without this parameter given. +No DH ciphers can be used without this parameter. openssl dhparam -out config/dhparams.pem 2048 @@ -117,12 +111,14 @@ Whether Haraka should request a certificate from a connecting client. requestCert=[true|false] (default: true) + ### rejectUnauthorized Reject connections from clients without a CA validated TLS certificate. rejectUnauthorized=[true|false] (default: false) + ### secureProtocol Specifies the OpenSSL API function used for handling the TLS session. Choose @@ -130,6 +126,7 @@ one of the methods described at the [OpenSSL API page](https://www.openssl.org/docs/manmaster/ssl/ssl.html). The default is `SSLv23_method`. + ### enableOCSPStapling Specifies that OCSP Stapling should be enabled, according to RFC 6066. @@ -144,6 +141,7 @@ they are valid, and get refreshed after that time. A server restart requires the OCSP responses to be fetched again upon the first client connection. + ## Inbound Specific Configuration By default the above options are shared with outbound mail (either diff --git a/package.json b/package.json index cf28f4b74..21b5442fa 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "sprintf-js" : "~1.0.3", "haraka-tld" : "*", "haraka-constants" : "*", - "haraka-net-utils" : ">=1.0.6", + "haraka-net-utils" : ">=1.0.8", "haraka-results" : "^1.0.1", "haraka-utils" : "*", "haraka-plugin-asn" : "*", diff --git a/plugins/tls.js b/plugins/tls.js index 62d71b34c..752c47824 100644 --- a/plugins/tls.js +++ b/plugins/tls.js @@ -2,9 +2,11 @@ // TLS is built into Haraka. This plugin conditionally advertises STARTTLS. // see 'haraka -h tls' for help -var net_utils = require('haraka-net-utils'); var tls_socket = require('./tls_socket'); +// exported so tests can override config dir +exports.net_utils = require('haraka-net-utils'); + exports.register = function () { var plugin = this; plugin.load_errs = []; @@ -33,47 +35,49 @@ exports.register = function () { plugin.register_hook('capabilities', 'advertise_starttls'); plugin.register_hook('unrecognized_command', 'upgrade_connection'); -}; +} exports.shutdown = function () { if (tls_socket.shutdown) tls_socket.shutdown(); -}; +} exports.load_err = function (errMsg) { this.logcrit(errMsg + " See 'haraka -h tls'"); this.load_errs.push(errMsg); -}; +} exports.load_pem = function (file) { var plugin = this; return plugin.config.get(file, 'binary'); -}; +} exports.load_tls_ini = function () { var plugin = this; - plugin.cfg = net_utils.load_tls_ini(function () { + plugin.cfg = plugin.net_utils.load_tls_ini(function () { plugin.load_tls_ini(); }); - var config_options = ['ciphers','requestCert','rejectUnauthorized', - 'key','cert','honorCipherOrder','ecdhCurve','dhparam', - 'secureProtocol','enableOCSPStapling']; + var config_options = [ + 'ciphers', 'requestCert', 'rejectUnauthorized', + 'key', 'cert', 'honorCipherOrder', 'ecdhCurve', 'dhparam', + 'secureProtocol', 'enableOCSPStapling' + ]; for (let i = 0; i < config_options.length; i++) { let opt = config_options[i]; - if (plugin.cfg.main[opt] === undefined) { continue; } + if (plugin.cfg.main[opt] === undefined) continue; plugin.tls_opts[opt] = plugin.cfg.main[opt]; } if (plugin.cfg.inbound) { for (let i = 0; i < config_options.length; i++) { let opt = config_options[i]; - if (plugin.cfg.inbound[opt] === undefined) { continue; } + if (plugin.cfg.inbound[opt] === undefined) continue; plugin.tls_opts[opt] = plugin.cfg.inbound[opt]; } } -}; +} exports.load_tls_opts = function () { var plugin = this; @@ -101,32 +105,39 @@ exports.load_tls_opts = function () { plugin.tls_opts.cert.length + ")."); } + plugin.loadPemFiles(); + + plugin.logdebug(plugin.tls_opts); +} + +exports.loadPemFiles = function () { + var plugin = this; + // turn key/cert file names into actual key/cert binary data - plugin.tls_opts.key = plugin.tls_opts.key.map(function (keyFileName) { + plugin.tls_opts.key = plugin.tls_opts.key.map(keyFileName => { var key = plugin.load_pem(keyFileName); if (!key) { plugin.load_err("tls key " + keyFileName + " could not be loaded."); } return key; }); - plugin.tls_opts.cert = plugin.tls_opts.cert.map(function (certFileName) { + + plugin.tls_opts.cert = plugin.tls_opts.cert.map(certFileName => { var cert = plugin.load_pem(certFileName); if (!cert) { plugin.load_err("tls cert " + certFileName + " could not be loaded."); } return cert; }); - - plugin.logdebug(plugin.tls_opts); -}; +} exports.advertise_starttls = function (next, connection) { /* Caution: do not advertise STARTTLS if already TLS upgraded */ - if (connection.tls.enabled) { return next(); } + if (connection.tls.enabled) return next(); var plugin = this; - if (net_utils.ip_in_list(plugin.cfg.no_tls_hosts, connection.remote.ip)) { + if (plugin.net_utils.ip_in_list(plugin.cfg.no_tls_hosts, connection.remote.ip)) { return next(); } @@ -143,7 +154,7 @@ exports.advertise_starttls = function (next, connection) { var redis = server.notes.redis; var dbkey = 'no_tls|' + connection.remote.ip; - redis.get(dbkey, function (err, dbr) { + redis.get(dbkey, (err, dbr) => { if (err) { connection.results.add(plugin, {err: err}); return enable_tls(); @@ -160,7 +171,7 @@ exports.advertise_starttls = function (next, connection) { connection.results.add(plugin, { msg: 'tls disabled'}); return next(); }); -}; +} exports.set_notls = function (ip) { var plugin = this; @@ -169,7 +180,7 @@ exports.set_notls = function (ip) { if (!server.notes.redis) return; server.notes.redis.set('no_tls|' + ip, true); -}; +} exports.upgrade_connection = function (next, connection, params) { if (!connection.tls.advertised) return next(); @@ -201,11 +212,11 @@ exports.upgrade_connection = function (next, connection, params) { connection.notes.cleanUpDisconnect = nextOnce; /* Upgrade the connection to TLS. */ - connection.client.upgrade(plugin.tls_opts, function (authorized, verifyErr, cert, cipher) { - if (called_next) { return; } + connection.client.upgrade(plugin.tls_opts, (authorized, verifyErr, cert, cipher) => { + if (called_next) return; clearTimeout(connection.notes.tls_timer); called_next = true; - connection.reset_transaction(function () { + connection.reset_transaction(() => { connection.set('hello', 'host', undefined); connection.set('tls', 'enabled', true); connection.set('tls', 'cipher', cipher); @@ -218,33 +229,32 @@ exports.upgrade_connection = function (next, connection, params) { connection.results.add(plugin, connection.tls); plugin.emit_upgrade_msg(connection, authorized, verifyErr, cert, cipher); return next(OK); // Return OK as we responded to the client - }); - }); -}; + }) + }) +} exports.hook_disconnect = function (next, connection) { if (connection.notes.cleanUpDisconnect) { connection.notes.cleanUpDisconnect(true); } return next(); -}; +} exports.emit_upgrade_msg = function (c, authorized, verifyErr, cert, cipher) { var plugin = this; var msg = 'secured:'; if (cipher) { - msg += ' cipher=' + cipher.name + ' version=' + cipher.version; + msg += ` cipher=${cipher.name} version=${cipher.version}`; } - msg += ' verified=' + authorized; - if (verifyErr) msg += ' error="' + verifyErr + '"'; + msg += ` verified=${authorized}`; + if (verifyErr) msg += ` error="${verifyErr}"`; if (cert) { if (cert.subject) { - msg += ' cn="' + cert.subject.CN + '"' + - ' organization="' + cert.subject.O + '"'; + msg += ` cn="${cert.subject.CN}" organization="${cert.subject.O}"`; } - if (cert.issuer) msg += ' issuer="' + cert.issuer.O + '"'; - if (cert.valid_to) msg += ' expires="' + cert.valid_to + '"'; - if (cert.fingerprint) msg += ' fingerprint=' + cert.fingerprint; + if (cert.issuer) msg += ` issuer="${cert.issuer.O}"`; + if (cert.valid_to) msg += ` expires="${cert.valid_to}"`; + if (cert.fingerprint) msg += ` fingerprint=${cert.fingerprint}`; } c.loginfo(plugin, msg); diff --git a/tests/config/tls/haraka.local.pem b/tests/config/tls/haraka.local.pem new file mode 100644 index 000000000..f9cb8cc78 --- /dev/null +++ b/tests/config/tls/haraka.local.pem @@ -0,0 +1,51 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDcg5h5TVO1gECK +yNCXbpJduNqJvhE6hyVb9q0WRR8zKHE7DnOog9JqOcrz4mv3TmszDZ2511mGxo/v +j2dAYakU7HxEuSqKX1CsjbDP3xa7IvVCaKQbcMImq7dzmenXkCdPivuglZmooZen +c18QZ6YkrunDeh/OcP8WAA+9yYXp82lmKPzKNkJETfGVXff0loXWR5SSjGXYvjqm +XK6IXqPcuNriUTmylLx2149O6RWJPaZ2YYxTOHTeV5OMeEHjLpAOShCfZi4VYvAe +GZSFq5UdMv5QD1k6BueS1Ruf6ChCLjfqkMcCAw8DOY8oxvzovgM/sgBcx3L00/pe +QeSegG+3AgMBAAECggEAW9qJEcYvH0SMHgNmOB375ARTK9s7S/jti/Aly0gBpgqr +l+D+NmyqokruikZ/mKVWrA546+eTSDu/yxcd+Eh16NxVKz9CRB9N+IKQ6xXPXyZB +qWbzLOb8SKVwpjuvl3ZZmZ2YER0fw4mEJWE+cRPrtg4SG7XsN88DwoNGC1U9beSL +vZ3lDuOMHNBNGx5k608i5HCV8Ty3Sz8ksvL9fPFwUpKAvRuG3lYgS+GxSrijH+KO +vIwXAu3E+7MIPXdbsg14HBRRHEfyQlRGV8rj3e8neDLFarB8fXDVqIsko6/D5qZZ +D/jXYW7pcafOGT0PTbHxUMrUxahhAjKKfPt3Y7JVcQKBgQDzIhmN7SbQvu2dl54X +XezrgKteDVd4q7VYVpiAjNEZA+kNdBcXC6k2T5WMNPesNwyFIoDLDFWUg4FMG/Mc +KA1eArBocGQ0gQvLlnlUxBy519e/AAj7jTspLi7gAwLqfTo0TGdTH/Hfq+1uCXys +dd3BKYnDKc4aDPTQCEkRzfPHDwKBgQDoLw7MZecGnNNrqArqsOaOO1uUKFysW6pi +2ZU7KKj1x6vKGIW88YmzNBPCj4LU8XlXbR1isaSs5TaWgc6N0aAEXwA6TN/8PXhq +6IXarR1WJIq2DEDtdBrj2AiaPoF8Y6hsebsQs+EpyLx8MCGLaucYJvqyK87ep000 +mXQuyfgM2QKBgHCLy2qAaeRdTV8S7TKB3wcQ88LAyEnqqjJvO37eMHi076+zmnCn +jDfA1UgmyLNmdBw44YeceQ0bZsHVek8BV1a6RfDCfhAz4ELor9eGRInemVcn7ACN +2uHwJ/C4VCQ5vbSx3W6ELhHM40Z5i8XFddZRpRy7gFVcxAJ8o15jiMIPAoGAHiq3 +DoGS8b4AjjVILdQMMKCvtmFEITTLv4orpIMU6NInlNt4zOLJFFqI0reYtRgmvuAz +eDZCgiBJ5mY5Mx3wX4EEY47Hb1uBQMqzUYU6kY2v5BVVfkSelcnk3D2Qz1uXb3il +gHcOo0IskyohwZ6DJhUyb2HXwAAWvOXPPaEKNIkCgYAqWhzYCAoxZ6f7i2I3J1JO +5OaMi4+9YR3ivTDcKVYp1ZOzANe0gsQlal/gf2fTjG35b9FyK3Lo5rp0QLKxv2vh +6dR+sk+58jJhk8H1kPNIVzvA4SAd51YoE0x7edi7+5vDMwlz57yz5rwMQvN1wUOO +5Ev9F9qrVJZFF4Ei+fRW9Q== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIJAOa6xwibkwczMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ8w +DQYDVQQKDAZIYXJha2ExFTATBgNVBAMMDGhhcmFrYS5sb2NhbDEgMB4GCSqGSIb3 +DQEJARYRbWF0dEBoYXJha2EubG9jYWwwHhcNMTcwMzA0MjMyODQ5WhcNMjMwMzAz +MjMyODQ5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4G +A1UEBwwHU2VhdHRsZTEPMA0GA1UECgwGSGFyYWthMRUwEwYDVQQDDAxoYXJha2Eu +bG9jYWwxIDAeBgkqhkiG9w0BCQEWEW1hdHRAaGFyYWthLmxvY2FsMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3IOYeU1TtYBAisjQl26SXbjaib4ROocl +W/atFkUfMyhxOw5zqIPSajnK8+Jr905rMw2duddZhsaP749nQGGpFOx8RLkqil9Q +rI2wz98WuyL1QmikG3DCJqu3c5np15AnT4r7oJWZqKGXp3NfEGemJK7pw3ofznD/ +FgAPvcmF6fNpZij8yjZCRE3xlV339JaF1keUkoxl2L46plyuiF6j3Lja4lE5spS8 +dtePTukViT2mdmGMUzh03leTjHhB4y6QDkoQn2YuFWLwHhmUhauVHTL+UA9ZOgbn +ktUbn+goQi436pDHAgMPAzmPKMb86L4DP7IAXMdy9NP6XkHknoBvtwIDAQABo1Aw +TjAdBgNVHQ4EFgQUsfam5K8yKVyyJG7NQfKXjpr6gYEwHwYDVR0jBBgwFoAUsfam +5K8yKVyyJG7NQfKXjpr6gYEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AQEAL7vMH/sYj/rDzo+pYn8qK9eZBpTPQLsuiotNYqoH/tOJzaOBI2cMSrbGM3zG +aegTqKOduO6vpXun7MT4tGilOxt+1eve6RbsjKMeTkZr+A44rRRUZ4LHZe38oaGk +A8jB6YHiiefH1mgfSN1igcT7SkOxv8WM4jsCyOCy3wG8b4V8wvDytOtJOJo041hX +njmypWHaZ1IDYu1ybnb/Macnno0NMkEMx0uIlG/c3+KvJwpz2WHrKrKA5l5oGp/n ++F3pt6NhSJCDG0cktax07LIvxMpAoOaJ6cYSfwB/v0HiYh2733bEXEiQ2jxO5vXo +NS2fKRYBqs2ATDVHpzknBC1KcA== +-----END CERTIFICATE----- diff --git a/tests/plugins/tls.js b/tests/plugins/tls.js index 93a165441..d462b380a 100644 --- a/tests/plugins/tls.js +++ b/tests/plugins/tls.js @@ -4,13 +4,18 @@ var fs = require('fs'); var path = require('path'); var fixtures = require('haraka-test-fixtures'); - -var Connection = fixtures.connection; var Plugin = fixtures.plugin; var _set_up = function (done) { this.plugin = new Plugin('tls'); - this.connection = Connection.createConnection(); + this.connection = new fixtures.connection.createConnection(); + + // use tests/config instead of ./config + this.plugin.config = + this.plugin.config.module_config(path.resolve('tests')); + this.plugin.net_utils.config = + this.plugin.net_utils.config.module_config(path.resolve('tests')); + this.plugin.tls_opts = {}; done(); }; @@ -134,7 +139,7 @@ exports.dont_register = { setUp : function (done) { this.plugin = new Plugin('tls'); - // overload load_pem to get files from tests/config + // overload load_pem this.plugin.load_pem = function (file) { try { return fs.readFileSync('./non-exist/config/' + file); diff --git a/tests/server.js b/tests/server.js index 6f05f29a3..2dedbcacd 100644 --- a/tests/server.js +++ b/tests/server.js @@ -188,6 +188,8 @@ exports.createServer = { function (error, info){ if (error){ console.log(error); + test.done(); + return; } test.deepEqual(info.accepted, [ 'discard@haraka.local' ]); console.log('Message sent: ' + info.response); diff --git a/tests/tls_socket.js b/tests/tls_socket.js index 35e2d7167..239b30ac7 100644 --- a/tests/tls_socket.js +++ b/tests/tls_socket.js @@ -1 +1,39 @@ require('../configfile').watch_files = false; + +var tls_socket = require('../tls_socket'); + +exports.tls_socket = { + 'loads' : function (test) { + test.expect(1); + test.ok(tls_socket); + test.done(); + }, + 'exports createConnection' : function (test) { + test.expect(1); + test.equal(typeof tls_socket.createConnection, 'function'); + test.done(); + }, + 'exports createServer' : function (test) { + test.expect(1); + // console.log(tls_socket); + test.equal(typeof tls_socket.createServer, 'function'); + test.done(); + }, + 'exports shutdown' : function (test) { + test.expect(1); + // console.log(tls_socket); + test.equal(typeof tls_socket.shutdown, 'function'); + test.done(); + }, +} + +exports.createServer = { + 'returns a net.Server' : function (test) { + test.expect(1); + var server = tls_socket.createServer(function (socket) { + // console.log(socket); + }); + test.ok(server); + test.done(); + } +} \ No newline at end of file diff --git a/tls_socket.js b/tls_socket.js index de4b1df38..a41017431 100644 --- a/tls_socket.js +++ b/tls_socket.js @@ -322,7 +322,7 @@ function connect (port, host, cb) { if (err.reason) { log.logerror("client TLS error: " + err); } - }); + }) cleartext.on('secureConnect', function () { log.logdebug('client TLS secured.'); @@ -346,9 +346,9 @@ function connect (port, host, cb) { socket.attach(socket.cleartext); log.logdebug('client TLS upgrade in progress, awaiting secured.'); - }; + } - return (socket); + return socket; } exports.connect = connect;