UNPKG

@kloak-it/tq-proxy

Version:
373 lines (372 loc) 14.6 kB
"use strict"; /*! * Copyright 2017 QTGate systems Inc. All Rights Reserved. * * QTGate systems Inc. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.blockBuffer16 = exports.printStream = exports.getDecryptClientStreamHttp = exports.decryptStream = exports.encryptStream = exports.openPacket = exports.packetBuffer = exports.decrypt = exports.encrypt = void 0; const crypto_1 = require("crypto"); const async_1 = require("async"); const stream_1 = require("stream"); const safe_1 = __importDefault(require("colors/safe")); const log_1 = require("../GateWay/log"); const EOF = Buffer.from('\r\n\r\n'); const encrypt = (text, masterkey, CallBack) => { let salt = null; (0, async_1.waterfall)([ next => (0, crypto_1.randomBytes)(64, next), (_salt, next) => { salt = _salt; (0, crypto_1.pbkdf2)(masterkey, salt, 2145, 32, 'sha512', next); } ], (err, derivedKey) => { if (err) return CallBack(err); (0, crypto_1.randomBytes)(12, (err1, iv) => { if (err1) return CallBack(err1); const cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', derivedKey, iv); let _text = Buffer.concat([Buffer.alloc(4, 0), text]); _text.writeUInt32BE(text.length, 0); if (text.length < 500) { _text = Buffer.concat([_text, Buffer.alloc(100 + Math.random() * 1000)]); } const encrypted = Buffer.concat([cipher.update(_text), cipher.final()]); const ret = Buffer.concat([salt, iv, cipher.getAuthTag(), encrypted]); return CallBack(null, ret); }); }); }; exports.encrypt = encrypt; /** * Decrypts text by given key * @param String base64 encoded input data * @param Buffer masterkey * @returns String decrypted (original) text */ const decrypt = (data, masterkey, CallBack) => { if (!data || !data.length) return CallBack(new Error('null')); try { // base64 decoding // convert data to buffers const salt = data.slice(0, 64); const iv = data.slice(64, 76); const tag = data.slice(76, 92); const text = data.slice(92); // derive key using; 32 byte key length (0, crypto_1.pbkdf2)(masterkey, salt, 2145, 32, 'sha512', (err, derivedKey) => { if (err) return CallBack(err); // AES 256 GCM Mode try { const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', derivedKey, iv); decipher.setAuthTag(tag); const decrypted = Buffer.concat([decipher.update(text), decipher.final()]); const leng = decrypted.slice(4, 4 + decrypted.readUInt32BE(0)); return CallBack(null, leng); } catch (ex) { console.log(`decrypt catch error [${ex.message}]`); } }); } catch (e) { return CallBack(e); } }; exports.decrypt = decrypt; const packetBuffer = (bit0, _serial, id, buffer) => { const _buffer = Buffer.alloc(6, 0); _buffer.writeUInt8(bit0, 0); _buffer.writeUInt32BE(_serial, 1); const uuid = Buffer.from(id); _buffer.writeUInt8(id.length, 5); if (buffer && buffer.length) return Buffer.concat([_buffer, uuid, buffer]); return Buffer.concat([_buffer, uuid]); }; exports.packetBuffer = packetBuffer; const openPacket = (buffer) => { const idLength = buffer.readUInt8(5); return { command: buffer.readUInt8(0), serial: buffer.readUInt32BE(1), uuid: buffer.toString('utf8', 6, 6 + idLength), buffer: buffer.slice(6 + idLength) }; }; exports.openPacket = openPacket; const HTTP_HEADER = Buffer.from(`HTTP/1.1 200 OK\r\nDate: ${new Date().toUTCString()}\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\nVary: Accept-Encoding\r\n\r\n`, 'utf8'); const HTTP_EOF = Buffer.from('\r\n\r\n', 'utf8'); class encryptStream extends stream_1.Transform { BlockBuffer(_buf) { return Buffer.from(_buf.length.toString(16).toUpperCase() + '\r\n', 'utf8'); } init(callback) { return (0, async_1.waterfall)([ next => (0, crypto_1.randomBytes)(64, next), (_salt, next) => { this.salt = _salt; (0, crypto_1.randomBytes)(12, next); }, (_iv, next) => { this.iv = _iv; (0, crypto_1.pbkdf2)(this.password, this.salt, 2145, 32, 'sha512', next); } ], (err, derivedKey) => { this.derivedKey = derivedKey; return callback(err); }); } constructor(id, password, random, httpHeader, debug) { super(); this.id = id; this.password = password; this.random = random; this.httpHeader = httpHeader; this.debug = debug; this.first = 0; this.derivedKey = null; } _transform(chunk, encode, cb) { if (!this.derivedKey) { return this.init(() => { return this._transform(chunk, encode, cb); }); } this.first++; if (this.debug) { (0, log_1.logger)(`${this.id} encryptStream get DATA [${chunk.length}] 【${safe_1.default.red(this.first.toString())}】`); (0, log_1.hexDebug)(chunk); } if (this.first < 5) { const cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', this.derivedKey, this.iv); let _text = Buffer.concat([Buffer.alloc(4, 0), chunk]); _text.writeUInt32BE(chunk.length, 0); if (chunk.length < this.random) { _text = Buffer.concat([_text, Buffer.allocUnsafe(Math.random() * 1000)]); } const _buf = Buffer.concat([cipher.update(_text), cipher.final()]); const getAuthTag = cipher.getAuthTag(); const _buf1 = Buffer.concat([getAuthTag, _buf]); if (this.first === 1) { const black = Buffer.concat([this.salt, this.iv, _buf1]).toString('base64'); // 76 if (!this.httpHeader) { const _buf4 = Buffer.from(black, 'base64'); return cb(null, Buffer.concat([HTTP_HEADER, this.BlockBuffer(_buf4), _buf4, EOF])); } const _buf2 = this.httpHeader(black); if (this.debug) { (0, log_1.logger)(safe_1.default.blue(`${this.id} encryptStream FIRST push data---> [${_buf2.length}]`)); (0, log_1.hexDebug)(_buf2); } return cb(null, _buf2); } const _buf2 = _buf1.toString('base64'); this.debug ? (0, log_1.logger)(safe_1.default.blue(`encryptStream [${this.id}: step ${safe_1.default.red(this.first.toString())}] push data---> [${_buf2.length}]`)) : null; return cb(null, Buffer.from(_buf2 + EOF)); } const _buffer = Buffer.from(chunk.toString('base64') + EOF); if (this.debug) { (0, log_1.logger)(`${this.id} encryptStream DIRECT send data to Gateway [${_buffer.length}] 【${safe_1.default.red(this.first.toString())}】`); (0, log_1.hexDebug)(_buffer); } return cb(null, _buffer); } } exports.encryptStream = encryptStream; class decryptStream extends stream_1.Transform { _decrypt(_text) { const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', this.derivedKey, this.iv); decipher.setAuthTag(_text.slice(0, 16)); try { const _buf = Buffer.concat([decipher.update(_text.slice(16)), decipher.final()]); const leng = _buf.slice(4, 4 + _buf.readUInt32BE(0)); if (leng && leng.length) { return leng; } return Buffer.allocUnsafe(0); } catch (e) { console.log('class decryptStream _decrypt error:', e.message); return Buffer.allocUnsafe(0); } } _First(chunk, CallBack) { this.salt = chunk.slice(0, 64); this.iv = chunk.slice(64, 76); return (0, crypto_1.pbkdf2)(this.password, this.salt, 2145, 32, 'sha512', (err, derivedKey) => { if (err) { console.log(`${this.id} decryptStream crypto.pbkdf2 ERROR: ${err.message}`); return CallBack(err); } this.derivedKey = derivedKey; const text = this._decrypt(chunk.slice(76)); if (!text.length) { (0, log_1.logger)(safe_1.default.red(`decryptStream First get empty DATA send ERROR`)); return CallBack(new Error('lenth = 0')); } if (this.debug) { (0, log_1.logger)(safe_1.default.green(`decryptStream First <-- from gateway`)); (0, log_1.hexDebug)(text); } return CallBack(null, text); }); } constructor(password, id, debug) { super(); this.password = password; this.id = id; this.debug = debug; this.first = 0; this.derivedKey = null; debug ? (0, log_1.logger)(safe_1.default.blue(`new decryptStream`)) : null; } _transform(chunk, encode, cb) { this.first++; if (this.first < 5) { if (!this.derivedKey) { return this._First(chunk, cb); } const text = this._decrypt(chunk); if (!text.length) { (0, log_1.logger)(safe_1.default.red(`decryptStream get empty DATA send ERROR`)); return cb(new Error('lenth = 0')); } if (this.debug) { (0, log_1.logger)(safe_1.default.green(`decryptStream <-- from gateway 【${safe_1.default.red(this.first.toString())}】decrypted`)); (0, log_1.hexDebug)(text); } return cb(null, text); } if (this.debug) { (0, log_1.logger)(safe_1.default.green(`decryptStream <-- from gateway 【${safe_1.default.red(this.first.toString())}】direct`)); (0, log_1.hexDebug)(chunk); } return cb(null, chunk); } } exports.decryptStream = decryptStream; class encode extends stream_1.Transform { constructor() { super(); this.kk = null; } _transform(chunk, encode, cb) { let start = chunk.slice(0); while (start.length) { const point = start.indexOf(0x0a); if (point < 0) { this.push(start); break; } const _buf = start.slice(0, point); this.push(_buf); start = start.slice(point + 1); } return cb(); } } class encodeHex extends stream_1.Transform { constructor() { super(); } _transform(chunk, encode, cb) { return cb(null, chunk.toString('utf8')); } } class getDecryptClientStreamHttp extends stream_1.Transform { constructor(debug, id) { super(); this.debug = debug; this.id = id; this.first = 0; this.text = ''; } _transform(chunk, encode, cb) { this.text += chunk.toString(); const line = this.text.split('\r\n\r\n'); if (line.length < 2) { return cb(); } if (this.debug) { (0, log_1.logger)(safe_1.default.gray(this.text)); } let currentBlock = line.shift(); this.text = line.join('\r\n\r\n'); this.first++; if (this.first === 1) { const headers = currentBlock.split('\r\n'); const command = headers[0].split(' '); if (command[1] !== '200') { (0, log_1.logger)(safe_1.default.red(`${this.id} !200 ERROR getDecryptClientStreamHttp <--- from Gateway`)); return cb(new Error('Gateway return !200')); } return this._transform(Buffer.from(''), encode, cb); } const _block = Buffer.from(currentBlock, 'base64'); if (this.debug) { (0, log_1.logger)(safe_1.default.blue(`${this.id} getDecryptClientStreamHttp <--- from Gateway [${chunk.length}] 【${safe_1.default.red(this.first.toString())}】`)); (0, log_1.logger)(safe_1.default.yellow(currentBlock)); } this.push(_block); return this._transform(Buffer.from(''), encode, cb); } _flush(cb) { if (this.text.length) { const _block = Buffer.from(this.text, 'base64'); (0, log_1.logger)(safe_1.default.red(`${this.id} getDecryptClientStreamHttp on _flush`)); if (this.debug) { (0, log_1.logger)(safe_1.default.yellow(_block.toString())); } this.push(_block); } cb(); } } exports.getDecryptClientStreamHttp = getDecryptClientStreamHttp; class printStream extends stream_1.Transform { constructor(headString) { super(); this.headString = headString; } _transform(chunk, encode, cb) { console.log(this.headString); console.log(chunk.toString('hex')); console.log(this.headString); return cb(null, chunk); } } exports.printStream = printStream; class blockBuffer16 extends stream_1.Writable { constructor(socket) { super(); this.socket = socket; this.socket.pause(); } _write(chunk, encoding, cb) { if (this.socket.writable) { console.log('blockBuffer16 socket.write :', chunk.length); this.socket.write(chunk); this.socket.resume(); return cb(); } console.log('blockBuffer16 socket.writable false'); return cb(); } } exports.blockBuffer16 = blockBuffer16;