@polyv/request-plugin-sm2 2.0.0

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/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # SM2RequestPlugin
2
+
3
+ 用途:SM2 加密插件,用于对请求和响应数据进行国密 SM2 加密和解密处理。
4
+
5
+ ## 实例化参数
6
+
7
+ | 参数名 | 用途 | 类型 | 默认值 |
8
+ | - | - | - | - |
9
+ | `platformPublicKey` | 平台公钥(接口提交参数加密用) | `string` | - |
10
+ | `userPrivateKey` | 用户私钥(接口响应内容解密用) | `string` | - |
11
+ | `includeIgnoreEncryptFields` | 额外的忽略加密字段 | `string[]` | - |
12
+ | `useSM2` | 是否使用 SM2 加密 | `boolean` | `true` |
13
+ | `encryptPostSM2` | 是否对 POST 请求主体进行 SM2 加密 | `boolean` | `true` |
14
+
15
+ ## 请求选项
16
+
17
+ | 参数名 | 用途 | 类型 | 默认值 |
18
+ | - | - | - | - |
19
+ | `useSM2` | 是否使用 SM2 加密 | `boolean` | `true` |
20
+ | `encryptPostSM2` | 是否对 POST 请求主体进行 SM2 加密 | `boolean` | `true` |
21
+
22
+ ## 使用方式
23
+
24
+ ```js
25
+ import { PolyvRequest } from '@polyv/request-core';
26
+ import { SM2RequestPlugin } from '@polyv/request-plugin-sm2';
27
+
28
+ // 基础用法:提供平台公钥和用户私钥
29
+ const requester = new PolyvRequest({
30
+ requestPlugins: [
31
+ new SM2RequestPlugin({
32
+ platformPublicKey: 'yourPlatformPublicKey',
33
+ userPrivateKey: 'yourUserPrivateKey',
34
+ }),
35
+ ]
36
+ });
37
+
38
+ // 配置额外的忽略加密字段
39
+ const requester = new PolyvRequest({
40
+ requestPlugins: [
41
+ new SM2RequestPlugin({
42
+ platformPublicKey: 'yourPlatformPublicKey',
43
+ userPrivateKey: 'yourUserPrivateKey',
44
+ includeIgnoreEncryptFields: ['customField1', 'customField2'],
45
+ }),
46
+ ]
47
+ });
48
+ // 此时请求时,被忽略的字段不会加入到加密内容中
49
+ requester.post('/api/data', {
50
+ customField1: 'abc',
51
+ }, {
52
+ params: {
53
+ customField2: 'def',
54
+ }
55
+ });
56
+
57
+ // 禁用 POST 请求主体加密
58
+ const requester = new PolyvRequest({
59
+ requestPlugins: [
60
+ new SM2RequestPlugin({
61
+ platformPublicKey: 'yourPlatformPublicKey',
62
+ userPrivateKey: 'yourUserPrivateKey',
63
+ encryptPostSM2: false,
64
+ }),
65
+ ]
66
+ });
67
+
68
+ // 在某个请求中禁用 SM2 加密
69
+ requester.get('/api/data', {}, {
70
+ useSM2: false,
71
+ });
72
+
73
+ // 在某个请求中禁用 POST 请求主体加密
74
+ requester.post('/api/data', { name: 'test' }, {
75
+ encryptPostSM2: false,
76
+ });
77
+ ```
package/config.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 忽略的加密字段
3
+ */
4
+ export declare const defaultIgnoreEncryptFields: string[];
5
+ /**
6
+ * @ignore
7
+ */
8
+ export declare const INTERNAL_SM2_PUBLIC_KEY: string;
9
+ /**
10
+ * @ignore
11
+ */
12
+ export declare const INTERNAL_SM2_PRIVATE_KEY: string;
package/config.js ADDED
@@ -0,0 +1,17 @@
1
+ import { decode } from 'js-base64';
2
+ /**
3
+ * 忽略的加密字段
4
+ */
5
+ export const defaultIgnoreEncryptFields = ['appId', 'signatureMethod', 'signatureNonce', 'timestamp', 'viewerToken', 'sign', 'encryptResponseType'];
6
+ /**
7
+ * @ignore
8
+ */
9
+ // 内部的加密公钥:045B8155E9747C283ACF842895D3048E6CA943CC454AFA6A9A452B5903CC30FCE278A6F88F602EDF803DAB40BD45E35F660676BF68FD7CCBE0F6AD32388136CD0D
10
+ // 如果接口 params 中没带 appId 参数的,则使用该私钥进行解密
11
+ export const INTERNAL_SM2_PUBLIC_KEY = decode('MDQ1QjgxNTVFOTc0N0MyODNBQ0Y4NDI4OTVEMzA0OEU2Q0E5NDNDQzQ1NEFGQTZBOUE0NTJCNTkwM0NDMzBGQ0UyNzhBNkY4OEY2MDJFREY4MDNEQUI0MEJENDVFMzVGNjYwNjc2QkY2OEZEN0NDQkUwRjZBRDMyMzg4MTM2Q0QwRA==');
12
+ /**
13
+ * @ignore
14
+ */
15
+ // 内部的解密私钥:00B319618B4B1BAEB8CC6765D8903C05A0B16D9D9FCD163271BB6F1B88F657A28E
16
+ // 如果接口 params 中没带 appId 参数的,则使用该私钥进行解密
17
+ export const INTERNAL_SM2_PRIVATE_KEY = decode('MDBCMzE5NjE4QjRCMUJBRUI4Q0M2NzY1RDg5MDNDMDVBMEIxNkQ5RDlGQ0QxNjMyNzFCQjZGMUI4OEY2NTdBMjhF');
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './sm2-plugin';
2
+ export * from './types';
3
+ export * from './utils';
4
+ export * from './request-options';
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './sm2-plugin';
2
+ export * from './types';
3
+ export * from './utils';
4
+ export * from './request-options';
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@polyv/request-plugin-sm2",
3
+ "version": "2.0.0",
4
+ "main": "./index.js",
5
+ "dependencies": {
6
+ "@polyv/request-core": "2.0.0",
7
+ "sm-crypto": "^0.3.7",
8
+ "js-base64": "^3.7.5"
9
+ },
10
+ "types": "./index.d.ts"
11
+ }
@@ -0,0 +1,22 @@
1
+ export interface SM2RequestOptions {
2
+ /**
3
+ * 是否使用 sm2 加密
4
+ * @default true
5
+ * @plugin `SM2RequestPlugin`
6
+ */
7
+ useSM2?: boolean;
8
+ /**
9
+ * 是否对 post 请求主体进行 sm2 加密
10
+ * @default true
11
+ * @plugin `SM2RequestPlugin`
12
+ */
13
+ encryptPostSM2?: boolean;
14
+ }
15
+ declare module '@polyv/request-core' {
16
+ interface RequestCustomOptions extends SM2RequestOptions {
17
+ }
18
+ }
19
+ declare module 'axios' {
20
+ interface AxiosRequestConfig extends SM2RequestOptions {
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ import type { RequestOptions, RequestPlugin, RequestResult } from '@polyv/request-core';
2
+ import { SM2RequestPluginConfig } from './types';
3
+ /**
4
+ * SM2 请求插件
5
+ */
6
+ export declare class SM2RequestPlugin implements RequestPlugin {
7
+ /** 公钥(加密用) */
8
+ private __platformPublicKey;
9
+ /** 私钥(解密用) */
10
+ private __userPrivateKey;
11
+ /** 忽略加密的字段 */
12
+ private __ignoreEncryptFields;
13
+ private __useSM2;
14
+ private __encryptPostSM2;
15
+ constructor(config: SM2RequestPluginConfig);
16
+ usePlugin(options: RequestOptions): boolean;
17
+ interceptIncludeParams(): object | void;
18
+ interceptEncryptRequest(options: RequestOptions): RequestOptions;
19
+ interceptDecryptResponse(result: RequestResult, options: RequestOptions): RequestResult;
20
+ /**
21
+ * 处理 headers
22
+ * @param options 请求选项
23
+ */
24
+ private __handleHeaders;
25
+ /**
26
+ * 处理 params
27
+ * @param options 请求选项
28
+ */
29
+ private __handleParams;
30
+ /**
31
+ * 处理 data
32
+ * @param options 请求选项
33
+ */
34
+ private __handleData;
35
+ /**
36
+ * 加密 sm2 对象
37
+ * @param targetObj 处理对象
38
+ * @param options 请求选项
39
+ * @param encryptedField 密文字段
40
+ */
41
+ private __encryptObjectSM2;
42
+ /**
43
+ * 加密 sm2 文本
44
+ * @param targetText 需要加密的字符串
45
+ * @param options 请求选项
46
+ * @returns 加密后的密文
47
+ */
48
+ private __encryptTextSM2;
49
+ /**
50
+ * 解密 sm2 文本
51
+ * @param encryptedText 加密密文
52
+ */
53
+ private __decryptTextSM2;
54
+ /**
55
+ * 获取解密的私钥
56
+ * @param options 请求选项
57
+ */
58
+ private __getPrivateKey;
59
+ /**
60
+ * 获取加密的公钥
61
+ */
62
+ private __getPublicKey;
63
+ }
package/sm2-plugin.js ADDED
@@ -0,0 +1,205 @@
1
+ import { sm2 } from 'sm-crypto';
2
+ import { defaultIgnoreEncryptFields, INTERNAL_SM2_PRIVATE_KEY, INTERNAL_SM2_PUBLIC_KEY } from './config';
3
+ import { SM2EncryptMode } from './types';
4
+ import { padding04 } from './utils';
5
+ // 1. 插件会在请求中自动添加 `encryptResponseType: 2` 参数,告知服务端需要返回 SM2 加密数据
6
+ // 2. 对于 POST 请求,默认会对请求体进行 SM2 加密,并添加 `x-e-type: 2` 请求头
7
+ // 3. 在加密请求参数时,会自动分离需要加密和不需要加密的字段:
8
+ // - 默认忽略的字段: `appId`, `signatureMethod`, `signatureNonce`, `timestamp`, `viewerToken`, `sign`, `encryptResponseType`
9
+ // - 可以通过 `includeIgnoreEncryptFields` 配置额外的忽略加密字段
10
+ // 4. 加密后的字段会被重新组织:
11
+ // - 对 POST 请求的 URL 参数,加密内容会放在 `xurlparam` 字段中
12
+ // - 对 POST 请求的 body 数据(requetType 为 json),加密内容会作为请求提提交
13
+ // - 对 POST 请求的 body 数据(requetType 为 form),加密内容会放在 `xparam` 字段中
14
+ // 5. 当服务端返回加密数据时(`encryption: true`),插件会自动对响应数据进行解密
15
+ // 6. 如果请求中包含 `appId` 参数,将使用配置的公钥和私钥;否则使用内部的默认密钥
16
+ /**
17
+ * SM2 请求插件
18
+ */
19
+ export class SM2RequestPlugin {
20
+ /** 公钥(加密用) */
21
+ __platformPublicKey;
22
+ /** 私钥(解密用) */
23
+ __userPrivateKey;
24
+ /** 忽略加密的字段 */
25
+ __ignoreEncryptFields;
26
+ __useSM2;
27
+ __encryptPostSM2;
28
+ constructor(config) {
29
+ this.__platformPublicKey = config.platformPublicKey;
30
+ this.__userPrivateKey = config.userPrivateKey;
31
+ this.__ignoreEncryptFields = [...defaultIgnoreEncryptFields];
32
+ if (config.includeIgnoreEncryptFields) {
33
+ this.__ignoreEncryptFields.push(...config.includeIgnoreEncryptFields);
34
+ }
35
+ this.__useSM2 = config.useSM2 ?? true;
36
+ this.__encryptPostSM2 = config.encryptPostSM2 ?? true;
37
+ }
38
+ usePlugin(options) {
39
+ const { useSM2 = this.__useSM2 } = options;
40
+ return useSM2;
41
+ }
42
+ interceptIncludeParams() {
43
+ return {
44
+ // 插入 encryptResponseType 为 2,表示需要进行 sm2 加密
45
+ encryptResponseType: 2,
46
+ };
47
+ }
48
+ interceptEncryptRequest(options) {
49
+ // 处理 params
50
+ const params = this.__handleParams(options);
51
+ // 处理 data
52
+ const data = this.__handleData(options);
53
+ // 处理 header
54
+ const headers = this.__handleHeaders(options);
55
+ return {
56
+ ...options,
57
+ params,
58
+ data,
59
+ headers,
60
+ };
61
+ }
62
+ interceptDecryptResponse(result, options) {
63
+ const res = result.data;
64
+ if (!res.encryption || typeof res.data !== 'string') {
65
+ return result;
66
+ }
67
+ const data = res.data;
68
+ res.data = this.__decryptTextSM2(data, options);
69
+ return result;
70
+ }
71
+ /**
72
+ * 处理 headers
73
+ * @param options 请求选项
74
+ */
75
+ __handleHeaders(options) {
76
+ const headers = (options.headers || {});
77
+ const { encryptPostSM2 = this.__encryptPostSM2 } = options;
78
+ if (options.method?.toLocaleUpperCase() === 'POST' && encryptPostSM2) {
79
+ headers['x-e-type'] = 2;
80
+ }
81
+ return headers;
82
+ }
83
+ /**
84
+ * 处理 params
85
+ * @param options 请求选项
86
+ */
87
+ __handleParams(options) {
88
+ const { encryptPostSM2 = this.__encryptPostSM2 } = options;
89
+ const params = (options.params || {});
90
+ if (options.method?.toLocaleUpperCase() !== 'POST' || !encryptPostSM2) {
91
+ return params;
92
+ }
93
+ return this.__encryptObjectSM2(params, options, 'xurlparam');
94
+ }
95
+ /**
96
+ * 处理 data
97
+ * @param options 请求选项
98
+ */
99
+ __handleData(options) {
100
+ const data = options.data || {};
101
+ if (options.method?.toLocaleUpperCase() !== 'POST') {
102
+ return data;
103
+ }
104
+ let newData = data;
105
+ const { encryptPostSM2 = this.__encryptPostSM2 } = options;
106
+ if (encryptPostSM2) {
107
+ if (typeof data === 'string') {
108
+ newData = this.__encryptTextSM2(data, options);
109
+ }
110
+ else if (options.requestType === 'json') {
111
+ // 如果明确使用 json 为 content-type,则直接把密文作为请求体
112
+ newData = this.__encryptTextSM2(JSON.stringify(options.data || {}), options);
113
+ }
114
+ else {
115
+ // 其他情况,则把密文放在 xparam 字段中进行提交
116
+ newData = this.__encryptObjectSM2(data, options, 'xparam');
117
+ }
118
+ }
119
+ return newData;
120
+ }
121
+ /**
122
+ * 加密 sm2 对象
123
+ * @param targetObj 处理对象
124
+ * @param options 请求选项
125
+ * @param encryptedField 密文字段
126
+ */
127
+ __encryptObjectSM2(targetObj, options, encryptedField) {
128
+ const keys = Object.keys(targetObj);
129
+ if (keys.length === 0)
130
+ return targetObj;
131
+ // 忽略参数
132
+ const ignoreObj = {};
133
+ // 业务参数
134
+ const businessObj = {};
135
+ keys.forEach((item) => {
136
+ if (this.__ignoreEncryptFields.includes(item)) {
137
+ ignoreObj[item] = targetObj[item];
138
+ }
139
+ else {
140
+ businessObj[item] = targetObj[item];
141
+ }
142
+ });
143
+ // 重新构建的对象
144
+ const newObj = {
145
+ ...ignoreObj,
146
+ };
147
+ // 注入密文字段
148
+ if (Object.keys(businessObj).length) {
149
+ const encryptedText = this.__encryptTextSM2(JSON.stringify(businessObj), options);
150
+ newObj[encryptedField] = encryptedText;
151
+ }
152
+ return newObj;
153
+ }
154
+ /**
155
+ * 加密 sm2 文本
156
+ * @param targetText 需要加密的字符串
157
+ * @param options 请求选项
158
+ * @returns 加密后的密文
159
+ */
160
+ __encryptTextSM2(targetText, options) {
161
+ const publicKey = this.__getPublicKey(options);
162
+ return padding04(sm2.doEncrypt(targetText, publicKey, SM2EncryptMode.C1C2C3), true);
163
+ }
164
+ /**
165
+ * 解密 sm2 文本
166
+ * @param encryptedText 加密密文
167
+ */
168
+ __decryptTextSM2(encryptedText, options) {
169
+ const privateKey = this.__getPrivateKey(options);
170
+ let decryptedText = '';
171
+ try {
172
+ decryptedText = sm2.doDecrypt(padding04(encryptedText, false), privateKey, SM2EncryptMode.C1C2C3);
173
+ }
174
+ catch (e) {
175
+ console.error('decryptTextSM2 decrypt error', e, { encryptedText });
176
+ }
177
+ let res = decryptedText;
178
+ try {
179
+ res = JSON.parse(res);
180
+ }
181
+ catch (e) { }
182
+ return res;
183
+ }
184
+ /**
185
+ * 获取解密的私钥
186
+ * @param options 请求选项
187
+ */
188
+ __getPrivateKey(options) {
189
+ const params = options.params || {};
190
+ if ('appId' in params) {
191
+ return this.__userPrivateKey;
192
+ }
193
+ return INTERNAL_SM2_PRIVATE_KEY;
194
+ }
195
+ /**
196
+ * 获取加密的公钥
197
+ */
198
+ __getPublicKey(options) {
199
+ const params = options.params || {};
200
+ if ('appId' in params) {
201
+ return this.__platformPublicKey;
202
+ }
203
+ return INTERNAL_SM2_PUBLIC_KEY;
204
+ }
205
+ }
package/types.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ export interface SM2RequestPluginConfig {
2
+ /**
3
+ * 平台公钥(接口提交参数加密用)
4
+ */
5
+ platformPublicKey: string;
6
+ /**
7
+ * 用户私钥(接口响应内容解密用)
8
+ */
9
+ userPrivateKey: string;
10
+ /**
11
+ * 加入忽略加密字段
12
+ */
13
+ includeIgnoreEncryptFields?: string[];
14
+ /**
15
+ * 是否使用 sm2
16
+ * @default true
17
+ */
18
+ useSM2?: boolean;
19
+ /**
20
+ * 是否对 post 请求主体进行 sm2 加密
21
+ * @default true
22
+ */
23
+ encryptPostSM2?: boolean;
24
+ }
25
+ /**
26
+ * SM2加密模式
27
+ */
28
+ export declare enum SM2EncryptMode {
29
+ /** C1C3C2 */
30
+ C1C3C2 = 1,
31
+ /** C1C2C3顺序 */
32
+ C1C2C3 = 0
33
+ }
34
+ export interface SM2InnerResData {
35
+ encryption: boolean;
36
+ data: unknown;
37
+ }
package/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SM2加密模式
3
+ */
4
+ export var SM2EncryptMode;
5
+ (function (SM2EncryptMode) {
6
+ /** C1C3C2 */
7
+ SM2EncryptMode[SM2EncryptMode["C1C3C2"] = 1] = "C1C3C2";
8
+ /** C1C2C3顺序 */
9
+ SM2EncryptMode[SM2EncryptMode["C1C2C3"] = 0] = "C1C2C3";
10
+ })(SM2EncryptMode || (SM2EncryptMode = {}));
package/utils.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 控制是否开头填充04
3
+ * 注意:
4
+ * 1.前端sm-crypto加密后密文需要在开头补充04再传给后端 (https://juejin.cn/post/7084038026971578398)
5
+ * 2.后端加密密文已有添加04,前端sm-crypto解密时默认在开头再补充添加04,需要将后端密文手动剔除04
6
+ * @param encryptedText 密文
7
+ * @param isNeed 是否需要开头04
8
+ * @returns
9
+ */
10
+ export declare function padding04(encryptedText: string, isNeed: boolean): string;
package/utils.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 控制是否开头填充04
3
+ * 注意:
4
+ * 1.前端sm-crypto加密后密文需要在开头补充04再传给后端 (https://juejin.cn/post/7084038026971578398)
5
+ * 2.后端加密密文已有添加04,前端sm-crypto解密时默认在开头再补充添加04,需要将后端密文手动剔除04
6
+ * @param encryptedText 密文
7
+ * @param isNeed 是否需要开头04
8
+ * @returns
9
+ */
10
+ export function padding04(encryptedText, isNeed) {
11
+ const hasPadding = /^04/.test(encryptedText);
12
+ if (hasPadding && !isNeed) {
13
+ return encryptedText.substring(2);
14
+ }
15
+ if (!hasPadding && isNeed) {
16
+ return `04${encryptedText}`;
17
+ }
18
+ return encryptedText;
19
+ }