refactor(utils): use "blinksocks-utils" package instead

This commit is contained in:
Micooz 2017-04-23 19:47:05 +08:00
parent 85ee38cb8d
commit a333eb1408
27 changed files with 99 additions and 709 deletions

@ -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,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);

@ -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"