@tmsfe/tms-core 0.0.168 → 0.0.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import md5 from'./md5';const ecc=require('./nacl.min.js');const base64Util=require('./nacl-util.min.js');const logger=wx.getLogManager({});interface BaseResp<T>{success:boolean,msg:string,res:T,}interface CryptoKeyInfo{sharedByte:Uint8Array,clientPublicKey:string,serverPubId:string,}const SERVER_HOST_MAP={production:'https://tim.map.qq.com',test:'https://tim.sparta.html5.qq.com',development:'https://tim.sparta.html5.qq.com/dev',predist:'https://tim.sparta.html5.qq.com/pre',mock:'http://localhost:8003',};const baseUtil={_isObject:(obj:any):boolean=>Object.prototype.toString.call(obj)==='[object Object]',_formatLog(args:any[]):any[]{const time=new Date().toISOString().replace('T',' ').substring(0,19).replace(/-/g,'-').replace(/:/g,':');args.unshift(time);return args},logInfo:(...args)=>{args.unshift('request_encrypt_log');const items=baseUtil._formatLog(args);console.log(...items);logger.log(...items)},encUrl:(input:Uint8Array):string=>{let base64=base64Util.encode(input);base64=base64 .replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');return base64},decUrl:(input:string):Uint8Array=>{let base64=input.replace(/-/g,'+').replace(/_/g,'/');while(base64.length%4){base64+='='}return base64Util.decode(base64)},formatHeader:(header):any=>{if(!header||!baseUtil._isObject(header)){return{}}const formatHeader={};Object.keys(header).forEach((key)=>{formatHeader[key.toLocaleLowerCase()]=header[key]});return formatHeader},formatGetData:(params:any):string=>{if(!params||!baseUtil._isObject(params)){return''}return Object.keys(params).map(key=>`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&')},getSinanHost:():string=>{if(wx.$_encryptEnvInfo){return SERVER_HOST_MAP[wx.$_encryptEnvInfo.envName]}return''},getClientEncryptOpen:():boolean=>wx.$_encryptEnvInfo?.requestEncryptOpen,BaseRespFac:class BaseRespFac<T>implements BaseResp<T>{success:boolean;msg:string;res:T;constructor(res:T,success?:boolean,msg?:string){this.success=success===undefined?true:success;this.msg=msg===undefined?'':msg;this.res=res}},};let composeParamsFunc=null;const eccUtil={_refreshPromise:null,_refreshPubKeyInfo:async(forceRefresh:boolean):Promise<boolean>=>{if(!composeParamsFunc){return false}if(!wx.$_publicKey||forceRefresh){eccUtil._refreshPromise=new Promise(async(resolve)=>{const url=`${baseUtil.getSinanHost()}/basic/crypto/lastkey2`;const data=await composeParamsFunc({});wx.request({url,method:'POST',data,enableHttp2:true,success:()=>{resolve(true)},fail:()=>{resolve(false)},})})}return eccUtil._refreshPromise},_updateGlobalPublicKeyInfo:(clear:boolean,resHeader?:{[key:string]:any}):BaseResp<boolean>=>{if(clear){wx.$_publicKey=null;return}const header=baseUtil.formatHeader(resHeader);const{'x-gateway-code':code,'x-crypto-pub-id':pubId,'x-crypto-pub-key':pubKey,'x-crypto-pub-exp':pubExp,'x-crypto-path':pathRule,}=header;const success=!code&&pubId;wx.$_publicKey=success?{pubId,pubKey,pubExp,pathRule}:null;return new baseUtil.BaseRespFac(success)},_privateKeyInfo:null,getPrivateKeyInfo:(forceUpdate=false):CryptoKeyInfo=>{if(!wx.$_publicKey)return null;const serverPubInfo=wx.$_publicKey;if(forceUpdate||!eccUtil._privateKeyInfo||eccUtil._privateKeyInfo.serverPubId!==serverPubInfo.pubId){const keyPair=ecc.box.keyPair();eccUtil._privateKeyInfo={serverPubId:serverPubInfo.pubId,sharedByte:ecc.box.before(baseUtil.decUrl(serverPubInfo.pubKey),keyPair.secretKey),clientPublicKey:baseUtil.encUrl(keyPair.publicKey),}}return eccUtil._privateKeyInfo},resolveGwCode:async(codeStr:string):Promise<{retry:boolean,success:boolean}>=>{if(!codeStr)return{retry:false,success:true};const code=parseInt(codeStr,10);switch(code){case 0:return{retry:false,success:true};case 11305:case 11306:{eccUtil._refreshPubKeyInfo(true).then((genPubSuccess)=>{genPubSuccess&&eccUtil.getPrivateKeyInfo()});return{retry:true,success:false}}case 11308:{eccUtil.getPrivateKeyInfo(true);return{retry:true,success:false}}case 11300:case 11301:case 11302:case 11303:case 11304:case 11307:return{retry:true,success:false};case 11309:return{retry:false,success:false};default:return{retry:false,success:true}}},_getSignSharedByte:()=>baseUtil.decUrl('mEufQpM1n5J8-OZZoJE7ucYMC2suTjfsHUq_6z5cyh8'),getClientCryptoSign:(data={},header={},sharedByte):string=>{const obj=baseUtil.formatHeader(Object.assign({},data,header));const str=Object.keys(obj).filter(item=>obj[item]).sort().reduce((pre,cur)=>{pre.push(`${cur}=${obj[cur]}`);return pre},[]).join('&');baseUtil.logInfo('---客户端签名---:before',str);const md5Str=md5(str);const nonce=ecc.randomBytes(ecc.box.nonceLength);const encrypted=ecc.box.after(md5Str,nonce,sharedByte);const combined=new Uint8Array(nonce.length+encrypted.length);combined.set(nonce);combined.set(encrypted,nonce.length);return baseUtil.encUrl(combined)},verifyServerCryptoSign:(traceId:string,resHeader={}):boolean=>{try{const formatHeader=baseUtil.formatHeader(resHeader);const signStr=formatHeader['x-crypto-sign'];if(!signStr){return false}const obj={'x-encrypt-key':formatHeader['x-encrypt-key'],'x-encrypt-response':formatHeader['x-encrypt-response'],'x-response-header-name':formatHeader['x-response-header-name'],'x-encrypted-headers':formatHeader['x-encrypted-headers'],'x-crypto-enable':formatHeader['x-crypto-enable'],'x-gateway-code':formatHeader['x-gateway-code'],'x-crypto-pub-id':formatHeader['x-crypto-pub-id'],'x-crypto-pub-key':formatHeader['x-crypto-pub-key'],'x-crypto-pub-exp':formatHeader['x-crypto-pub-exp'],'x-crypto-path':formatHeader['x-crypto-path'],'x-trace-id':traceId,};const msg=baseUtil.decUrl(signStr);const decrypted=ecc.sign.open(msg,eccUtil._getSignSharedByte());const str=Object.keys(obj).filter(item=>obj[item]).sort().reduce((pre,cur)=>{pre.push(`${cur}=${obj[cur]}`);return pre},[]).join('&');baseUtil.logInfo('---验证服务端的客户端签名---:before',str,traceId);const preHashArr=md5(str);const verified=preHashArr.length===decrypted.length&&preHashArr.every((v,i)=>v===decrypted[i]);return verified}catch(e){console.error('verifyServerCryptoSign error',e);return false}},execEncrypt:(input:string,ignoreNull=false):BaseResp<{cryptoKeyInfo:CryptoKeyInfo,encryptedContent:any}|null>=>{try{const eccKeyInfo=eccUtil.getPrivateKeyInfo();if(!eccKeyInfo){return new baseUtil.BaseRespFac(null,false,'加密失败:无加密秘钥信息')}const cryptoKeyInfo=Object.assign({},eccKeyInfo);if(ignoreNull&&(!input||input==='{}')){return new baseUtil.BaseRespFac({cryptoKeyInfo,encryptedContent:''})}const{sharedByte}=cryptoKeyInfo;const nonce=ecc.randomBytes(ecc.box.nonceLength);const encrypted=ecc.box.after(base64Util.decodeUTF8(input),nonce,sharedByte);const combined=new Uint8Array(nonce.length+encrypted.length);combined.set(nonce);combined.set(encrypted,nonce.length);const encryptedStr=baseUtil.encUrl(combined);return new baseUtil.BaseRespFac({cryptoKeyInfo,encryptedContent:encryptedStr,})}catch(e){return new baseUtil.BaseRespFac(null,false,`execEncrypt失败:${JSON.stringify(e)}`)}},execDecrypt:(msg:Uint8Array,cryptoKeyInfo:CryptoKeyInfo):BaseResp<string>=>{try{const{sharedByte}=cryptoKeyInfo;const nonce=msg.slice(0,ecc.box.nonceLength);const encrypted=msg.slice(ecc.box.nonceLength);const decrypted=ecc.box.open.after(encrypted,nonce,sharedByte);const decryptedStr=base64Util.encodeUTF8(decrypted);return new baseUtil.BaseRespFac(decryptedStr)}catch(err){return new baseUtil.BaseRespFac('',false,`execDecrypt失败:${JSON.stringify(err)}`)}},checkCryptoOpen:():boolean=>!!eccUtil._privateKeyInfo,closeCrypto:()=>{eccUtil._privateKeyInfo=null;eccUtil._updateGlobalPublicKeyInfo(true)},openCrypto:async():Promise<boolean>=>{const genPubSuccess=await eccUtil._refreshPubKeyInfo(false);if(!genPubSuccess)return false;eccUtil.getPrivateKeyInfo();return true},};const cryptRuleUtil={isServerOpen:():boolean=>!!wx.$_publicKey,pathInEnablePrefix:(path:string):boolean=>{if(!wx.$_publicKey){return false}const{pathRule}=wx.$_publicKey;if(pathRule==='*'){return true}const prefixArr=pathRule.split(',').map(item=>item.trim());for(let i=0,len=prefixArr.length;i<len;i++){if(path.indexOf(prefixArr[i])>-1){return true}}return false},isPerformanceReport:(path:string,params:any):boolean=>{if(path.indexOf('basic/event/upload')>-1){if(params.batch?.length===1&¶ms.batch[0]?.[31]==='tms-performance-log'){return true}return false}return false},_encryptPathRule:{'tim.map.qq.com':['^/user/login','^/api/getClientConfigs','^/basic/crypto/lastkey2'],'tim.sparta.html5.qq.com':['^/user/login','^/cnabroad','^~/ReChargeCard/','^/gasolinerecharge/v2/','^/gasolinerecharge/rechargecard/','^/tde','^/basic/crypto/lastkey2',],},isHostValid:(url)=>{const urlPattern=/^(https?:\/\/)?([^/?#]+)([/?#].*)?$/;const matches=url.match(urlPattern);if(!matches){console.error('Invalid URL:',url);return false}const domain=matches[2];const path=matches[3]||'/';if(cryptRuleUtil._encryptPathRule[domain]){const pathRules=cryptRuleUtil._encryptPathRule[domain];for(const rule of pathRules){const regex=new RegExp(rule);if(regex.test(path)){return false}}return true}return false},};const init=(composeFunc:Function):BaseResp<null>=>{composeParamsFunc=(...args)=>composeFunc(...args);return new baseUtil.BaseRespFac(null)};const isCryptoRuleMath=(path:string,reqData:any):BaseResp<boolean>=>{if(!wx.$_encryptEnvInfo.requestEncryptOpen){return new baseUtil.BaseRespFac(false,false,'本地加密未开启')}if(!cryptRuleUtil.isServerOpen()){return new baseUtil.BaseRespFac(false,false,'服务端加密未开启')}if(!cryptRuleUtil.pathInEnablePrefix(path)){return new baseUtil.BaseRespFac(false,false,'未命中服务端加密规则')}if(cryptRuleUtil.isPerformanceReport(path,reqData)){return new baseUtil.BaseRespFac(false,false,'性能埋点')}if(!cryptRuleUtil.isHostValid(path)){return new baseUtil.BaseRespFac(false,false,'非sinan网关加密接口')}return new baseUtil.BaseRespFac(true)};const reqEncrypt=(method:string,data:any,header:{[key:string]:any},encryptedResponseHeaderName:string):BaseResp<{cryptoKeyInfo:CryptoKeyInfo,header:any,data:any,}|null>=>{const reqHeader=baseUtil.formatHeader(header);if(reqHeader.contentType){return new baseUtil.BaseRespFac(null,false,'户自定义了请求contentType')}let finalData={};if(method.toUpperCase()==='GET'){const searchParams=baseUtil.formatGetData(data);if(!searchParams){return new baseUtil.BaseRespFac(null,false,`GET请求参数不满足加密的规则:${JSON.stringify(data)}`)}const{success,msg,res}=eccUtil.execEncrypt(searchParams);if(!success){return new baseUtil.BaseRespFac(null,false,`GET请求参数加密失败:${msg}`)}finalData={tmsec:res.encryptedContent}}else{const{success,msg,res}=eccUtil.execEncrypt(JSON.stringify(data));if(!success){return new baseUtil.BaseRespFac(null,false,`${method}请求参数加密失败:${msg}`)}finalData=res.encryptedContent}const{success,msg,res}=eccUtil.execEncrypt(JSON.stringify(header),true);if(!success){return new baseUtil.BaseRespFac(null,false,`请求Header加密失败:${msg}`)}const cryptoHeader={'X-Crypto-Mode':'2','X-Encrypted-Headers':res.encryptedContent,'X-Encrypt-Pub':res.cryptoKeyInfo.serverPubId,'X-Encrypt-Key':res.cryptoKeyInfo.clientPublicKey,'X-Encrypt-Response':'3','X-Response-Header-Name':encryptedResponseHeaderName,};const cryptoSign=eccUtil.getClientCryptoSign(baseUtil._isObject(finalData)?finalData:{body:finalData,},cryptoHeader,res.cryptoKeyInfo.sharedByte);return new baseUtil.BaseRespFac({cryptoKeyInfo:res.cryptoKeyInfo,data:finalData,header:{...cryptoHeader,'Content-Type':'text/plain','X-Crypto-Sign':cryptoSign,},})};const resDecrypt=async(requestTraceId:string,header,data,cryptoKeyInfo:CryptoKeyInfo):Promise<BaseResp<{retry:boolean,header:any,data:any,}>>=>{try{const formatHeader=baseUtil.formatHeader(header);const{'x-encrypt-response':encryptResponseMode,'x-response-header-name':encryptedResponseHeaderName,'x-encrypted-headers':encryptedHeaders,'x-gateway-code':gatewayCode,'content-type':contentType,}=formatHeader;if(!encryptResponseMode||encryptResponseMode==='0'){const dataStr=base64Util.encodeUTF8(new Uint8Array(data));return new baseUtil.BaseRespFac({header,data:JSON.parse(dataStr),retry:false,})}const{retry,success:gwSuccess}=await eccUtil.resolveGwCode(gatewayCode);if(!gwSuccess){const verified=eccUtil.verifyServerCryptoSign(requestTraceId,header);if(!verified){return new baseUtil.BaseRespFac({header:null,data:null,retry:false},false,'响应被篡改')}return new baseUtil.BaseRespFac({header:null,data:null,retry},false,`网关返回错误码:${gatewayCode}`)}let decryptedHeaders={};if(encryptedResponseHeaderName){const{success,msg,res}=eccUtil.execDecrypt(baseUtil.decUrl(encryptedHeaders),cryptoKeyInfo);if(!success){return new baseUtil.BaseRespFac({header:null,data:null,retry:true},false,`解密响应Header失败:${msg}`)}decryptedHeaders=JSON.parse(res)}const needDecode=contentType.indexOf('text/plain')>-1;const cipher=needDecode?baseUtil.decUrl(data):new Uint8Array(data);const{success,msg,res}=eccUtil.execDecrypt(cipher,cryptoKeyInfo);if(!success){return new baseUtil.BaseRespFac({header:null,data:null,retry:true},false,`解密响应Body失败:${msg}`)}const decryptedBody=JSON.parse(res);return new baseUtil.BaseRespFac({retry:false,header:{...header,...decryptedHeaders,},data:decryptedBody,})}catch(e){return new baseUtil.BaseRespFac({header:null,data:null,retry:true},false,`解密响应Body失败:${JSON.stringify(e)}`)}};let dealEncryptionSwitching=false;const dealEncryptionSwitch=async(path:string,traceId:string,resHeader):Promise<void>=>{if((!resHeader||dealEncryptionSwitching)){return}dealEncryptionSwitching=true;const formatHeader=baseUtil.formatHeader(resHeader);const cryptoDisabled=formatHeader['x-crypto-enable']==='0';if((eccUtil.checkCryptoOpen()&&cryptoDisabled)){const verified=eccUtil.verifyServerCryptoSign(traceId,formatHeader);if(!verified){dealEncryptionSwitching=false;baseUtil.logInfo(`验签失败:${path}:${traceId}`);return}}if(cryptoDisabled){eccUtil.closeCrypto()}else if(formatHeader['x-crypto-enable']==='1'){await eccUtil.openCrypto()}dealEncryptionSwitching=false;return};const dealRes=(path:string,traceId:string,resHeader,reqData):BaseResp<boolean>=>{const specialPath=[`${baseUtil.getSinanHost()}/user/login`,`${baseUtil.getSinanHost()}/basic/crypto/lastkey2`,].indexOf(path)>-1;if(specialPath){const formatHeader=baseUtil.formatHeader(resHeader);const verified=eccUtil.verifyServerCryptoSign(traceId,formatHeader);if(!verified){return new baseUtil.BaseRespFac(false,false,`验签失败:${path}:${traceId}`)}eccUtil._updateGlobalPublicKeyInfo(false,resHeader)}return new baseUtil.BaseRespFac(!cryptRuleUtil.isPerformanceReport(path,reqData))};const encryptUtil={init,isCryptoRuleMath,logInfo:baseUtil.logInfo,dealRes,reqEncrypt,resDecrypt,dealEncryptionSwitch,};export default encryptUtil;
|
|
1
|
+
import md5 from'./md5';const ecc=require('./nacl.min.js');const base64Util=require('./nacl-util.min.js');const logger=wx.getLogManager({});interface BaseResp<T>{success:boolean,msg:string,res:T,}interface CryptoKeyInfo{sharedByte:Uint8Array,clientPublicKey:string,serverPubId:string,}const SERVER_HOST_MAP={production:'https://tim.map.qq.com',test:'https://tim.sparta.html5.qq.com',development:'https://tim.sparta.html5.qq.com/dev',predist:'https://tim.sparta.html5.qq.com/pre',mock:'http://localhost:8003',};const baseUtil={_isObject:(obj:any):boolean=>Object.prototype.toString.call(obj)==='[object Object]',_formatLog(args:any[]):any[]{const time=new Date().toISOString().replace('T',' ').substring(0,19).replace(/-/g,'-').replace(/:/g,':');args.unshift(time);return args},logInfo:(...args)=>{args.unshift('request_encrypt_log');const items=baseUtil._formatLog(args);logger.log(...items)},encUrl:(input:Uint8Array):string=>{let base64=base64Util.encode(input);base64=base64 .replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');return base64},decUrl:(input:string):Uint8Array=>{let base64=input.replace(/-/g,'+').replace(/_/g,'/');while(base64.length%4){base64+='='}return base64Util.decode(base64)},formatHeader:(header):any=>{if(!header||!baseUtil._isObject(header)){return{}}const formatHeader={};Object.keys(header).forEach((key)=>{formatHeader[key.toLocaleLowerCase()]=header[key]});return formatHeader},formatGetData:(params:any):string=>{if(!params||!baseUtil._isObject(params)){return''}return Object.keys(params).map(key=>`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&')},getSinanHost:():string=>{if(wx.$_encryptEnvInfo){return SERVER_HOST_MAP[wx.$_encryptEnvInfo.envName]}return''},getClientEncryptOpen:():boolean=>wx.$_encryptEnvInfo?.requestEncryptOpen,BaseRespFac:class BaseRespFac<T>implements BaseResp<T>{success:boolean;msg:string;res:T;constructor(res:T,success?:boolean,msg?:string){this.success=success===undefined?true:success;this.msg=msg===undefined?'':msg;this.res=res}},};let composeParamsFunc=null;const eccUtil={_refreshPromise:null,_refreshPubKeyInfo:async(forceRefresh:boolean):Promise<boolean>=>{if(!composeParamsFunc){return false}if(!wx.$_publicKey||forceRefresh){eccUtil._refreshPromise=new Promise(async(resolve)=>{const url=`${baseUtil.getSinanHost()}/basic/crypto/lastkey2`;const data=await composeParamsFunc({});wx.request({url,method:'POST',data,enableHttp2:true,success:()=>{resolve(true)},fail:()=>{resolve(false)},})})}return eccUtil._refreshPromise},_updateGlobalPublicKeyInfo:(clear:boolean,resHeader?:{[key:string]:any}):BaseResp<boolean>=>{if(clear){wx.$_publicKey=null;return}const header=baseUtil.formatHeader(resHeader);const{'x-gateway-code':code,'x-crypto-pub-id':pubId,'x-crypto-pub-key':pubKey,'x-crypto-pub-exp':pubExp,'x-crypto-path':pathRule,}=header;const success=!code&&pubId;wx.$_publicKey=success?{pubId,pubKey,pubExp,pathRule}:null;return new baseUtil.BaseRespFac(success)},_privateKeyInfo:null,getPrivateKeyInfo:(forceUpdate=false):CryptoKeyInfo=>{if(!wx.$_publicKey)return null;const serverPubInfo=wx.$_publicKey;if(forceUpdate||!eccUtil._privateKeyInfo||eccUtil._privateKeyInfo.serverPubId!==serverPubInfo.pubId){const keyPair=ecc.box.keyPair();eccUtil._privateKeyInfo={serverPubId:serverPubInfo.pubId,sharedByte:ecc.box.before(baseUtil.decUrl(serverPubInfo.pubKey),keyPair.secretKey),clientPublicKey:baseUtil.encUrl(keyPair.publicKey),}}return eccUtil._privateKeyInfo},resolveGwCode:async(codeStr:string):Promise<{retry:boolean,success:boolean}>=>{if(!codeStr)return{retry:false,success:true};const code=parseInt(codeStr,10);switch(code){case 0:return{retry:false,success:true};case 11305:case 11306:{eccUtil._refreshPubKeyInfo(true).then((genPubSuccess)=>{genPubSuccess&&eccUtil.getPrivateKeyInfo()});return{retry:true,success:false}}case 11308:{eccUtil.getPrivateKeyInfo(true);return{retry:true,success:false}}case 11300:case 11301:case 11302:case 11303:case 11304:case 11307:return{retry:true,success:false};case 11309:return{retry:false,success:false};default:return{retry:false,success:true}}},_getSignSharedByte:()=>baseUtil.decUrl('mEufQpM1n5J8-OZZoJE7ucYMC2suTjfsHUq_6z5cyh8'),getClientCryptoSign:(data={},header={},sharedByte):string=>{const obj=baseUtil.formatHeader(Object.assign({},data,header));const str=Object.keys(obj).filter(item=>obj[item]).sort().reduce((pre,cur)=>{pre.push(`${cur}=${obj[cur]}`);return pre},[]).join('&');baseUtil.logInfo('---客户端签名---:before',str);const md5Str=md5(str);const nonce=ecc.randomBytes(ecc.box.nonceLength);const encrypted=ecc.box.after(md5Str,nonce,sharedByte);const combined=new Uint8Array(nonce.length+encrypted.length);combined.set(nonce);combined.set(encrypted,nonce.length);return baseUtil.encUrl(combined)},verifyServerCryptoSign:(traceId:string,resHeader={}):boolean=>{try{const formatHeader=baseUtil.formatHeader(resHeader);const signStr=formatHeader['x-crypto-sign'];if(!signStr){return false}const obj={'x-encrypt-key':formatHeader['x-encrypt-key'],'x-encrypt-response':formatHeader['x-encrypt-response'],'x-response-header-name':formatHeader['x-response-header-name'],'x-encrypted-headers':formatHeader['x-encrypted-headers'],'x-crypto-enable':formatHeader['x-crypto-enable'],'x-gateway-code':formatHeader['x-gateway-code'],'x-crypto-pub-id':formatHeader['x-crypto-pub-id'],'x-crypto-pub-key':formatHeader['x-crypto-pub-key'],'x-crypto-pub-exp':formatHeader['x-crypto-pub-exp'],'x-crypto-path':formatHeader['x-crypto-path'],'x-trace-id':traceId,};const msg=baseUtil.decUrl(signStr);const decrypted=ecc.sign.open(msg,eccUtil._getSignSharedByte());const str=Object.keys(obj).filter(item=>obj[item]).sort().reduce((pre,cur)=>{pre.push(`${cur}=${obj[cur]}`);return pre},[]).join('&');baseUtil.logInfo('---验证服务端的客户端签名---:before',str,traceId);const preHashArr=md5(str);const verified=preHashArr.length===decrypted.length&&preHashArr.every((v,i)=>v===decrypted[i]);return verified}catch(e){console.error('verifyServerCryptoSign error',e);return false}},execEncrypt:(input:string,ignoreNull=false):BaseResp<{cryptoKeyInfo:CryptoKeyInfo,encryptedContent:any}|null>=>{try{const eccKeyInfo=eccUtil.getPrivateKeyInfo();if(!eccKeyInfo){return new baseUtil.BaseRespFac(null,false,'加密失败:无加密秘钥信息')}const cryptoKeyInfo=Object.assign({},eccKeyInfo);if(ignoreNull&&(!input||input==='{}')){return new baseUtil.BaseRespFac({cryptoKeyInfo,encryptedContent:''})}const{sharedByte}=cryptoKeyInfo;const nonce=ecc.randomBytes(ecc.box.nonceLength);const encrypted=ecc.box.after(base64Util.decodeUTF8(input),nonce,sharedByte);const combined=new Uint8Array(nonce.length+encrypted.length);combined.set(nonce);combined.set(encrypted,nonce.length);const encryptedStr=baseUtil.encUrl(combined);return new baseUtil.BaseRespFac({cryptoKeyInfo,encryptedContent:encryptedStr,})}catch(e){return new baseUtil.BaseRespFac(null,false,`execEncrypt失败:${JSON.stringify(e)}`)}},execDecrypt:(msg:Uint8Array,cryptoKeyInfo:CryptoKeyInfo):BaseResp<string>=>{try{const{sharedByte}=cryptoKeyInfo;const nonce=msg.slice(0,ecc.box.nonceLength);const encrypted=msg.slice(ecc.box.nonceLength);const decrypted=ecc.box.open.after(encrypted,nonce,sharedByte);const decryptedStr=base64Util.encodeUTF8(decrypted);return new baseUtil.BaseRespFac(decryptedStr)}catch(err){return new baseUtil.BaseRespFac('',false,`execDecrypt失败:${JSON.stringify(err)}`)}},checkCryptoOpen:():boolean=>!!eccUtil._privateKeyInfo,closeCrypto:()=>{eccUtil._privateKeyInfo=null;eccUtil._updateGlobalPublicKeyInfo(true)},openCrypto:async():Promise<boolean>=>{const genPubSuccess=await eccUtil._refreshPubKeyInfo(false);if(!genPubSuccess)return false;eccUtil.getPrivateKeyInfo();return true},};const cryptRuleUtil={isServerOpen:():boolean=>!!wx.$_publicKey,pathInEnablePrefix:(path:string):boolean=>{if(!wx.$_publicKey){return false}const{pathRule}=wx.$_publicKey;if(pathRule==='*'){return true}const prefixArr=pathRule.split(',').map(item=>item.trim());for(let i=0,len=prefixArr.length;i<len;i++){if(path.indexOf(prefixArr[i])>-1){return true}}return false},isPerformanceReport:(path:string,params:any):boolean=>{if(path.indexOf('basic/event/upload')>-1){if(params.batch?.length===1&¶ms.batch[0]?.[31]==='tms-performance-log'){return true}return false}return false},_encryptPathRule:{'tim.map.qq.com':['^/user/login','^/api/getClientConfigs','^/basic/crypto/lastkey2'],'tim.sparta.html5.qq.com':['^/user/login','^/cnabroad','^~/ReChargeCard/','^/gasolinerecharge/v2/','^/gasolinerecharge/rechargecard/','^/tde','^/basic/crypto/lastkey2',],},isHostValid:(url)=>{const urlPattern=/^(https?:\/\/)?([^/?#]+)([/?#].*)?$/;const matches=url.match(urlPattern);if(!matches){console.error('Invalid URL:',url);return false}const domain=matches[2];const path=matches[3]||'/';if(cryptRuleUtil._encryptPathRule[domain]){const pathRules=cryptRuleUtil._encryptPathRule[domain];for(const rule of pathRules){const regex=new RegExp(rule);if(regex.test(path)){return false}}return true}return false},};const init=(composeFunc:Function):BaseResp<null>=>{composeParamsFunc=(...args)=>composeFunc(...args);return new baseUtil.BaseRespFac(null)};const isCryptoRuleMath=(path:string,reqData:any):BaseResp<boolean>=>{if(!wx.$_encryptEnvInfo.requestEncryptOpen){return new baseUtil.BaseRespFac(false,false,'本地加密未开启')}if(!cryptRuleUtil.isServerOpen()){return new baseUtil.BaseRespFac(false,false,'服务端加密未开启')}if(!cryptRuleUtil.pathInEnablePrefix(path)){return new baseUtil.BaseRespFac(false,false,'未命中服务端加密规则')}if(cryptRuleUtil.isPerformanceReport(path,reqData)){return new baseUtil.BaseRespFac(false,false,'性能埋点')}if(!cryptRuleUtil.isHostValid(path)){return new baseUtil.BaseRespFac(false,false,'非sinan网关加密接口')}return new baseUtil.BaseRespFac(true)};const reqEncrypt=(method:string,data:any,header:{[key:string]:any},encryptedResponseHeaderName:string):BaseResp<{cryptoKeyInfo:CryptoKeyInfo,header:any,data:any,}|null>=>{const reqHeader=baseUtil.formatHeader(header);if(reqHeader.contentType){return new baseUtil.BaseRespFac(null,false,'户自定义了请求contentType')}let finalData={};if(method.toUpperCase()==='GET'){const searchParams=baseUtil.formatGetData(data);if(!searchParams){return new baseUtil.BaseRespFac(null,false,`GET请求参数不满足加密的规则:${JSON.stringify(data)}`)}const{success,msg,res}=eccUtil.execEncrypt(searchParams);if(!success){return new baseUtil.BaseRespFac(null,false,`GET请求参数加密失败:${msg}`)}finalData={tmsec:res.encryptedContent}}else{const{success,msg,res}=eccUtil.execEncrypt(JSON.stringify(data));if(!success){return new baseUtil.BaseRespFac(null,false,`${method}请求参数加密失败:${msg}`)}finalData=res.encryptedContent}const{success,msg,res}=eccUtil.execEncrypt(JSON.stringify(header),true);if(!success){return new baseUtil.BaseRespFac(null,false,`请求Header加密失败:${msg}`)}const cryptoHeader={'X-Crypto-Mode':'2','X-Encrypted-Headers':res.encryptedContent,'X-Encrypt-Pub':res.cryptoKeyInfo.serverPubId,'X-Encrypt-Key':res.cryptoKeyInfo.clientPublicKey,'X-Encrypt-Response':'3','X-Response-Header-Name':encryptedResponseHeaderName,};const cryptoSign=eccUtil.getClientCryptoSign(baseUtil._isObject(finalData)?finalData:{body:finalData,},cryptoHeader,res.cryptoKeyInfo.sharedByte);return new baseUtil.BaseRespFac({cryptoKeyInfo:res.cryptoKeyInfo,data:finalData,header:{...cryptoHeader,'Content-Type':'text/plain','X-Crypto-Sign':cryptoSign,},})};const resDecrypt=async(requestTraceId:string,header,data,cryptoKeyInfo:CryptoKeyInfo):Promise<BaseResp<{retry:boolean,header:any,data:any,}>>=>{try{const formatHeader=baseUtil.formatHeader(header);const{'x-encrypt-response':encryptResponseMode,'x-response-header-name':encryptedResponseHeaderName,'x-encrypted-headers':encryptedHeaders,'x-gateway-code':gatewayCode,'content-type':contentType,}=formatHeader;if(!encryptResponseMode||encryptResponseMode==='0'){const dataStr=base64Util.encodeUTF8(new Uint8Array(data));return new baseUtil.BaseRespFac({header,data:JSON.parse(dataStr),retry:false,})}const{retry,success:gwSuccess}=await eccUtil.resolveGwCode(gatewayCode);if(!gwSuccess){const verified=eccUtil.verifyServerCryptoSign(requestTraceId,header);if(!verified){return new baseUtil.BaseRespFac({header:null,data:null,retry:false},false,'响应被篡改')}return new baseUtil.BaseRespFac({header:null,data:null,retry},false,`网关返回错误码:${gatewayCode}`)}let decryptedHeaders={};if(encryptedResponseHeaderName){const{success,msg,res}=eccUtil.execDecrypt(baseUtil.decUrl(encryptedHeaders),cryptoKeyInfo);if(!success){return new baseUtil.BaseRespFac({header:null,data:null,retry:true},false,`解密响应Header失败:${msg}`)}decryptedHeaders=JSON.parse(res)}const needDecode=contentType.indexOf('text/plain')>-1;const cipher=needDecode?baseUtil.decUrl(data):new Uint8Array(data);const{success,msg,res}=eccUtil.execDecrypt(cipher,cryptoKeyInfo);if(!success){return new baseUtil.BaseRespFac({header:null,data:null,retry:true},false,`解密响应Body失败:${msg}`)}const decryptedBody=JSON.parse(res);return new baseUtil.BaseRespFac({retry:false,header:{...header,...decryptedHeaders,},data:decryptedBody,})}catch(e){return new baseUtil.BaseRespFac({header:null,data:null,retry:true},false,`解密响应Body失败:${JSON.stringify(e)}`)}};let dealEncryptionSwitching=false;const dealEncryptionSwitch=async(path:string,traceId:string,resHeader):Promise<void>=>{if((!resHeader||dealEncryptionSwitching)){return}dealEncryptionSwitching=true;const formatHeader=baseUtil.formatHeader(resHeader);const cryptoDisabled=formatHeader['x-crypto-enable']==='0';if((eccUtil.checkCryptoOpen()&&cryptoDisabled)){const verified=eccUtil.verifyServerCryptoSign(traceId,formatHeader);if(!verified){dealEncryptionSwitching=false;baseUtil.logInfo(`验签失败:${path}:${traceId}`);return}}if(cryptoDisabled){eccUtil.closeCrypto()}else if(formatHeader['x-crypto-enable']==='1'){await eccUtil.openCrypto()}dealEncryptionSwitching=false;return};const dealRes=(path:string,traceId:string,resHeader,reqData):BaseResp<boolean>=>{const specialPath=[`${baseUtil.getSinanHost()}/user/login`,`${baseUtil.getSinanHost()}/basic/crypto/lastkey2`,].indexOf(path)>-1;if(specialPath){const formatHeader=baseUtil.formatHeader(resHeader);const verified=eccUtil.verifyServerCryptoSign(traceId,formatHeader);if(!verified){return new baseUtil.BaseRespFac(false,false,`验签失败:${path}:${traceId}`)}eccUtil._updateGlobalPublicKeyInfo(false,resHeader)}return new baseUtil.BaseRespFac(!cryptRuleUtil.isPerformanceReport(path,reqData))};const encryptUtil={init,isCryptoRuleMath,logInfo:baseUtil.logInfo,dealRes,reqEncrypt,resDecrypt,dealEncryptionSwitch,};export default encryptUtil;
|
package/src/encrypt/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import encryptUtil from'./encrypt-util';import{genTraceparent}from'./traceUtils';const util={logInfo:(...args)=>encryptUtil.logInfo(...args),reportFunc:(...args)=>{util.logInfo('reportFunc init fail:',...args)},reqEncrypt:(obj:{url:string,method:string,data:any,header:any}):{data:any,header:any,msg:string,cryptoKeyInfo?:any}=>{const{url,method,data,header={}}=obj;const{success,msg}=encryptUtil.isCryptoRuleMath(url,data);if(!success){return{data,header,msg}}const reqEncryptRes=encryptUtil.reqEncrypt(method,data,header,'');if(!reqEncryptRes.success){return{data,header,msg}}return Object.assign({msg},reqEncryptRes.res)},};let originalRequestApi;let originalUploadFileApi;const requestInit=(utilFunc)=>{if(!wx.reqCryptoFlag){originalRequestApi=wx.request;const{report,composeParamsFunc}=utilFunc;encryptUtil.init(composeParamsFunc);util.reportFunc=(...args)=>{util.logInfo(...args);report('request_encrypt_log',...args)};proxyWxRequest();wx.reqCryptoFlag=true}if(!wx.uploadFileCryptoFlag){originalUploadFileApi=wx.uploadFile;proxyWxUploadFile();wx.uploadFileCryptoFlag=true}};function proxyWxRequest():void{Object.defineProperty(wx,'request',{writable:true,enumerable:true,configurable:true,value(options:any){const{url,method,data,header={},success,fail,complete,dataType,responseType}=options;const traceparent=genTraceparent();const traceId=traceparent.split('-')[1];const originalOptions={...options};if(dataType||responseType){util.reportFunc(url,traceparent,'用户自定义了dataType和responseType');originalRequestApi.call(this,{...originalOptions,success:(res)=>{encryptUtil.dealEncryptionSwitch(url,traceId,res.header);success?.call(this,res)},header:{...header,Traceparent:traceparent},});return}const{data:formatData,header:formatHeader,msg,cryptoKeyInfo}=util.reqEncrypt({url,method,data,header});if(!cryptoKeyInfo){
|
|
1
|
+
import encryptUtil from'./encrypt-util';import{genTraceparent}from'./traceUtils';const util={logInfo:(...args)=>encryptUtil.logInfo(...args),reportFunc:(...args)=>{util.logInfo('reportFunc init fail:',...args)},reqEncrypt:(obj:{url:string,method:string,data:any,header:any}):{data:any,header:any,msg:string,cryptoKeyInfo?:any}=>{const{url,method,data,header={}}=obj;const{success,msg}=encryptUtil.isCryptoRuleMath(url,data);if(!success){return{data,header,msg}}const reqEncryptRes=encryptUtil.reqEncrypt(method,data,header,'');if(!reqEncryptRes.success){return{data,header,msg}}return Object.assign({msg},reqEncryptRes.res)},};let originalRequestApi;let originalUploadFileApi;const requestInit=(utilFunc)=>{if(!wx.reqCryptoFlag){originalRequestApi=wx.request;const{report,composeParamsFunc}=utilFunc;encryptUtil.init(composeParamsFunc);util.reportFunc=(...args)=>{util.logInfo(...args);report('request_encrypt_log',...args)};proxyWxRequest();wx.reqCryptoFlag=true}if(!wx.uploadFileCryptoFlag){originalUploadFileApi=wx.uploadFile;proxyWxUploadFile();wx.uploadFileCryptoFlag=true}};function proxyWxRequest():void{Object.defineProperty(wx,'request',{writable:true,enumerable:true,configurable:true,value(options:any){const{url,method,data,header={},success,fail,complete,dataType,responseType}=options;const traceparent=genTraceparent();const traceId=traceparent.split('-')[1];const originalOptions={...options};if(dataType||responseType){util.reportFunc(url,traceparent,'用户自定义了dataType和responseType');originalRequestApi.call(this,{...originalOptions,success:(res)=>{encryptUtil.dealEncryptionSwitch(url,traceId,res.header);success?.call(this,res)},header:{...header,Traceparent:traceparent},});return}const{data:formatData,header:formatHeader,msg,cryptoKeyInfo}=util.reqEncrypt({url,method,data,header});if(!cryptoKeyInfo){originalRequestApi.call(this,{...originalOptions,success:async(res)=>{const{success:dealSuccess,res:needDealHeader}=encryptUtil.dealRes(url,traceId,res.header,formatData);if(dealSuccess){needDealHeader&&encryptUtil.dealEncryptionSwitch(url,traceId,res.header);success?.call(this,res)}else{util.reportFunc(url,traceparent,`加密验签不通过:${JSON.stringify(res)}`);fail?.call(this,new Error('加密验签不通过'))}},header:{...header,Traceparent:traceparent},});return}let completeResolver;const completePromp=new Promise((resolve)=>{completeResolver=resolve});originalRequestApi.call(this,{...originalOptions,data:formatData,header:{...formatHeader,Traceparent:traceparent},dataType:'其他',responseType:'arraybuffer',success:async(result)=>{const{header:resHeader,data:resData}=result;const{success:decSuccess,msg,res}=await encryptUtil.resDecrypt(traceId,resHeader,resData,cryptoKeyInfo);if(res.retry){util.reportFunc(url,traceparent,`解密失败:${msg}`);const newTraceparent=genTraceparent();const traceId=newTraceparent.split('-')[1];originalRequestApi.call(this,{...originalOptions,success:(res)=>{encryptUtil.dealEncryptionSwitch(url,traceId,res.header);success?.call(this,res)},header:{...header,Traceparent:newTraceparent},});return}if(decSuccess){encryptUtil.dealEncryptionSwitch(url,traceId,resHeader);success?.call(this,res)}else{util.reportFunc(url,traceparent,`解密失败:${msg}`);fail?.call(this,new Error(msg))}const completeRes:any=await completePromp;completeRes.header=resHeader;completeRes.data=resData;complete?.call(this,completeRes)},fail:async(err)=>{fail?.call(this,err);const completeRes:any=await completePromp;complete?.call(this,completeRes)},complete:(res)=>{completeResolver(res)},})},})}function proxyWxUploadFile():void{Object.defineProperty(wx,'uploadFile',{writable:true,enumerable:true,configurable:true,value(options:any){originalUploadFileApi.call(this,Object.assign(options,{header:{...options.header,Traceparent:genTraceparent()},}))},})}export const encryptObjInit=(utilFunc:{composeParamsFunc:Function,report:Function,})=>{requestInit(utilFunc)};
|