@tmsfe/tms-core 0.0.164 → 0.0.167

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmsfe/tms-core",
3
- "version": "0.0.164",
3
+ "version": "0.0.167",
4
4
  "description": "tms运行时框架",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,589 +1 @@
1
- /**
2
- * 加密相关的原子工具能力
3
- * 暴露出来的工具函数封装原则:不可以向外throw错误,返回执行结果统一格式:{ success: boolean, msg: string, res: any }
4
- * https://iwiki.woa.com/p/4013041987#1.-RSA+AES-%E5%88%87%E6%8D%A2-Curve25519+XSalsa20%EF%BC%9A
5
- */
6
- import md5 from './md5';
7
- /* eslint-disable @typescript-eslint/no-require-imports */
8
- const ecc = require('./nacl.min.js');
9
- const base64Util = require('./nacl-util.min.js');
10
- /* eslint-enable @typescript-eslint/no-require-imports */
11
-
12
- const logger = wx.getLogManager({});
13
-
14
- interface BaseResp<T> {
15
- success: boolean,
16
- msg: string,
17
- res: T,
18
- }
19
-
20
- interface CryptoKeyInfo {
21
- sharedByte: Uint8Array,
22
- clientPublicKey: string,
23
- serverPubId: string,
24
- }
25
-
26
- // 出行接口域名
27
- const SERVER_HOST_MAP = {
28
- production: 'https://tim.map.qq.com', // 出行服务正式环境域名
29
- test: 'https://tim.sparta.html5.qq.com', // 出行服务测试环境域名
30
- development: 'https://tim.sparta.html5.qq.com/dev', // 出行服务开发环境域名
31
- predist: 'https://tim.sparta.html5.qq.com/pre', // 出行服务灰度环境域名
32
- mock: 'http://localhost:8003', // 本地mock环境
33
- };
34
-
35
- // 基础工具
36
- const baseUtil = {
37
- _isObject: (obj: any): boolean => Object.prototype.toString.call(obj) === '[object Object]',
38
- // 统一格式化日志输出
39
- _formatLog(args: any[]): any[] {
40
- // 小程序日志管理器都只是精确到秒,我们补上毫秒方便分析
41
- const time = new Date()
42
- .toISOString()
43
- .replace('T', ' ')
44
- .substring(0, 19)
45
- .replace(/-/g, '-')
46
- .replace(/:/g, ':');
47
- args.unshift(time);
48
- return args;
49
- },
50
- logInfo: (...args) => {
51
- args.unshift('request_encrypt_log');
52
- const items = baseUtil._formatLog(args);
53
- console.log(...items);
54
- logger.log(...items);
55
- },
56
- // Uint8Array转为url安全的base64编码
57
- encUrl: (input: Uint8Array): string => {
58
- let base64 = base64Util.encode(input);
59
- base64 = base64
60
- .replace(/\+/g, '-')
61
- .replace(/\//g, '_')
62
- .replace(/=+$/, '');
63
- return base64;
64
- },
65
- // url安全的base64解码
66
- decUrl: (input: string): Uint8Array => {
67
- let base64 = input.replace(/-/g, '+').replace(/_/g, '/');
68
- while (base64.length % 4) {
69
- base64 += '=';
70
- }
71
- return base64Util.decode(base64);
72
- },
73
- // header格式化,将header的key转换为小写
74
- formatHeader: (header): any => {
75
- if (!header || !baseUtil._isObject(header)) {
76
- return {};
77
- }
78
- const formatHeader = {};
79
- Object.keys(header).forEach((key) => {
80
- formatHeader[key.toLocaleLowerCase()] = header[key];
81
- });
82
- return formatHeader;
83
- },
84
- formatGetData: (params: any): string => {
85
- if (!params || !baseUtil._isObject(params)) {
86
- return '';
87
- }
88
- return Object.keys(params)
89
- .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
90
- .join('&');
91
- },
92
- getSinanHost: (): string => {
93
- if (wx.$_encryptEnvInfo) {
94
- return SERVER_HOST_MAP[wx.$_encryptEnvInfo.envName];
95
- }
96
- return '';
97
- },
98
- getClientEncryptOpen: (): boolean => wx.$_encryptEnvInfo?.requestEncryptOpen,
99
- BaseRespFac: class BaseRespFac<T> implements BaseResp<T> {
100
- success: boolean;
101
- msg: string;
102
- res: T;
103
-
104
- constructor(res: T, success?: boolean, msg?: string) {
105
- this.success = success === undefined ? true : success;
106
- this.msg = msg === undefined ? '' : msg;
107
- this.res = res;
108
- }
109
- },
110
- };
111
- let composeParamsFunc = null; // 请求参数处理函数,加密初始化调用后之后会赋值
112
- // 加密工具
113
- const eccUtil = {
114
- _refreshPromise: null,
115
- _refreshPubKeyInfo: async (forceRefresh: boolean): Promise<boolean> => {
116
- // 未完成初始化,则不支持秘钥刷新
117
- if (!composeParamsFunc) {
118
- return false;
119
- }
120
- if (!wx.$_publicKey || forceRefresh) {
121
- // eslint-disable-next-line
122
- eccUtil._refreshPromise = new Promise(async (resolve) => {
123
- const url = `${baseUtil.getSinanHost()}/basic/crypto/lastkey2`;
124
- const data = await composeParamsFunc({});
125
- wx.request({
126
- url,
127
- method: 'POST',
128
- data,
129
- enableHttp2: true,
130
- success: () => {
131
- resolve(true);
132
- },
133
- fail: () => {
134
- resolve(false);
135
- },
136
- });
137
- });
138
- }
139
- return eccUtil._refreshPromise;
140
- },
141
- // 解析resHeader详情,更新全局公钥信息
142
- _updateGlobalPublicKeyInfo: (clear: boolean, resHeader?: {[key: string]: any}): BaseResp<boolean> => {
143
- if (clear) {
144
- wx.$_publicKey = null;
145
- return;
146
- }
147
- const header = baseUtil.formatHeader(resHeader);
148
- const {
149
- 'x-gateway-code': code,
150
- 'x-crypto-pub-id': pubId, 'x-crypto-pub-key': pubKey, 'x-crypto-pub-exp': pubExp, 'x-crypto-path': pathRule,
151
- } = header;
152
- const success = !code && pubId; // login接口会出现接口成功,但是不返回publicKeyInfo的情况
153
- wx.$_publicKey = success ? { pubId, pubKey, pubExp, pathRule } : null;
154
- return new baseUtil.BaseRespFac(success);
155
- },
156
- _privateKeyInfo: null,
157
- getPrivateKeyInfo: (forceUpdate = false): CryptoKeyInfo => {
158
- if (!wx.$_publicKey) return null;
159
- const serverPubInfo = wx.$_publicKey;
160
- if (forceUpdate || !eccUtil._privateKeyInfo || eccUtil._privateKeyInfo.serverPubId !== serverPubInfo.pubId) {
161
- const keyPair = ecc.box.keyPair(); // 生成客户端公钥私钥
162
- eccUtil._privateKeyInfo = {
163
- serverPubId: serverPubInfo.pubId, // 服务端公钥id
164
- sharedByte: ecc.box.before(baseUtil.decUrl(serverPubInfo.pubKey), keyPair.secretKey), // 共享秘钥
165
- clientPublicKey: baseUtil.encUrl(keyPair.publicKey), // base64 url safe 编码后的客户端公钥
166
- };
167
- }
168
- return eccUtil._privateKeyInfo;
169
- },
170
- // 解析gwCode
171
- /* eslint-disable complexity */
172
- resolveGwCode: async (codeStr: string): Promise<{ retry: boolean, success: boolean }> => {
173
- if (!codeStr) return { retry: false, success: true };
174
- const code = parseInt(codeStr, 10);
175
- switch (code) {
176
- case 0:
177
- return { retry: false, success: true };
178
- case 11305: // 公钥id无效
179
- case 11306: { // 公钥过期
180
- // 1. 获取公钥
181
- eccUtil._refreshPubKeyInfo(true).then((genPubSuccess) => {
182
- genPubSuccess && eccUtil.getPrivateKeyInfo(); // 2. 生成私钥
183
- });
184
- return { retry: true, success: false };
185
- }
186
- case 11308: { // 密钥对有问题
187
- eccUtil.getPrivateKeyInfo(true); // 重新生成私钥
188
- return { retry: true, success: false };
189
- }
190
- case 11300: // 解密失败
191
- case 11301:
192
- case 11302:
193
- case 11303:
194
- case 11304:
195
- case 11307: // 加密未开启
196
- return { retry: true, success: false };
197
- case 11309:
198
- return { retry: false, success: false };
199
- default: // 其他网关错误码
200
- return { retry: false, success: true };
201
- }
202
- },
203
- _getSignSharedByte: () => baseUtil.decUrl('mEufQpM1n5J8-OZZoJE7ucYMC2suTjfsHUq_6z5cyh8'),
204
- // 计算客户端加密签名
205
- getClientCryptoSign: (data = {}, header = {}, sharedByte): string => {
206
- const obj = baseUtil.formatHeader(Object.assign({}, data, header));
207
- // 1. 生成签名前的字符串
208
- const str = Object.keys(obj).filter(item => obj[item])
209
- .sort()
210
- .reduce((pre, cur) => {
211
- pre.push(`${cur}=${obj[cur]}`);
212
- return pre;
213
- }, [])
214
- .join('&');
215
- baseUtil.logInfo('---客户端签名---:before', str);
216
- // 2. md5
217
- const md5Str = md5(str);
218
- const nonce = ecc.randomBytes(ecc.box.nonceLength);
219
- const encrypted = ecc.box.after(md5Str, nonce, sharedByte);
220
- const combined = new Uint8Array(nonce.length + encrypted.length);
221
- combined.set(nonce);
222
- combined.set(encrypted, nonce.length);
223
- return baseUtil.encUrl(combined);
224
- },
225
- // 验证服务端加密签名
226
- verifyServerCryptoSign: (traceId: string, resHeader = {}): boolean => {
227
- try {
228
- const formatHeader = baseUtil.formatHeader(resHeader);
229
- const signStr = formatHeader['x-crypto-sign'];
230
- if (!signStr) {
231
- return false;
232
- }
233
- const obj = {
234
- 'x-encrypt-key': formatHeader['x-encrypt-key'],
235
- 'x-encrypt-response': formatHeader['x-encrypt-response'],
236
- 'x-response-header-name': formatHeader['x-response-header-name'],
237
- 'x-encrypted-headers': formatHeader['x-encrypted-headers'],
238
- 'x-crypto-enable': formatHeader['x-crypto-enable'],
239
- // 'content-type': formatHeader['content-type'],
240
- 'x-gateway-code': formatHeader['x-gateway-code'],
241
- 'x-crypto-pub-id': formatHeader['x-crypto-pub-id'],
242
- 'x-crypto-pub-key': formatHeader['x-crypto-pub-key'],
243
- 'x-crypto-pub-exp': formatHeader['x-crypto-pub-exp'],
244
- 'x-crypto-path': formatHeader['x-crypto-path'],
245
- 'x-trace-id': traceId,
246
- };
247
- const msg = baseUtil.decUrl(signStr);
248
- const decrypted = ecc.sign.open(msg, eccUtil._getSignSharedByte());
249
- const str = Object.keys(obj).filter(item => obj[item])
250
- .sort()
251
- .reduce((pre, cur) => {
252
- pre.push(`${cur}=${obj[cur]}`);
253
- return pre;
254
- }, [])
255
- .join('&');
256
- baseUtil.logInfo('---验证服务端的客户端签名---:before', str, traceId);
257
- const preHashArr = md5(str);
258
- const verified = preHashArr.length === decrypted.length && preHashArr.every((v, i) => v === decrypted[i]);
259
- return verified;
260
- } catch (e) {
261
- console.error('verifyServerCryptoSign error', e);
262
- return false;
263
- }
264
- },
265
- /* eslint-enable complexity */
266
- execEncrypt: (input: string, ignoreNull = false): BaseResp<{
267
- cryptoKeyInfo: CryptoKeyInfo,
268
- encryptedContent: any } | null> => {
269
- try {
270
- const eccKeyInfo = eccUtil.getPrivateKeyInfo();
271
- if (!eccKeyInfo) {
272
- return new baseUtil.BaseRespFac(null, false, '加密失败:无加密秘钥信息');
273
- }
274
- const cryptoKeyInfo = Object.assign({}, eccKeyInfo); // 因为这里的加密数据要在后续流程中复用,所以需要值拷贝
275
- if (ignoreNull && (!input || input === '{}')) { // 如果需要忽略空值
276
- return new baseUtil.BaseRespFac({ cryptoKeyInfo, encryptedContent: '' });
277
- }
278
- const { sharedByte } = cryptoKeyInfo;
279
- const nonce = ecc.randomBytes(ecc.box.nonceLength);
280
- const encrypted = ecc.box.after(base64Util.decodeUTF8(input), nonce, sharedByte);
281
- const combined = new Uint8Array(nonce.length + encrypted.length);
282
- combined.set(nonce);
283
- combined.set(encrypted, nonce.length);
284
- const encryptedStr = baseUtil.encUrl(combined);
285
- return new baseUtil.BaseRespFac({
286
- cryptoKeyInfo,
287
- encryptedContent: encryptedStr,
288
- });
289
- } catch (e) {
290
- return new baseUtil.BaseRespFac(null, false, `execEncrypt失败:${JSON.stringify(e)}`);
291
- }
292
- },
293
- execDecrypt: (msg: Uint8Array, cryptoKeyInfo: CryptoKeyInfo): BaseResp<string> => {
294
- try {
295
- const { sharedByte } = cryptoKeyInfo;
296
- const nonce = msg.slice(0, ecc.box.nonceLength);
297
- const encrypted = msg.slice(ecc.box.nonceLength);
298
- const decrypted = ecc.box.open.after(encrypted, nonce, sharedByte);
299
- const decryptedStr = base64Util.encodeUTF8(decrypted);
300
- return new baseUtil.BaseRespFac(decryptedStr);
301
- } catch (err) {
302
- return new baseUtil.BaseRespFac('', false, `execDecrypt失败:${JSON.stringify(err)}`);;
303
- }
304
- },
305
- checkCryptoOpen: (): boolean => !!eccUtil._privateKeyInfo,
306
- closeCrypto: () => {
307
- eccUtil._privateKeyInfo = null;
308
- eccUtil._updateGlobalPublicKeyInfo(true);
309
- },
310
- openCrypto: async (): Promise<boolean> => {
311
- const genPubSuccess = await eccUtil._refreshPubKeyInfo(false); // 1. 获取公钥
312
- if (!genPubSuccess) return false;
313
- eccUtil.getPrivateKeyInfo(); // 2. 生成私钥
314
- return true;
315
- },
316
- };
317
- // 加密规则判断工具
318
- const cryptRuleUtil = {
319
- // 远程加密服务是否开启
320
- isServerOpen: (): boolean => !!wx.$_publicKey,
321
- // 检查path是否符合下发的路由前缀
322
- pathInEnablePrefix: (path: string): boolean => {
323
- if (!wx.$_publicKey) {
324
- return false;
325
- }
326
- const { pathRule } = wx.$_publicKey;
327
- if (pathRule === '*') {
328
- return true;
329
- }
330
- const prefixArr = pathRule.split(',').map(item => item.trim());
331
- for (let i = 0, len = prefixArr.length; i < len; i++) {
332
- if (path.indexOf(prefixArr[i]) > -1) {
333
- return true;
334
- }
335
- }
336
- return false;
337
- },
338
- // 判断是否是性能埋点上报接口
339
- isPerformanceReport: (path: string, params: any): boolean => {
340
- // 如果是日志上报接口,需要过滤性能日志,不需要加密
341
- if (path.indexOf('basic/event/upload') > -1) {
342
- if (params.batch?.length === 1 && params.batch[0]?.[31] === 'tms-performance-log') {
343
- return true;
344
- }
345
- return false;
346
- }
347
- return false;
348
- },
349
- // 不参与加密的请求路径规则: { 允许的域名: 不需要加密的path }
350
- _encryptPathRule: {
351
- 'tim.map.qq.com': ['^/user/login', '^/api/getClientConfigs', '^/basic/crypto/lastkey2'],
352
- 'tim.sparta.html5.qq.com': [
353
- '^/user/login',
354
- '^/cnabroad', '^~/ReChargeCard/', '^/gasolinerecharge/v2/', '^/gasolinerecharge/rechargecard/',
355
- '^/tde', '^/basic/crypto/lastkey2',
356
- ],
357
- },
358
- isHostValid: (url) => {
359
- // 使用正则表达式解析URL
360
- const urlPattern = /^(https?:\/\/)?([^/?#]+)([/?#].*)?$/;
361
- const matches = url.match(urlPattern);
362
- if (!matches) {
363
- console.error('Invalid URL:', url);
364
- return false; // 如果URL无效,默认返回false
365
- }
366
- const domain = matches[2];
367
- const path = matches[3] || '/';
368
- if (cryptRuleUtil._encryptPathRule[domain]) {
369
- const pathRules = cryptRuleUtil._encryptPathRule[domain];
370
- for (const rule of pathRules) {
371
- const regex = new RegExp(rule);
372
- if (regex.test(path)) {
373
- return false; // 匹配到规则,不需要加密
374
- }
375
- }
376
- return true; // 此域名下,默认需要加密
377
- }
378
- return false; // 没有匹配到规则,不需要加密
379
- },
380
- };
381
-
382
- // 初始化加密工具
383
- const init = (composeFunc: Function): BaseResp<null> => {
384
- composeParamsFunc = (...args) => composeFunc(...args); // 加密服务器公共参数
385
- return new baseUtil.BaseRespFac(null);
386
- };
387
-
388
- // 判断是否满足加密规则
389
- const isCryptoRuleMath = (path: string, reqData: any): BaseResp<boolean> => {
390
- if (!wx.$_encryptEnvInfo.requestEncryptOpen) {
391
- return new baseUtil.BaseRespFac(false, false, '本地加密未开启');
392
- }
393
- // 如果服务端下发的加密开关关闭,不走加密
394
- if (!cryptRuleUtil.isServerOpen()) {
395
- return new baseUtil.BaseRespFac(false, false, '服务端加密未开启');
396
- }
397
- // 请求路由不满足服务端下发的加密规则,不走加密
398
- if (!cryptRuleUtil.pathInEnablePrefix(path)) {
399
- return new baseUtil.BaseRespFac(false, false, '未命中服务端加密规则');
400
- }
401
- // 请求接口是加密性能埋点上报接口,不加密
402
- if (cryptRuleUtil.isPerformanceReport(path, reqData)) {
403
- return new baseUtil.BaseRespFac(false, false, '性能埋点');
404
- }
405
- // 请求路由不走sinan网关,不加密
406
- if (!cryptRuleUtil.isHostValid(path)) {
407
- return new baseUtil.BaseRespFac(false, false, '非sinan网关加密接口');
408
- }
409
- return new baseUtil.BaseRespFac(true);;
410
- };
411
- // 请求加密
412
- const reqEncrypt = (method: string, data: any, header: {
413
- [key: string]: any }, encryptedResponseHeaderName: string): BaseResp<{
414
- cryptoKeyInfo: CryptoKeyInfo,
415
- header: any,
416
- data: any,
417
- } | null> => {
418
- const reqHeader = baseUtil.formatHeader(header);
419
- if (reqHeader.contentType) {
420
- return new baseUtil.BaseRespFac(null, false, '户自定义了请求contentType');
421
- }
422
- let finalData = {};
423
- // 1. 处理请求参数
424
- if (method.toUpperCase() === 'GET') {
425
- const searchParams = baseUtil.formatGetData(data);
426
- if (!searchParams) {
427
- return new baseUtil.BaseRespFac(null, false, `GET请求参数不满足加密的规则: ${JSON.stringify(data)}`);
428
- }
429
- const { success, msg, res } = eccUtil.execEncrypt(searchParams);
430
- if (!success) {
431
- return new baseUtil.BaseRespFac(null, false, `GET请求参数加密失败: ${msg}`);
432
- }
433
- finalData = { tmsec: res.encryptedContent };
434
- } else {
435
- const { success, msg, res } = eccUtil.execEncrypt(JSON.stringify(data));
436
- if (!success) {
437
- return new baseUtil.BaseRespFac(null, false, `${method}请求参数加密失败: ${msg}`);
438
- }
439
- finalData = res.encryptedContent;
440
- }
441
- // 2. 处理Headers
442
- const { success, msg, res } = eccUtil.execEncrypt(JSON.stringify(header), true);
443
- if (!success) {
444
- return new baseUtil.BaseRespFac(null, false, `请求Header加密失败: ${msg}`);
445
- }
446
- const cryptoHeader = {
447
- 'X-Crypto-Mode': '2', // ecc加密模式
448
- 'X-Encrypted-Headers': res.encryptedContent,
449
- 'X-Encrypt-Pub': res.cryptoKeyInfo.serverPubId,
450
- 'X-Encrypt-Key': res.cryptoKeyInfo.clientPublicKey,
451
- 'X-Encrypt-Response': '3', // 加密,二进制
452
- 'X-Response-Header-Name': encryptedResponseHeaderName,
453
- };
454
- const cryptoSign = eccUtil.getClientCryptoSign(baseUtil._isObject(finalData) ? finalData : {
455
- body: finalData,
456
- }, cryptoHeader, res.cryptoKeyInfo.sharedByte);
457
- return new baseUtil.BaseRespFac({
458
- cryptoKeyInfo: res.cryptoKeyInfo,
459
- data: finalData,
460
- header: {
461
- ...cryptoHeader,
462
- 'Content-Type': 'text/plain',
463
- 'X-Crypto-Sign': cryptoSign,
464
- },
465
- });
466
- };
467
- // 解密请求结果
468
- const resDecrypt = async (requestTraceId: string, header, data, cryptoKeyInfo: CryptoKeyInfo): Promise<BaseResp<{
469
- retry: boolean, // 是否需要明文重试
470
- header: any,
471
- data: any,
472
- }>> => {
473
- try {
474
- const formatHeader = baseUtil.formatHeader(header);
475
- const {
476
- // 'x-encrypt-key': clientPublicKey, // base64 url safe编码后的客户端公钥
477
- 'x-encrypt-response': encryptResponseMode, // 期望的响应加密模式 2: 加密、不压缩 3: 加密、二进制
478
- 'x-response-header-name': encryptedResponseHeaderName, // 需要加密的响应Header: X-Header1,X-Header2
479
- 'x-encrypted-headers': encryptedHeaders, // 加密后的Header信息
480
- 'x-gateway-code': gatewayCode, // 网关返回的错误码
481
- 'content-type': contentType, // 响应内容类型
482
- } = formatHeader;
483
- if (!encryptResponseMode || encryptResponseMode === '0') { // 不需要解密,直接返回
484
- const dataStr = base64Util.encodeUTF8(new Uint8Array(data));
485
- return new baseUtil.BaseRespFac({
486
- header,
487
- data: JSON.parse(dataStr),
488
- retry: false,
489
- });
490
- }
491
- const { retry, success: gwSuccess } = await eccUtil.resolveGwCode(gatewayCode);
492
- if (!gwSuccess) {
493
- // 网关加密流程出现问题,需要验证网关签名
494
- const verified = eccUtil.verifyServerCryptoSign(requestTraceId, header);
495
- if (!verified) {
496
- // 验证失败,表示请求被篡改
497
- return new baseUtil.BaseRespFac({ header: null, data: null, retry: false }, false, '响应被篡改');
498
- }
499
- return new baseUtil.BaseRespFac({ header: null, data: null, retry }, false, `网关返回错误码: ${gatewayCode}`);
500
- }
501
- let decryptedHeaders = {};
502
- if (encryptedResponseHeaderName) { // 解密响应Header
503
- const { success, msg, res } = eccUtil.execDecrypt(baseUtil.decUrl(encryptedHeaders), cryptoKeyInfo);
504
- if (!success) {
505
- return new baseUtil.BaseRespFac({ header: null, data: null, retry: true }, false, `解密响应Header失败: ${msg}`);
506
- }
507
- decryptedHeaders = JSON.parse(res);
508
- }
509
- const needDecode = contentType.indexOf('text/plain') > -1;
510
- const cipher = needDecode ? baseUtil.decUrl(data) : new Uint8Array(data);
511
- const { success, msg, res } = eccUtil.execDecrypt(cipher, cryptoKeyInfo);
512
- if (!success) {
513
- return new baseUtil.BaseRespFac({ header: null, data: null, retry: true }, false, `解密响应Body失败: ${msg}`);
514
- }
515
- const decryptedBody = JSON.parse(res); // 解密响应Body
516
- return new baseUtil.BaseRespFac({
517
- retry: false,
518
- header: {
519
- ...header,
520
- ...decryptedHeaders,
521
- },
522
- data: decryptedBody,
523
- });
524
- } catch (e) {
525
- // 因为上面的逻辑有parse, 所以这里再加一个catch
526
- return new baseUtil.BaseRespFac({ header: null, data: null, retry: true }, false, `解密响应Body失败: ${JSON.stringify(e)}`);
527
- }
528
- };
529
- // 处理接下来的请求开关
530
- let dealEncryptionSwitching = false;
531
- const dealEncryptionSwitch = async (path: string, traceId: string, resHeader): Promise<void> => {
532
- if ((!resHeader || dealEncryptionSwitching)) {
533
- return;
534
- }
535
- dealEncryptionSwitching = true;
536
- const formatHeader = baseUtil.formatHeader(resHeader);
537
- // 加密关闭或者`login接口和lastkey接口`,都需要先执行验签
538
- const cryptoDisabled = formatHeader['x-crypto-enable'] === '0';
539
- if ((eccUtil.checkCryptoOpen() && cryptoDisabled)) {
540
- const verified = eccUtil.verifyServerCryptoSign(traceId, formatHeader);
541
- if (!verified) {
542
- // 验签失败,表示响应被篡改
543
- dealEncryptionSwitching = false;
544
- baseUtil.logInfo(`验签失败: ${path} : ${traceId}`);
545
- return;
546
- }
547
- }
548
- if (cryptoDisabled) {
549
- eccUtil.closeCrypto();
550
- } else if (formatHeader['x-crypto-enable'] === '1') {
551
- await eccUtil.openCrypto();
552
- } // 0是关闭,1是开启, 2是保持
553
- dealEncryptionSwitching = false;
554
- return;
555
- };
556
-
557
- /**
558
- * 处理非加密请求的响应
559
- * @params path traceId resHeader reqData
560
- * @returns 是否需要根据响应内容处理加密开关
561
- */
562
- const dealRes = (path: string, traceId: string, resHeader, reqData): BaseResp<boolean> => {
563
- const specialPath = [
564
- `${baseUtil.getSinanHost()}/user/login`,
565
- `${baseUtil.getSinanHost()}/basic/crypto/lastkey2`,
566
- ].indexOf(path) > -1;
567
- if (specialPath) {
568
- const formatHeader = baseUtil.formatHeader(resHeader);
569
- const verified = eccUtil.verifyServerCryptoSign(traceId, formatHeader);
570
- if (!verified) {
571
- // 验签失败,表示响应被篡改
572
- return new baseUtil.BaseRespFac(false, false, `验签失败: ${path} : ${traceId}`);
573
- }
574
- eccUtil._updateGlobalPublicKeyInfo(false, resHeader);
575
- }
576
- return new baseUtil.BaseRespFac(!cryptRuleUtil.isPerformanceReport(path, reqData));
577
- }
578
-
579
- const encryptUtil = {
580
- init, // 初始化加密工具
581
- isCryptoRuleMath, // 请求是否符合加密规则
582
- logInfo: baseUtil.logInfo, // 本地日志打印
583
- dealRes, // 处理不加密请求的响应
584
- reqEncrypt, // 请求加密:header和data
585
- resDecrypt, // 响应解密
586
- dealEncryptionSwitch, // 处理加密开关
587
- };
588
-
589
- 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({});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=>Object.prototype.toString.call(obj)==='[object Object]',_formatLog(args){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=>{let base64=base64Util.encode(input);return base64.replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'')},decUrl:input=>{let base64=input.replace(/-/g,'+').replace(/_/g,'/');while(base64.length%4)base64+='=';return base64Util.decode(base64)},formatHeader:header=>{if(!header||!baseUtil._isObject(header))return{};const formatHeader={};Object.keys(header).forEach(key=>{formatHeader[key.toLowerCase()]=header[key]});return formatHeader},formatGetData:params=>{if(!params||!baseUtil._isObject(params))return'';return Object.keys(params).map(key=>`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&')},getSinanHost:()=>wx.$_encryptEnvInfo?SERVER_HOST_MAP[wx.$_encryptEnvInfo.envName]:'',getClientEncryptOpen:()=>wx.$_encryptEnvInfo?.requestEncryptOpen,BaseRespFac:class BaseRespFac{constructor(res,success=true,msg=''){this.success=success;this.msg=msg;this.res=res}}};let composeParamsFunc=null;const eccUtil={_refreshPromise:null,_refreshPubKeyInfo:async forceRefresh=>{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,resHeader)=>{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=>{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=>{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)=>{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,resHeader={})=>{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);return preHashArr.length===decrypted.length&&preHashArr.every((v,i)=>v===decrypted[i])}catch(e){console.error('verifyServerCryptoSign error',e);return false}},execEncrypt:(input,ignoreNull=false)=>{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,cryptoKeyInfo)=>{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:()=>!!eccUtil._privateKeyInfo,closeCrypto:()=>{eccUtil._privateKeyInfo=null;eccUtil._updateGlobalPublicKeyInfo(true)},openCrypto:async()=>{const genPubSuccess=await eccUtil._refreshPubKeyInfo(false);if(!genPubSuccess)return false;eccUtil.getPrivateKeyInfo();return true}};const cryptRuleUtil={isServerOpen:()=>!!wx.$_publicKey,pathInEnablePrefix:path=>{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,params)=>{if(path.indexOf('basic/event/upload')>-1){if(params.batch?.length===1&&params.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=>{composeParamsFunc=(...args)=>composeFunc(...args);return new baseUtil.BaseRespFac(null)};const isCryptoRuleMath=(path,reqData)=>{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,data,header,encryptedResponseHeaderName)=>{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,header,data,cryptoKeyInfo)=>{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
@@ -1,172 +1 @@
1
- import encryptUtil from './encrypt-util';
2
- import { genTraceparent } from './traceUtils';
3
-
4
-
5
- const util = {
6
- logInfo: (...args) => encryptUtil.logInfo(...args),
7
- reportFunc: (...args) => {
8
- util.logInfo('reportFunc init fail:', ...args);
9
- },
10
- reqEncrypt: (obj: { url: string, method: string, data: any, header: any }): {
11
- data: any, header: any, msg: string, cryptoKeyInfo?: any } => {
12
- const { url, method, data, header = {} } = obj;
13
- // 判断请求是否满足加密要求,如果不满足则走默认请求
14
- const { success, msg } = encryptUtil.isCryptoRuleMath(url, data);
15
- if (!success) {
16
- return { data, header, msg };
17
- }
18
- // 加密请求数据
19
- const reqEncryptRes = encryptUtil.reqEncrypt(method, data, header, '');
20
- if (!reqEncryptRes.success) {
21
- return { data, header, msg };
22
- }
23
- return Object.assign({ msg }, reqEncryptRes.res);
24
- },
25
- };
26
- let originalRequestApi;
27
- let originalUploadFileApi;
28
- // 劫持wx.request和wx.uploadFile函数
29
- const requestInit = (utilFunc) => {
30
- if (!wx.request.cryptoFlag) {
31
- originalRequestApi = wx.request;
32
- // 初始化参数加签函数和性能上报函数
33
- const { report, composeParamsFunc } = utilFunc;
34
- encryptUtil.init(composeParamsFunc);
35
- util.reportFunc = (...args) => {
36
- util.logInfo(...args);
37
- report('request_encrypt_log', ...args);
38
- };
39
- proxyWxRequest();
40
- wx.request.cryptoFlag = true;
41
- }
42
- if (!wx.uploadFile.cryptoFlag) {
43
- originalUploadFileApi = wx.uploadFile;
44
- proxyWxUploadFile();
45
- wx.uploadFile.cryptoFlag = true;
46
- }
47
- };
48
-
49
- function proxyWxRequest(): void {
50
- Object.defineProperty(wx, 'request', {
51
- writable: true,
52
- enumerable: true,
53
- configurable: true,
54
- value(options: any) {
55
- const { url, method, data, header = {}, success, fail, complete, dataType, responseType } = options;
56
- const traceparent = genTraceparent();
57
- const traceId = traceparent.split('-')[1];
58
- const originalOptions = { ...options };
59
-
60
- // 如果用户自定义了dataType或者responseType,则不做处理
61
- if (dataType || responseType) {
62
- util.reportFunc(url, traceparent, '用户自定义了dataType和responseType');
63
- originalRequestApi.call(this, {
64
- ...originalOptions,
65
- success: (res) => {
66
- encryptUtil.dealEncryptionSwitch(url, traceId, res.header);
67
- success?.call(this, res);
68
- },
69
- header: { ...header, Traceparent: traceparent },
70
- });
71
- return;
72
- }
73
- // 加密请求数据
74
- const { data: formatData, header: formatHeader, msg, cryptoKeyInfo } = util
75
- .reqEncrypt({ url, method, data, header });
76
- if (!cryptoKeyInfo) {
77
- // 如果没有加密信息,则不走加密
78
- util.logInfo(url, traceparent, msg);
79
- originalRequestApi.call(this, {
80
- ...originalOptions,
81
- success: async (res) => {
82
- const { success: dealSuccess, res: needDealHeader } = encryptUtil.dealRes(url, traceId,
83
- res.header, formatData);
84
- // 性能埋点接口不走验签逻辑
85
- if (dealSuccess) {
86
- needDealHeader && encryptUtil.dealEncryptionSwitch(url, traceId, res.header);
87
- success?.call(this, res);
88
- } else {
89
- util.reportFunc(url, traceparent, `加密验签不通过: ${JSON.stringify(res)}`);
90
- fail?.call(this, new Error('加密验签不通过'));
91
- }
92
- },
93
- header: { ...header, Traceparent: traceparent },
94
- });
95
- return;
96
- }
97
-
98
- let completeResolver;
99
- const completePromp = new Promise((resolve) => {
100
- completeResolver = resolve;
101
- });
102
- originalRequestApi.call(this, {
103
- ...originalOptions,
104
- data: formatData,
105
- header: { ...formatHeader, Traceparent: traceparent },
106
- dataType: '其他',
107
- responseType: 'arraybuffer',
108
- success: async (result) => {
109
- const { header: resHeader, data: resData } = result;
110
- const { success: decSuccess, msg, res } = await encryptUtil
111
- .resDecrypt(traceId, resHeader, resData, cryptoKeyInfo);
112
- if (res.retry) { // 解密出现问题,需要明文重试
113
- util.reportFunc(url, traceparent, `解密失败:${msg}`);
114
- const newTraceparent = genTraceparent();
115
- const traceId = newTraceparent.split('-')[1];
116
- originalRequestApi.call(this, {
117
- ...originalOptions,
118
- success: (res) => {
119
- encryptUtil.dealEncryptionSwitch(url, traceId, res.header);
120
- success?.call(this, res);
121
- },
122
- header: { ...header, Traceparent: newTraceparent },
123
- });
124
- return;
125
- }
126
- if (decSuccess) {
127
- // util.logInfo(url, traceparent, '解密成功');
128
- encryptUtil.dealEncryptionSwitch(url, traceId, resHeader);
129
- success?.call(this, res);
130
- } else { // 不支持明文重试,且解密失败
131
- util.reportFunc(url, traceparent, `解密失败:${msg}`);
132
- fail?.call(this, new Error(msg));
133
- }
134
- const completeRes: any = await completePromp;
135
- completeRes.header = resHeader;
136
- completeRes.data = resData;
137
- complete?.call(this, completeRes);
138
- },
139
- fail: async (err) => {
140
- fail?.call(this, err);
141
- const completeRes: any = await completePromp;
142
- complete?.call(this, completeRes);
143
- },
144
- complete: (res) => {
145
- completeResolver(res);
146
- },
147
- });
148
- },
149
- });
150
- }
151
-
152
- function proxyWxUploadFile(): void {
153
- Object.defineProperty(wx, 'uploadFile', {
154
- writable: true,
155
- enumerable: true,
156
- configurable: true,
157
- value(options: any) {
158
- originalUploadFileApi.call(this, Object.assign(options, {
159
- header: { ...options.header, Traceparent: genTraceparent() },
160
- }));
161
- },
162
- });
163
- }
164
-
165
- export const encryptObjInit = (utilFunc: {
166
- composeParamsFunc: Function,
167
- report: Function,
168
- }) => {
169
- // 劫持wx.request和wx.uploadFile函数
170
- requestInit(utilFunc);
171
- };
172
-
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:({url,method,data,header={}})=>{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,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(){Object.defineProperty(wx,'request',{writable:true,enumerable:true,configurable:true,value(options){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 newTraceId=newTraceparent.split('-')[1];originalRequestApi.call(this,{...originalOptions,success:(res)=>{encryptUtil.dealEncryptionSwitch(url,newTraceId,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=await completePromp;completeRes.header=resHeader;completeRes.data=resData;complete?.call(this,completeRes)},fail:async(err)=>{fail?.call(this,err);const completeRes=await completePromp;complete?.call(this,completeRes)},complete:(res)=>{completeResolver(res)},})},})}function proxyWxUploadFile(){Object.defineProperty(wx,'uploadFile',{writable:true,enumerable:true,configurable:true,value(options){originalUploadFileApi.call(this,{...options,header:{...options.header,Traceparent:genTraceparent()}})},})}export const encryptObjInit=(utilFunc)=>{requestInit(utilFunc)};
@@ -1,2 +1,2 @@
1
1
  /* eslint-disable */
2
- function safeAdd(x,y){const lsw=(x&0xffff)+(y&0xffff);const msw=(x>>16)+(y>>16)+(lsw>>16);return(msw<<16)|(lsw&0xffff)}function bitRotateLeft(num,cnt){return(num<<cnt)|(num>>>(32-cnt))}function md5cmn(q,a,b,x,s,t){return safeAdd(bitRotateLeft(safeAdd(safeAdd(a,q),safeAdd(x,t)),s),b)}function md5ff(a,b,c,d,x,s,t){return md5cmn((b&c)|(~b&d),a,b,x,s,t)}function md5gg(a,b,c,d,x,s,t){return md5cmn((b&d)|(c&~d),a,b,x,s,t)}function md5hh(a,b,c,d,x,s,t){return md5cmn(b^c^d,a,b,x,s,t)}function md5ii(a,b,c,d,x,s,t){return md5cmn(c^(b|~d),a,b,x,s,t)}function binlMD5(x,len){x[len>>5]|=0x80<<len%32;x[(((len+64)>>>9)<<4)+14]=len;let a=1732584193;let b=-271733879;let c=-1732584194;let d=271733878;for(let i=0;i<x.length;i+=16){const olda=a;const oldb=b;const oldc=c;const oldd=d;a=md5ff(a,b,c,d,x[i],7,-680876936);d=md5ff(d,a,b,c,x[i+1],12,-389564586);c=md5ff(c,d,a,b,x[i+2],17,606105819);b=md5ff(b,c,d,a,x[i+3],22,-1044525330);a=md5ff(a,b,c,d,x[i+4],7,-176418897);d=md5ff(d,a,b,c,x[i+5],12,1200080426);c=md5ff(c,d,a,b,x[i+6],17,-1473231341);b=md5ff(b,c,d,a,x[i+7],22,-45705983);a=md5ff(a,b,c,d,x[i+8],7,1770035416);d=md5ff(d,a,b,c,x[i+9],12,-1958414417);c=md5ff(c,d,a,b,x[i+10],17,-42063);b=md5ff(b,c,d,a,x[i+11],22,-1990404162);a=md5ff(a,b,c,d,x[i+12],7,1804603682);d=md5ff(d,a,b,c,x[i+13],12,-40341101);c=md5ff(c,d,a,b,x[i+14],17,-1502002290);b=md5ff(b,c,d,a,x[i+15],22,1236535329);a=md5gg(a,b,c,d,x[i+1],5,-165796510);d=md5gg(d,a,b,c,x[i+6],9,-1069501632);c=md5gg(c,d,a,b,x[i+11],14,643717713);b=md5gg(b,c,d,a,x[i],20,-373897302);a=md5gg(a,b,c,d,x[i+5],5,-701558691);d=md5gg(d,a,b,c,x[i+10],9,38016083);c=md5gg(c,d,a,b,x[i+15],14,-660478335);b=md5gg(b,c,d,a,x[i+4],20,-405537848);a=md5gg(a,b,c,d,x[i+9],5,568446438);d=md5gg(d,a,b,c,x[i+14],9,-1019803690);c=md5gg(c,d,a,b,x[i+3],14,-187363961);b=md5gg(b,c,d,a,x[i+8],20,1163531501);a=md5gg(a,b,c,d,x[i+13],5,-1444681467);d=md5gg(d,a,b,c,x[i+2],9,-51403784);c=md5gg(c,d,a,b,x[i+7],14,1735328473);b=md5gg(b,c,d,a,x[i+12],20,-1926607734);a=md5hh(a,b,c,d,x[i+5],4,-378558);d=md5hh(d,a,b,c,x[i+8],11,-2022574463);c=md5hh(c,d,a,b,x[i+11],16,1839030562);b=md5hh(b,c,d,a,x[i+14],23,-35309556);a=md5hh(a,b,c,d,x[i+1],4,-1530992060);d=md5hh(d,a,b,c,x[i+4],11,1272893353);c=md5hh(c,d,a,b,x[i+7],16,-155497632);b=md5hh(b,c,d,a,x[i+10],23,-1094730640);a=md5hh(a,b,c,d,x[i+13],4,681279174);d=md5hh(d,a,b,c,x[i],11,-358537222);c=md5hh(c,d,a,b,x[i+3],16,-722521979);b=md5hh(b,c,d,a,x[i+6],23,76029189);a=md5hh(a,b,c,d,x[i+9],4,-640364487);d=md5hh(d,a,b,c,x[i+12],11,-421815835);c=md5hh(c,d,a,b,x[i+15],16,530742520);b=md5hh(b,c,d,a,x[i+2],23,-995338651);a=md5ii(a,b,c,d,x[i],6,-198630844);d=md5ii(d,a,b,c,x[i+7],10,1126891415);c=md5ii(c,d,a,b,x[i+14],15,-1416354905);b=md5ii(b,c,d,a,x[i+5],21,-57434055);a=md5ii(a,b,c,d,x[i+12],6,1700485571);d=md5ii(d,a,b,c,x[i+3],10,-1894986606);c=md5ii(c,d,a,b,x[i+10],15,-1051523);b=md5ii(b,c,d,a,x[i+1],21,-2054922799);a=md5ii(a,b,c,d,x[i+8],6,1873313359);d=md5ii(d,a,b,c,x[i+15],10,-30611744);c=md5ii(c,d,a,b,x[i+6],15,-1560198380);b=md5ii(b,c,d,a,x[i+13],21,1309151649);a=md5ii(a,b,c,d,x[i+4],6,-145523070);d=md5ii(d,a,b,c,x[i+11],10,-1120210379);c=md5ii(c,d,a,b,x[i+2],15,718787259);b=md5ii(b,c,d,a,x[i+9],21,-343485551);a=safeAdd(a,olda);b=safeAdd(b,oldb);c=safeAdd(c,oldc);d=safeAdd(d,oldd)}return[a,b,c,d]}function binl2rstr(input){let output='';const length32=input.length*32;for(let i=0;i<length32;i+=8){output+=String.fromCharCode((input[i>>5]>>>i%32)&0xff)}return output}function rstr2binl(input){const output=[];output[(input.length>>2)-1]=undefined;for(let i=0;i<output.length;i+=1){output[i]=0}const length8=input.length*8;for(let i=0;i<length8;i+=8){output[i>>5]|=(input.charCodeAt(i/8)&0xff)<<i%32}return output}function rstrMD5(s){return binl2rstr(binlMD5(rstr2binl(s),s.length*8))}function str2rstrUTF8(input){return unescape(encodeURIComponent(input))}function rawMD5(s){return rstrMD5(str2rstrUTF8(s))}function rstr2uint8array(input){const output=new Uint8Array(input.length);for(let i=0;i<input.length;i++){output[i]=input.charCodeAt(i)}return output}function md5(string){return rstr2uint8array(rawMD5(string))}export default md5;
2
+ function safeAdd(x,y){const lsw=(x&0xffff)+(y&0xffff);const msw=(x>>16)+(y>>16)+(lsw>>16);return(msw<<16)|(lsw&0xffff)}function bitRotateLeft(num,cnt){return(num<<cnt)|(num>>>(32-cnt))}function md5cmn(q,a,b,x,s,t){return safeAdd(bitRotateLeft(safeAdd(safeAdd(a,q),safeAdd(x,t)),s),b)}function md5ff(a,b,c,d,x,s,t){return md5cmn((b&c)|(~b&d),a,b,x,s,t)}function md5gg(a,b,c,d,x,s,t){return md5cmn((b&d)|(c&~d),a,b,x,s,t)}function md5hh(a,b,c,d,x,s,t){return md5cmn(b^c^d,a,b,x,s,t)}function md5ii(a,b,c,d,x,s,t){return md5cmn(c^(b|~d),a,b,x,s,t)}function binlMD5(x,len){x[len>>5]|=0x80<<len%32;x[(((len+64)>>>9)<<4)+14]=len;let a=1732584193,b=-271733879,c=-1732584194,d=271733878;for(let i=0;i<x.length;i+=16){const olda=a,oldb=b,oldc=c,oldd=d;a=md5ff(a,b,c,d,x[i],7,-680876936);a=safeAdd(a,olda);b=safeAdd(b,oldb);c=safeAdd(c,oldc);d=safeAdd(d,oldd)}return[a,b,c,d]}function binl2rstr(input){let output='';const length32=input.length*32;for(let i=0;i<length32;i+=8){output+=String.fromCharCode((input[i>>5]>>>i%32)&0xff)}return output}function rstrMD5(s){return binl2rstr(binlMD5(rstr2binl(s),s.length*8))}function str2rstrUTF8(input){return unescape(encodeURIComponent(input))}function rawMD5(s){return rstrMD5(str2rstrUTF8(s))}function rstr2uint8array(input){const output=new Uint8Array(input.length);for(let i=0;i<input.length;i++){output[i]=input.charCodeAt(i)}return output}function md5(string){return rstr2uint8array(rawMD5(string))}export default md5;