summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorShigeki Ohtsu <ohtsu@ohtsu.org>2018-04-12 22:10:59 +0200
committerMyles Borins <mylesborins@google.com>2018-06-12 20:46:09 -0400
commit785e5ba48cb57a05c9c0966a502d34ac03084561 (patch)
treedab489f7db38382c98a4544fe46390fc7123db77 /test
parent0cb3325f124805c0f8911627a38cfb34be35b675 (diff)
downloadandroid-node-v8-785e5ba48cb57a05c9c0966a502d34ac03084561.tar.gz
android-node-v8-785e5ba48cb57a05c9c0966a502d34ac03084561.tar.bz2
android-node-v8-785e5ba48cb57a05c9c0966a502d34ac03084561.zip
test: add tls write error regression test
Add a mock TLS socket implementation and a regression test for the previous commit. Refs: https://github.com/nodejs-private/security/issues/189 PR-URL: https://github.com/nodejs-private/node-private/pull/127 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Evan Lucas <evanlucas@me.com>
Diffstat (limited to 'test')
-rw-r--r--test/common/tls.js176
-rw-r--r--test/parallel/test-tls-write-error.js55
2 files changed, 231 insertions, 0 deletions
diff --git a/test/common/tls.js b/test/common/tls.js
new file mode 100644
index 0000000000..3560af671b
--- /dev/null
+++ b/test/common/tls.js
@@ -0,0 +1,176 @@
+/* eslint-disable node-core/required-modules, node-core/crypto-check */
+
+'use strict';
+const crypto = require('crypto');
+const net = require('net');
+
+exports.ccs = Buffer.from('140303000101', 'hex');
+
+class TestTLSSocket extends net.Socket {
+ constructor(server_cert) {
+ super();
+ this.server_cert = server_cert;
+ this.version = Buffer.from('0303', 'hex');
+ this.handshake_list = [];
+ // AES128-GCM-SHA256
+ this.ciphers = Buffer.from('000002009c0', 'hex');
+ this.pre_master_secret =
+ Buffer.concat([this.version, crypto.randomBytes(46)]);
+ this.master_secret = null;
+ this.write_seq = 0;
+ this.client_random = crypto.randomBytes(32);
+
+ this.on('handshake', (msg) => {
+ this.handshake_list.push(msg);
+ });
+
+ this.on('server_random', (server_random) => {
+ this.master_secret = PRF12('sha256', this.pre_master_secret,
+ 'master secret',
+ Buffer.concat([this.client_random,
+ server_random]),
+ 48);
+ const key_block = PRF12('sha256', this.master_secret,
+ 'key expansion',
+ Buffer.concat([server_random,
+ this.client_random]),
+ 40);
+ this.client_writeKey = key_block.slice(0, 16);
+ this.client_writeIV = key_block.slice(32, 36);
+ });
+ }
+
+ createClientHello() {
+ const compressions = Buffer.from('0100', 'hex'); // null
+ const msg = addHandshakeHeader(0x01, Buffer.concat([
+ this.version, this.client_random, this.ciphers, compressions
+ ]));
+ this.emit('handshake', msg);
+ return addRecordHeader(0x16, msg);
+ }
+
+ createClientKeyExchange() {
+ const encrypted_pre_master_secret = crypto.publicEncrypt({
+ key: this.server_cert,
+ padding: crypto.constants.RSA_PKCS1_PADDING
+ }, this.pre_master_secret);
+ const length = Buffer.alloc(2);
+ length.writeUIntBE(encrypted_pre_master_secret.length, 0, 2);
+ const msg = addHandshakeHeader(0x10, Buffer.concat([
+ length, encrypted_pre_master_secret]));
+ this.emit('handshake', msg);
+ return addRecordHeader(0x16, msg);
+ }
+
+ createFinished() {
+ const shasum = crypto.createHash('sha256');
+ shasum.update(Buffer.concat(this.handshake_list));
+ const message_hash = shasum.digest();
+ const r = PRF12('sha256', this.master_secret,
+ 'client finished', message_hash, 12);
+ const msg = addHandshakeHeader(0x14, r);
+ this.emit('handshake', msg);
+ return addRecordHeader(0x16, msg);
+ }
+
+ createIllegalHandshake() {
+ const illegal_handshake = Buffer.alloc(5);
+ return addRecordHeader(0x16, illegal_handshake);
+ }
+
+ parseTLSFrame(buf) {
+ let offset = 0;
+ const record = buf.slice(offset, 5);
+ const type = record[0];
+ const length = record.slice(3, 5).readUInt16BE(0);
+ offset += 5;
+ let remaining = buf.slice(offset, offset + length);
+ if (type === 0x16) {
+ do {
+ remaining = this.parseTLSHandshake(remaining);
+ } while (remaining.length > 0);
+ }
+ offset += length;
+ return buf.slice(offset);
+ }
+
+ parseTLSHandshake(buf) {
+ let offset = 0;
+ const handshake_type = buf[offset];
+ if (handshake_type === 0x02) {
+ const server_random = buf.slice(6, 6 + 32);
+ this.emit('server_random', server_random);
+ }
+ offset += 1;
+ const length = buf.readUIntBE(offset, 3);
+ offset += 3;
+ const handshake = buf.slice(0, offset + length);
+ this.emit('handshake', handshake);
+ offset += length;
+ const remaining = buf.slice(offset);
+ return remaining;
+ }
+
+ encrypt(plain) {
+ const type = plain.slice(0, 1);
+ const version = plain.slice(1, 3);
+ const nonce = crypto.randomBytes(8);
+ const iv = Buffer.concat([this.client_writeIV.slice(0, 4), nonce]);
+ const bob = crypto.createCipheriv('aes-128-gcm', this.client_writeKey, iv);
+ const write_seq = Buffer.alloc(8);
+ write_seq.writeUInt32BE(this.write_seq++, 4);
+ const aad = Buffer.concat([write_seq, plain.slice(0, 5)]);
+ bob.setAAD(aad);
+ const encrypted1 = bob.update(plain.slice(5));
+ const encrypted = Buffer.concat([encrypted1, bob.final()]);
+ const tag = bob.getAuthTag();
+ const length = Buffer.alloc(2);
+ length.writeUInt16BE(nonce.length + encrypted.length + tag.length, 0);
+ return Buffer.concat([type, version, length, nonce, encrypted, tag]);
+ }
+}
+
+function addRecordHeader(type, frame) {
+ const record_layer = Buffer.from('0003030000', 'hex');
+ record_layer[0] = type;
+ record_layer.writeUInt16BE(frame.length, 3);
+ return Buffer.concat([record_layer, frame]);
+}
+
+function addHandshakeHeader(type, msg) {
+ const handshake_header = Buffer.alloc(4);
+ handshake_header[0] = type;
+ handshake_header.writeUIntBE(msg.length, 1, 3);
+ return Buffer.concat([handshake_header, msg]);
+}
+
+function PRF12(algo, secret, label, seed, size) {
+ const newSeed = Buffer.concat([Buffer.from(label, 'utf8'), seed]);
+ return P_hash(algo, secret, newSeed, size);
+}
+
+function P_hash(algo, secret, seed, size) {
+ const result = Buffer.alloc(size);
+ let hmac = crypto.createHmac(algo, secret);
+ hmac.update(seed);
+ let a = hmac.digest();
+ let j = 0;
+ while (j < size) {
+ hmac = crypto.createHmac(algo, secret);
+ hmac.update(a);
+ hmac.update(seed);
+ const b = hmac.digest();
+ let todo = b.length;
+ if (j + todo > size) {
+ todo = size - j;
+ }
+ b.copy(result, j, 0, todo);
+ j += todo;
+ hmac = crypto.createHmac(algo, secret);
+ hmac.update(a);
+ a = hmac.digest();
+ }
+ return result;
+}
+
+exports.TestTLSSocket = TestTLSSocket;
diff --git a/test/parallel/test-tls-write-error.js b/test/parallel/test-tls-write-error.js
new file mode 100644
index 0000000000..2783e62d06
--- /dev/null
+++ b/test/parallel/test-tls-write-error.js
@@ -0,0 +1,55 @@
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const { TestTLSSocket, ccs } = require('../common/tls');
+const fixtures = require('../common/fixtures');
+const https = require('https');
+
+// Regression test for an use-after-free bug in the TLS implementation that
+// would occur when `SSL_write()` failed.
+// Refs: https://github.com/nodejs-private/security/issues/189
+
+const server_key = fixtures.readKey('agent1-key.pem');
+const server_cert = fixtures.readKey('agent1-cert.pem');
+
+const opts = {
+ key: server_key,
+ cert: server_cert
+};
+
+const server = https.createServer(opts, (req, res) => {
+ res.write('hello');
+}).listen(0, common.mustCall(() => {
+ const client = new TestTLSSocket(server_cert);
+
+ client.connect({
+ host: 'localhost',
+ port: server.address().port
+ }, common.mustCall(() => {
+ const ch = client.createClientHello();
+ client.write(ch);
+ }));
+
+ client.once('data', common.mustCall((buf) => {
+ let remaining = buf;
+ do {
+ remaining = client.parseTLSFrame(remaining);
+ } while (remaining.length > 0);
+
+ const cke = client.createClientKeyExchange();
+ const finished = client.createFinished();
+ const ill = client.createIllegalHandshake();
+ const frames = Buffer.concat([
+ cke,
+ ccs,
+ client.encrypt(finished),
+ client.encrypt(ill)
+ ]);
+ client.write(frames, common.mustCall(() => {
+ client.end();
+ server.close();
+ }));
+ }));
+}));