diff options
author | Denys Otrishko <shishugi@gmail.com> | 2018-10-29 11:38:43 +0300 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2019-12-25 14:24:42 +0300 |
commit | f8d7e2216e5821719b5a341c41251d5a860cf5f7 (patch) | |
tree | 8f3a1db4b065a844483ee1a8e388702463683570 /test | |
parent | 3d47c8592d179991d3bfa4902f12c4fce07ac2d3 (diff) |
tls: add PSK support
Add the `pskCallback` client/server option, which resolves an identity
or identity hint to a pre-shared key.
Add the `pskIdentityHint` server option to set the identity hint for the
ServerKeyExchange message.
Co-authored-by: Chris Osborn <chris.osborn@sitelier.com>
Co-authored-by: stephank <gh@stephank.nl>
Co-authored-by: Taylor Zane Glaeser <tzglaeser@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/23188
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'test')
-rw-r--r-- | test/parallel/test-tls-psk-circuit.js | 72 | ||||
-rw-r--r-- | test/parallel/test-tls-psk-errors.js | 32 | ||||
-rw-r--r-- | test/parallel/test-tls-psk-server.js | 77 | ||||
-rw-r--r-- | test/sequential/test-tls-psk-client.js | 96 |
4 files changed, 277 insertions, 0 deletions
diff --git a/test/parallel/test-tls-psk-circuit.js b/test/parallel/test-tls-psk-circuit.js new file mode 100644 index 00000000000..4bcdf368606 --- /dev/null +++ b/test/parallel/test-tls-psk-circuit.js @@ -0,0 +1,72 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const tls = require('tls'); + +const CIPHERS = 'PSK+HIGH:TLS_AES_128_GCM_SHA256'; +const USERS = { + UserA: Buffer.allocUnsafe(128), + UserB: Buffer.from('82072606b502b0f4025e90eb75fe137d', 'hex'), +}; +const TEST_DATA = 'x'; + +const serverOptions = { + ciphers: CIPHERS, + pskCallback(socket, id) { + assert.ok(socket instanceof tls.TLSSocket); + assert.ok(typeof id === 'string'); + return USERS[id]; + }, +}; + +function test(secret, opts, error) { + const cb = !error ? + common.mustCall((c) => { c.pipe(c); }) : + common.mustNotCall(); + const server = tls.createServer(serverOptions, cb); + server.listen(0, common.mustCall(() => { + const options = { + port: server.address().port, + ciphers: CIPHERS, + checkServerIdentity: () => {}, + pskCallback: common.mustCall(() => secret), + ...opts, + }; + + if (!error) { + const client = tls.connect(options, common.mustCall(() => { + client.end(TEST_DATA); + + client.on('data', common.mustCall((data) => { + assert.strictEqual(data.toString(), TEST_DATA); + })); + client.on('close', common.mustCall(() => server.close())); + })); + } else { + const client = tls.connect(options, common.mustNotCall()); + client.on('error', common.mustCall((err) => { + assert.strictEqual(err.message, error); + server.close(); + })); + } + })); +} + +const DISCONNECT_MESSAGE = + 'Client network socket disconnected before ' + + 'secure TLS connection was established'; + +test({ psk: USERS.UserA, identity: 'UserA' }); +test({ psk: USERS.UserA, identity: 'UserA' }, { maxVersion: 'TLSv1.2' }); +test({ psk: USERS.UserA, identity: 'UserA' }, { minVersion: 'TLSv1.3' }); +test({ psk: USERS.UserB, identity: 'UserB' }); +test({ psk: USERS.UserB, identity: 'UserB' }, { minVersion: 'TLSv1.3' }); +// Unrecognized user should fail handshake +test({ psk: USERS.UserB, identity: 'UserC' }, {}, DISCONNECT_MESSAGE); +// Recognized user but incorrect secret should fail handshake +test({ psk: USERS.UserA, identity: 'UserB' }, {}, DISCONNECT_MESSAGE); +test({ psk: USERS.UserB, identity: 'UserB' }); diff --git a/test/parallel/test-tls-psk-errors.js b/test/parallel/test-tls-psk-errors.js new file mode 100644 index 00000000000..4864a66f555 --- /dev/null +++ b/test/parallel/test-tls-psk-errors.js @@ -0,0 +1,32 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const tls = require('tls'); + +{ + // Check tlsClientError on invalid pskIdentityHint. + + const server = tls.createServer({ + ciphers: 'PSK+HIGH', + pskCallback: () => {}, + pskIdentityHint: 'a'.repeat(512), // Too long identity hint. + }); + server.on('tlsClientError', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED'); + server.close(); + }); + server.listen(0, () => { + const client = tls.connect({ + port: server.address().port, + ciphers: 'PSK+HIGH', + checkServerIdentity: () => {}, + pskCallback: () => {}, + }, () => {}); + client.on('error', common.expectsError({ code: 'ECONNRESET' })); + }); +} diff --git a/test/parallel/test-tls-psk-server.js b/test/parallel/test-tls-psk-server.js new file mode 100644 index 00000000000..69b850c7022 --- /dev/null +++ b/test/parallel/test-tls-psk-server.js @@ -0,0 +1,77 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); +if (!common.opensslCli) + common.skip('missing openssl cli'); + +const assert = require('assert'); + +const tls = require('tls'); +const spawn = require('child_process').spawn; + +const CIPHERS = 'PSK+HIGH'; +const KEY = 'd731ef57be09e5204f0b205b60627028'; +const IDENTITY = 'TestUser'; + +const server = tls.createServer({ + ciphers: CIPHERS, + pskIdentityHint: IDENTITY, + pskCallback(socket, identity) { + assert.ok(socket instanceof tls.TLSSocket); + assert.ok(typeof identity === 'string'); + if (identity === IDENTITY) + return Buffer.from(KEY, 'hex'); + } +}); + +server.on('connection', common.mustCall()); + +server.on('secureConnection', (socket) => { + socket.write('hello\r\n'); + + socket.on('data', (data) => { + socket.write(data); + }); +}); + +let gotHello = false; +let sentWorld = false; +let gotWorld = false; + +server.listen(0, () => { + const client = spawn(common.opensslCli, [ + 's_client', + '-connect', '127.0.0.1:' + server.address().port, + '-cipher', CIPHERS, + '-psk', KEY, + '-psk_identity', IDENTITY + ]); + + let out = ''; + + client.stdout.setEncoding('utf8'); + client.stdout.on('data', (d) => { + out += d; + + if (!gotHello && /hello/.test(out)) { + gotHello = true; + client.stdin.write('world\r\n'); + sentWorld = true; + } + + if (!gotWorld && /world/.test(out)) { + gotWorld = true; + client.stdin.end(); + } + }); + + client.on('exit', common.mustCall((code) => { + assert.ok(gotHello); + assert.ok(sentWorld); + assert.ok(gotWorld); + assert.strictEqual(code, 0); + server.close(); + })); +}); diff --git a/test/sequential/test-tls-psk-client.js b/test/sequential/test-tls-psk-client.js new file mode 100644 index 00000000000..7c9fb939674 --- /dev/null +++ b/test/sequential/test-tls-psk-client.js @@ -0,0 +1,96 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); +if (!common.opensslCli) + common.skip('missing openssl cli'); + +const assert = require('assert'); +const tls = require('tls'); +const net = require('net'); +const { spawn } = require('child_process'); + +const CIPHERS = 'PSK+HIGH'; +const KEY = 'd731ef57be09e5204f0b205b60627028'; +const IDENTITY = 'Client_identity'; // Hardcoded by `openssl s_server` + +const server = spawn(common.opensslCli, [ + 's_server', + '-accept', common.PORT, + '-cipher', CIPHERS, + '-psk', KEY, + '-psk_hint', IDENTITY, + '-nocert', + '-rev', +]); + +const cleanUp = (err) => { + clearTimeout(timeout); + if (err) + console.log('Failed:', err); + server.kill(); + process.exitCode = err ? 1 : 0; +}; + +const timeout = setTimeout(() => cleanUp('Timeouted'), 5000); + +function waitForPort(port, cb) { + const socket = net.connect(common.PORT, () => { + socket.end(); + socket.on('end', cb); + }); + socket.on('error', (e) => { + if (e.code === 'ENOENT' || e.code === 'ECONNREFUSED') { + setTimeout(() => waitForPort(port, cb), 1000); + } else { + cb(e); + } + }); +} + +waitForPort(common.PORT, common.mustCall((err) => { + if (err) { + cleanUp(err); + return; + } + + const message = 'hello'; + const reverse = message.split('').reverse().join(''); + runClient(message, common.mustCall((err, data) => { + try { + if (!err) assert.strictEqual(data.trim(), reverse); + } finally { + cleanUp(err); + } + })); +})); + +function runClient(message, cb) { + const s = tls.connect(common.PORT, { + ciphers: CIPHERS, + checkServerIdentity: () => {}, + pskCallback(hint) { + // 'hint' will be null in TLS1.3. + if (hint === null || hint === IDENTITY) { + return { + identity: IDENTITY, + psk: Buffer.from(KEY, 'hex') + }; + } + } + }); + s.on('secureConnect', common.mustCall(() => { + let data = ''; + s.on('data', common.mustCallAtLeast((d) => { + data += d; + })); + s.on('end', common.mustCall(() => { + cb(null, data); + })); + s.end(message); + })); + s.on('error', (e) => { + cb(e); + }); +} |