UNPKG

vpn.email.server.gfw

Version:
359 lines (358 loc) 13.3 kB
/*! * Copyright 2017 Vpn.Email network security technology Canada Inc. All Rights Reserved. * * Vpn.Email network technolog Canada Ltd. * 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. */ "use strict"; const crypto = require("crypto"); const Async = require("async"); const Stream = require("stream"); const EOF = Buffer.from('\r\n\r\n', 'utf8'); exports.encrypt = (text, masterkey, CallBack) => { let salt = null; Async.waterfall([ next => crypto.randomBytes(64, next), (_salt, next) => { salt = _salt; crypto.pbkdf2(masterkey, salt, 2145, 32, 'sha512', next); } ], (err, derivedKey) => { if (err) return CallBack(err); crypto.randomBytes(12, (err1, iv) => { if (err1) return CallBack(err1); const cipher = crypto.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); }); }); }; /** * Decrypts text by given key * @param String base64 encoded input data * @param Buffer masterkey * @returns String decrypted (original) text */ exports.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 crypto.pbkdf2(masterkey, salt, 2145, 32, 'sha512', (err, derivedKey) => { if (err) return CallBack(err); // AES 256 GCM Mode try { const decipher = crypto.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.packetBuffer = (bit0, _serial, id, buffer) => { const _buffer = new Buffer(6); _buffer.fill(0); _buffer.writeUInt8(bit0, 0); _buffer.writeUInt32BE(_serial, 1); const uuid = new Buffer(id, 'utf8'); _buffer.writeUInt8(id.length, 5); if (buffer && buffer.length) return Buffer.concat([_buffer, uuid, buffer]); return Buffer.concat([_buffer, uuid]); }; exports.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) }; }; 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.Transform { constructor(password, random, httpHeader, CallBack) { super(); this.password = password; this.random = random; this.httpHeader = httpHeader; this.ERR = null; this.first = true; this.derivedKey = null; Async.waterfall([ next => crypto.randomBytes(64, next), (_salt, next) => { this.salt = _salt; crypto.randomBytes(12, next); }, (_iv, next) => { this.iv = _iv; crypto.pbkdf2(password, this.salt, 2145, 32, 'sha512', next); } ], (err, derivedKey) => { if (err) return this.ERR = err; this.derivedKey = derivedKey; return CallBack(err); }); } BlockBuffer(_buf) { return Buffer.from(_buf.length.toString(16).toUpperCase() + '\r\n', 'utf8'); } _transform(chunk, encode, cb) { const cipher = crypto.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 _buf1 = Buffer.concat([cipher.getAuthTag(), _buf]); if (this.first) { this.first = false; const black = Buffer.concat([this.salt, this.iv, _buf1]).toString('base64'); if (!this.httpHeader) { const _buf4 = Buffer.from(black, 'utf8'); return cb(null, Buffer.concat([HTTP_HEADER, this.BlockBuffer(_buf4), _buf4, EOF])); } return cb(null, this.httpHeader(black)); } const _buf2 = _buf1.toString('base64'); if (this.httpHeader) { return cb(null, this.httpHeader(_buf2)); } const _buf3 = Buffer.from(_buf2, 'utf8'); return cb(null, Buffer.concat([this.BlockBuffer(_buf3), _buf3, EOF])); } } exports.encryptStream = encryptStream; class decryptStream extends Stream.Transform { constructor(password) { super(); this.password = password; this.first = true; this.derivedKey = null; this.decipher = null; } firstProcess(chunk, CallBack) { this.first = false; this.salt = chunk.slice(0, 64); this.iv = chunk.slice(64, 76); return crypto.pbkdf2(this.password, this.salt, 2145, 32, 'sha512', (err, derivedKey) => { if (err) { console.log(`decryptStream crypto.pbkdf2 ERROR: ${err.message}`); return CallBack(err); } this.derivedKey = derivedKey; const _buf = chunk.slice(76); this.decipher = crypto.createDecipheriv('aes-256-gcm', this.derivedKey, this.iv); this.decipher.setAuthTag(_buf.slice(0, 16)); try { const _Buf = Buffer.concat([this.decipher.update(_buf.slice(16)), this.decipher.final()]); return CallBack(null, _Buf.slice(4, 4 + _Buf.readUInt32BE(0))); } catch (e) { return CallBack(e); } }); } _transform(chunk, encode, cb) { if (this.first) { return this.firstProcess(chunk, cb); } this.decipher = crypto.createDecipheriv('aes-256-gcm', this.derivedKey, this.iv); this.decipher.setAuthTag(chunk.slice(0, 16)); try { const _Buf = Buffer.concat([this.decipher.update(chunk.slice(16)), this.decipher.final()]); return cb(null, _Buf.slice(4, 4 + _Buf.readUInt32BE(0))); } catch (e) { console.log('class decryptStream _decrypt error:', e.message); return cb(); } } } exports.decryptStream = decryptStream; class encode extends Stream.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.Transform { constructor() { super(); } _transform(chunk, encode, cb) { return cb(null, chunk.toString('utf8')); } } class getDecryptClientStreamFromHttp extends Stream.Transform { constructor() { super(); this.first = true; this.text = ''; } getBlock(block) { const uu = block.split('\r\n'); if (uu.length !== 2) { return null; } const length = parseInt(uu[0], 16); const text = uu[1]; if (length === text.length) { return text; } console.log(`length[${length}] !== text.length[${text.length}]`); return null; } _transform(chunk, encode, cb) { this.text += chunk.toString('utf8'); const line = this.text.split('\r\n\r\n'); while (this.first && line.length > 1 || !this.first && line.length) { if (this.first) { this.first = false; line.shift(); } const _text = line.shift(); if (!_text.length) continue; const text = this.getBlock(_text); if (!text) { // middle data can't get block if (line.length) { console.log('getDecryptStreamFromHttp have ERROR:\n*****************************\n'); console.log(text); return this.unpipe(); } this.text = _text; return cb(); } this.push(Buffer.from(text, 'base64')); } this.text = ''; return cb(); } } exports.getDecryptClientStreamFromHttp = getDecryptClientStreamFromHttp; class getDecrypGatwayStreamFromHttp extends Stream.Transform { constructor(saveLog) { super(); this.saveLog = saveLog; this.text = ''; } formatErr(text) { const log = 'getDecryptRequestStreamFromHttp format ERROR:\n*****************************\n' + text + '\r\n'; console.log(log); this.saveLog(log); } _transform(chunk, encode, cb) { this.text += chunk.toString('utf8'); const block = this.text.split('\r\n\r\n'); while (block.length > 1) { const blockText = block.shift(); if (!blockText.length) continue; if (/^GET /i.test(blockText)) { const _line = blockText.split('\r\n')[0]; const _url = _line.split(' '); if (_url.length < 2) { if (block.length > 1) { this.formatErr(blockText); return this.unpipe(); } this.text = blockText; return cb(); } const text = Buffer.from(_url[1].slice(1), 'base64'); this.push(text); continue; } if (/^POST /i.test(blockText)) { if (block.length > 0) { const header = blockText.split('\r\n'); const _length = header.findIndex(n => { return /^Content-Length: /i.test(n); }); if (_length === -1) { this.formatErr(blockText); return this.unpipe(); } const lengthString = header[_length].split(' '); if (lengthString.length !== 2) { this.formatErr(blockText); return this.unpipe(); } const length = parseInt(lengthString[1]); if (!length) { this.formatErr(blockText); return this.unpipe(); } const _text = block.shift(); if (length !== _text.length) { const log = `${blockText}\r\n\r\n${_text}`; if (block.length > 0) { this.formatErr(log); return this.unpipe(); } this.text = log; return cb(); } this.push(Buffer.from(_text, 'base64')); continue; } this.text = blockText; return cb(); } } this.text = block[0]; return cb(); } } exports.getDecrypGatwayStreamFromHttp = getDecrypGatwayStreamFromHttp;