sm-crypto-wasm 0.3.1 → 0.7.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.
Files changed (72) hide show
  1. package/README.md +70 -7
  2. package/dist/asn1/decode.d.ts +74 -0
  3. package/dist/asn1/decode.js +338 -0
  4. package/dist/asn1/encode.d.ts +68 -0
  5. package/dist/asn1/encode.js +275 -0
  6. package/dist/asn1/index.d.ts +11 -0
  7. package/dist/asn1/index.js +11 -0
  8. package/dist/asn1/oid.d.ts +22 -0
  9. package/dist/asn1/oid.js +107 -0
  10. package/dist/asn1/time.d.ts +39 -0
  11. package/dist/asn1/time.js +111 -0
  12. package/dist/asn1/types.d.ts +76 -0
  13. package/dist/asn1/types.js +71 -0
  14. package/dist/cms/errors.d.ts +13 -0
  15. package/dist/cms/errors.js +11 -0
  16. package/dist/cms/index.d.ts +13 -0
  17. package/dist/cms/index.js +10 -0
  18. package/dist/cms/oids.d.ts +17 -0
  19. package/dist/cms/oids.js +17 -0
  20. package/dist/cms/parse.d.ts +75 -0
  21. package/dist/cms/parse.js +326 -0
  22. package/dist/cms/sign.d.ts +43 -0
  23. package/dist/cms/sign.js +142 -0
  24. package/dist/cms/verify.d.ts +42 -0
  25. package/dist/cms/verify.js +173 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +3 -0
  28. package/dist/pem/asn1.d.ts +10 -69
  29. package/dist/pem/asn1.js +10 -292
  30. package/dist/pem/pkcs8.js +9 -1
  31. package/dist/pem/sm2.d.ts +16 -0
  32. package/dist/pem/sm2.js +37 -5
  33. package/dist/sm2/index.d.ts +69 -0
  34. package/dist/sm2/index.js +188 -0
  35. package/dist/sm2/sm2.wasm +0 -0
  36. package/dist/webcrypto/algorithms.d.ts +42 -0
  37. package/dist/webcrypto/algorithms.js +120 -0
  38. package/dist/webcrypto/cryptoKey.d.ts +64 -0
  39. package/dist/webcrypto/cryptoKey.js +109 -0
  40. package/dist/webcrypto/errors.d.ts +19 -0
  41. package/dist/webcrypto/errors.js +45 -0
  42. package/dist/webcrypto/index.d.ts +14 -0
  43. package/dist/webcrypto/index.js +11 -0
  44. package/dist/webcrypto/subtle.d.ts +68 -0
  45. package/dist/webcrypto/subtle.js +358 -0
  46. package/dist/x509/encode.d.ts +59 -0
  47. package/dist/x509/encode.js +175 -0
  48. package/dist/x509/errors.d.ts +14 -0
  49. package/dist/x509/errors.js +14 -0
  50. package/dist/x509/extensions.d.ts +27 -0
  51. package/dist/x509/extensions.js +215 -0
  52. package/dist/x509/index.d.ts +14 -0
  53. package/dist/x509/index.js +11 -0
  54. package/dist/x509/ip.d.ts +12 -0
  55. package/dist/x509/ip.js +101 -0
  56. package/dist/x509/issue.d.ts +44 -0
  57. package/dist/x509/issue.js +414 -0
  58. package/dist/x509/oids.d.ts +40 -0
  59. package/dist/x509/oids.js +52 -0
  60. package/dist/x509/parse.d.ts +24 -0
  61. package/dist/x509/parse.js +436 -0
  62. package/dist/x509/sha1.d.ts +9 -0
  63. package/dist/x509/sha1.js +72 -0
  64. package/dist/x509/sha256.d.ts +9 -0
  65. package/dist/x509/sha256.js +84 -0
  66. package/dist/x509/sig.d.ts +20 -0
  67. package/dist/x509/sig.js +77 -0
  68. package/dist/x509/types.d.ts +235 -0
  69. package/dist/x509/types.js +7 -0
  70. package/dist/x509/verify.d.ts +44 -0
  71. package/dist/x509/verify.js +66 -0
  72. package/package.json +15 -3
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `crypto.subtle` 风格的 SM2 / SM3 / SM4 适配层。
3
+ *
4
+ * 注意:这是**独立对象**,不是原生 `crypto.subtle` 的 polyfill —— 浏览器
5
+ * 不允许注册自定义算法,生态库也不会自动识别它。
6
+ *
7
+ * 所有方法都是异步的(WebCrypto 语义),内部调用仍是同步的 wasm 计算;
8
+ * 首次使用时按算法自动 `ensureLoaded()`。
9
+ */
10
+ import { type AlgorithmInput } from './algorithms.js';
11
+ import { CryptoKey } from './cryptoKey.js';
12
+ /** 密钥对(结构兼容 WebCrypto 的 `CryptoKeyPair`)。 */
13
+ export interface SmCryptoKeyPair {
14
+ readonly privateKey: CryptoKey;
15
+ readonly publicKey: CryptoKey;
16
+ }
17
+ /** 支持的密钥格式。JWK 不在范围内。 */
18
+ export type SmKeyFormat = 'raw' | 'pkcs8' | 'spki' | 'jwk';
19
+ /** `sm-crypto-wasm/webcrypto` 的 `subtle` 对象。 */
20
+ export declare const subtle: {
21
+ /**
22
+ * 摘要。仅支持 `{ name: 'SM3' }`。
23
+ */
24
+ digest(algorithm: AlgorithmInput, data: ArrayBuffer | ArrayBufferView): Promise<ArrayBuffer>;
25
+ /**
26
+ * 生成密钥。
27
+ *
28
+ * - `{ name: 'SM2' }` → `{ privateKey, publicKey }`
29
+ * - `{ name: 'HMAC', hash: 'SM3', length?: bits }` → secret key
30
+ * - `{ name: 'SM4-*', length: 128 }` → secret key
31
+ */
32
+ generateKey(algorithm: AlgorithmInput, extractable: boolean, keyUsages: readonly string[]): Promise<CryptoKey | SmCryptoKeyPair>;
33
+ /**
34
+ * 导入密钥。
35
+ *
36
+ * - `raw`:HMAC / SM4
37
+ * - `pkcs8`:SM2 私钥 DER
38
+ * - `spki`:SM2 公钥 DER
39
+ */
40
+ importKey(format: SmKeyFormat, keyData: ArrayBuffer | ArrayBufferView, algorithm: AlgorithmInput, extractable: boolean, keyUsages: readonly string[]): Promise<CryptoKey>;
41
+ /**
42
+ * 导出密钥。
43
+ *
44
+ * `extractable === false` 时抛 `InvalidAccessError`(红线 #7)。
45
+ */
46
+ exportKey(format: SmKeyFormat, key: CryptoKey): Promise<ArrayBuffer>;
47
+ /**
48
+ * 签名。
49
+ *
50
+ * - `HMAC` + `hash: 'SM3'`
51
+ * - `SM2`(64 字节 `r ‖ s`,与 `sm2.sign` 一致)
52
+ */
53
+ sign(algorithm: AlgorithmInput, key: CryptoKey, data: ArrayBuffer | ArrayBufferView): Promise<ArrayBuffer>;
54
+ /**
55
+ * 验签。
56
+ */
57
+ verify(algorithm: AlgorithmInput, key: CryptoKey, signature: ArrayBuffer | ArrayBufferView, data: ArrayBuffer | ArrayBufferView): Promise<boolean>;
58
+ /**
59
+ * 加密。
60
+ *
61
+ * SM4-GCM 输出 `ciphertext ‖ tag`(tag 在尾部,与原生 AES-GCM 一致)。
62
+ */
63
+ encrypt(algorithm: AlgorithmInput, key: CryptoKey, data: ArrayBuffer | ArrayBufferView): Promise<ArrayBuffer>;
64
+ /**
65
+ * 解密。
66
+ */
67
+ decrypt(algorithm: AlgorithmInput, key: CryptoKey, data: ArrayBuffer | ArrayBufferView): Promise<ArrayBuffer>;
68
+ };
@@ -0,0 +1,358 @@
1
+ /**
2
+ * `crypto.subtle` 风格的 SM2 / SM3 / SM4 适配层。
3
+ *
4
+ * 注意:这是**独立对象**,不是原生 `crypto.subtle` 的 polyfill —— 浏览器
5
+ * 不允许注册自定义算法,生态库也不会自动识别它。
6
+ *
7
+ * 所有方法都是异步的(WebCrypto 语义),内部调用仍是同步的 wasm 计算;
8
+ * 首次使用时按算法自动 `ensureLoaded()`。
9
+ */
10
+ import { encodeSm2PrivateKeyDer, encodeSm2PublicKeyDer, parseSm2PrivateKeyDer, parseSm2PrivateKeyPem, parseSm2PublicKeyDer, parseSm2PublicKeyPem, } from '../pem/sm2.js';
11
+ import { hexToBytes } from '../runtime/convert.js';
12
+ import { randomBytes } from '../runtime/random.js';
13
+ import * as sm2 from '../sm2/index.js';
14
+ import * as sm3 from '../sm3/index.js';
15
+ import * as sm4 from '../sm4/index.js';
16
+ import { bufferSourceBytes, hashName, isSm4, normalizeAlgorithm, optionalAad, optionalUserId, requireAesLength, requireCtrLength, requireGcmTagLength, requireIv, } from './algorithms.js';
17
+ import { assertKeyAlgorithm, assertKeyType, assertUsage, assertUsagesAllowed, CryptoKey, createBytesKey, filterUsages, getKeyBytes, normalizeUsages, } from './cryptoKey.js';
18
+ import { dataError, invalidAccess, notSupported, toDOMException } from './errors.js';
19
+ const SM2_PRIVATE_USAGES = ['sign', 'decrypt'];
20
+ const SM2_PUBLIC_USAGES = ['verify', 'encrypt'];
21
+ const HMAC_USAGES = ['sign', 'verify'];
22
+ const SM4_USAGES = ['encrypt', 'decrypt'];
23
+ /** `sm-crypto-wasm/webcrypto` 的 `subtle` 对象。 */
24
+ export const subtle = {
25
+ /**
26
+ * 摘要。仅支持 `{ name: 'SM3' }`。
27
+ */
28
+ async digest(algorithm, data) {
29
+ try {
30
+ const alg = normalizeAlgorithm(algorithm);
31
+ if (alg.name !== 'SM3')
32
+ throw notSupported(`digest does not support ${alg.name}`);
33
+ const bytes = bufferSourceBytes(data, 'data');
34
+ await sm3.ensureLoaded();
35
+ return toArrayBuffer(hexToBytes(sm3.hash(bytes), 'digest'));
36
+ }
37
+ catch (err) {
38
+ throw toDOMException(err);
39
+ }
40
+ },
41
+ /**
42
+ * 生成密钥。
43
+ *
44
+ * - `{ name: 'SM2' }` → `{ privateKey, publicKey }`
45
+ * - `{ name: 'HMAC', hash: 'SM3', length?: bits }` → secret key
46
+ * - `{ name: 'SM4-*', length: 128 }` → secret key
47
+ */
48
+ async generateKey(algorithm, extractable, keyUsages) {
49
+ try {
50
+ const alg = normalizeAlgorithm(algorithm);
51
+ const usages = normalizeUsages(keyUsages);
52
+ if (alg.name === 'SM2') {
53
+ await sm2.ensureLoaded();
54
+ const pair = await sm2.generateKeyPair();
55
+ // 复用既有 PEM 解析器,不重复实现 PEM 解包。
56
+ const priv = new Uint8Array(parseSm2PrivateKeyPem(pair.privateKeyPem));
57
+ const pub = new Uint8Array(parseSm2PublicKeyPem(pair.publicKeyPem));
58
+ return {
59
+ privateKey: createBytesKey('private', { name: 'SM2' }, extractable, filterUsages(usages, SM2_PRIVATE_USAGES), priv),
60
+ publicKey: createBytesKey('public', { name: 'SM2' }, extractable, filterUsages(usages, SM2_PUBLIC_USAGES), pub),
61
+ };
62
+ }
63
+ if (alg.name === 'HMAC') {
64
+ await sm3.ensureLoaded();
65
+ assertUsagesAllowed(usages, HMAC_USAGES, 'HMAC');
66
+ const hash = hashName(alg.hash);
67
+ const rawLength = alg.length;
68
+ const lengthBits = rawLength === undefined ? 256 : rawLength;
69
+ if (typeof lengthBits !== 'number' ||
70
+ !Number.isInteger(lengthBits) ||
71
+ lengthBits <= 0 ||
72
+ lengthBits % 8 !== 0) {
73
+ throw dataError('HMAC length must be a positive multiple of 8 bits');
74
+ }
75
+ return createBytesKey('secret', { name: 'HMAC', hash: { name: hash }, length: lengthBits }, extractable, usages, randomBytes(lengthBits / 8));
76
+ }
77
+ if (isSm4(alg.name)) {
78
+ requireAesLength(alg);
79
+ assertUsagesAllowed(usages, SM4_USAGES, alg.name);
80
+ return createBytesKey('secret', { name: alg.name, length: 128 }, extractable, usages, randomBytes(16));
81
+ }
82
+ throw notSupported(`generateKey does not support ${alg.name}`);
83
+ }
84
+ catch (err) {
85
+ throw toDOMException(err);
86
+ }
87
+ },
88
+ /**
89
+ * 导入密钥。
90
+ *
91
+ * - `raw`:HMAC / SM4
92
+ * - `pkcs8`:SM2 私钥 DER
93
+ * - `spki`:SM2 公钥 DER
94
+ */
95
+ async importKey(format, keyData, algorithm, extractable, keyUsages) {
96
+ try {
97
+ if (format === 'jwk')
98
+ throw notSupported('JWK is not supported');
99
+ if (format !== 'raw' && format !== 'pkcs8' && format !== 'spki') {
100
+ throw notSupported(`unsupported key format "${String(format)}"`);
101
+ }
102
+ const alg = normalizeAlgorithm(algorithm);
103
+ const usages = normalizeUsages(keyUsages);
104
+ const bytes = keyDataBytes(keyData);
105
+ if (format === 'raw') {
106
+ if (alg.name === 'HMAC') {
107
+ assertUsagesAllowed(usages, HMAC_USAGES, 'HMAC');
108
+ const hash = hashName(alg.hash);
109
+ return createBytesKey('secret', { name: 'HMAC', hash: { name: hash }, length: bytes.length * 8 }, extractable, usages, bytes);
110
+ }
111
+ if (isSm4(alg.name)) {
112
+ requireAesLength(alg);
113
+ if (bytes.length !== 16)
114
+ throw dataError('SM4 key must be 16 bytes');
115
+ assertUsagesAllowed(usages, SM4_USAGES, alg.name);
116
+ return createBytesKey('secret', { name: alg.name, length: 128 }, extractable, usages, bytes);
117
+ }
118
+ throw notSupported(`importKey('raw') does not support ${alg.name}`);
119
+ }
120
+ if (alg.name !== 'SM2') {
121
+ throw notSupported(`importKey('${format}') only supports SM2`);
122
+ }
123
+ if (format === 'pkcs8') {
124
+ return createBytesKey('private', { name: 'SM2' }, extractable, filterUsages(usages, SM2_PRIVATE_USAGES), parseSm2PrivateKeyDer(bytes));
125
+ }
126
+ return createBytesKey('public', { name: 'SM2' }, extractable, filterUsages(usages, SM2_PUBLIC_USAGES), parseSm2PublicKeyDer(bytes));
127
+ }
128
+ catch (err) {
129
+ throw toDOMException(err);
130
+ }
131
+ },
132
+ /**
133
+ * 导出密钥。
134
+ *
135
+ * `extractable === false` 时抛 `InvalidAccessError`(红线 #7)。
136
+ */
137
+ async exportKey(format, key) {
138
+ try {
139
+ if (!(key instanceof CryptoKey))
140
+ throw new TypeError('exportKey: key must be a CryptoKey');
141
+ if (!key.extractable)
142
+ throw invalidAccess('exportKey: key is not extractable');
143
+ if (format === 'raw') {
144
+ if (key.type !== 'secret') {
145
+ throw invalidAccess('exportKey: raw export requires a secret key');
146
+ }
147
+ return toArrayBuffer(getKeyBytes(key));
148
+ }
149
+ if (format === 'pkcs8') {
150
+ if (key.type !== 'private' || key.algorithm.name !== 'SM2') {
151
+ throw invalidAccess('exportKey: pkcs8 export requires an SM2 private key');
152
+ }
153
+ return toArrayBuffer(encodeSm2PrivateKeyDer(getKeyBytes(key)));
154
+ }
155
+ if (format === 'spki') {
156
+ if (key.type !== 'public' || key.algorithm.name !== 'SM2') {
157
+ throw invalidAccess('exportKey: spki export requires an SM2 public key');
158
+ }
159
+ return toArrayBuffer(encodeSm2PublicKeyDer(getKeyBytes(key)));
160
+ }
161
+ throw notSupported(`exportKey format "${String(format)}" is not supported`);
162
+ }
163
+ catch (err) {
164
+ throw toDOMException(err);
165
+ }
166
+ },
167
+ /**
168
+ * 签名。
169
+ *
170
+ * - `HMAC` + `hash: 'SM3'`
171
+ * - `SM2`(64 字节 `r ‖ s`,与 `sm2.sign` 一致)
172
+ */
173
+ async sign(algorithm, key, data) {
174
+ try {
175
+ const alg = normalizeAlgorithm(algorithm);
176
+ const bytes = bufferSourceBytes(data, 'data');
177
+ if (alg.name === 'HMAC') {
178
+ assertKeyType(key, 'secret', 'sign');
179
+ assertUsage(key, 'sign', 'sign');
180
+ assertKeyAlgorithm(key, 'HMAC', 'sign');
181
+ assertHmacHash(key);
182
+ await sm3.ensureLoaded();
183
+ return toArrayBuffer(hexToBytes(sm3.hmac(getKeyBytes(key), bytes), 'signature'));
184
+ }
185
+ if (alg.name === 'SM2') {
186
+ assertKeyType(key, 'private', 'sign');
187
+ assertUsage(key, 'sign', 'sign');
188
+ assertKeyAlgorithm(key, 'SM2', 'sign');
189
+ await sm2.ensureLoaded();
190
+ const userID = optionalUserId(alg);
191
+ const signature = userID === undefined
192
+ ? sm2.sign(getKeyBytes(key), bytes)
193
+ : sm2.signWithID(getKeyBytes(key), bytes, userID);
194
+ return toArrayBuffer(hexToBytes(signature, 'signature'));
195
+ }
196
+ throw notSupported(`sign does not support ${alg.name}`);
197
+ }
198
+ catch (err) {
199
+ throw toDOMException(err);
200
+ }
201
+ },
202
+ /**
203
+ * 验签。
204
+ */
205
+ async verify(algorithm, key, signature, data) {
206
+ try {
207
+ const alg = normalizeAlgorithm(algorithm);
208
+ const bytes = bufferSourceBytes(data, 'data');
209
+ const signatureBytes = bufferSourceBytes(signature, 'signature');
210
+ if (alg.name === 'HMAC') {
211
+ assertKeyType(key, 'secret', 'verify');
212
+ assertUsage(key, 'verify', 'verify');
213
+ assertKeyAlgorithm(key, 'HMAC', 'verify');
214
+ assertHmacHash(key);
215
+ await sm3.ensureLoaded();
216
+ const expected = hexToBytes(sm3.hmac(getKeyBytes(key), bytes), 'signature');
217
+ return constantTimeEqual(expected, signatureBytes);
218
+ }
219
+ if (alg.name === 'SM2') {
220
+ assertKeyType(key, 'public', 'verify');
221
+ assertUsage(key, 'verify', 'verify');
222
+ assertKeyAlgorithm(key, 'SM2', 'verify');
223
+ if (signatureBytes.length !== 64) {
224
+ throw dataError('SM2 signature must be 64 bytes (r || s)');
225
+ }
226
+ await sm2.ensureLoaded();
227
+ const userID = optionalUserId(alg);
228
+ return userID === undefined
229
+ ? sm2.verify(getKeyBytes(key), bytes, signatureBytes)
230
+ : sm2.verifyWithID(getKeyBytes(key), bytes, signatureBytes, userID);
231
+ }
232
+ throw notSupported(`verify does not support ${alg.name}`);
233
+ }
234
+ catch (err) {
235
+ throw toDOMException(err);
236
+ }
237
+ },
238
+ /**
239
+ * 加密。
240
+ *
241
+ * SM4-GCM 输出 `ciphertext ‖ tag`(tag 在尾部,与原生 AES-GCM 一致)。
242
+ */
243
+ async encrypt(algorithm, key, data) {
244
+ try {
245
+ const alg = normalizeAlgorithm(algorithm);
246
+ const bytes = bufferSourceBytes(data, 'data');
247
+ assertKeyType(key, 'secret', 'encrypt');
248
+ assertUsage(key, 'encrypt', 'encrypt');
249
+ assertKeyAlgorithm(key, alg.name, 'encrypt');
250
+ await sm4.ensureLoaded();
251
+ const k = getKeyBytes(key);
252
+ switch (alg.name) {
253
+ case 'SM4-GCM': {
254
+ requireGcmTagLength(alg);
255
+ const iv = requireIv(alg);
256
+ const aad = optionalAad(alg);
257
+ const { ct, tag } = sm4.encryptGCM(k, iv, bytes, aad);
258
+ return toArrayBuffer(concat(hexToBytes(ct, 'ciphertext'), hexToBytes(tag, 'tag')));
259
+ }
260
+ case 'SM4-CBC':
261
+ return toArrayBuffer(hexToBytes(sm4.encryptCBC(k, requireIv(alg), bytes), 'ciphertext'));
262
+ case 'SM4-CTR': {
263
+ requireCtrLength(alg);
264
+ return toArrayBuffer(hexToBytes(sm4.cryptCTR(k, requireIv(alg), bytes), 'ciphertext'));
265
+ }
266
+ case 'SM4-CFB':
267
+ return toArrayBuffer(hexToBytes(sm4.encryptCFB(k, requireIv(alg), bytes), 'ciphertext'));
268
+ case 'SM4-OFB':
269
+ return toArrayBuffer(hexToBytes(sm4.cryptOFB(k, requireIv(alg), bytes), 'ciphertext'));
270
+ case 'SM4-ECB':
271
+ return toArrayBuffer(hexToBytes(sm4.encryptECB(k, bytes), 'ciphertext'));
272
+ default:
273
+ throw notSupported(`encrypt does not support ${alg.name}`);
274
+ }
275
+ }
276
+ catch (err) {
277
+ throw toDOMException(err);
278
+ }
279
+ },
280
+ /**
281
+ * 解密。
282
+ */
283
+ async decrypt(algorithm, key, data) {
284
+ try {
285
+ const alg = normalizeAlgorithm(algorithm);
286
+ const bytes = bufferSourceBytes(data, 'data');
287
+ assertKeyType(key, 'secret', 'decrypt');
288
+ assertUsage(key, 'decrypt', 'decrypt');
289
+ assertKeyAlgorithm(key, alg.name, 'decrypt');
290
+ await sm4.ensureLoaded();
291
+ const k = getKeyBytes(key);
292
+ switch (alg.name) {
293
+ case 'SM4-GCM': {
294
+ requireGcmTagLength(alg);
295
+ if (bytes.length < 16)
296
+ throw dataError('SM4-GCM ciphertext is shorter than the tag');
297
+ const iv = requireIv(alg);
298
+ const aad = optionalAad(alg);
299
+ const ct = bytes.subarray(0, bytes.length - 16);
300
+ const tag = bytes.subarray(bytes.length - 16);
301
+ const plaintext = sm4.decryptGCM(k, iv, ct, tag, { aad });
302
+ return toArrayBuffer(hexToBytes(plaintext, 'plaintext'));
303
+ }
304
+ case 'SM4-CBC':
305
+ return toArrayBuffer(hexToBytes(sm4.decryptCBC(k, requireIv(alg), bytes), 'plaintext'));
306
+ case 'SM4-CTR': {
307
+ requireCtrLength(alg);
308
+ return toArrayBuffer(hexToBytes(sm4.cryptCTR(k, requireIv(alg), bytes), 'plaintext'));
309
+ }
310
+ case 'SM4-CFB':
311
+ return toArrayBuffer(hexToBytes(sm4.decryptCFB(k, requireIv(alg), bytes), 'plaintext'));
312
+ case 'SM4-OFB':
313
+ return toArrayBuffer(hexToBytes(sm4.cryptOFB(k, requireIv(alg), bytes), 'plaintext'));
314
+ case 'SM4-ECB':
315
+ return toArrayBuffer(hexToBytes(sm4.decryptECB(k, bytes), 'plaintext'));
316
+ default:
317
+ throw notSupported(`decrypt does not support ${alg.name}`);
318
+ }
319
+ }
320
+ catch (err) {
321
+ throw toDOMException(err);
322
+ }
323
+ },
324
+ };
325
+ // ---------- 内部 ----------
326
+ function assertHmacHash(key) {
327
+ const hash = key.algorithm.hash;
328
+ if (hash?.name !== 'SM3') {
329
+ throw invalidAccess('HMAC key is not bound to SM3');
330
+ }
331
+ }
332
+ function keyDataBytes(keyData) {
333
+ if (keyData instanceof ArrayBuffer)
334
+ return new Uint8Array(keyData.slice(0));
335
+ if (ArrayBuffer.isView(keyData)) {
336
+ return new Uint8Array(keyData.buffer, keyData.byteOffset, keyData.byteLength).slice();
337
+ }
338
+ throw dataError('keyData must be an ArrayBuffer or ArrayBufferView');
339
+ }
340
+ function toArrayBuffer(bytes) {
341
+ const copy = new Uint8Array(bytes.length);
342
+ copy.set(bytes);
343
+ return copy.buffer;
344
+ }
345
+ function concat(a, b) {
346
+ const out = new Uint8Array(a.length + b.length);
347
+ out.set(a, 0);
348
+ out.set(b, a.length);
349
+ return out;
350
+ }
351
+ function constantTimeEqual(a, b) {
352
+ if (a.length !== b.length)
353
+ return false;
354
+ let diff = 0;
355
+ for (let i = 0; i < a.length; i++)
356
+ diff |= a[i] ^ b[i];
357
+ return diff === 0;
358
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * X.509 证书 DER 编码(签发侧)。
3
+ *
4
+ * 只负责把结构化输入编码为 RFC 5280 证书;签名算法固定为 SM3withSM2。
5
+ * 编码复用 `src/asn1/`,不重复实现任何算法。
6
+ */
7
+ import { type Asn1Node } from '../asn1/index.js';
8
+ import type { BasicConstraintsInput, DistinguishedNameInput, SubjectAltNameInput, Validity } from './types.js';
9
+ /** 待编码的一个扩展。 */
10
+ export interface EncodedExtension {
11
+ readonly oid: string;
12
+ readonly critical: boolean;
13
+ readonly value: Uint8Array;
14
+ }
15
+ /** 编码 Name(RDNSequence)。 */
16
+ export declare function encodeName(dn: DistinguishedNameInput): Asn1Node;
17
+ /** 编码 SM2 的 SubjectPublicKeyInfo。 */
18
+ export declare function encodeSubjectPublicKeyInfo(publicKey: Uint8Array): Asn1Node;
19
+ /** 编码 Validity。 */
20
+ export declare function encodeValidity(validity: Validity): Asn1Node;
21
+ /** 编码 AlgorithmIdentifier(SM3withSM2,无参数)。 */
22
+ export declare function encodeSignatureAlgorithm(): Asn1Node;
23
+ /** 编码 BasicConstraints 的 extnValue。 */
24
+ export declare function encodeBasicConstraints(input: BasicConstraintsInput): Uint8Array;
25
+ /** 编码 KeyUsage 的 extnValue(BIT STRING)。 */
26
+ export declare function encodeKeyUsage(names: readonly string[]): Uint8Array;
27
+ /** 编码 ExtendedKeyUsage 的 extnValue(SEQUENCE OF OID)。 */
28
+ export declare function encodeExtendedKeyUsage(oids: readonly string[]): Uint8Array;
29
+ /** 编码 SubjectAltName 的 extnValue(SEQUENCE OF GeneralName)。 */
30
+ export declare function encodeSubjectAltName(names: readonly SubjectAltNameInput[]): Uint8Array;
31
+ /** 编码 SubjectKeyIdentifier 的 extnValue(OCTET STRING)。 */
32
+ export declare function encodeSubjectKeyIdentifier(keyIdentifier: Uint8Array): Uint8Array;
33
+ /** 编码 AuthorityKeyIdentifier 的 extnValue([0] keyIdentifier)。 */
34
+ export declare function encodeAuthorityKeyIdentifier(keyIdentifier: Uint8Array): Uint8Array;
35
+ /** 把扩展列表编码为 `[3] EXPLICIT Extensions`。 */
36
+ export declare function encodeExtensions(extensions: readonly EncodedExtension[]): Asn1Node;
37
+ /** 编码 TBSCertificate。 */
38
+ export declare function encodeTbsCertificate(input: {
39
+ version: 1 | 2 | 3;
40
+ serialNumber: bigint;
41
+ issuer: DistinguishedNameInput;
42
+ validity: Validity;
43
+ subject: DistinguishedNameInput;
44
+ publicKey: Uint8Array;
45
+ extensions: readonly EncodedExtension[];
46
+ }): Asn1Node;
47
+ /** 组装 Certificate ::= SEQUENCE { tbs, signatureAlgorithm, signatureValue }。 */
48
+ export declare function encodeCertificate(tbs: Asn1Node, signatureDer: Uint8Array): Uint8Array;
49
+ /** 64/65 字节公钥 → 65 字节未压缩点。 */
50
+ export declare function normalizePublicKey(publicKey: Uint8Array): Uint8Array;
51
+ /** 扩展 OID 常量(供签发逻辑使用)。 */
52
+ export declare const EXTENSION_OIDS: {
53
+ readonly basicConstraints: "2.5.29.19";
54
+ readonly keyUsage: "2.5.29.15";
55
+ readonly extendedKeyUsage: "2.5.29.37";
56
+ readonly subjectAltName: "2.5.29.17";
57
+ readonly subjectKeyIdentifier: "2.5.29.14";
58
+ readonly authorityKeyIdentifier: "2.5.29.35";
59
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * X.509 证书 DER 编码(签发侧)。
3
+ *
4
+ * 只负责把结构化输入编码为 RFC 5280 证书;签名算法固定为 SM3withSM2。
5
+ * 编码复用 `src/asn1/`,不重复实现任何算法。
6
+ */
7
+ import { bitString, boolean, explicit, ia5String, implicit, integer, octetString, oid, printableString, seq, set, time, utf8String, } from '../asn1/index.js';
8
+ import { CertificateError } from './errors.js';
9
+ import { parseIpAddress } from './ip.js';
10
+ import { OID_AUTHORITY_KEY_IDENTIFIER, OID_BASIC_CONSTRAINTS, OID_COMMON_NAME, OID_COUNTRY, OID_EMAIL_ADDRESS, OID_EXTENDED_KEY_USAGE, OID_ID_EC_PUBLIC_KEY, OID_KEY_USAGE, OID_LOCALITY, OID_ORGANIZATION, OID_ORGANIZATIONAL_UNIT, OID_SM2_CURVE, OID_SM3_WITH_SM2, OID_STATE, OID_SUBJECT_ALT_NAME, OID_SUBJECT_KEY_IDENTIFIER, } from './oids.js';
11
+ /** KeyUsage 的位顺序(RFC 5280)。 */
12
+ const KEY_USAGE_ORDER = [
13
+ 'digitalSignature',
14
+ 'nonRepudiation',
15
+ 'keyEncipherment',
16
+ 'dataEncipherment',
17
+ 'keyAgreement',
18
+ 'keyCertSign',
19
+ 'cRLSign',
20
+ 'encipherOnly',
21
+ 'decipherOnly',
22
+ ];
23
+ /** 编码 Name(RDNSequence)。 */
24
+ export function encodeName(dn) {
25
+ const rdns = [];
26
+ const add = (value, attrOid, encodeValue) => {
27
+ if (value === undefined || value.length === 0)
28
+ return;
29
+ rdns.push(set([seq([oid(attrOid), encodeValue(value)])]));
30
+ };
31
+ add(dn.country, OID_COUNTRY, printableString);
32
+ add(dn.state, OID_STATE, utf8String);
33
+ add(dn.locality, OID_LOCALITY, utf8String);
34
+ add(dn.organization, OID_ORGANIZATION, utf8String);
35
+ add(dn.organizationalUnit, OID_ORGANIZATIONAL_UNIT, utf8String);
36
+ add(dn.commonName, OID_COMMON_NAME, utf8String);
37
+ add(dn.emailAddress, OID_EMAIL_ADDRESS, ia5String);
38
+ if (rdns.length === 0) {
39
+ throw new CertificateError('Name: at least one attribute is required');
40
+ }
41
+ return seq(rdns);
42
+ }
43
+ /** 编码 SM2 的 SubjectPublicKeyInfo。 */
44
+ export function encodeSubjectPublicKeyInfo(publicKey) {
45
+ const point = normalizePublicKey(publicKey);
46
+ return seq([seq([oid(OID_ID_EC_PUBLIC_KEY), oid(OID_SM2_CURVE)]), bitString(point)]);
47
+ }
48
+ /** 编码 Validity。 */
49
+ export function encodeValidity(validity) {
50
+ return seq([time(validity.notBefore), time(validity.notAfter)]);
51
+ }
52
+ /** 编码 AlgorithmIdentifier(SM3withSM2,无参数)。 */
53
+ export function encodeSignatureAlgorithm() {
54
+ return seq([oid(OID_SM3_WITH_SM2)]);
55
+ }
56
+ /** 编码 BasicConstraints 的 extnValue。 */
57
+ export function encodeBasicConstraints(input) {
58
+ if (!input.ca && input.pathLen !== undefined) {
59
+ throw new RangeError('basicConstraints.pathLen is only valid for CA certificates');
60
+ }
61
+ if (input.pathLen !== undefined && (!Number.isInteger(input.pathLen) || input.pathLen < 0)) {
62
+ throw new RangeError(`basicConstraints.pathLen must be a non-negative integer`);
63
+ }
64
+ const children = [];
65
+ if (input.ca)
66
+ children.push(boolean(true));
67
+ if (input.pathLen !== undefined)
68
+ children.push(integer(BigInt(input.pathLen)));
69
+ return seq(children).raw;
70
+ }
71
+ /** 编码 KeyUsage 的 extnValue(BIT STRING)。 */
72
+ export function encodeKeyUsage(names) {
73
+ let maxBit = -1;
74
+ for (const name of names) {
75
+ const index = KEY_USAGE_ORDER.indexOf(name);
76
+ if (index < 0)
77
+ throw new CertificateError(`keyUsage: unknown bit "${name}"`);
78
+ if (index > maxBit)
79
+ maxBit = index;
80
+ }
81
+ if (maxBit < 0)
82
+ throw new CertificateError('keyUsage: at least one bit is required');
83
+ const bytes = new Uint8Array(Math.floor(maxBit / 8) + 1);
84
+ for (const name of names) {
85
+ const index = KEY_USAGE_ORDER.indexOf(name);
86
+ const byteIndex = Math.floor(index / 8);
87
+ bytes[byteIndex] = bytes[byteIndex] | (0x80 >> (index % 8));
88
+ }
89
+ const unusedBits = bytes.length * 8 - (maxBit + 1);
90
+ return bitString(bytes, unusedBits).raw;
91
+ }
92
+ /** 编码 ExtendedKeyUsage 的 extnValue(SEQUENCE OF OID)。 */
93
+ export function encodeExtendedKeyUsage(oids) {
94
+ return seq(oids.map((value) => oid(value))).raw;
95
+ }
96
+ /** 编码 SubjectAltName 的 extnValue(SEQUENCE OF GeneralName)。 */
97
+ export function encodeSubjectAltName(names) {
98
+ return seq(names.map(encodeGeneralName)).raw;
99
+ }
100
+ function encodeGeneralName(name) {
101
+ switch (name.type) {
102
+ case 'dns':
103
+ return implicit(2, ia5String(name.value));
104
+ case 'ip':
105
+ return implicit(7, octetString(parseIpAddress(name.value)));
106
+ case 'email':
107
+ return implicit(1, ia5String(name.value));
108
+ case 'uri':
109
+ return implicit(6, ia5String(name.value));
110
+ default: {
111
+ const exhaustive = name.type;
112
+ throw new CertificateError(`subjectAltName: unknown type ${String(exhaustive)}`);
113
+ }
114
+ }
115
+ }
116
+ /** 编码 SubjectKeyIdentifier 的 extnValue(OCTET STRING)。 */
117
+ export function encodeSubjectKeyIdentifier(keyIdentifier) {
118
+ return octetString(keyIdentifier).raw;
119
+ }
120
+ /** 编码 AuthorityKeyIdentifier 的 extnValue([0] keyIdentifier)。 */
121
+ export function encodeAuthorityKeyIdentifier(keyIdentifier) {
122
+ return seq([implicit(0, octetString(keyIdentifier))]).raw;
123
+ }
124
+ /** 把扩展列表编码为 `[3] EXPLICIT Extensions`。 */
125
+ export function encodeExtensions(extensions) {
126
+ const entries = extensions.map((ext) => {
127
+ const children = [oid(ext.oid)];
128
+ if (ext.critical)
129
+ children.push(boolean(true));
130
+ children.push(octetString(ext.value));
131
+ return seq(children);
132
+ });
133
+ return explicit(3, seq(entries));
134
+ }
135
+ /** 编码 TBSCertificate。 */
136
+ export function encodeTbsCertificate(input) {
137
+ const children = [
138
+ explicit(0, integer(BigInt(input.version - 1))),
139
+ integer(input.serialNumber),
140
+ encodeSignatureAlgorithm(),
141
+ encodeName(input.issuer),
142
+ encodeValidity(input.validity),
143
+ encodeName(input.subject),
144
+ encodeSubjectPublicKeyInfo(input.publicKey),
145
+ ];
146
+ if (input.extensions.length > 0) {
147
+ children.push(encodeExtensions(input.extensions));
148
+ }
149
+ return seq(children);
150
+ }
151
+ /** 组装 Certificate ::= SEQUENCE { tbs, signatureAlgorithm, signatureValue }。 */
152
+ export function encodeCertificate(tbs, signatureDer) {
153
+ return seq([tbs, encodeSignatureAlgorithm(), bitString(signatureDer)]).raw;
154
+ }
155
+ /** 64/65 字节公钥 → 65 字节未压缩点。 */
156
+ export function normalizePublicKey(publicKey) {
157
+ if (publicKey.length === 65 && publicKey[0] === 0x04)
158
+ return new Uint8Array(publicKey);
159
+ if (publicKey.length === 64) {
160
+ const out = new Uint8Array(65);
161
+ out[0] = 0x04;
162
+ out.set(publicKey, 1);
163
+ return out;
164
+ }
165
+ throw new CertificateError('publicKey: expected 64 or 65 (uncompressed) bytes');
166
+ }
167
+ /** 扩展 OID 常量(供签发逻辑使用)。 */
168
+ export const EXTENSION_OIDS = {
169
+ basicConstraints: OID_BASIC_CONSTRAINTS,
170
+ keyUsage: OID_KEY_USAGE,
171
+ extendedKeyUsage: OID_EXTENDED_KEY_USAGE,
172
+ subjectAltName: OID_SUBJECT_ALT_NAME,
173
+ subjectKeyIdentifier: OID_SUBJECT_KEY_IDENTIFIER,
174
+ authorityKeyIdentifier: OID_AUTHORITY_KEY_IDENTIFIER,
175
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * X.509 错误类型。
3
+ *
4
+ * 所有证书结构/校验错误都是 `CertificateError`(`SmCryptoError` 的子类),
5
+ * 便于调用方与其它库错误一起捕获。
6
+ */
7
+ import { SmCryptoError } from '../runtime/errors.js';
8
+ /** 证书解析或校验失败。 */
9
+ export declare class CertificateError extends SmCryptoError {
10
+ readonly name: string;
11
+ constructor(message: string, options?: {
12
+ cause?: unknown;
13
+ });
14
+ }