Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls consistency cleanups #1851

Merged
merged 4 commits into from Mar 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 11 additions & 13 deletions docs/plugins/tls.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -117,19 +111,22 @@ 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
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.
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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" : "*",
Expand Down
84 changes: 47 additions & 37 deletions plugins/tls.js
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}

Expand All @@ -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();
Expand All @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
51 changes: 51 additions & 0 deletions 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-----
13 changes: 9 additions & 4 deletions tests/plugins/tls.js
Expand Up @@ -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();
};
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions tests/server.js
Expand Up @@ -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);
Expand Down