Browse files

Merge pull request #597 from msimerson/tls

TLS: added register(), timeout, load tls.ini and *.pem files with cb
  • Loading branch information...
2 parents 691e048 + 5975af8 commit 09cd7615a09b1381ba996784130a5fe2343848ca @smfreegard smfreegard committed Jun 27, 2014
Showing with 181 additions and 54 deletions.
  1. +9 −0 config/tls.ini
  2. +51 −3 docs/plugins/tls.md
  3. +107 −45 plugins/tls.js
  4. +14 −6 tls_socket.js
View
9 config/tls.ini
@@ -0,0 +1,9 @@
+; See 'haraka -h tls'
+
+; ciphers: a list of permitted ciphers
+ciphers=ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4
+
+; no_tls_hosts - if you find servers with broken TLS, add their IP to this
+; section to disable TLS for them.
+; [no_tls_hosts]
+; 192.168.1.1=true
View
54 docs/plugins/tls.md
@@ -4,11 +4,23 @@ 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
+
+ config/tls_key.pem
+ config/tls_cert.pem
+
## Purchased Certificate
-If you have a purchased certificate, install the key as config/tls\_key.pem and the
-certificate (appended with any intermediate/chained/ca-cert files) as
-config/tls\_cert.pem.
+If you have a purchased certificate, append any intermediate/chained/ca-cert
+files to the certificate in this order:
+
+1. The CA signed SSL cert
+2. Any intermediate certificates
+3. The CA root certificate
+
+Example:
+
+ cat mail.example.com.crt intermediary_cert.crt ca-cert.crt > config/tls_cert.pem
See also [Setting Up TLS](https://github.com/baudehlo/Haraka/wiki/Setting-up-TLS-with-CA-certificates)
@@ -23,3 +35,39 @@ command:
You will be prompted to provide details of your organization. Make sure the
Common Name is set to your servers Fully Qualified Domain Name, which should
be the same as the contents of your `config/me` file.
+
+## Configuration
+
+The following settings can be specified in config/tls.ini.
+
+### `no_tls_hosts`
+
+If needed, add this section to the tls.ini file and list any IPs that have
+broken TLS. Ex:
+
+ [no_tls_hosts]
+ 192.168.1.3=true
+
+
+The [Node.js TLS](http://nodejs.org/api/tls.html) page has additional information
+about the following options.
+
+### ciphers
+
+A list of allowable ciphers to use.
+
+ `ciphers=...`
+
+See also: [Strong SSL Ciphers](http://cipherli.st) and the [SSLlabs Test Page](https://www.ssllabs.com/ssltest/index.html)
+
+### requestCert
+
+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)
View
152 plugins/tls.js
@@ -1,63 +1,125 @@
-// Enables TLS. This is built into the server anyway, but enabling this plugin
-// just advertises it.
+// TLS is built into Haraka. Enabling this plugin advertises STARTTLS.
+// see 'haraka -h tls' for help
var utils = require('./utils');
// To create a key:
// openssl req -x509 -nodes -days 2190 -newkey rsa:2048 \
// -keyout config/tls_key.pem -out config/tls_cert.pem
-exports.hook_capabilities = function (next, connection) {
- /* Caution: We cannot advertise STARTTLS if the upgrade has already been done. */
- if (!connection.using_tls) {
- var key = this.config.get('tls_key.pem', 'binary');
- if (key) {
- connection.capabilities.push('STARTTLS');
- connection.notes.tls_enabled = 1;
+exports.register = function () {
+ var plugin = this;
+
+ plugin.tls_opts = {
+ key: false,
+ cert: false,
+ };
+
+ var config_options = ['ciphers','requestCert','rejectUnauthorized'];
+
+ var load_config = function () {
+ plugin.loginfo("loading tls.ini");
+ plugin.cfg = plugin.config.get('tls.ini', {
+ booleans: [
+ '+main.requestCert',
+ '-main.rejectUnauthorized',
+ ]
+ }, load_config);
+
+ for (var i in config_options) {
+ if (plugin.cfg.main[config_options[i]] === undefined) { continue; }
+ plugin.tls_opts[config_options[i]] = plugin.cfg.main[config_options[i]];
+ }
+ };
+ load_config();
+
+ var load_key = function () {
+ plugin.loginfo("loading tls_key.pem");
+ plugin.tls_opts.key = plugin.config.get('tls_key.pem', 'binary', load_key);
+ if (!plugin.tls_opts.key) {
+ plugin.logcrit("config/tls_key.pem not loaded. See 'haraka -h tls'");
}
- else {
- connection.logcrit("TLS plugin enabled but no key found. Please see plugin docs.");
+ };
+ load_key();
+
+ var load_cert = function () {
+ plugin.loginfo("loading tls_cert.pem");
+ plugin.tls_opts.cert = plugin.config.get('tls_cert.pem', 'binary', load_cert);
+ if (!plugin.tls_opts.cert) {
+ plugin.logcrit("config/tls_cert.pem not loaded. See 'haraka -h tls'");
}
+ };
+ load_cert();
+ plugin.logdebug(plugin.tls_opts);
+};
+
+exports.hook_capabilities = function (next, connection) {
+ /* Caution: do not advertise STARTTLS if the upgrade has already been done. */
+ if (connection.using_tls) { return next(); }
+
+ var plugin = this;
+ if (plugin.cfg.no_tls_hosts) {
+ if (plugin.cfg.no_tls_hosts[connection.remote_ip]) {
+ return next();
+ }
+ }
+
+ if (!plugin.tls_opts.key) {
+ connection.logcrit("No TLS key found. See 'harka -h tls'");
+ return next();
}
+
+ if (!plugin.tls_opts.cert) {
+ connection.logcrit("No TLS cert found. See 'harka -h tls'");
+ return next();
+ }
+
+ connection.capabilities.push('STARTTLS');
+ connection.notes.tls_enabled = 1;
+
/* Let the plugin chain continue. */
next();
};
exports.hook_unrecognized_command = function (next, connection, params) {
/* Watch for STARTTLS directive from client. */
- if (connection.notes.tls_enabled && params[0] === 'STARTTLS') {
- var key = this.config.get('tls_key.pem', 'binary');
- var cert = this.config.get('tls_cert.pem', 'binary');
- var options = { key: key, cert: cert, requestCert: true };
-
- /* Respond to STARTTLS command. */
- connection.respond(220, "Go ahead.");
- /* Upgrade the connection to TLS. */
- var self = this;
- connection.client.upgrade(options, function (authorized, verifyError, cert, cipher) {
- connection.reset_transaction(function () {
- connection.hello_host = undefined;
- connection.using_tls = true;
- connection.notes.tls = {
- authorized: authorized,
- authorizationError: verifyError,
- peerCertificate: cert,
- cipher: cipher
- };
- connection.loginfo(self, 'secured:' +
- ((cipher) ? ' cipher=' + cipher.name + ' version=' + cipher.version : '') +
- ' verified=' + authorized +
- ((verifyError) ? ' error="' + verifyError + '"' : '' ) +
- ((cert && cert.subject) ? ' cn="' + cert.subject.CN + '"' +
- ' organization="' + cert.subject.O + '"' : '') +
- ((cert && cert.issuer) ? ' issuer="' + cert.issuer.O + '"' : '') +
- ((cert && cert.valid_to) ? ' expires="' + cert.valid_to + '"' : '') +
- ((cert && cert.fingerprint) ? ' fingerprint=' + cert.fingerprint : ''));
- return next(OK); // Return OK as we responded to the client
- });
+ if (!connection.notes.tls_enabled) { return next(); }
+ if (params[0] !== 'STARTTLS') { return next(); }
+
+ /* Respond to STARTTLS command. */
+ connection.respond(220, "Go ahead.");
+
+ var plugin = this;
+ // adjust plugin.timeout like so: echo '45' > config/tls.timeout
+ var timeout = plugin.timeout - 1;
+
+ var timer = setTimeout(function () {
+ connection.logerror(plugin, 'timeout');
+ return next(DENYSOFTDISCONNECT);
+ }, timeout * 1000);
+
+ /* Upgrade the connection to TLS. */
+ connection.client.upgrade(plugin.tls_opts, function (authorized, verifyError, cert, cipher) {
+ clearTimeout(timer);
+ connection.reset_transaction(function () {
+ connection.hello_host = undefined;
+ connection.using_tls = true;
+ connection.notes.tls = {
+ authorized: authorized,
+ authorizationError: verifyError,
+ peerCertificate: cert,
+ cipher: cipher
+ };
+ connection.loginfo(plugin, 'secured:' +
+ ((cipher) ? ' cipher=' + cipher.name + ' version=' + cipher.version : '') +
+ ' verified=' + authorized +
+ ((verifyError) ? ' error="' + verifyError + '"' : '' ) +
+ ((cert && cert.subject) ? ' cn="' + cert.subject.CN + '"' +
+ ' organization="' + cert.subject.O + '"' : '') +
+ ((cert && cert.issuer) ? ' issuer="' + cert.issuer.O + '"' : '') +
+ ((cert && cert.valid_to) ? ' expires="' + cert.valid_to + '"' : '') +
+ ((cert && cert.fingerprint) ? ' fingerprint=' + cert.fingerprint : ''));
+ return next(OK); // Return OK as we responded to the client
});
- }
- else {
- return next();
- }
+ });
};
View
20 tls_socket.js
@@ -170,7 +170,7 @@ function createServer(cb) {
socket.upgrade = function (options, cb) {
log.logdebug("Upgrading to TLS");
-
+
socket.clean();
cryptoSocket.removeAllListeners('data');
@@ -179,10 +179,17 @@ function createServer(cb) {
if (!options) options = {};
// TODO: bug in Node means we can't do this until it's fixed
// options.secureOptions = SSL_OP_ALL;
-
+
+ var requestCert = true;
+ var rejectUnauthorized = false;
+ if (options) {
+ if (options.requestCert !== undefined) { requestCert = options.requestCert; }
+ if (options.rejectUnauthorized !== undefined) { rejectUnauthorized = options.rejectUnauthorized; }
+ }
var sslcontext = crypto.createCredentials(options);
- var pair = tls.createSecurePair(sslcontext, true, true, false);
+ // tls.createSecurePair(credentials, isServer, requestCert, rejectUnauthorized)
+ var pair = tls.createSecurePair(sslcontext, true, requestCert, rejectUnauthorized);
var cleartext = pipe(pair, cryptoSocket);
@@ -211,7 +218,7 @@ function createServer(cb) {
cleartext._controlReleased = true;
socket.cleartext = cleartext;
-
+
if (socket._timeout) {
cleartext.setTimeout(socket._timeout);
}
@@ -265,12 +272,13 @@ function connect(port, host, cb) {
var sslcontext = crypto.createCredentials(options);
+ // tls.createSecurePair([credentials], [isServer]);
var pair = tls.createSecurePair(sslcontext, false);
socket.pair = pair;
var cleartext = pipe(pair, cryptoSocket);
-
+
pair.on('error', function(exception) {
socket.emit('error', exception);
});
@@ -297,7 +305,7 @@ function connect(port, host, cb) {
cleartext._controlReleased = true;
socket.cleartext = cleartext;
-
+
if (socket._timeout) {
cleartext.setTimeout(socket._timeout);
}

0 comments on commit 09cd761

Please sign in to comment.