|
| 1 | +/* eslint-disable required-modules, crypto-check */ |
| 2 | + |
| 3 | +'use strict'; |
| 4 | +const crypto = require('crypto'); |
| 5 | +const net = require('net'); |
| 6 | + |
| 7 | +exports.ccs = Buffer.from('140303000101', 'hex'); |
| 8 | + |
| 9 | +class TestTLSSocket extends net.Socket { |
| 10 | + constructor(server_cert) { |
| 11 | + super(); |
| 12 | + this.server_cert = server_cert; |
| 13 | + this.version = Buffer.from('0303', 'hex'); |
| 14 | + this.handshake_list = []; |
| 15 | + // AES128-GCM-SHA256 |
| 16 | + this.ciphers = Buffer.from('000002009c0', 'hex'); |
| 17 | + this.pre_master_secret = |
| 18 | + Buffer.concat([this.version, crypto.randomBytes(46)]); |
| 19 | + this.master_secret = null; |
| 20 | + this.write_seq = 0; |
| 21 | + this.client_random = crypto.randomBytes(32); |
| 22 | + |
| 23 | + this.on('handshake', (msg) => { |
| 24 | + this.handshake_list.push(msg); |
| 25 | + }); |
| 26 | + |
| 27 | + this.on('server_random', (server_random) => { |
| 28 | + this.master_secret = PRF12('sha256', this.pre_master_secret, |
| 29 | + 'master secret', |
| 30 | + Buffer.concat([this.client_random, |
| 31 | + server_random]), |
| 32 | + 48); |
| 33 | + const key_block = PRF12('sha256', this.master_secret, |
| 34 | + 'key expansion', |
| 35 | + Buffer.concat([server_random, |
| 36 | + this.client_random]), |
| 37 | + 40); |
| 38 | + this.client_writeKey = key_block.slice(0, 16); |
| 39 | + this.client_writeIV = key_block.slice(32, 36); |
| 40 | + }); |
| 41 | + } |
| 42 | + |
| 43 | + createClientHello() { |
| 44 | + const compressions = Buffer.from('0100', 'hex'); // null |
| 45 | + const msg = addHandshakeHeader(0x01, Buffer.concat([ |
| 46 | + this.version, this.client_random, this.ciphers, compressions |
| 47 | + ])); |
| 48 | + this.emit('handshake', msg); |
| 49 | + return addRecordHeader(0x16, msg); |
| 50 | + } |
| 51 | + |
| 52 | + createClientKeyExchange() { |
| 53 | + const encrypted_pre_master_secret = crypto.publicEncrypt({ |
| 54 | + key: this.server_cert, |
| 55 | + padding: crypto.constants.RSA_PKCS1_PADDING |
| 56 | + }, this.pre_master_secret); |
| 57 | + const length = Buffer.alloc(2); |
| 58 | + length.writeUIntBE(encrypted_pre_master_secret.length, 0, 2); |
| 59 | + const msg = addHandshakeHeader(0x10, Buffer.concat([ |
| 60 | + length, encrypted_pre_master_secret])); |
| 61 | + this.emit('handshake', msg); |
| 62 | + return addRecordHeader(0x16, msg); |
| 63 | + } |
| 64 | + |
| 65 | + createFinished() { |
| 66 | + const shasum = crypto.createHash('sha256'); |
| 67 | + shasum.update(Buffer.concat(this.handshake_list)); |
| 68 | + const message_hash = shasum.digest(); |
| 69 | + const r = PRF12('sha256', this.master_secret, |
| 70 | + 'client finished', message_hash, 12); |
| 71 | + const msg = addHandshakeHeader(0x14, r); |
| 72 | + this.emit('handshake', msg); |
| 73 | + return addRecordHeader(0x16, msg); |
| 74 | + } |
| 75 | + |
| 76 | + createIllegalHandshake() { |
| 77 | + const illegal_handshake = Buffer.alloc(5); |
| 78 | + return addRecordHeader(0x16, illegal_handshake); |
| 79 | + } |
| 80 | + |
| 81 | + parseTLSFrame(buf) { |
| 82 | + let offset = 0; |
| 83 | + const record = buf.slice(offset, 5); |
| 84 | + const type = record[0]; |
| 85 | + const length = record.slice(3, 5).readUInt16BE(0); |
| 86 | + offset += 5; |
| 87 | + let remaining = buf.slice(offset, offset + length); |
| 88 | + if (type === 0x16) { |
| 89 | + do { |
| 90 | + remaining = this.parseTLSHandshake(remaining); |
| 91 | + } while (remaining.length > 0); |
| 92 | + } |
| 93 | + offset += length; |
| 94 | + return buf.slice(offset); |
| 95 | + } |
| 96 | + |
| 97 | + parseTLSHandshake(buf) { |
| 98 | + let offset = 0; |
| 99 | + const handshake_type = buf[offset]; |
| 100 | + if (handshake_type === 0x02) { |
| 101 | + const server_random = buf.slice(6, 6 + 32); |
| 102 | + this.emit('server_random', server_random); |
| 103 | + } |
| 104 | + offset += 1; |
| 105 | + const length = buf.readUIntBE(offset, 3); |
| 106 | + offset += 3; |
| 107 | + const handshake = buf.slice(0, offset + length); |
| 108 | + this.emit('handshake', handshake); |
| 109 | + offset += length; |
| 110 | + const remaining = buf.slice(offset); |
| 111 | + return remaining; |
| 112 | + } |
| 113 | + |
| 114 | + encrypt(plain) { |
| 115 | + const type = plain.slice(0, 1); |
| 116 | + const version = plain.slice(1, 3); |
| 117 | + const nonce = crypto.randomBytes(8); |
| 118 | + const iv = Buffer.concat([this.client_writeIV.slice(0, 4), nonce]); |
| 119 | + const bob = crypto.createCipheriv('aes-128-gcm', this.client_writeKey, iv); |
| 120 | + const write_seq = Buffer.alloc(8); |
| 121 | + write_seq.writeUInt32BE(this.write_seq++, 4); |
| 122 | + const aad = Buffer.concat([write_seq, plain.slice(0, 5)]); |
| 123 | + bob.setAAD(aad); |
| 124 | + const encrypted1 = bob.update(plain.slice(5)); |
| 125 | + const encrypted = Buffer.concat([encrypted1, bob.final()]); |
| 126 | + const tag = bob.getAuthTag(); |
| 127 | + const length = Buffer.alloc(2); |
| 128 | + length.writeUInt16BE(nonce.length + encrypted.length + tag.length, 0); |
| 129 | + return Buffer.concat([type, version, length, nonce, encrypted, tag]); |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +function addRecordHeader(type, frame) { |
| 134 | + const record_layer = Buffer.from('0003030000', 'hex'); |
| 135 | + record_layer[0] = type; |
| 136 | + record_layer.writeUInt16BE(frame.length, 3); |
| 137 | + return Buffer.concat([record_layer, frame]); |
| 138 | +} |
| 139 | + |
| 140 | +function addHandshakeHeader(type, msg) { |
| 141 | + const handshake_header = Buffer.alloc(4); |
| 142 | + handshake_header[0] = type; |
| 143 | + handshake_header.writeUIntBE(msg.length, 1, 3); |
| 144 | + return Buffer.concat([handshake_header, msg]); |
| 145 | +} |
| 146 | + |
| 147 | +function PRF12(algo, secret, label, seed, size) { |
| 148 | + const newSeed = Buffer.concat([Buffer.from(label, 'utf8'), seed]); |
| 149 | + return P_hash(algo, secret, newSeed, size); |
| 150 | +} |
| 151 | + |
| 152 | +function P_hash(algo, secret, seed, size) { |
| 153 | + const result = Buffer.alloc(size); |
| 154 | + let hmac = crypto.createHmac(algo, secret); |
| 155 | + hmac.update(seed); |
| 156 | + let a = hmac.digest(); |
| 157 | + let j = 0; |
| 158 | + while (j < size) { |
| 159 | + hmac = crypto.createHmac(algo, secret); |
| 160 | + hmac.update(a); |
| 161 | + hmac.update(seed); |
| 162 | + const b = hmac.digest(); |
| 163 | + let todo = b.length; |
| 164 | + if (j + todo > size) { |
| 165 | + todo = size - j; |
| 166 | + } |
| 167 | + b.copy(result, j, 0, todo); |
| 168 | + j += todo; |
| 169 | + hmac = crypto.createHmac(algo, secret); |
| 170 | + hmac.update(a); |
| 171 | + a = hmac.digest(); |
| 172 | + } |
| 173 | + return result; |
| 174 | +} |
| 175 | + |
| 176 | +exports.TestTLSSocket = TestTLSSocket; |
0 commit comments