refactor(utils): use "blinksocks-utils" package instead
This commit is contained in:
parent
85ee38cb8d
commit
a333eb1408
@ -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;
|
||||
'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;
|
@ -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;
|
||||
'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;
|
File diff suppressed because one or more lines are too long
@ -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]}})});
|
||||
'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]}})});
|
File diff suppressed because one or more lines are too long
@ -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<size){return}const salt=buffer.slice(0,size);this._decipherKey=_utils.Utils.HKDF(HKDF_HASH_ALGORITHM,salt,_utils.Utils.EVP_BytesToKey(__KEY__,size,16),this._info,size);return buffer.slice(size)}if(buffer.length<MIN_CHUNK_LEN){return}var _ref=[buffer.slice(0,2),buffer.slice(2,2+TAG_LEN)];const encLen=_ref[0],lenTag=_ref[1];const dataLen=this.decrypt(encLen,lenTag);if(dataLen===null){fail(`unexpected DataLen_TAG=${lenTag.toString('hex')} when verify DataLen=${encLen.toString('hex')}`);return-1}return 2+TAG_LEN+dataLen.readUInt16BE(0)+TAG_LEN}onChunkReceived(chunk,{next,fail}){var _ref2=[chunk.slice(2+TAG_LEN,-TAG_LEN),chunk.slice(-TAG_LEN)];const encData=_ref2[0],dataTag=_ref2[1];const data=this.decrypt(encData,dataTag);if(data===null){fail(`unexpected Data_TAG=${dataTag.toString('hex')} when verify Data=${encData.slice(0,60).toString('hex')}`);return}next(data)}encrypt(message){const cipher=_crypto2.default.createCipheriv(this._cipherName,this._cipherKey,_utils.Utils.numberToUInt(this._cipherNonce,NONCE_LEN,_utils.BYTE_ORDER_LE));const encrypted=Buffer.concat([cipher.update(message),cipher.final()]);const tag=cipher.getAuthTag();this._cipherNonce+=1;return[encrypted,tag]}decrypt(ciphertext,tag){const decipher=_crypto2.default.createDecipheriv(this._cipherName,this._decipherKey,_utils.Utils.numberToUInt(this._decipherNonce,NONCE_LEN,_utils.BYTE_ORDER_LE));decipher.setAuthTag(tag);try{const decrypted=Buffer.concat([decipher.update(ciphertext),decipher.final()]);this._decipherNonce+=1;return decrypted}catch(err){return null}}}exports.default=SSAeadCipherPreset;
|
||||
'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 _blinksocksUtils=require('blinksocks-utils');var _defs=require('./defs');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 _blinksocksUtils.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=(0,_blinksocksUtils.HKDF)(HKDF_HASH_ALGORITHM,salt,(0,_blinksocksUtils.EVP_BytesToKey)(__KEY__,size,16),this._info,size)}const chunks=(0,_blinksocksUtils.getRandomChunks)(buffer,MIN_CHUNK_SPLIT_LEN,MAX_CHUNK_SPLIT_LEN).map(chunk=>{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<size){return}const salt=buffer.slice(0,size);this._decipherKey=(0,_blinksocksUtils.HKDF)(HKDF_HASH_ALGORITHM,salt,(0,_blinksocksUtils.EVP_BytesToKey)(__KEY__,size,16),this._info,size);return buffer.slice(size)}if(buffer.length<MIN_CHUNK_LEN){return}var _ref=[buffer.slice(0,2),buffer.slice(2,2+TAG_LEN)];const encLen=_ref[0],lenTag=_ref[1];const dataLen=this.decrypt(encLen,lenTag);if(dataLen===null){fail(`unexpected DataLen_TAG=${lenTag.toString('hex')} when verify DataLen=${encLen.toString('hex')}`);return-1}return 2+TAG_LEN+dataLen.readUInt16BE(0)+TAG_LEN}onChunkReceived(chunk,{next,fail}){var _ref2=[chunk.slice(2+TAG_LEN,-TAG_LEN),chunk.slice(-TAG_LEN)];const encData=_ref2[0],dataTag=_ref2[1];const data=this.decrypt(encData,dataTag);if(data===null){fail(`unexpected Data_TAG=${dataTag.toString('hex')} when verify Data=${encData.slice(0,60).toString('hex')}`);return}next(data)}encrypt(message){const cipher=_crypto2.default.createCipheriv(this._cipherName,this._cipherKey,(0,_blinksocksUtils.numberToBuffer)(this._cipherNonce,NONCE_LEN,_blinksocksUtils.BYTE_ORDER_LE));const encrypted=Buffer.concat([cipher.update(message),cipher.final()]);const tag=cipher.getAuthTag();this._cipherNonce+=1;return[encrypted,tag]}decrypt(ciphertext,tag){const decipher=_crypto2.default.createDecipheriv(this._cipherName,this._decipherKey,(0,_blinksocksUtils.numberToBuffer)(this._decipherNonce,NONCE_LEN,_blinksocksUtils.BYTE_ORDER_LE));decipher.setAuthTag(tag);try{const decrypted=Buffer.concat([decipher.update(ciphertext),decipher.final()]);this._decipherNonce+=1;return decrypted}catch(err){return null}}}exports.default=SSAeadCipherPreset;
|
@ -1 +1 @@
|
||||
'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _ip=require('ip');var _ip2=_interopRequireDefault(_ip);var _defs=require('./defs');var _utils=require('../utils');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?_utils.Utils.numberToUInt(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<domainLen+4){fail(`invalid length: ${buffer.length}`);return}addr=buffer.slice(2,2+domainLen).toString();if(!_utils.Utils.isValidHostname(addr)){fail(`addr=${addr} is an invalid hostname`);return}port=buffer.slice(2+domainLen,4+domainLen).readUInt16BE(0);offset+=domainLen+1;break;default:fail(`invalid atyp: ${atyp}`);return;}const data=buffer.slice(offset);broadcast({type:_defs.SOCKET_CONNECT_TO_DST,payload:{targetAddress:{type:atyp,host:addr,port},onConnected:()=>{next(Buffer.concat([this._staging,data]));this._isHandshakeDone=true;this._staging=null}}});this._isAddressReceived=true}else{return buffer}}}exports.default=SSBasePreset;
|
||||
'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<domainLen+4){fail(`invalid length: ${buffer.length}`);return}addr=buffer.slice(2,2+domainLen).toString();if(!(0,_blinksocksUtils.isValidHostname)(addr)){fail(`addr=${addr} is an invalid hostname`);return}port=buffer.slice(2+domainLen,4+domainLen).readUInt16BE(0);offset+=domainLen+1;break;default:fail(`invalid atyp: ${atyp}`);return;}const data=buffer.slice(offset);broadcast({type:_defs.SOCKET_CONNECT_TO_DST,payload:{targetAddress:{type:atyp,host:addr,port},onConnected:()=>{next(Buffer.concat([this._staging,data]));this._isHandshakeDone=true;this._staging=null}}});this._isAddressReceived=true}else{return buffer}}}exports.default=SSBasePreset;
|
@ -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;
|
||||
'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;
|
@ -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.length<expectLen){this._nextLength=expectLen;return buffer}if(buffer.length>expectLen){this.emit('data',buffer.slice(0,expectLen),...args);this._nextLength=0;return this._digest(buffer.slice(expectLen),...args)}}}exports.AdvancedBuffer=AdvancedBuffer;
|
@ -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]}})});
|
@ -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(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}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}static getUTC(){const ts=Math.floor(new Date().getTime()/1e3);return this.numberToUInt(ts,4,BYTE_ORDER_BE)}static hexStringToBuffer(str){return Buffer.from(str,'hex')}static isValidHostname(hostname){if(hostname.length<1||hostname.length>253){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).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)}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<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])}return okm.slice(0,length)}}exports.Utils=Utils;
|
@ -29,6 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"blinksocks-utils": "^0.0.2",
|
||||
"commander": "^2.9.0",
|
||||
"ip": "^1.1.5",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
@ -60,6 +61,7 @@
|
||||
"http",
|
||||
"proxy",
|
||||
"nodejs",
|
||||
"blinksocks",
|
||||
"shadowsocks",
|
||||
"shadowsocksr",
|
||||
"middleware",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Utils} from '../utils';
|
||||
import {parseURI} from 'blinksocks-utils';
|
||||
|
||||
import {
|
||||
IdentifierMessage,
|
||||
@ -127,7 +127,7 @@ export class ClientProxy {
|
||||
const request = HttpRequestMessage.parse(buffer);
|
||||
if (request !== null) {
|
||||
const {METHOD, HOST} = request;
|
||||
const addr = Utils.parseURI(HOST.toString());
|
||||
const addr = parseURI(HOST.toString());
|
||||
|
||||
this.onHandshakeDone(addr, (onForward) => {
|
||||
if (METHOD.toString() === 'CONNECT') {
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './core';
|
||||
export * from './utils';
|
||||
|
@ -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 () {
|
||||
|
@ -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,7 +79,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
|
||||
@ -102,17 +107,17 @@ 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
|
||||
...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)
|
||||
...Utils.numberToUInt(this._sni.length), // Server Name length
|
||||
...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
|
||||
@ -136,22 +141,22 @@ export default class ObfsTLS12TicketPreset extends IPreset {
|
||||
...cipher_suites, // Cipher Suites
|
||||
...stb('01'), // Compression Methods Length
|
||||
...stb('00'), // Compression Methods = [null]
|
||||
...Utils.numberToUInt(exts.length), // Extension Length
|
||||
...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
|
||||
...numberToBuffer(1 + 3 + body.length), // Length
|
||||
...stb('01'), // Handshake Type: ClientHello
|
||||
...Utils.numberToUInt(body.length, 3) // Length
|
||||
...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
|
||||
@ -207,16 +212,16 @@ export default class ObfsTLS12TicketPreset extends IPreset {
|
||||
...session, // Session
|
||||
...stb('c02f'), // Cipher Suite
|
||||
...stb('00'), // Compression Method
|
||||
...Utils.numberToUInt(exts.length), // Extension Length
|
||||
...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
|
||||
...numberToBuffer(1 + 3 + body.length), // Length
|
||||
...stb('02'), // Handshake Type: Server Hello
|
||||
...Utils.numberToUInt(body.length, 3) // Length
|
||||
...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
|
||||
...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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
@ -1,187 +0,0 @@
|
||||
import {Utils, BYTE_ORDER_LE} from '../../utils';
|
||||
|
||||
describe('Utils#numberToUInt', function () {
|
||||
|
||||
it('should return <Buffer 01, 02> in big-endian when pass 258', function () {
|
||||
expect(Utils.numberToUInt(258).equals(Buffer.from([0x01, 0x02]))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return <Buffer 02, 01> 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);
|
||||
});
|
||||
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './advanced-buffer';
|
||||
export * from './utils';
|
@ -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<Buffer>}
|
||||
*/
|
||||
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<Buffer>}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user