UNPKG

vpn.email.server.gfw

Version:
432 lines (354 loc) 11.7 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. */ import * as crypto from 'crypto' import * as Async from 'async' import * as Stream from 'stream' const EOF = Buffer.from ( '\r\n\r\n', 'utf8' ) import * as fs from 'fs' export interface packetBuffer { command: number; uuid: string; buffer: Buffer; serial: number } export interface pairConnect { serverListen: string; clientListen: string; } export const encrypt = ( text: Buffer, masterkey: string, 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 */ export const decrypt = ( data: Buffer, 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 ) } } export const packetBuffer = ( bit0: number, _serial: number, id: string, buffer: 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 ]) } export const openPacket = ( buffer: 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' ) export class encryptStream extends Stream.Transform { private salt: Buffer private iv: Buffer public ERR: Error = null private first = true public derivedKey: Buffer = null private BlockBuffer ( _buf: Buffer ) { return Buffer.from( _buf.length.toString( 16 ).toUpperCase() + '\r\n', 'utf8' ) } constructor ( private password: string, private random: number, private httpHeader : ( str: string ) => Buffer, CallBack ) { super () 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 ) }) } public _transform ( chunk: Buffer, 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 ])) } } export class decryptStream extends Stream.Transform { private first = true private salt: Buffer private iv: Buffer private derivedKey: Buffer = null private decipher: crypto.Decipher = null public firstProcess ( chunk: Buffer, CallBack: ( err?: Error, text?: Buffer ) => void ) { 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 ) } }) } constructor ( private password: string ) { super () } public _transform ( chunk: Buffer, 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 () } } } class encode extends Stream.Transform { constructor () { super ()} private kk = null public _transform ( chunk: Buffer, 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 ()} public _transform ( chunk: Buffer, encode, cb ) { return cb ( null, chunk.toString ('utf8')) } } export class getDecryptClientStreamFromHttp extends Stream.Transform { private first = true private text = '' constructor () { super ()} public getBlock ( block: string ) { 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 } public _transform ( chunk: Buffer, 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 () } } export class getDecrypGatwayStreamFromHttp extends Stream.Transform { private text = '' private formatErr ( text: string ) { const log = 'getDecryptRequestStreamFromHttp format ERROR:\n*****************************\n' + text + '\r\n' console.log ( log ) this.saveLog ( log ) } constructor ( private saveLog: ( str: string ) => void ) { super ()} public _transform ( chunk: Buffer, 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 () } }