diff --git a/lib/core/client-proxy.js b/lib/core/client-proxy.js index acfd053..69e0ac9 100644 --- a/lib/core/client-proxy.js +++ b/lib/core/client-proxy.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.ClientProxy=undefined;var _utils=require('../utils');var _socks=require('../proxies/socks5');var _socks2=require('../proxies/socks4');var _http=require('../proxies/http');var _common=require('../proxies/common');class ClientProxy{constructor(props){this._socksTcpReady=false;this._socksUdpReady=false;this._httpReady=false;this.onHandshakeDone=props.onHandshakeDone}isDone(){return[this._socksTcpReady,this._socksUdpReady,this._httpReady].some(v=>!!v)}makeHandshake(socket,buffer){this._trySocksHandshake(socket,buffer);if(!this.isDone()){this._tryHttpHandshake(socket,buffer)}}_trySocksHandshake(socket,buffer){if(!this.isDone()){this._trySocks5Handshake(socket,buffer)}if(!this.isDone()){this._trySocks4Handshake(socket,buffer)}}_trySocks4Handshake(socket,buffer){const request=_socks2.RequestMessage.parse(buffer);if(request!==null){const CMD=request.CMD,DSTIP=request.DSTIP,DSTADDR=request.DSTADDR,DSTPORT=request.DSTPORT;if(CMD===_common.REQUEST_COMMAND_CONNECT){const addr={type:DSTADDR.length>0?_common.ATYP_DOMAIN:_common.ATYP_V4,host:DSTADDR.length>0?DSTADDR:DSTIP,port:DSTPORT};this.onHandshakeDone(addr,()=>{const message=new _socks2.ReplyMessage({CMD:_common.REPLY_GRANTED});socket.write(message.toBuffer());this._socksTcpReady=true})}}}_trySocks5Handshake(socket,buffer){const identifier=_socks.IdentifierMessage.parse(buffer);if(identifier!==null){const message=new _socks.SelectMessage;socket.write(message.toBuffer());return}const request=_socks.RequestMessage.parse(buffer);if(request!==null){const type=request.CMD;switch(type){case _common.REQUEST_COMMAND_UDP:case _common.REQUEST_COMMAND_CONNECT:{const addr={type:request.ATYP,host:request.DSTADDR,port:request.DSTPORT};this.onHandshakeDone(addr,()=>{const message=new _socks.ReplyMessage({REP:_common.REPLY_SUCCEEDED});socket.write(message.toBuffer());if(type===_common.REQUEST_COMMAND_CONNECT){this._socksTcpReady=true}else{this._socksUdpReady=true}});break}default:{const message=new _socks.ReplyMessage({REP:_common.REPLY_COMMAND_NOT_SUPPORTED});socket.write(message.toBuffer());break}}}}_tryHttpHandshake(socket,buffer){const request=_http.HttpRequestMessage.parse(buffer);if(request!==null){const METHOD=request.METHOD,HOST=request.HOST;const addr=_utils.Utils.parseURI(HOST.toString());this.onHandshakeDone(addr,onForward=>{if(METHOD.toString()==='CONNECT'){const message=new _http.ConnectReplyMessage;socket.write(message.toBuffer())}else{onForward(buffer)}this._httpReady=true})}}}exports.ClientProxy=ClientProxy; \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.ClientProxy=undefined;var _blinksocksUtils=require('blinksocks-utils');var _socks=require('../proxies/socks5');var _socks2=require('../proxies/socks4');var _http=require('../proxies/http');var _common=require('../proxies/common');class ClientProxy{constructor(props){this._socksTcpReady=false;this._socksUdpReady=false;this._httpReady=false;this.onHandshakeDone=props.onHandshakeDone}isDone(){return[this._socksTcpReady,this._socksUdpReady,this._httpReady].some(v=>!!v)}makeHandshake(socket,buffer){this._trySocksHandshake(socket,buffer);if(!this.isDone()){this._tryHttpHandshake(socket,buffer)}}_trySocksHandshake(socket,buffer){if(!this.isDone()){this._trySocks5Handshake(socket,buffer)}if(!this.isDone()){this._trySocks4Handshake(socket,buffer)}}_trySocks4Handshake(socket,buffer){const request=_socks2.RequestMessage.parse(buffer);if(request!==null){const CMD=request.CMD,DSTIP=request.DSTIP,DSTADDR=request.DSTADDR,DSTPORT=request.DSTPORT;if(CMD===_common.REQUEST_COMMAND_CONNECT){const addr={type:DSTADDR.length>0?_common.ATYP_DOMAIN:_common.ATYP_V4,host:DSTADDR.length>0?DSTADDR:DSTIP,port:DSTPORT};this.onHandshakeDone(addr,()=>{const message=new _socks2.ReplyMessage({CMD:_common.REPLY_GRANTED});socket.write(message.toBuffer());this._socksTcpReady=true})}}}_trySocks5Handshake(socket,buffer){const identifier=_socks.IdentifierMessage.parse(buffer);if(identifier!==null){const message=new _socks.SelectMessage;socket.write(message.toBuffer());return}const request=_socks.RequestMessage.parse(buffer);if(request!==null){const type=request.CMD;switch(type){case _common.REQUEST_COMMAND_UDP:case _common.REQUEST_COMMAND_CONNECT:{const addr={type:request.ATYP,host:request.DSTADDR,port:request.DSTPORT};this.onHandshakeDone(addr,()=>{const message=new _socks.ReplyMessage({REP:_common.REPLY_SUCCEEDED});socket.write(message.toBuffer());if(type===_common.REQUEST_COMMAND_CONNECT){this._socksTcpReady=true}else{this._socksUdpReady=true}});break}default:{const message=new _socks.ReplyMessage({REP:_common.REPLY_COMMAND_NOT_SUPPORTED});socket.write(message.toBuffer());break}}}}_tryHttpHandshake(socket,buffer){const request=_http.HttpRequestMessage.parse(buffer);if(request!==null){const METHOD=request.METHOD,HOST=request.HOST;const addr=(0,_blinksocksUtils.parseURI)(HOST.toString());this.onHandshakeDone(addr,onForward=>{if(METHOD.toString()==='CONNECT'){const message=new _http.ConnectReplyMessage;socket.write(message.toBuffer())}else{onForward(buffer)}this._httpReady=true})}}}exports.ClientProxy=ClientProxy; \ No newline at end of file diff --git a/lib/core/config.js b/lib/core/config.js index c7ef305..820b374 100755 --- a/lib/core/config.js +++ b/lib/core/config.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.Config=exports.DEFAULT_LOG_LEVEL=undefined;var _fs=require('fs');var _fs2=_interopRequireDefault(_fs);var _winston=require('winston');var _winston2=_interopRequireDefault(_winston);var _utils=require('../utils');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const DEFAULT_LOG_LEVEL=exports.DEFAULT_LOG_LEVEL='error';class Config{static init(json){if(typeof json!=='object'||Array.isArray(json)){throw Error('Invalid configuration file')}if(typeof json.host!=='string'||json.host===''){throw Error('\'host\' must be provided and is not empty')}global.__LOCAL_HOST__=json.host;if(!_utils.Utils.isValidPort(json.port)){throw Error('\'port\' is invalid')}global.__LOCAL_PORT__=json.port;if(typeof json.servers!=='undefined'){if(!Array.isArray(json.servers)){throw Error('\'servers\' must be provided as an array')}const servers=json.servers.filter(server=>server.enabled===true);if(servers.length<1){throw Error('\'servers\' must have at least one enabled item')}global.__SERVERS__=servers;global.__IS_SERVER__=false;global.__IS_CLIENT__=true}else{global.__IS_SERVER__=true;global.__IS_CLIENT__=false;this.initServer(json)}if(typeof json.redirect==='string'&&json.redirect!==''){const address=json.redirect.split(':');if(address.length!==2||!_utils.Utils.isValidPort(+address[1])){throw Error('\'redirect\' is an invalid address')}}global.__REDIRECT__=json.redirect;if(typeof json.timeout!=='number'){throw Error('\'timeout\' must be a number')}if(json.timeout<1){throw Error('\'timeout\' must be greater than 0')}if(json.timeout<60){console.warn(`==> [config] 'timeout' is too short, is ${json.timeout}s expected?`)}global.__TIMEOUT__=json.timeout;global.__PROFILE__=!!json.profile;global.__IS_WATCH__=!!json.watch;global.__LOG_LEVEL__=this.setUpLogger(json.log_level||DEFAULT_LOG_LEVEL);global.__ALL_CONFIG__=json}static initServer(server){if(typeof server.transport!=='string'){throw Error('\'server.transport\' must be a string')}if(!['tcp','udp'].includes(server.transport.toLowerCase())){throw Error('\'server.transport\' must be one of "tcp" or "udp"')}global.__TRANSPORT__=server.transport;if(typeof server.host!=='string'||server.host===''){throw Error('\'server.host\' must be provided and is not empty')}global.__SERVER_HOST__=server.host;if(!_utils.Utils.isValidPort(server.port)){throw Error('\'server.port\' is invalid')}global.__SERVER_PORT__=server.port;if(typeof server.key!=='string'){throw Error('\'server.key\' must be a string')}if(server.key===''){throw Error('\'server.key\' cannot be empty')}global.__KEY__=server.key;if(!Array.isArray(server.presets)){throw Error('\'server.presets\' must be an array')}if(server.presets.length<1){throw Error('\'server.presets\' must contain at least one preset')}var _iteratorNormalCompletion=true;var _didIteratorError=false;var _iteratorError=undefined;try{for(var _iterator=server.presets[Symbol.iterator](),_step;!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=true){const preset=_step.value;const name=preset.name,params=preset.params;if(typeof name==='undefined'){throw Error('\'server.presets[].name\' must be a string')}if(name===''){throw Error('\'server.presets[].name\' cannot be empty')}const ps=require(`../presets/${preset.name}`).default;if(name!==server.presets[0].name){delete new ps(params||{})}}}catch(err){_didIteratorError=true;_iteratorError=err}finally{try{if(!_iteratorNormalCompletion&&_iterator.return){_iterator.return()}}finally{if(_didIteratorError){throw _iteratorError}}}global.__PRESETS__=server.presets}static setUpLogger(level=''){try{_fs2.default.lstatSync('logs')}catch(err){if(err.code==='ENOENT'){_fs2.default.mkdirSync('logs')}}let _level=level.toLowerCase();switch(_level){case'silly':case'debug':case'verbose':case'info':case'warn':case'error':break;default:_level=DEFAULT_LOG_LEVEL;break;}_winston2.default.configure({level:_level,transports:[new _winston2.default.transports.Console({colorize:true,prettyPrint:true}),new _winston2.default.transports.File({filename:`logs/blinksocks-${__IS_CLIENT__?'client':'server'}.log`,maxsize:2*1024*1024,silent:['test','debug'].includes(process.env.NODE_ENV)})]});return _level}}exports.Config=Config; \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.Config=exports.DEFAULT_LOG_LEVEL=undefined;var _fs=require('fs');var _fs2=_interopRequireDefault(_fs);var _winston=require('winston');var _winston2=_interopRequireDefault(_winston);var _blinksocksUtils=require('blinksocks-utils');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const DEFAULT_LOG_LEVEL=exports.DEFAULT_LOG_LEVEL='error';class Config{static init(json){if(typeof json!=='object'||Array.isArray(json)){throw Error('Invalid configuration file')}if(typeof json.host!=='string'||json.host===''){throw Error('\'host\' must be provided and is not empty')}global.__LOCAL_HOST__=json.host;if(!(0,_blinksocksUtils.isValidPort)(json.port)){throw Error('\'port\' is invalid')}global.__LOCAL_PORT__=json.port;if(typeof json.servers!=='undefined'){if(!Array.isArray(json.servers)){throw Error('\'servers\' must be provided as an array')}const servers=json.servers.filter(server=>server.enabled===true);if(servers.length<1){throw Error('\'servers\' must have at least one enabled item')}global.__SERVERS__=servers;global.__IS_SERVER__=false;global.__IS_CLIENT__=true}else{global.__IS_SERVER__=true;global.__IS_CLIENT__=false;this.initServer(json)}if(typeof json.redirect==='string'&&json.redirect!==''){const address=json.redirect.split(':');if(address.length!==2||!(0,_blinksocksUtils.isValidPort)(+address[1])){throw Error('\'redirect\' is an invalid address')}}global.__REDIRECT__=json.redirect;if(typeof json.timeout!=='number'){throw Error('\'timeout\' must be a number')}if(json.timeout<1){throw Error('\'timeout\' must be greater than 0')}if(json.timeout<60){console.warn(`==> [config] 'timeout' is too short, is ${json.timeout}s expected?`)}global.__TIMEOUT__=json.timeout;global.__PROFILE__=!!json.profile;global.__IS_WATCH__=!!json.watch;global.__LOG_LEVEL__=this.setUpLogger(json.log_level||DEFAULT_LOG_LEVEL);global.__ALL_CONFIG__=json}static initServer(server){if(typeof server.transport!=='string'){throw Error('\'server.transport\' must be a string')}if(!['tcp','udp'].includes(server.transport.toLowerCase())){throw Error('\'server.transport\' must be one of "tcp" or "udp"')}global.__TRANSPORT__=server.transport;if(typeof server.host!=='string'||server.host===''){throw Error('\'server.host\' must be provided and is not empty')}global.__SERVER_HOST__=server.host;if(!(0,_blinksocksUtils.isValidPort)(server.port)){throw Error('\'server.port\' is invalid')}global.__SERVER_PORT__=server.port;if(typeof server.key!=='string'){throw Error('\'server.key\' must be a string')}if(server.key===''){throw Error('\'server.key\' cannot be empty')}global.__KEY__=server.key;if(!Array.isArray(server.presets)){throw Error('\'server.presets\' must be an array')}if(server.presets.length<1){throw Error('\'server.presets\' must contain at least one preset')}var _iteratorNormalCompletion=true;var _didIteratorError=false;var _iteratorError=undefined;try{for(var _iterator=server.presets[Symbol.iterator](),_step;!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=true){const preset=_step.value;const name=preset.name,params=preset.params;if(typeof name==='undefined'){throw Error('\'server.presets[].name\' must be a string')}if(name===''){throw Error('\'server.presets[].name\' cannot be empty')}const ps=require(`../presets/${preset.name}`).default;if(name!==server.presets[0].name){delete new ps(params||{})}}}catch(err){_didIteratorError=true;_iteratorError=err}finally{try{if(!_iteratorNormalCompletion&&_iterator.return){_iterator.return()}}finally{if(_didIteratorError){throw _iteratorError}}}global.__PRESETS__=server.presets}static setUpLogger(level=''){try{_fs2.default.lstatSync('logs')}catch(err){if(err.code==='ENOENT'){_fs2.default.mkdirSync('logs')}}let _level=level.toLowerCase();switch(_level){case'silly':case'debug':case'verbose':case'info':case'warn':case'error':break;default:_level=DEFAULT_LOG_LEVEL;break;}_winston2.default.configure({level:_level,transports:[new _winston2.default.transports.Console({colorize:true,prettyPrint:true}),new _winston2.default.transports.File({filename:`logs/blinksocks-${__IS_CLIENT__?'client':'server'}.log`,maxsize:2*1024*1024,silent:['test','debug'].includes(process.env.NODE_ENV)})]});return _level}}exports.Config=Config; \ No newline at end of file diff --git a/lib/core/socket.js b/lib/core/socket.js index accac34..3f2503f 100755 --- a/lib/core/socket.js +++ b/lib/core/socket.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.Socket=undefined;var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break}}catch(err){_d=true;_e=err}finally{try{if(!_n&&_i['return'])_i['return']()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr)){return arr}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i)}else{throw new TypeError('Invalid attempt to destructure non-iterable instance')}}}();var _extends=Object.assign||function(target){for(var i=1;i0){this.dumpTrack()}this._fsocket=null}onBackwardSocketClose(){if(this._bsocket!==null&&!this._bsocket.destroyed){this._bsocket.destroy()}this.onForwardSocketClose();if(__IS_SERVER__&&this._tracks.length>0){this.dumpTrack()}clearInterval(this._timeout_timer);this._bsocket=null;this._onClose(this)}onHandshakeDone(addr,callback){const server=_balancer.Balancer.getFastest();if(lastServer===null||!(0,_lodash2.default)(server,lastServer)){_config.Config.initServer(server);lastServer=server;_winston2.default.info(`[balancer] use: ${__SERVER_HOST__}:${__SERVER_PORT__}`)}return this.connect({host:__SERVER_HOST__,port:__SERVER_PORT__},()=>{this.createPipe(addr);this._tracks.push(`${addr.host.toString()}:${addr.port.readUInt16BE(0)}`);this._isHandshakeDone=true;callback(this.onForward)})}clientOut(buffer){let _buffer=buffer;if(this._socksUdpReady){const request=_socks.UdpRequestMessage.parse(buffer);if(request!==null){_buffer=request.DATA}else{_winston2.default.warn(`[socket] [${this.remote}] -x-> dropped unidentified packet ${buffer.length} bytes`);return}}if(this._fsocket!==null&&!this._fsocket.destroyed){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_UPWARD,_buffer)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}serverIn(buffer){if(this._fsocket!==null&&!this._fsocket.destroyed||!this._isHandshakeDone){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_DOWNWARD,buffer);this._tracks.push(TRACK_CHAR_DOWNLOAD);this._tracks.push(buffer.length)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}serverOut(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_UPWARD,buffer)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}clientIn(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_DOWNWARD,buffer);this._tracks.push(TRACK_CHAR_DOWNLOAD);this._tracks.push(buffer.length)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}send(direction,buffer){this._timeout=__TIMEOUT__;if(direction===_middleware.MIDDLEWARE_DIRECTION_UPWARD){if(__IS_CLIENT__){this.clientForward(buffer)}else{this.serverBackward(buffer)}}else{if(__IS_CLIENT__){this.clientBackward(buffer)}else{this.serverForward(buffer)}}_profile.Profile.totalOut+=buffer.length}clientForward(buffer){if(this._fsocket!==null&&!this._fsocket.destroyed){this._fsocket.write(buffer);this._tracks.push(TRACK_CHAR_UPLOAD);this._tracks.push(buffer.length)}}serverForward(buffer){if(this._fsocket!==null&&!this._fsocket.destroyed){this._fsocket.write(buffer)}}serverBackward(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){this._bsocket.write(buffer);this._tracks.push(TRACK_CHAR_UPLOAD);this._tracks.push(buffer.length)}}clientBackward(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){this._bsocket.write(buffer)}}connect({host,port},callback){var _this=this;return _asyncToGenerator(function*(){if(host&&port){_winston2.default.info(`[socket] [${_this.remote}] connecting to: ${host}:${port}`);_this._tracks.push(`${host}:${port}`);try{const ip=yield dnsCache.get(host);_this._fsocket=_net2.default.connect({host:ip,port},callback);_this._fsocket.on('error',_this.onError);_this._fsocket.on('close',_this.onForwardSocketClose);_this._fsocket.on('data',_this.onBackward)}catch(err){_winston2.default.error(`[socket] [${_this.remote}] connect to ${host}:${port} failed due to: ${err.message}`)}}else{_winston2.default.warn(`unexpected host=${host} port=${port}`);_this.onBackwardSocketClose()}})()}createPipe(addr){const presets=__PRESETS__.map((preset,i)=>(0,_middleware.createMiddleware)(preset.name,_extends({},preset.params,i===0?addr:{})));this._pipe=new _pipe.Pipe({onNotified:this.onPipeNotified.bind(this)});this._pipe.setMiddlewares(_middleware.MIDDLEWARE_DIRECTION_UPWARD,presets);this._pipe.on(`next_${_middleware.MIDDLEWARE_DIRECTION_UPWARD}`,buf=>this.send(_middleware.MIDDLEWARE_DIRECTION_UPWARD,buf));this._pipe.on(`next_${_middleware.MIDDLEWARE_DIRECTION_DOWNWARD}`,buf=>this.send(_middleware.MIDDLEWARE_DIRECTION_DOWNWARD,buf))}onPipeNotified(action){if(__IS_SERVER__&&action.type===_defs.SOCKET_CONNECT_TO_DST){var _action$payload=action.payload;const targetAddress=_action$payload.targetAddress,onConnected=_action$payload.onConnected;return this.connect(targetAddress,()=>{this._isHandshakeDone=true;onConnected()})}if(action.type===_defs.PROCESSING_FAILED){return this.onPresetFailed(action)}}onPresetFailed(action){var _action$payload2=action.payload;const message=_action$payload2.message,orgData=_action$payload2.orgData;if(__IS_SERVER__&&__REDIRECT__!==''&&this._fsocket===null){var _REDIRECT__$split=__REDIRECT__.split(':'),_REDIRECT__$split2=_slicedToArray(_REDIRECT__$split,2);const host=_REDIRECT__$split2[0],port=_REDIRECT__$split2[1];_winston2.default.error(`[socket] [${this.remote}] connection is redirected to ${host}:${port} due to: ${message}`);this.connect({host,port},()=>{this._isRedirect=true;this._fsocket.write(orgData)})}else{const timeout=_utils.Utils.getRandomInt(10,40);_winston2.default.error(`[socket] [${this.remote}] connection will be closed in ${timeout}s due to: ${message}`);setTimeout(()=>{this.onForwardSocketClose();this.onBackwardSocketClose()},timeout*1e3)}_profile.Profile.fatals+=1}setupTimeout(){this._timeout=__TIMEOUT__;this._timeout_timer=setInterval(()=>{if(--this._timeout<1){_winston2.default.warn(`[socket] [${this.remote}] timeout: no I/O on the connection for ${__TIMEOUT__}s`);this.onForwardSocketClose();this.onBackwardSocketClose()}},1e3)}dumpTrack(){let strs=[];let dp=0,db=0;let up=0,ub=0;let ud='';var _iteratorNormalCompletion=true;var _didIteratorError=false;var _iteratorError=undefined;try{for(var _iterator=this._tracks[Symbol.iterator](),_step;!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=true){const el=_step.value;if(el===TRACK_CHAR_UPLOAD||el===TRACK_CHAR_DOWNLOAD){if(ud===el){continue}ud=el}if(Number.isInteger(el)){if(ud===TRACK_CHAR_DOWNLOAD){dp+=1;db+=el}if(ud===TRACK_CHAR_UPLOAD){up+=1;ub+=el}}strs.push(el)}}catch(err){_didIteratorError=true;_iteratorError=err}finally{try{if(!_iteratorNormalCompletion&&_iterator.return){_iterator.return()}}finally{if(_didIteratorError){throw _iteratorError}}}const perSize=Math.floor(TRACK_MAX_SIZE/2);if(strs.length>TRACK_MAX_SIZE){strs=strs.slice(0,perSize).concat([' ... ']).concat(strs.slice(-perSize))}const summary=__IS_CLIENT__?`out/in = ${up}/${dp}, ${ub}b/${db}b`:`in/out = ${dp}/${up}, ${db}b/${ub}b`;_winston2.default.info(`[socket] [${this.remote}] closed with summary(${summary}) abstract(${strs.join(' ')})`);this._tracks=[]}}exports.Socket=Socket; \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.Socket=undefined;var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break}}catch(err){_d=true;_e=err}finally{try{if(!_n&&_i['return'])_i['return']()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr)){return arr}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i)}else{throw new TypeError('Invalid attempt to destructure non-iterable instance')}}}();var _extends=Object.assign||function(target){for(var i=1;i0){this.dumpTrack()}this._fsocket=null}onBackwardSocketClose(){if(this._bsocket!==null&&!this._bsocket.destroyed){this._bsocket.destroy()}this.onForwardSocketClose();if(__IS_SERVER__&&this._tracks.length>0){this.dumpTrack()}clearInterval(this._timeout_timer);this._bsocket=null;this._onClose(this)}onHandshakeDone(addr,callback){const server=_balancer.Balancer.getFastest();if(lastServer===null||!(0,_lodash2.default)(server,lastServer)){_config.Config.initServer(server);lastServer=server;_winston2.default.info(`[balancer] use: ${__SERVER_HOST__}:${__SERVER_PORT__}`)}return this.connect({host:__SERVER_HOST__,port:__SERVER_PORT__},()=>{this.createPipe(addr);this._tracks.push(`${addr.host.toString()}:${addr.port.readUInt16BE(0)}`);this._isHandshakeDone=true;callback(this.onForward)})}clientOut(buffer){let _buffer=buffer;if(this._socksUdpReady){const request=_socks.UdpRequestMessage.parse(buffer);if(request!==null){_buffer=request.DATA}else{_winston2.default.warn(`[socket] [${this.remote}] -x-> dropped unidentified packet ${buffer.length} bytes`);return}}if(this._fsocket!==null&&!this._fsocket.destroyed){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_UPWARD,_buffer)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}serverIn(buffer){if(this._fsocket!==null&&!this._fsocket.destroyed||!this._isHandshakeDone){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_DOWNWARD,buffer);this._tracks.push(TRACK_CHAR_DOWNLOAD);this._tracks.push(buffer.length)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}serverOut(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_UPWARD,buffer)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}clientIn(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){try{this._pipe.feed(_middleware.MIDDLEWARE_DIRECTION_DOWNWARD,buffer);this._tracks.push(TRACK_CHAR_DOWNLOAD);this._tracks.push(buffer.length)}catch(err){_winston2.default.error(`[socket] [${this.remote}]`,err)}}}send(direction,buffer){this._timeout=__TIMEOUT__;if(direction===_middleware.MIDDLEWARE_DIRECTION_UPWARD){if(__IS_CLIENT__){this.clientForward(buffer)}else{this.serverBackward(buffer)}}else{if(__IS_CLIENT__){this.clientBackward(buffer)}else{this.serverForward(buffer)}}_profile.Profile.totalOut+=buffer.length}clientForward(buffer){if(this._fsocket!==null&&!this._fsocket.destroyed){this._fsocket.write(buffer);this._tracks.push(TRACK_CHAR_UPLOAD);this._tracks.push(buffer.length)}}serverForward(buffer){if(this._fsocket!==null&&!this._fsocket.destroyed){this._fsocket.write(buffer)}}serverBackward(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){this._bsocket.write(buffer);this._tracks.push(TRACK_CHAR_UPLOAD);this._tracks.push(buffer.length)}}clientBackward(buffer){if(this._bsocket!==null&&!this._bsocket.destroyed){this._bsocket.write(buffer)}}connect({host,port},callback){var _this=this;return _asyncToGenerator(function*(){if(host&&port){_winston2.default.info(`[socket] [${_this.remote}] connecting to: ${host}:${port}`);_this._tracks.push(`${host}:${port}`);try{const ip=yield dnsCache.get(host);_this._fsocket=_net2.default.connect({host:ip,port},callback);_this._fsocket.on('error',_this.onError);_this._fsocket.on('close',_this.onForwardSocketClose);_this._fsocket.on('data',_this.onBackward)}catch(err){_winston2.default.error(`[socket] [${_this.remote}] connect to ${host}:${port} failed due to: ${err.message}`)}}else{_winston2.default.warn(`unexpected host=${host} port=${port}`);_this.onBackwardSocketClose()}})()}createPipe(addr){const presets=__PRESETS__.map((preset,i)=>(0,_middleware.createMiddleware)(preset.name,_extends({},preset.params,i===0?addr:{})));this._pipe=new _pipe.Pipe({onNotified:this.onPipeNotified.bind(this)});this._pipe.setMiddlewares(_middleware.MIDDLEWARE_DIRECTION_UPWARD,presets);this._pipe.on(`next_${_middleware.MIDDLEWARE_DIRECTION_UPWARD}`,buf=>this.send(_middleware.MIDDLEWARE_DIRECTION_UPWARD,buf));this._pipe.on(`next_${_middleware.MIDDLEWARE_DIRECTION_DOWNWARD}`,buf=>this.send(_middleware.MIDDLEWARE_DIRECTION_DOWNWARD,buf))}onPipeNotified(action){if(__IS_SERVER__&&action.type===_defs.SOCKET_CONNECT_TO_DST){var _action$payload=action.payload;const targetAddress=_action$payload.targetAddress,onConnected=_action$payload.onConnected;return this.connect(targetAddress,()=>{this._isHandshakeDone=true;onConnected()})}if(action.type===_defs.PROCESSING_FAILED){return this.onPresetFailed(action)}}onPresetFailed(action){var _action$payload2=action.payload;const message=_action$payload2.message,orgData=_action$payload2.orgData;if(__IS_SERVER__&&__REDIRECT__!==''&&this._fsocket===null){var _REDIRECT__$split=__REDIRECT__.split(':'),_REDIRECT__$split2=_slicedToArray(_REDIRECT__$split,2);const host=_REDIRECT__$split2[0],port=_REDIRECT__$split2[1];_winston2.default.error(`[socket] [${this.remote}] connection is redirected to ${host}:${port} due to: ${message}`);this.connect({host,port},()=>{this._isRedirect=true;this._fsocket.write(orgData)})}else{const timeout=(0,_blinksocksUtils.getRandomInt)(10,40);_winston2.default.error(`[socket] [${this.remote}] connection will be closed in ${timeout}s due to: ${message}`);setTimeout(()=>{this.onForwardSocketClose();this.onBackwardSocketClose()},timeout*1e3)}_profile.Profile.fatals+=1}setupTimeout(){this._timeout=__TIMEOUT__;this._timeout_timer=setInterval(()=>{if(--this._timeout<1){_winston2.default.warn(`[socket] [${this.remote}] timeout: no I/O on the connection for ${__TIMEOUT__}s`);this.onForwardSocketClose();this.onBackwardSocketClose()}},1e3)}dumpTrack(){let strs=[];let dp=0,db=0;let up=0,ub=0;let ud='';var _iteratorNormalCompletion=true;var _didIteratorError=false;var _iteratorError=undefined;try{for(var _iterator=this._tracks[Symbol.iterator](),_step;!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=true){const el=_step.value;if(el===TRACK_CHAR_UPLOAD||el===TRACK_CHAR_DOWNLOAD){if(ud===el){continue}ud=el}if(Number.isInteger(el)){if(ud===TRACK_CHAR_DOWNLOAD){dp+=1;db+=el}if(ud===TRACK_CHAR_UPLOAD){up+=1;ub+=el}}strs.push(el)}}catch(err){_didIteratorError=true;_iteratorError=err}finally{try{if(!_iteratorNormalCompletion&&_iterator.return){_iterator.return()}}finally{if(_didIteratorError){throw _iteratorError}}}const perSize=Math.floor(TRACK_MAX_SIZE/2);if(strs.length>TRACK_MAX_SIZE){strs=strs.slice(0,perSize).concat([' ... ']).concat(strs.slice(-perSize))}const summary=__IS_CLIENT__?`out/in = ${up}/${dp}, ${ub}b/${db}b`:`in/out = ${dp}/${up}, ${db}b/${ub}b`;_winston2.default.info(`[socket] [${this.remote}] closed with summary(${summary}) abstract(${strs.join(' ')})`);this._tracks=[]}}exports.Socket=Socket; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index c1da3b1..18a6c42 100755 --- a/lib/index.js +++ b/lib/index.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _core=require('./core');Object.keys(_core).forEach(function(key){if(key==='default'||key==='__esModule')return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _core[key]}})});var _utils=require('./utils');Object.keys(_utils).forEach(function(key){if(key==='default'||key==='__esModule')return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _utils[key]}})}); \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _core=require('./core');Object.keys(_core).forEach(function(key){if(key==='default'||key==='__esModule')return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _core[key]}})}); \ No newline at end of file diff --git a/lib/presets/obfs-tls1.2-ticket.js b/lib/presets/obfs-tls1.2-ticket.js index 4472a24..992bdab 100755 --- a/lib/presets/obfs-tls1.2-ticket.js +++ b/lib/presets/obfs-tls1.2-ticket.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _crypto=require('crypto');var _crypto2=_interopRequireDefault(_crypto);var _defs=require('./defs');var _utils=require('../utils');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const stb=_utils.Utils.hexStringToBuffer;const TLS_STAGE_HELLO=1;const TLS_STAGE_CHANGE_CIPHER_SPEC=2;const TLS_STAGE_APPLICATION_DATA=3;const MIN_AD_PAYLOAD_LEN=2048;const MAX_AD_PAYLOAD_LEN=16383;function ApplicationData(buffer){const len=_utils.Utils.numberToUInt(buffer.length);return Buffer.concat([stb('170303'),len,buffer])}class ObfsTLS12TicketPreset extends _defs.IPreset{constructor({sni}){super();this._sni=null;this._stage=TLS_STAGE_HELLO;this._pending=null;this._adBuf=null;this.onReceiving=this.onReceiving.bind(this);this.onChunkReceived=this.onChunkReceived.bind(this);this._sni=Buffer.from(sni||'');this._adBuf=new _utils.AdvancedBuffer({getPacketLength:this.onReceiving});this._adBuf.on('data',this.onChunkReceived)}clientOut({buffer,direct}){if(this._stage===TLS_STAGE_HELLO){this._stage=TLS_STAGE_CHANGE_CIPHER_SPEC;this._pending=buffer;const random=[..._utils.Utils.getUTC(),..._crypto2.default.randomBytes(28)];const session=[...stb('20'),..._crypto2.default.randomBytes(32)];const cipher_suites=[...stb('001a'),...stb('c02b'),...stb('c02f'),...stb('c02c'),...stb('c030'),...stb('cc14'),...stb('cc13'),...stb('c013'),...stb('c014'),...stb('009c'),...stb('009d'),...stb('002f'),...stb('0035'),...stb('000a')];const ext_server_name=[...stb('0000'),..._utils.Utils.numberToUInt(2+1+2+this._sni.length),..._utils.Utils.numberToUInt(1+2+this._sni.length),...stb('00'),..._utils.Utils.numberToUInt(this._sni.length),...this._sni];const ticketLen=_utils.Utils.getRandomInt(200,400);const session_ticket=[...stb('0023'),..._utils.Utils.numberToUInt(ticketLen),..._crypto2.default.randomBytes(ticketLen)];const exts=[...stb('ff01000100'),...ext_server_name,...stb('00170000'),...session_ticket,...stb('000d00140012040308040401050308050501080606010201'),...stb('000500050100000000'),...stb('00120000'),...stb('75500000'),...stb('000b00020100'),...stb('000a0006000400170018')];const body=[...stb('0303'),...random,...session,...cipher_suites,...stb('01'),...stb('00'),..._utils.Utils.numberToUInt(exts.length),...exts];const header=[...stb('16'),...stb('0301'),..._utils.Utils.numberToUInt(1+3+body.length),...stb('01'),..._utils.Utils.numberToUInt(body.length,3)];return direct(Buffer.from([...header,...body]))}if(this._stage===TLS_STAGE_APPLICATION_DATA){const chunks=_utils.Utils.getRandomChunks(buffer,MIN_AD_PAYLOAD_LEN,MAX_AD_PAYLOAD_LEN).map(chunk=>ApplicationData(chunk));return Buffer.concat(chunks)}}serverIn({buffer,next,direct,fail}){if(this._stage===TLS_STAGE_HELLO){this._stage=TLS_STAGE_CHANGE_CIPHER_SPEC;if(buffer.length<200){fail(`TLS handshake header(${buffer.length} bytes) is too short`);return}if(!buffer.slice(0,3).equals(stb('160301'))){fail('invalid TLS handshake header');return}const tlsLen=buffer.slice(3,5).readUInt16BE(0);if(tlsLen!==buffer.length-5){fail('unexpected TLS handshake header length');return}const random=[..._utils.Utils.getUTC(),..._crypto2.default.randomBytes(28)];const session=[...stb('20'),..._crypto2.default.randomBytes(32)];const exts=[...stb('ff01000100'),...stb('00050000'),...stb('00170000')];const body=[...stb('0303'),...random,...session,...stb('c02f'),...stb('00'),..._utils.Utils.numberToUInt(exts.length),...exts];const header=[...stb('16'),...stb('0303'),..._utils.Utils.numberToUInt(1+3+body.length),...stb('02'),..._utils.Utils.numberToUInt(body.length,3)];const server_hello=[...header,...body];const change_cipher_spec=[...stb('140303000101')];const finishedLen=_utils.Utils.getRandomInt(32,40);const finished=[...stb('16'),...stb('0303'),..._utils.Utils.numberToUInt(finishedLen),..._crypto2.default.randomBytes(finishedLen)];return direct(Buffer.from([...server_hello,...change_cipher_spec,...finished]),true)}let _buffer=buffer;if(this._stage===TLS_STAGE_CHANGE_CIPHER_SPEC){this._stage=TLS_STAGE_APPLICATION_DATA;_buffer=buffer.slice(43)}this._adBuf.put(_buffer,{next,fail})}serverOut({buffer}){const chunks=_utils.Utils.getRandomChunks(buffer,MIN_AD_PAYLOAD_LEN,MAX_AD_PAYLOAD_LEN).map(chunk=>ApplicationData(chunk));return Buffer.concat(chunks)}clientIn({buffer,next,direct,fail}){if(this._stage===TLS_STAGE_CHANGE_CIPHER_SPEC){this._stage=TLS_STAGE_APPLICATION_DATA;const change_cipher_spec=[...stb('140303000101')];const finished=[...stb('16'),...stb('0303'),...stb('0020'),..._crypto2.default.randomBytes(32)];const chunks=_utils.Utils.getRandomChunks(this._pending,MIN_AD_PAYLOAD_LEN,MAX_AD_PAYLOAD_LEN).map(chunk=>ApplicationData(chunk));this._pending=null;return direct(Buffer.from([...change_cipher_spec,...finished,...Buffer.concat(chunks)]),true)}this._adBuf.put(buffer,{next,fail})}onReceiving(buffer,{fail}){if(buffer.length<5){fail(`Application Data is too short: ${buffer.length} bytes`);return}return 5+buffer.readUInt16BE(3)}onChunkReceived(chunk,{next}){next(chunk.slice(5))}}exports.default=ObfsTLS12TicketPreset; \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _crypto=require('crypto');var _crypto2=_interopRequireDefault(_crypto);var _blinksocksUtils=require('blinksocks-utils');var _defs=require('./defs');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const TLS_STAGE_HELLO=1;const TLS_STAGE_CHANGE_CIPHER_SPEC=2;const TLS_STAGE_APPLICATION_DATA=3;const MIN_AD_PAYLOAD_LEN=2048;const MAX_AD_PAYLOAD_LEN=16383;function ApplicationData(buffer){const len=(0,_blinksocksUtils.numberToBuffer)(buffer.length);return Buffer.concat([(0,_blinksocksUtils.hexStringToBuffer)('170303'),len,buffer])}class ObfsTLS12TicketPreset extends _defs.IPreset{constructor({sni}){super();this._sni=null;this._stage=TLS_STAGE_HELLO;this._pending=null;this._adBuf=null;this.onReceiving=this.onReceiving.bind(this);this.onChunkReceived=this.onChunkReceived.bind(this);this._sni=Buffer.from(sni||'');this._adBuf=new _blinksocksUtils.AdvancedBuffer({getPacketLength:this.onReceiving});this._adBuf.on('data',this.onChunkReceived)}clientOut({buffer,direct}){if(this._stage===TLS_STAGE_HELLO){this._stage=TLS_STAGE_CHANGE_CIPHER_SPEC;this._pending=buffer;const random=[...(0,_blinksocksUtils.getUTC)(),..._crypto2.default.randomBytes(28)];const session=[...(0,_blinksocksUtils.hexStringToBuffer)('20'),..._crypto2.default.randomBytes(32)];const cipher_suites=[...(0,_blinksocksUtils.hexStringToBuffer)('001a'),...(0,_blinksocksUtils.hexStringToBuffer)('c02b'),...(0,_blinksocksUtils.hexStringToBuffer)('c02f'),...(0,_blinksocksUtils.hexStringToBuffer)('c02c'),...(0,_blinksocksUtils.hexStringToBuffer)('c030'),...(0,_blinksocksUtils.hexStringToBuffer)('cc14'),...(0,_blinksocksUtils.hexStringToBuffer)('cc13'),...(0,_blinksocksUtils.hexStringToBuffer)('c013'),...(0,_blinksocksUtils.hexStringToBuffer)('c014'),...(0,_blinksocksUtils.hexStringToBuffer)('009c'),...(0,_blinksocksUtils.hexStringToBuffer)('009d'),...(0,_blinksocksUtils.hexStringToBuffer)('002f'),...(0,_blinksocksUtils.hexStringToBuffer)('0035'),...(0,_blinksocksUtils.hexStringToBuffer)('000a')];const ext_server_name=[...(0,_blinksocksUtils.hexStringToBuffer)('0000'),...(0,_blinksocksUtils.numberToBuffer)(2+1+2+this._sni.length),...(0,_blinksocksUtils.numberToBuffer)(1+2+this._sni.length),...(0,_blinksocksUtils.hexStringToBuffer)('00'),...(0,_blinksocksUtils.numberToBuffer)(this._sni.length),...this._sni];const ticketLen=(0,_blinksocksUtils.getRandomInt)(200,400);const session_ticket=[...(0,_blinksocksUtils.hexStringToBuffer)('0023'),...(0,_blinksocksUtils.numberToBuffer)(ticketLen),..._crypto2.default.randomBytes(ticketLen)];const exts=[...(0,_blinksocksUtils.hexStringToBuffer)('ff01000100'),...ext_server_name,...(0,_blinksocksUtils.hexStringToBuffer)('00170000'),...session_ticket,...(0,_blinksocksUtils.hexStringToBuffer)('000d00140012040308040401050308050501080606010201'),...(0,_blinksocksUtils.hexStringToBuffer)('000500050100000000'),...(0,_blinksocksUtils.hexStringToBuffer)('00120000'),...(0,_blinksocksUtils.hexStringToBuffer)('75500000'),...(0,_blinksocksUtils.hexStringToBuffer)('000b00020100'),...(0,_blinksocksUtils.hexStringToBuffer)('000a0006000400170018')];const body=[...(0,_blinksocksUtils.hexStringToBuffer)('0303'),...random,...session,...cipher_suites,...(0,_blinksocksUtils.hexStringToBuffer)('01'),...(0,_blinksocksUtils.hexStringToBuffer)('00'),...(0,_blinksocksUtils.numberToBuffer)(exts.length),...exts];const header=[...(0,_blinksocksUtils.hexStringToBuffer)('16'),...(0,_blinksocksUtils.hexStringToBuffer)('0301'),...(0,_blinksocksUtils.numberToBuffer)(1+3+body.length),...(0,_blinksocksUtils.hexStringToBuffer)('01'),...(0,_blinksocksUtils.numberToBuffer)(body.length,3)];return direct(Buffer.from([...header,...body]))}if(this._stage===TLS_STAGE_APPLICATION_DATA){const chunks=(0,_blinksocksUtils.getRandomChunks)(buffer,MIN_AD_PAYLOAD_LEN,MAX_AD_PAYLOAD_LEN).map(chunk=>ApplicationData(chunk));return Buffer.concat(chunks)}}serverIn({buffer,next,direct,fail}){if(this._stage===TLS_STAGE_HELLO){this._stage=TLS_STAGE_CHANGE_CIPHER_SPEC;if(buffer.length<200){fail(`TLS handshake header(${buffer.length} bytes) is too short`);return}if(!buffer.slice(0,3).equals((0,_blinksocksUtils.hexStringToBuffer)('160301'))){fail('invalid TLS handshake header');return}const tlsLen=buffer.slice(3,5).readUInt16BE(0);if(tlsLen!==buffer.length-5){fail('unexpected TLS handshake header length');return}const random=[...(0,_blinksocksUtils.getUTC)(),..._crypto2.default.randomBytes(28)];const session=[...(0,_blinksocksUtils.hexStringToBuffer)('20'),..._crypto2.default.randomBytes(32)];const exts=[...(0,_blinksocksUtils.hexStringToBuffer)('ff01000100'),...(0,_blinksocksUtils.hexStringToBuffer)('00050000'),...(0,_blinksocksUtils.hexStringToBuffer)('00170000')];const body=[...(0,_blinksocksUtils.hexStringToBuffer)('0303'),...random,...session,...(0,_blinksocksUtils.hexStringToBuffer)('c02f'),...(0,_blinksocksUtils.hexStringToBuffer)('00'),...(0,_blinksocksUtils.numberToBuffer)(exts.length),...exts];const header=[...(0,_blinksocksUtils.hexStringToBuffer)('16'),...(0,_blinksocksUtils.hexStringToBuffer)('0303'),...(0,_blinksocksUtils.numberToBuffer)(1+3+body.length),...(0,_blinksocksUtils.hexStringToBuffer)('02'),...(0,_blinksocksUtils.numberToBuffer)(body.length,3)];const server_hello=[...header,...body];const change_cipher_spec=[...(0,_blinksocksUtils.hexStringToBuffer)('140303000101')];const finishedLen=(0,_blinksocksUtils.getRandomInt)(32,40);const finished=[...(0,_blinksocksUtils.hexStringToBuffer)('16'),...(0,_blinksocksUtils.hexStringToBuffer)('0303'),...(0,_blinksocksUtils.numberToBuffer)(finishedLen),..._crypto2.default.randomBytes(finishedLen)];return direct(Buffer.from([...server_hello,...change_cipher_spec,...finished]),true)}let _buffer=buffer;if(this._stage===TLS_STAGE_CHANGE_CIPHER_SPEC){this._stage=TLS_STAGE_APPLICATION_DATA;_buffer=buffer.slice(43)}this._adBuf.put(_buffer,{next,fail})}serverOut({buffer}){const chunks=(0,_blinksocksUtils.getRandomChunks)(buffer,MIN_AD_PAYLOAD_LEN,MAX_AD_PAYLOAD_LEN).map(chunk=>ApplicationData(chunk));return Buffer.concat(chunks)}clientIn({buffer,next,direct,fail}){if(this._stage===TLS_STAGE_CHANGE_CIPHER_SPEC){this._stage=TLS_STAGE_APPLICATION_DATA;const change_cipher_spec=[...(0,_blinksocksUtils.hexStringToBuffer)('140303000101')];const finished=[...(0,_blinksocksUtils.hexStringToBuffer)('16'),...(0,_blinksocksUtils.hexStringToBuffer)('0303'),...(0,_blinksocksUtils.hexStringToBuffer)('0020'),..._crypto2.default.randomBytes(32)];const chunks=(0,_blinksocksUtils.getRandomChunks)(this._pending,MIN_AD_PAYLOAD_LEN,MAX_AD_PAYLOAD_LEN).map(chunk=>ApplicationData(chunk));this._pending=null;return direct(Buffer.from([...change_cipher_spec,...finished,...Buffer.concat(chunks)]),true)}this._adBuf.put(buffer,{next,fail})}onReceiving(buffer,{fail}){if(buffer.length<5){fail(`Application Data is too short: ${buffer.length} bytes, ${buffer.toString('hex')}`);return}return 5+buffer.readUInt16BE(3)}onChunkReceived(chunk,{next}){next(chunk.slice(5))}}exports.default=ObfsTLS12TicketPreset; \ No newline at end of file diff --git a/lib/presets/ss-aead-cipher.js b/lib/presets/ss-aead-cipher.js index 0c201fa..8e960b6 100644 --- a/lib/presets/ss-aead-cipher.js +++ b/lib/presets/ss-aead-cipher.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break}}catch(err){_d=true;_e=err}finally{try{if(!_n&&_i['return'])_i['return']()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr)){return arr}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i)}else{throw new TypeError('Invalid attempt to destructure non-iterable instance')}}}();var _crypto=require('crypto');var _crypto2=_interopRequireDefault(_crypto);var _defs=require('./defs');var _utils=require('../utils');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const NONCE_LEN=12;const TAG_LEN=16;const MIN_CHUNK_LEN=TAG_LEN*2+3;const MIN_CHUNK_SPLIT_LEN=2048;const MAX_CHUNK_SPLIT_LEN=16383;const ciphers=['aes-128-gcm','aes-192-gcm','aes-256-gcm'];const HKDF_HASH_ALGORITHM='sha1';class SSAeadCipherPreset extends _defs.IPreset{constructor({method,info}){super();this._cipherName='';this._info=null;this._cipherKey=null;this._decipherKey=null;this._cipherNonce=0;this._decipherNonce=0;this._adBuf=null;if(typeof method==='undefined'||method===''){throw Error('\'method\' must be set.')}if(!ciphers.includes(method)){throw Error(`method \'${method}\' is not supported.`)}this._cipherName=method;this._info=Buffer.from(info);this._adBuf=new _utils.AdvancedBuffer({getPacketLength:this.onReceiving.bind(this)});this._adBuf.on('data',this.onChunkReceived.bind(this))}beforeOut({buffer}){let salt=null;if(this._cipherKey===null){const size=this._cipherName.split('-')[1]/8;salt=_crypto2.default.randomBytes(size);this._cipherKey=_utils.Utils.HKDF(HKDF_HASH_ALGORITHM,salt,_utils.Utils.EVP_BytesToKey(__KEY__,size,16),this._info,size)}const chunks=_utils.Utils.getRandomChunks(buffer,MIN_CHUNK_SPLIT_LEN,MAX_CHUNK_SPLIT_LEN).map(chunk=>{const dataLen=_utils.Utils.numberToUInt(chunk.length);var _encrypt=this.encrypt(dataLen),_encrypt2=_slicedToArray(_encrypt,2);const encLen=_encrypt2[0],lenTag=_encrypt2[1];var _encrypt3=this.encrypt(chunk),_encrypt4=_slicedToArray(_encrypt3,2);const encData=_encrypt4[0],dataTag=_encrypt4[1];return Buffer.concat([encLen,lenTag,encData,dataTag])});if(salt){return Buffer.concat([salt,...chunks])}else{return Buffer.concat(chunks)}}beforeIn({buffer,next,fail}){this._adBuf.put(buffer,{next,fail})}onReceiving(buffer,{fail}){if(this._decipherKey===null){const size=this._cipherName.split('-')[1]/8;if(buffer.length{const dataLen=(0,_blinksocksUtils.numberToBuffer)(chunk.length);var _encrypt=this.encrypt(dataLen),_encrypt2=_slicedToArray(_encrypt,2);const encLen=_encrypt2[0],lenTag=_encrypt2[1];var _encrypt3=this.encrypt(chunk),_encrypt4=_slicedToArray(_encrypt3,2);const encData=_encrypt4[0],dataTag=_encrypt4[1];return Buffer.concat([encLen,lenTag,encData,dataTag])});if(salt){return Buffer.concat([salt,...chunks])}else{return Buffer.concat(chunks)}}beforeIn({buffer,next,fail}){this._adBuf.put(buffer,{next,fail})}onReceiving(buffer,{fail}){if(this._decipherKey===null){const size=this._cipherName.split('-')[1]/8;if(buffer.length{next(Buffer.concat([this._staging,data]));this._isHandshakeDone=true;this._staging=null}}});this._isAddressReceived=true}else{return buffer}}}exports.default=SSBasePreset; \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _ip=require('ip');var _ip2=_interopRequireDefault(_ip);var _blinksocksUtils=require('blinksocks-utils');var _defs=require('./defs');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const ATYP_V4=1;const ATYP_V6=4;const ATYP_DOMAIN=3;class SSBasePreset extends _defs.IPreset{constructor(addr){super();this._isHandshakeDone=false;this._isAddressReceived=false;this._atyp=ATYP_V4;this._addr=null;this._port=null;this._staging=Buffer.alloc(0);if(__IS_CLIENT__){const type=addr.type,host=addr.host,port=addr.port;this._atyp=type;this._addr=host;this._port=port}}clientOut({buffer}){if(!this._isHandshakeDone){this._isHandshakeDone=true;return Buffer.from([this._atyp,...(this._atyp===ATYP_DOMAIN?(0,_blinksocksUtils.numberToBuffer)(this._addr.length,1):[]),...this._addr,...this._port,...buffer])}else{return buffer}}serverIn({buffer,next,broadcast,fail}){if(!this._isHandshakeDone){if(this._isAddressReceived){this._staging=Buffer.concat([this._staging,buffer]);return}if(buffer.length<7){fail(`invalid length: ${buffer.length}`);return}const atyp=buffer[0];let addr;let port;let offset=3;switch(atyp){case ATYP_V4:addr=_ip2.default.toString(buffer.slice(1,5));port=buffer.slice(5,7).readUInt16BE(0);offset+=4;break;case ATYP_V6:if(buffer.length<19){fail(`invalid length: ${buffer.length}`);return}addr=_ip2.default.toString(buffer.slice(1,17));port=buffer.slice(16,18).readUInt16BE(0);offset+=16;break;case ATYP_DOMAIN:const domainLen=buffer[1];if(buffer.length{next(Buffer.concat([this._staging,data]));this._isHandshakeDone=true;this._staging=null}}});this._isAddressReceived=true}else{return buffer}}}exports.default=SSBasePreset; \ No newline at end of file diff --git a/lib/presets/ss-stream-cipher.js b/lib/presets/ss-stream-cipher.js index 07ef89e..f09286a 100644 --- a/lib/presets/ss-stream-cipher.js +++ b/lib/presets/ss-stream-cipher.js @@ -1 +1 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _crypto=require('crypto');var _crypto2=_interopRequireDefault(_crypto);var _defs=require('./defs');var _utils=require('../utils');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const IV_LEN=16;const ciphers=['aes-128-ctr','aes-192-ctr','aes-256-ctr','aes-128-cfb','aes-192-cfb','aes-256-cfb','camellia-128-cfb','camellia-192-cfb','camellia-256-cfb','aes-128-ofb','aes-192-ofb','aes-256-ofb','aes-128-cbc','aes-192-cbc','aes-256-cbc'];class SSStreamCipherPreset extends _defs.IPreset{constructor({method}){super();this._cipherName='';this._key=null;this._cipher=null;this._decipher=null;if(typeof method!=='string'||method===''){throw Error('\'method\' must be set')}if(!ciphers.includes(method)){throw Error(`method \'${method}\' is not supported.`)}this._cipherName=method;if(global.__KEY__){this._key=_utils.Utils.EVP_BytesToKey(__KEY__,this._cipherName.split('-')[1]/8,IV_LEN)}}beforeOut({buffer}){if(!this._cipher){const iv=_crypto2.default.randomBytes(IV_LEN);this._cipher=_crypto2.default.createCipheriv(this._cipherName,this._key,iv);return Buffer.concat([iv,this.encrypt(buffer)])}else{return this.encrypt(buffer)}}beforeIn({buffer}){if(!this._decipher){const iv=buffer.slice(0,IV_LEN);this._decipher=_crypto2.default.createDecipheriv(this._cipherName,this._key,iv);return this.decrypt(buffer.slice(IV_LEN))}else{return this.decrypt(buffer)}}encrypt(buffer){return this._cipher.update(buffer)}decrypt(buffer){return this._decipher.update(buffer)}}exports.default=SSStreamCipherPreset; \ No newline at end of file +'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _crypto=require('crypto');var _crypto2=_interopRequireDefault(_crypto);var _blinksocksUtils=require('blinksocks-utils');var _defs=require('./defs');function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const IV_LEN=16;const ciphers=['aes-128-ctr','aes-192-ctr','aes-256-ctr','aes-128-cfb','aes-192-cfb','aes-256-cfb','camellia-128-cfb','camellia-192-cfb','camellia-256-cfb','aes-128-ofb','aes-192-ofb','aes-256-ofb','aes-128-cbc','aes-192-cbc','aes-256-cbc'];class SSStreamCipherPreset extends _defs.IPreset{constructor({method}){super();this._cipherName='';this._key=null;this._cipher=null;this._decipher=null;if(typeof method!=='string'||method===''){throw Error('\'method\' must be set')}if(!ciphers.includes(method)){throw Error(`method \'${method}\' is not supported.`)}this._cipherName=method;if(global.__KEY__){this._key=(0,_blinksocksUtils.EVP_BytesToKey)(__KEY__,this._cipherName.split('-')[1]/8,IV_LEN)}}beforeOut({buffer}){if(!this._cipher){const iv=_crypto2.default.randomBytes(IV_LEN);this._cipher=_crypto2.default.createCipheriv(this._cipherName,this._key,iv);return Buffer.concat([iv,this.encrypt(buffer)])}else{return this.encrypt(buffer)}}beforeIn({buffer}){if(!this._decipher){const iv=buffer.slice(0,IV_LEN);this._decipher=_crypto2.default.createDecipheriv(this._cipherName,this._key,iv);return this.decrypt(buffer.slice(IV_LEN))}else{return this.decrypt(buffer)}}encrypt(buffer){return this._cipher.update(buffer)}decrypt(buffer){return this._decipher.update(buffer)}}exports.default=SSStreamCipherPreset; \ No newline at end of file diff --git a/lib/utils/advanced-buffer.js b/lib/utils/advanced-buffer.js deleted file mode 100644 index bafe6f9..0000000 --- a/lib/utils/advanced-buffer.js +++ /dev/null @@ -1 +0,0 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.AdvancedBuffer=undefined;var _events=require('events');var _events2=_interopRequireDefault(_events);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}class AdvancedBuffer extends _events2.default{constructor(options={}){super();this._buffer=Buffer.alloc(0);this._getPacketLength=null;this._nextLength=0;if(typeof options.getPacketLength!=='function'){throw Error('options.getPacketLength should be a function')}this._getPacketLength=options.getPacketLength}put(chunk,...args){if(!(chunk instanceof Buffer)){throw Error('chunk must be a Buffer')}this._buffer=this._digest(Buffer.concat([this._buffer,chunk]),...args)}final(){return this._buffer}_digest(buffer,...args){const expectLen=this._nextLength||this._getPacketLength(buffer,...args);if(expectLen===0||typeof expectLen==='undefined'){return buffer}if(expectLen===-1){return Buffer.alloc(0)}if(expectLen instanceof Buffer){return this._digest(expectLen,...args)}if(buffer.length===expectLen){this.emit('data',Buffer.from(buffer),...args);this._nextLength=0;return Buffer.alloc(0)}if(buffer.lengthexpectLen){this.emit('data',buffer.slice(0,expectLen),...args);this._nextLength=0;return this._digest(buffer.slice(expectLen),...args)}}}exports.AdvancedBuffer=AdvancedBuffer; \ No newline at end of file diff --git a/lib/utils/index.js b/lib/utils/index.js deleted file mode 100644 index 1051e85..0000000 --- a/lib/utils/index.js +++ /dev/null @@ -1 +0,0 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _advancedBuffer=require('./advanced-buffer');Object.keys(_advancedBuffer).forEach(function(key){if(key==='default'||key==='__esModule')return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _advancedBuffer[key]}})});var _utils=require('./utils');Object.keys(_utils).forEach(function(key){if(key==='default'||key==='__esModule')return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _utils[key]}})}); \ No newline at end of file diff --git a/lib/utils/utils.js b/lib/utils/utils.js deleted file mode 100755 index 40ad5ba..0000000 --- a/lib/utils/utils.js +++ /dev/null @@ -1 +0,0 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.Utils=exports.BYTE_ORDER_LE=exports.BYTE_ORDER_BE=exports.ATYP_V6=exports.ATYP_DOMAIN=exports.ATYP_V4=undefined;var _net=require('net');var _net2=_interopRequireDefault(_net);var _url=require('url');var _url2=_interopRequireDefault(_url);var _crypto=require('crypto');var _crypto2=_interopRequireDefault(_crypto);var _ip=require('ip');var _ip2=_interopRequireDefault(_ip);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const ATYP_V4=exports.ATYP_V4=1;const ATYP_DOMAIN=exports.ATYP_DOMAIN=3;const ATYP_V6=exports.ATYP_V6=4;const BYTE_ORDER_BE=exports.BYTE_ORDER_BE=0;const BYTE_ORDER_LE=exports.BYTE_ORDER_LE=1;class Utils{static numberToUInt(num,len=2,byteOrder=BYTE_ORDER_BE){if(len<1){throw Error('len must be greater than 0')}const isOutOfRange=num>parseInt(`0x${'ff'.repeat(len)}`);if(isOutOfRange){throw Error(`Number ${num} is too long to store in a '${len}' length buffer`)}const buf=Buffer.alloc(len);if(byteOrder===BYTE_ORDER_BE){buf.writeUIntBE(num,0,len)}else{buf.writeUIntLE(num,0,len)}return buf}static parseURI(uri){let _uri=uri;if(_uri.indexOf('http')!==0&&_uri.indexOf('https')!==0){if(_uri.indexOf(':443')!==-1){_uri=`https://${_uri}`}else{_uri=`http://${_uri}`}}var _url$parse=_url2.default.parse(_uri);const protocol=_url$parse.protocol,hostname=_url$parse.hostname;const addrType=_net2.default.isIP(hostname)?_net2.default.isIPv4(hostname)?ATYP_V4:ATYP_V6:ATYP_DOMAIN;const port={'http:':80,'https:':443}[protocol];return{type:addrType,host:_net2.default.isIP(hostname)?_ip2.default.toBuffer(hostname):Buffer.from(hostname),port:this.numberToUInt(port)}}static getRandomInt(min,max){min=Math.ceil(min);max=Math.ceil(max);return Math.floor(_crypto2.default.randomBytes(1)[0]/255*(max-min+1))+min}static getRandomChunks(buffer,min,max){const totalLen=buffer.length;const bufs=[];let ptr=0;while(ptr253){return false}if(/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i.test(hostname)===false){return false}if(/^[^.]{1,63}(\.[^.]{1,63})*$/.test(hostname)===false){return false}return true}static isValidPort(port){if(typeof port!=='number'){return false}if(port<0||port>65535){return false}return true}static md5(buffer){const md5=_crypto2.default.createHash('md5');md5.update(buffer);return md5.digest()}static hmac(algorithm,key,buffer){const hmac=_crypto2.default.createHmac(algorithm,key);return hmac.update(buffer).digest()}static EVP_BytesToKey(password,keyLen,ivLen){let _data=Buffer.from(password);let i=0;const bufs=[];while(Buffer.concat(bufs).length0){_data=Buffer.concat([bufs[i-1],Buffer.from(password)])}bufs.push(this.md5(_data));i+=1}return Buffer.concat(bufs).slice(0,keyLen)}static HKDF(hash,salt,ikm,info,length){const prk=this.hmac(hash,salt,ikm);let t=Buffer.alloc(0);let okm=Buffer.alloc(0);for(let i=0;i { if (METHOD.toString() === 'CONNECT') { diff --git a/src/core/config.js b/src/core/config.js index a28b7c6..4a48f28 100755 --- a/src/core/config.js +++ b/src/core/config.js @@ -1,6 +1,6 @@ import fs from 'fs'; import winston from 'winston'; -import {Utils} from '../utils'; +import {isValidPort} from 'blinksocks-utils'; export const DEFAULT_LOG_LEVEL = 'error'; @@ -21,7 +21,7 @@ export class Config { // port - if (!Utils.isValidPort(json.port)) { + if (!isValidPort(json.port)) { throw Error('\'port\' is invalid'); } @@ -55,7 +55,7 @@ export class Config { if (typeof json.redirect === 'string' && json.redirect !== '') { const address = json.redirect.split(':'); - if (address.length !== 2 || !Utils.isValidPort(+address[1])) { + if (address.length !== 2 || !isValidPort(+address[1])) { throw Error('\'redirect\' is an invalid address'); } } @@ -113,7 +113,7 @@ export class Config { // port - if (!Utils.isValidPort(server.port)) { + if (!isValidPort(server.port)) { throw Error('\'server.port\' is invalid'); } diff --git a/src/core/socket.js b/src/core/socket.js index e6ff418..44d0b4e 100755 --- a/src/core/socket.js +++ b/src/core/socket.js @@ -1,6 +1,7 @@ import net from 'net'; import logger from 'winston'; import isEqual from 'lodash.isequal'; +import {getRandomInt} from 'blinksocks-utils'; import {Config} from './config'; import {ClientProxy} from './client-proxy'; import {DNSCache} from './dns-cache'; @@ -13,7 +14,6 @@ import { createMiddleware } from './middleware'; -import {Utils} from '../utils'; import { SOCKET_CONNECT_TO_DST, PROCESSING_FAILED @@ -374,7 +374,7 @@ export class Socket { this._fsocket.write(orgData); }); } else { - const timeout = Utils.getRandomInt(10, 40); + const timeout = getRandomInt(10, 40); logger.error(`[socket] [${this.remote}] connection will be closed in ${timeout}s due to: ${message}`); setTimeout(() => { this.onForwardSocketClose(); diff --git a/src/index.js b/src/index.js index 2e2bc2c..4b0e041 100755 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1 @@ export * from './core'; -export * from './utils'; diff --git a/src/presets/__tests__/ss-base.test.js b/src/presets/__tests__/ss-base.test.js index 6ce9b0a..2eb05ee 100755 --- a/src/presets/__tests__/ss-base.test.js +++ b/src/presets/__tests__/ss-base.test.js @@ -1,4 +1,4 @@ -import {Utils} from '../../utils'; +import {numberToBuffer} from 'blinksocks-utils'; import SSBasePreset from '../ss-base'; describe('SSBasePreset#constructor', function () { @@ -8,7 +8,7 @@ describe('SSBasePreset#constructor', function () { const preset = new SSBasePreset({ type: 1, host: Buffer.from('example.com'), - port: Utils.numberToUInt(1080) + port: numberToBuffer(1080) }); expect(preset._atyp).toBe(1); expect(preset._addr.equals(Buffer.from([101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109]))).toBe(true); @@ -22,7 +22,7 @@ describe('SSBasePreset#clientOut', function () { const preset = new SSBasePreset({ type: 1, host: Buffer.from('example.com'), - port: Utils.numberToUInt(1080) + port: numberToBuffer(1080) }); it('should return more than 1 byte', function () { diff --git a/src/presets/obfs-tls1.2-ticket.js b/src/presets/obfs-tls1.2-ticket.js index 7ba99ae..da31a1d 100755 --- a/src/presets/obfs-tls1.2-ticket.js +++ b/src/presets/obfs-tls1.2-ticket.js @@ -1,8 +1,13 @@ import crypto from 'crypto'; +import { + hexStringToBuffer as stb, + numberToBuffer, + getUTC, + getRandomInt, + getRandomChunks, + AdvancedBuffer +} from 'blinksocks-utils'; import {IPreset} from './defs'; -import {Utils, AdvancedBuffer} from '../utils'; - -const stb = Utils.hexStringToBuffer; const TLS_STAGE_HELLO = 1; const TLS_STAGE_CHANGE_CIPHER_SPEC = 2; @@ -18,7 +23,7 @@ const MAX_AD_PAYLOAD_LEN = 0x3FFF; * @constructor */ function ApplicationData(buffer) { - const len = Utils.numberToUInt(buffer.length); + const len = numberToBuffer(buffer.length); return Buffer.concat([stb('170303'), len, buffer]); } @@ -74,8 +79,8 @@ export default class ObfsTLS12TicketPreset extends IPreset { // Random const random = [ - ...Utils.getUTC(), // GMT Unix Time - ...crypto.randomBytes(28), // Random Bytes + ...getUTC(), // GMT Unix Time + ...crypto.randomBytes(28), // Random Bytes ]; // Session const session = [ @@ -101,18 +106,18 @@ export default class ObfsTLS12TicketPreset extends IPreset { ]; // Extension: server_name const ext_server_name = [ - ...stb('0000'), // Type: server_name - ...Utils.numberToUInt(2 + 1 + 2 + this._sni.length), // Length - ...Utils.numberToUInt(1 + 2 + this._sni.length), // Server Name List length - ...stb('00'), // Server Name Type: host_name(0) - ...Utils.numberToUInt(this._sni.length), // Server Name length - ...this._sni, // Server Name + ...stb('0000'), // Type: server_name + ...numberToBuffer(2 + 1 + 2 + this._sni.length), // Length + ...numberToBuffer(1 + 2 + this._sni.length), // Server Name List length + ...stb('00'), // Server Name Type: host_name(0) + ...numberToBuffer(this._sni.length), // Server Name length + ...this._sni, // Server Name ]; // Extension: SessionTicket TLS - const ticketLen = Utils.getRandomInt(200, 400); + const ticketLen = getRandomInt(200, 400); const session_ticket = [ ...stb('0023'), // Type: SessionTicket TLS - ...Utils.numberToUInt(ticketLen), // Length + ...numberToBuffer(ticketLen), // Length ...crypto.randomBytes(ticketLen), // Data ]; // Extensions @@ -130,28 +135,28 @@ export default class ObfsTLS12TicketPreset extends IPreset { ]; const body = [ - ...stb('0303'), // Version: TLS 1.2 - ...random, // Random - ...session, // Session - ...cipher_suites, // Cipher Suites - ...stb('01'), // Compression Methods Length - ...stb('00'), // Compression Methods = [null] - ...Utils.numberToUInt(exts.length), // Extension Length - ...exts // Extensions + ...stb('0303'), // Version: TLS 1.2 + ...random, // Random + ...session, // Session + ...cipher_suites, // Cipher Suites + ...stb('01'), // Compression Methods Length + ...stb('00'), // Compression Methods = [null] + ...numberToBuffer(exts.length), // Extension Length + ...exts // Extensions ]; const header = [ - ...stb('16'), // Content Type: Handshake - ...stb('0301'), // Version: TLS 1.0 - ...Utils.numberToUInt(1 + 3 + body.length), // Length - ...stb('01'), // Handshake Type: ClientHello - ...Utils.numberToUInt(body.length, 3) // Length + ...stb('16'), // Content Type: Handshake + ...stb('0301'), // Version: TLS 1.0 + ...numberToBuffer(1 + 3 + body.length), // Length + ...stb('01'), // Handshake Type: ClientHello + ...numberToBuffer(body.length, 3) // Length ]; return direct(Buffer.from([...header, ...body])); } if (this._stage === TLS_STAGE_APPLICATION_DATA) { // Send Application Data - const chunks = Utils.getRandomChunks(buffer, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN) + const chunks = getRandomChunks(buffer, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN) .map((chunk) => ApplicationData(chunk)); return Buffer.concat(chunks); } @@ -186,7 +191,7 @@ export default class ObfsTLS12TicketPreset extends IPreset { // Random const random = [ - ...Utils.getUTC(), // GMT Unix Time + ...getUTC(), // GMT Unix Time ...crypto.randomBytes(28), // Random Bytes ]; // Session @@ -202,21 +207,21 @@ export default class ObfsTLS12TicketPreset extends IPreset { ]; const body = [ - ...stb('0303'), // Version: TLS 1.2 - ...random, // Random - ...session, // Session - ...stb('c02f'), // Cipher Suite - ...stb('00'), // Compression Method - ...Utils.numberToUInt(exts.length), // Extension Length - ...exts // Extensions + ...stb('0303'), // Version: TLS 1.2 + ...random, // Random + ...session, // Session + ...stb('c02f'), // Cipher Suite + ...stb('00'), // Compression Method + ...numberToBuffer(exts.length), // Extension Length + ...exts // Extensions ]; const header = [ - ...stb('16'), // Content Type: Handshake - ...stb('0303'), // Version: TLS 1.2 - ...Utils.numberToUInt(1 + 3 + body.length), // Length - ...stb('02'), // Handshake Type: Server Hello - ...Utils.numberToUInt(body.length, 3) // Length + ...stb('16'), // Content Type: Handshake + ...stb('0303'), // Version: TLS 1.2 + ...numberToBuffer(1 + 3 + body.length), // Length + ...stb('02'), // Handshake Type: Server Hello + ...numberToBuffer(body.length, 3) // Length ]; const server_hello = [...header, ...body]; @@ -232,11 +237,11 @@ export default class ObfsTLS12TicketPreset extends IPreset { ]; // Finished - const finishedLen = Utils.getRandomInt(32, 40); + const finishedLen = getRandomInt(32, 40); const finished = [ - ...stb('16'), // Content Type: Handshake - ...stb('0303'), // Version: TLS 1.2 - ...Utils.numberToUInt(finishedLen), // Length + ...stb('16'), // Content Type: Handshake + ...stb('0303'), // Version: TLS 1.2 + ...numberToBuffer(finishedLen), // Length ...crypto.randomBytes(finishedLen) ]; @@ -258,7 +263,7 @@ export default class ObfsTLS12TicketPreset extends IPreset { serverOut({buffer}) { // Send Application Data - const chunks = Utils.getRandomChunks(buffer, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN) + const chunks = getRandomChunks(buffer, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN) .map((chunk) => ApplicationData(chunk)); return Buffer.concat(chunks); } @@ -282,7 +287,7 @@ export default class ObfsTLS12TicketPreset extends IPreset { ...crypto.randomBytes(0x20), ]; // Application Data - const chunks = Utils.getRandomChunks(this._pending, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN) + const chunks = getRandomChunks(this._pending, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN) .map((chunk) => ApplicationData(chunk)); this._pending = null; return direct(Buffer.from([...change_cipher_spec, ...finished, ...Buffer.concat(chunks)]), true); @@ -292,7 +297,7 @@ export default class ObfsTLS12TicketPreset extends IPreset { onReceiving(buffer, {fail}) { if (buffer.length < 5) { - fail(`Application Data is too short: ${buffer.length} bytes`); + fail(`Application Data is too short: ${buffer.length} bytes, ${buffer.toString('hex')}`); return; } return 5 + buffer.readUInt16BE(3); diff --git a/src/presets/ss-aead-cipher.js b/src/presets/ss-aead-cipher.js index e2f0896..2f4d655 100644 --- a/src/presets/ss-aead-cipher.js +++ b/src/presets/ss-aead-cipher.js @@ -1,6 +1,13 @@ import crypto from 'crypto'; +import { + EVP_BytesToKey, + HKDF, + getRandomChunks, + numberToBuffer, + BYTE_ORDER_LE, + AdvancedBuffer +} from 'blinksocks-utils'; import {IPreset} from './defs'; -import {Utils, BYTE_ORDER_LE, AdvancedBuffer} from '../utils'; const NONCE_LEN = 12; const TAG_LEN = 16; @@ -103,10 +110,10 @@ export default class SSAeadCipherPreset extends IPreset { if (this._cipherKey === null) { const size = this._cipherName.split('-')[1] / 8; // key and salt size salt = crypto.randomBytes(size); - this._cipherKey = Utils.HKDF(HKDF_HASH_ALGORITHM, salt, Utils.EVP_BytesToKey(__KEY__, size, 16), this._info, size); + this._cipherKey = HKDF(HKDF_HASH_ALGORITHM, salt, EVP_BytesToKey(__KEY__, size, 16), this._info, size); } - const chunks = Utils.getRandomChunks(buffer, MIN_CHUNK_SPLIT_LEN, MAX_CHUNK_SPLIT_LEN).map((chunk) => { - const dataLen = Utils.numberToUInt(chunk.length); + const chunks = getRandomChunks(buffer, MIN_CHUNK_SPLIT_LEN, MAX_CHUNK_SPLIT_LEN).map((chunk) => { + const dataLen = numberToBuffer(chunk.length); const [encLen, lenTag] = this.encrypt(dataLen); const [encData, dataTag] = this.encrypt(chunk); return Buffer.concat([encLen, lenTag, encData, dataTag]); @@ -129,7 +136,7 @@ export default class SSAeadCipherPreset extends IPreset { return; // too short to get salt } const salt = buffer.slice(0, size); - this._decipherKey = Utils.HKDF(HKDF_HASH_ALGORITHM, salt, Utils.EVP_BytesToKey(__KEY__, size, 16), this._info, size); + this._decipherKey = HKDF(HKDF_HASH_ALGORITHM, salt, EVP_BytesToKey(__KEY__, size, 16), this._info, size); return buffer.slice(size); // drop salt } @@ -162,7 +169,7 @@ export default class SSAeadCipherPreset extends IPreset { const cipher = crypto.createCipheriv( this._cipherName, this._cipherKey, - Utils.numberToUInt(this._cipherNonce, NONCE_LEN, BYTE_ORDER_LE) + numberToBuffer(this._cipherNonce, NONCE_LEN, BYTE_ORDER_LE) ); const encrypted = Buffer.concat([cipher.update(message), cipher.final()]); const tag = cipher.getAuthTag(); @@ -174,7 +181,7 @@ export default class SSAeadCipherPreset extends IPreset { const decipher = crypto.createDecipheriv( this._cipherName, this._decipherKey, - Utils.numberToUInt(this._decipherNonce, NONCE_LEN, BYTE_ORDER_LE) + numberToBuffer(this._decipherNonce, NONCE_LEN, BYTE_ORDER_LE) ); decipher.setAuthTag(tag); try { diff --git a/src/presets/ss-base.js b/src/presets/ss-base.js index 34d86c0..a4b70f5 100755 --- a/src/presets/ss-base.js +++ b/src/presets/ss-base.js @@ -1,6 +1,6 @@ import ip from 'ip'; +import {isValidHostname, numberToBuffer} from 'blinksocks-utils'; import {IPreset, SOCKET_CONNECT_TO_DST} from './defs'; -import {Utils} from '../utils'; const ATYP_V4 = 0x01; const ATYP_V6 = 0x04; @@ -69,7 +69,7 @@ export default class SSBasePreset extends IPreset { this._isHandshakeDone = true; return Buffer.from([ this._atyp, - ...(this._atyp === ATYP_DOMAIN) ? Utils.numberToUInt(this._addr.length, 1) : [], + ...(this._atyp === ATYP_DOMAIN) ? numberToBuffer(this._addr.length, 1) : [], ...this._addr, ...this._port, ...buffer @@ -122,7 +122,7 @@ export default class SSBasePreset extends IPreset { return; } addr = buffer.slice(2, 2 + domainLen).toString(); - if (!Utils.isValidHostname(addr)) { + if (!isValidHostname(addr)) { fail(`addr=${addr} is an invalid hostname`); return; } diff --git a/src/presets/ss-stream-cipher.js b/src/presets/ss-stream-cipher.js index cdeb1d5..8e26b81 100644 --- a/src/presets/ss-stream-cipher.js +++ b/src/presets/ss-stream-cipher.js @@ -1,6 +1,6 @@ import crypto from 'crypto'; +import {EVP_BytesToKey} from 'blinksocks-utils'; import {IPreset} from './defs'; -import {Utils} from '../utils'; const IV_LEN = 16; @@ -76,7 +76,7 @@ export default class SSStreamCipherPreset extends IPreset { } this._cipherName = method; if (global.__KEY__) { - this._key = Utils.EVP_BytesToKey(__KEY__, this._cipherName.split('-')[1] / 8, IV_LEN); + this._key = EVP_BytesToKey(__KEY__, this._cipherName.split('-')[1] / 8, IV_LEN); } } diff --git a/src/utils/__tests__/advanced-buffer.test.js b/src/utils/__tests__/advanced-buffer.test.js deleted file mode 100755 index 05dd119..0000000 --- a/src/utils/__tests__/advanced-buffer.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import {AdvancedBuffer} from '../advanced-buffer'; - -describe('AdvancedBuffer#constructor', function () { - - it('should throw when getPacketLength not Function', function () { - expect(() => new AdvancedBuffer({getPacketLength: null})).toThrow(); - }); - -}); - -describe('AdvancedBuffer#put', function () { - - it('should throw when pass a non-buffer to put() ', function () { - const buffer = new AdvancedBuffer({ - getPacketLength: () => 0 - }); - expect(() => buffer.put()).toThrow(); - }); - - it('should leave 0xff', function () { - const buffer = new AdvancedBuffer({ - getPacketLength: (chunk) => { - return (chunk.length < 2) ? 0 : chunk.readUInt16BE(0); - } - }); - const callback = jest.fn(); - buffer.on('data', callback); - buffer.put(Buffer.from([0x00, 0x02])); // emit - buffer.put(Buffer.from([0x00])); - buffer.put(Buffer.from([0x02, 0x00])); // emit - buffer.put(Buffer.from([0x03])); - buffer.put(Buffer.from([0x00, 0xff])); // emit - - expect(buffer.final().equals(Buffer.from([0xff]))).toBeTruthy(); - expect(callback).toHaveBeenCalledTimes(3); - }); - - it('should drop the first byte', function () { - let dropped = false; - const buffer = new AdvancedBuffer({ - getPacketLength: (chunk) => { - if (!dropped) { - dropped = true; - return chunk.slice(1); - } else { - return chunk.length > 1 ? chunk.readUInt16BE(0) : 0; - } - } - }); - const callback = jest.fn(); - buffer.on('data', callback); - buffer.put(Buffer.from([0xff, 0x00, 0x02])); // emit - buffer.put(Buffer.from([0x00])); - buffer.put(Buffer.from([0x02, 0x00])); // emit - buffer.put(Buffer.from([0x03])); - buffer.put(Buffer.from([0x00, 0xff])); // emit - - expect(buffer.final().equals(Buffer.from([0xff]))).toBeTruthy(); - expect(callback).toHaveBeenCalledTimes(3); - }); - - it('should drop buffer', function () { - const buffer = new AdvancedBuffer({ - getPacketLength: () => -1 - }); - const callback = jest.fn(); - buffer.on('data', callback); - buffer.put(Buffer.from([0x00])); - - expect(buffer.final().equals(Buffer.alloc(0))).toBeTruthy(); - }); - -}); diff --git a/src/utils/__tests__/utils.test.js b/src/utils/__tests__/utils.test.js deleted file mode 100755 index d7db6dd..0000000 --- a/src/utils/__tests__/utils.test.js +++ /dev/null @@ -1,187 +0,0 @@ -import {Utils, BYTE_ORDER_LE} from '../../utils'; - -describe('Utils#numberToUInt', function () { - - it('should return in big-endian when pass 258', function () { - expect(Utils.numberToUInt(258).equals(Buffer.from([0x01, 0x02]))).toBe(true); - }); - - it('should return in little-endian when pass 258', function () { - expect(Utils.numberToUInt(258, 2, BYTE_ORDER_LE).equals(Buffer.from([0x02, 0x01]))).toBe(true); - }); - - it('should throw when len < 1', function () { - expect(() => Utils.numberToUInt(255, 0)).toThrow(); - }); - - it('should throw when pass an out of range number', function () { - expect(() => Utils.numberToUInt(65535 + 1, 2)).toThrow(); - }); - -}); - -describe('Utils#parseURI', function () { - - it('should return expected object', function () { - let addr = Utils.parseURI('http://bing.com'); - expect(addr).toMatchObject({ - type: 3, - host: Buffer.from('bing.com'), - port: Utils.numberToUInt(80) - }); - - addr = Utils.parseURI('bing.com'); - expect(addr).toMatchObject({ - type: 3, - host: Buffer.from('bing.com'), - port: Utils.numberToUInt(80) - }); - - addr = Utils.parseURI('bing.com:443'); - expect(addr).toMatchObject({ - type: 3, - host: Buffer.from('bing.com'), - port: Utils.numberToUInt(443) - }); - - addr = Utils.parseURI('https://bing.com'); - expect(addr).toMatchObject({ - type: 3, - host: Buffer.from('bing.com'), - port: Utils.numberToUInt(443) - }); - }); - -}); - -describe('Utils#getRandomInt', function () { - - it('should return a number', function () { - const number = Utils.getRandomInt(1, 2); - expect(number).toBeGreaterThanOrEqual(1); - expect(number).toBeLessThanOrEqual(2); - }); - -}); - -describe('Utils#getRandomChunks', function () { - - it('should return expected random chunks', function () { - const chunks = Utils.getRandomChunks([1, 2, 3], 1, 1); - expect(chunks[0]).toEqual([1]); - expect(chunks[1]).toEqual([2]); - expect(chunks[2]).toEqual([3]); - }); - -}); - -describe('Utils#getChunks', function () { - - it('should return expected chunks', function () { - const chunks = Utils.getChunks([1, 2, 3], 1); - expect(chunks[0]).toEqual([1]); - expect(chunks[1]).toEqual([2]); - expect(chunks[2]).toEqual([3]); - }); - -}); - -describe('Utils#getUTC', function () { - - it('should return 4 bytes', function () { - const utc = Utils.getUTC(); - expect(utc.length).toBe(4); - }); - -}); - -describe('Utils#hexStringToBuffer', function () { - - it('should return expected buffer', function () { - const buffer = Utils.hexStringToBuffer('abcd'); - expect(buffer.equals(Buffer.from([0xab, 0xcd]))).toBe(true); - }); - -}); - -describe('Utils#isValidHostname', function () { - - it('should return false', function () { - expect(Utils.isValidHostname('')).toBe(false); - }); - - it('should return false', function () { - expect(Utils.isValidHostname('a.')).toBe(false); - }); - - it('should return false', function () { - expect(Utils.isValidHostname(`${'a'.repeat(64)}.com`)).toBe(false); - }); - - it('should return true', function () { - expect(Utils.isValidHostname(`${'a'.repeat(63)}.com`)).toBe(true); - }); - -}); - -describe('Utils#isValidPort', function () { - - it('should return false', function () { - expect(Utils.isValidPort('')).toBe(false); - }); - - it('should return false', function () { - expect(Utils.isValidPort(-1)).toBe(false); - }); - - it('should return true', function () { - expect(Utils.isValidPort(80)).toBe(true); - }); - -}); - -describe('Utils#md5', function () { - - it('should return expected buffer', function () { - const src = Buffer.from([1, 2, 3, 4]); - const dst = Buffer.from('08d6c05a21512a79a1dfeb9d2a8f262f', 'hex'); - expect(Utils.md5(src).equals(dst)).toBe(true); - }); - -}); - -describe('Utils#hmac', function () { - - it('should return expected buffer', function () { - const src = Buffer.from([1, 2, 3, 4]); - const dst = Buffer.from('7f8adea19a1ac02186fa895af72a7fa1', 'hex'); - expect(Utils.hmac('md5', '', src).equals(dst)).toBe(true); - }); - -}); - -describe('Utils#EVP_BytesToKey', function () { - - it('should return true', function () { - const password = Buffer.from('password'); - const keyLen = 16; - const ivLen = 16; - const dst = Buffer.from('5f4dcc3b5aa765d61d8327deb882cf99', 'hex'); - expect(Utils.EVP_BytesToKey(password, keyLen, ivLen).equals(dst)).toBe(true); - }); - -}); - -describe('Utils#HKDF', function () { - - it('should return expected buffer', function () { - const hash = 'md5'; - const salt = Buffer.alloc(0); - const ikm = Buffer.from([1, 2, 3, 4]); - const info = Buffer.alloc(0); - const length = 16; - const dst = Buffer.from('160ade10f83c4275fca1c8cd0583e4e6', 'hex'); - expect(Utils.HKDF(hash, salt, ikm, info, length).equals(dst)).toBe(true); - }); - -}); diff --git a/src/utils/advanced-buffer.js b/src/utils/advanced-buffer.js deleted file mode 100644 index 65df97c..0000000 --- a/src/utils/advanced-buffer.js +++ /dev/null @@ -1,119 +0,0 @@ -import EventEmitter from 'events'; - -/** - * Provide a mechanism for dealing with packet sticking and incomplete packet - * when receiving data from a socket in a long connection over TCP. - * - * @glossary - * - * [0xff, 0x00, 0x04, 0xff, ...] = packet - * | | - * +--------chunk---------+ - * - * @options - * getPacketLength (Function): how to interpret the bytes to a number - * - * @methods - * .on('data', callback) - * .put(chunk); - * - * @examples - * const buffer = new AdvancedBuffer({ - * getPacketLength: (bytes) => 0 // default - * }); - * - * buffer.on('data', (all) => { - * // all = [0, 2] - * }); - * - * buffer.put(Buffer.from([0, 2])); - * buffer.put(Buffer.from([0])) - * buffer.put... - */ -export class AdvancedBuffer extends EventEmitter { - - // native Buffer instance to store our data - _buffer = Buffer.alloc(0); - - _getPacketLength = null; - - _nextLength = 0; - - constructor(options = {}) { - super(); - if (typeof options.getPacketLength !== 'function') { - throw Error('options.getPacketLength should be a function'); - } - this._getPacketLength = options.getPacketLength; - } - - /** - * put incoming chunk to the buffer, then digest them - * @param chunk{Buffer} - * @param args - */ - put(chunk, ...args) { - if (!(chunk instanceof Buffer)) { - throw Error('chunk must be a Buffer'); - } - this._buffer = this._digest(Buffer.concat([this._buffer, chunk]), ...args); - } - - /** - * get the rest of data in the buffer - * @returns {Buffer} - */ - final() { - return this._buffer; - } - - /** - * digest a buffer, emit an event if a complete packet was resolved - * @param buffer{Buffer}: a buffer to be digested - * @param args - * @returns {Buffer} - */ - _digest(buffer, ...args) { - const expectLen = this._nextLength || this._getPacketLength(buffer, ...args); - - if (expectLen === 0 || typeof expectLen === 'undefined') { - return buffer; // continue to put - } - - if (expectLen === -1) { - return Buffer.alloc(0); // drop this one - } - - if (expectLen instanceof Buffer) { - return this._digest(expectLen, ...args); // start from the new point - } - - // luckily: <- [chunk] - if (buffer.length === expectLen) { - this.emit('data', Buffer.from(buffer), ...args); - this._nextLength = 0; - return Buffer.alloc(0); - } - - // incomplete packet: <- [chu] - if (buffer.length < expectLen) { - // prevent redundant calling to getPacketLength() - this._nextLength = expectLen; - - // continue to put - return buffer; - } - - // packet sticking: <- [chunk][chunk][chu... - if (buffer.length > expectLen) { - this.emit('data', buffer.slice(0, expectLen), ...args); - - // note that each chunk has probably different length - this._nextLength = 0; - - // digest buffer recursively - return this._digest(buffer.slice(expectLen), ...args); - } - } - -} diff --git a/src/utils/index.js b/src/utils/index.js deleted file mode 100644 index e813aa7..0000000 --- a/src/utils/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './advanced-buffer'; -export * from './utils'; diff --git a/src/utils/utils.js b/src/utils/utils.js deleted file mode 100755 index 896b52e..0000000 --- a/src/utils/utils.js +++ /dev/null @@ -1,245 +0,0 @@ -import net from 'net'; -import url from 'url'; -import crypto from 'crypto'; -import ip from 'ip'; - -export const ATYP_V4 = 1; -export const ATYP_DOMAIN = 3; -export const ATYP_V6 = 4; - -export const BYTE_ORDER_BE = 0; -export const BYTE_ORDER_LE = 1; - -export class Utils { - - /** - * convert a number to a buffer with specified length in big-endian - * @param num - * @param len - * @param byteOrder - * @returns {Buffer} - */ - static numberToUInt(num, len = 2, byteOrder = BYTE_ORDER_BE) { - if (len < 1) { - throw Error('len must be greater than 0'); - } - - const isOutOfRange = num > parseInt(`0x${'ff'.repeat(len)}`); - if (isOutOfRange) { - throw Error(`Number ${num} is too long to store in a '${len}' length buffer`); - } - - const buf = Buffer.alloc(len); - if (byteOrder === BYTE_ORDER_BE) { - buf.writeUIntBE(num, 0, len); - } else { - buf.writeUIntLE(num, 0, len); - } - return buf; - } - - /** - * convert an uri to Address - * @param uri - * @returns {{type: Number, host: Buffer, port: Buffer}} - */ - static parseURI(uri) { - let _uri = uri; - if (_uri.indexOf('http') !== 0 && _uri.indexOf('https') !== 0) { - if (_uri.indexOf(':443') !== -1) { - // e.g, bing.com:443 - _uri = `https://${_uri}`; - } else { - // e.g, bing.com - _uri = `http://${_uri}`; - } - } - const {protocol, hostname} = url.parse(_uri); - const addrType = net.isIP(hostname) ? (net.isIPv4(hostname) ? ATYP_V4 : ATYP_V6) : ATYP_DOMAIN; - const port = {'http:': 80, 'https:': 443}[protocol]; - return { - type: addrType, - host: net.isIP(hostname) ? ip.toBuffer(hostname) : Buffer.from(hostname), - port: this.numberToUInt(port) - }; - } - - /** - * returns a random integer in [min, max]. - * @param min - * @param max - * @returns {Number} - */ - static getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.ceil(max); - return Math.floor(crypto.randomBytes(1)[0] / 0xff * (max - min + 1)) + min; - } - - /** - * split buffer into chunks, each chunk size is picked randomly from [min, max] - * @param buffer - * @param min - * @param max - * @returns {Array} - */ - static getRandomChunks(buffer, min, max) { - const totalLen = buffer.length; - const bufs = []; - let ptr = 0; - while (ptr < totalLen - 1) { - const offset = this.getRandomInt(min, max); - bufs.push(buffer.slice(ptr, ptr + offset)); - ptr += offset; - } - if (ptr < totalLen) { - bufs.push(buffer.slice(ptr)); - } - return bufs; - } - - /** - * split buffer into chunks, the max chunk size is maxSize - * @param buffer - * @param maxSize - * @returns {Array} - */ - static getChunks(buffer, maxSize) { - const totalLen = buffer.length; - const bufs = []; - let ptr = 0; - while (ptr < totalLen - 1) { - bufs.push(buffer.slice(ptr, ptr + maxSize)); - ptr += maxSize; - } - if (ptr < totalLen) { - bufs.push(buffer.slice(ptr)); - } - return bufs; - } - - /** - * return UTC timestamp as buffer - * @returns {Buffer} - */ - static getUTC() { - const ts = Math.floor((new Date()).getTime() / 1e3); - return this.numberToUInt(ts, 4, BYTE_ORDER_BE); - } - - /** - * convert string to buffer - * @param str - * @returns {Buffer} - */ - static hexStringToBuffer(str) { - return Buffer.from(str, 'hex'); - } - - /** - * verify hostname - * - * @param hostname - * @returns {boolean} - * - * @reference - * http://stackoverflow.com/questions/1755144/how-to-validate-domain-name-in-php - */ - static isValidHostname(hostname) { - // overall length check - if (hostname.length < 1 || hostname.length > 253) { - return false; - } - // valid chars check - if (/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i.test(hostname) === false) { - return false; - } - // length of each label - if (/^[^.]{1,63}(\.[^.]{1,63})*$/.test(hostname) === false) { - return false; - } - return true; - } - - /** - * whether a port is valid or not - * @param port - * @returns {boolean} - */ - static isValidPort(port) { - if (typeof port !== 'number') { - return false; - } - if (port < 0 || port > 65535) { - return false; - } - return true; - } - - /** - * md5 message digest - * @param buffer - * @returns {*} - */ - static md5(buffer) { - const md5 = crypto.createHash('md5'); - md5.update(buffer); - return md5.digest(); - } - - /** - * calculate the HMAC from key and message - * @param algorithm - * @param key - * @param buffer - * @returns {Buffer} - */ - static hmac(algorithm, key, buffer) { - const hmac = crypto.createHmac(algorithm, key); - return hmac.update(buffer).digest(); - } - - /** - * EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt - * - * @algorithm - * D_i = HASH^count(D_(i-1) || data || salt) - */ - static EVP_BytesToKey(password, keyLen, ivLen) { - let _data = Buffer.from(password); - let i = 0; - const bufs = []; - while (Buffer.concat(bufs).length < (keyLen + ivLen)) { - if (i > 0) { - _data = Buffer.concat([bufs[i - 1], Buffer.from(password)]); - } - bufs.push(this.md5(_data)); - i += 1; - } - return Buffer.concat(bufs).slice(0, keyLen); - } - - /** - * HMAC-based Extract-and-Expand Key Derivation Function - * @param hash, the message digest algorithm - * @param salt, a non-secret random value - * @param ikm, input keying material - * @param info, optional context and application specific information - * @param length, length of output keying material in octets - * @returns {Buffer} - */ - static HKDF(hash, salt, ikm, info, length) { - // Step 1: "extract" to fixed length pseudo-random key(prk) - const prk = this.hmac(hash, salt, ikm); - // Step 2: "expand" prk to several pseudo-random keys(okm) - let t = Buffer.alloc(0); - let okm = Buffer.alloc(0); - for (let i = 0; i < Math.ceil(length / prk.length); ++i) { - t = this.hmac(hash, prk, Buffer.concat([t, info, Buffer.alloc(1, i + 1)])); - okm = Buffer.concat([okm, t]); - } - // Step 3: crop okm to desired length - return okm.slice(0, length); - } - -} diff --git a/yarn.lock b/yarn.lock index 7bea6a8..283816e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -738,6 +738,12 @@ binary-extensions@^1.0.0: version "1.8.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" +blinksocks-utils@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/blinksocks-utils/-/blinksocks-utils-0.0.2.tgz#e1826bc2d2f3d57f541ad7d594f7aacd9b5ccf34" + dependencies: + ip "^1.1.5" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"