sm-crypto-wasm 0.3.0 → 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.
- package/README.md +76 -7
- package/dist/asn1/decode.d.ts +74 -0
- package/dist/asn1/decode.js +338 -0
- package/dist/asn1/encode.d.ts +68 -0
- package/dist/asn1/encode.js +275 -0
- package/dist/asn1/index.d.ts +11 -0
- package/dist/asn1/index.js +11 -0
- package/dist/asn1/oid.d.ts +22 -0
- package/dist/asn1/oid.js +107 -0
- package/dist/asn1/time.d.ts +39 -0
- package/dist/asn1/time.js +111 -0
- package/dist/asn1/types.d.ts +76 -0
- package/dist/asn1/types.js +71 -0
- package/dist/cms/errors.d.ts +13 -0
- package/dist/cms/errors.js +11 -0
- package/dist/cms/index.d.ts +13 -0
- package/dist/cms/index.js +10 -0
- package/dist/cms/oids.d.ts +17 -0
- package/dist/cms/oids.js +17 -0
- package/dist/cms/parse.d.ts +75 -0
- package/dist/cms/parse.js +326 -0
- package/dist/cms/sign.d.ts +43 -0
- package/dist/cms/sign.js +142 -0
- package/dist/cms/verify.d.ts +42 -0
- package/dist/cms/verify.js +173 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/pem/asn1.d.ts +10 -69
- package/dist/pem/asn1.js +10 -292
- package/dist/pem/pkcs8.js +9 -1
- package/dist/pem/sm2.d.ts +16 -0
- package/dist/pem/sm2.js +37 -5
- package/dist/runtime/base64.core.d.ts +22 -0
- package/dist/runtime/base64.core.js +68 -0
- package/dist/runtime/convert.d.ts +12 -3
- package/dist/runtime/convert.js +31 -12
- package/dist/sm2/index.d.ts +69 -0
- package/dist/sm2/index.js +188 -0
- package/dist/sm2/sm2.wasm +0 -0
- package/dist/webcrypto/algorithms.d.ts +42 -0
- package/dist/webcrypto/algorithms.js +120 -0
- package/dist/webcrypto/cryptoKey.d.ts +64 -0
- package/dist/webcrypto/cryptoKey.js +109 -0
- package/dist/webcrypto/errors.d.ts +19 -0
- package/dist/webcrypto/errors.js +45 -0
- package/dist/webcrypto/index.d.ts +14 -0
- package/dist/webcrypto/index.js +11 -0
- package/dist/webcrypto/subtle.d.ts +68 -0
- package/dist/webcrypto/subtle.js +358 -0
- package/dist/x509/encode.d.ts +59 -0
- package/dist/x509/encode.js +175 -0
- package/dist/x509/errors.d.ts +14 -0
- package/dist/x509/errors.js +14 -0
- package/dist/x509/extensions.d.ts +27 -0
- package/dist/x509/extensions.js +215 -0
- package/dist/x509/index.d.ts +14 -0
- package/dist/x509/index.js +11 -0
- package/dist/x509/ip.d.ts +12 -0
- package/dist/x509/ip.js +101 -0
- package/dist/x509/issue.d.ts +44 -0
- package/dist/x509/issue.js +414 -0
- package/dist/x509/oids.d.ts +40 -0
- package/dist/x509/oids.js +52 -0
- package/dist/x509/parse.d.ts +24 -0
- package/dist/x509/parse.js +436 -0
- package/dist/x509/sha1.d.ts +9 -0
- package/dist/x509/sha1.js +72 -0
- package/dist/x509/sha256.d.ts +9 -0
- package/dist/x509/sha256.js +84 -0
- package/dist/x509/sig.d.ts +20 -0
- package/dist/x509/sig.js +77 -0
- package/dist/x509/types.d.ts +235 -0
- package/dist/x509/types.js +7 -0
- package/dist/x509/verify.d.ts +44 -0
- package/dist/x509/verify.js +66 -0
- package/package.json +16 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 适配层的错误映射。
|
|
3
|
+
*
|
|
4
|
+
* 对外的所有失败都映射为 `DOMException`(WebCrypto 的约定):
|
|
5
|
+
* - `NotSupportedError`:未知算法/格式/不支持的参数;
|
|
6
|
+
* - `InvalidAccessError`:密钥用途或 `extractable` 不允许;
|
|
7
|
+
* - `DataError`:密钥/数据格式非法;
|
|
8
|
+
* - `OperationError`:加解密/验签失败。
|
|
9
|
+
*/
|
|
10
|
+
import { CiphertextError, DecryptionError, InvalidKeyError, NotImplementedError, SmCryptoError, } from '../runtime/errors.js';
|
|
11
|
+
/** 构造 `DOMException` 的便捷函数。 */
|
|
12
|
+
export function notSupported(message) {
|
|
13
|
+
return new DOMException(message, 'NotSupportedError');
|
|
14
|
+
}
|
|
15
|
+
/** 构造 `InvalidAccessError`。 */
|
|
16
|
+
export function invalidAccess(message) {
|
|
17
|
+
return new DOMException(message, 'InvalidAccessError');
|
|
18
|
+
}
|
|
19
|
+
/** 构造 `DataError`。 */
|
|
20
|
+
export function dataError(message) {
|
|
21
|
+
return new DOMException(message, 'DataError');
|
|
22
|
+
}
|
|
23
|
+
/** 构造 `OperationError`。 */
|
|
24
|
+
export function operationError(message) {
|
|
25
|
+
return new DOMException(message, 'OperationError');
|
|
26
|
+
}
|
|
27
|
+
/** 把库内错误映射为 `DOMException`;已经是 `DOMException` 时原样返回。 */
|
|
28
|
+
export function toDOMException(err, fallback = 'OperationError') {
|
|
29
|
+
if (err instanceof DOMException)
|
|
30
|
+
return err;
|
|
31
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32
|
+
if (err instanceof NotImplementedError)
|
|
33
|
+
return notSupported(message);
|
|
34
|
+
if (err instanceof DecryptionError)
|
|
35
|
+
return operationError(message);
|
|
36
|
+
if (err instanceof InvalidKeyError)
|
|
37
|
+
return dataError(message);
|
|
38
|
+
if (err instanceof CiphertextError)
|
|
39
|
+
return dataError(message);
|
|
40
|
+
if (err instanceof SmCryptoError)
|
|
41
|
+
return operationError(message);
|
|
42
|
+
if (err instanceof TypeError || err instanceof RangeError)
|
|
43
|
+
return dataError(message);
|
|
44
|
+
return new DOMException(message, fallback);
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 适配层(`sm-crypto-wasm/webcrypto`)。
|
|
3
|
+
*
|
|
4
|
+
* 这是一个**独立**的 `subtle` 对象,不是原生 `crypto.subtle` 的 polyfill:
|
|
5
|
+
* 浏览器不允许注册自定义算法,第三方库也不会自动识别它。
|
|
6
|
+
*
|
|
7
|
+
* 范围:SM3、HMAC(SM3)、SM2、SM4-ECB/CBC/CTR/CFB/OFB/GCM。
|
|
8
|
+
* 不做 JWK、deriveKey/deriveBits、wrapKey/unwrapKey、postMessage 克隆语义。
|
|
9
|
+
*/
|
|
10
|
+
export type { AlgorithmInput, NormalizedAlgorithm } from './algorithms.js';
|
|
11
|
+
export type { KeyAlgorithm, KeyMaterial, KeyProvider, KeyType, KeyUsage } from './cryptoKey.js';
|
|
12
|
+
export { CryptoKey } from './cryptoKey.js';
|
|
13
|
+
export type { SmCryptoKeyPair, SmKeyFormat } from './subtle.js';
|
|
14
|
+
export { subtle } from './subtle.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 适配层(`sm-crypto-wasm/webcrypto`)。
|
|
3
|
+
*
|
|
4
|
+
* 这是一个**独立**的 `subtle` 对象,不是原生 `crypto.subtle` 的 polyfill:
|
|
5
|
+
* 浏览器不允许注册自定义算法,第三方库也不会自动识别它。
|
|
6
|
+
*
|
|
7
|
+
* 范围:SM3、HMAC(SM3)、SM2、SM4-ECB/CBC/CTR/CFB/OFB/GCM。
|
|
8
|
+
* 不做 JWK、deriveKey/deriveBits、wrapKey/unwrapKey、postMessage 克隆语义。
|
|
9
|
+
*/
|
|
10
|
+
export { CryptoKey } from './cryptoKey.js';
|
|
11
|
+
export { subtle } from './subtle.js';
|
|
@@ -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
|
+
};
|