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
package/dist/sm2/index.d.ts
CHANGED
|
@@ -120,12 +120,81 @@ export declare function createSignaturePointPool(n: number): Promise<SignaturePo
|
|
|
120
120
|
* @returns 64 字节签名(r || s)的 hex。
|
|
121
121
|
*/
|
|
122
122
|
export declare function sign(privateKey: string | Uint8Array, message: string | Uint8Array, opts?: SignOptions): string;
|
|
123
|
+
/**
|
|
124
|
+
* SM2 签名(自定义用户标识)。
|
|
125
|
+
*
|
|
126
|
+
* 与 `sign` 的区别仅在于 Z 值使用 `userID` 计算。X.509 签发需要它:
|
|
127
|
+
* OpenSSL 的 X.509 路径默认使用空标识,GM/T 证书使用 `1234567812345678`。
|
|
128
|
+
*
|
|
129
|
+
* 不支持 `pointPool`(签发场景不需要;需要时用 `sign` 的默认标识路径)。
|
|
130
|
+
*
|
|
131
|
+
* @param privateKey 32 字节私钥(hex / Uint8Array / PEM)。
|
|
132
|
+
* @param message 待签名消息。
|
|
133
|
+
* @param userID 用户标识(可为空数组)。
|
|
134
|
+
* @returns 64 字节签名 `r ‖ s` 的 hex。
|
|
135
|
+
*/
|
|
136
|
+
export declare function signWithID(privateKey: string | Uint8Array, message: string | Uint8Array, userID: Uint8Array): string;
|
|
123
137
|
/**
|
|
124
138
|
* SM2 验签。
|
|
125
139
|
*
|
|
126
140
|
* @returns true 有效。
|
|
127
141
|
*/
|
|
128
142
|
export declare function verify(publicKey: string | Uint8Array, message: string | Uint8Array, signature: string | Uint8Array): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* SM2 验签(自定义用户标识)。
|
|
145
|
+
*
|
|
146
|
+
* 与 `verify` 的区别仅在于 Z 值使用 `userID` 计算。X.509 证书签名需要它:
|
|
147
|
+
* GM/T 证书使用默认标识 `1234567812345678`,而 OpenSSL 的 X.509 路径
|
|
148
|
+
* 默认使用空标识。
|
|
149
|
+
*
|
|
150
|
+
* @param publicKey 64/65 字节公钥。
|
|
151
|
+
* @param message 消息。
|
|
152
|
+
* @param signature 64 字节 `r ‖ s`。
|
|
153
|
+
* @param userID 用户标识(可为空数组)。
|
|
154
|
+
* @returns true 有效。
|
|
155
|
+
*/
|
|
156
|
+
export declare function verifyWithID(publicKey: string | Uint8Array, message: string | Uint8Array, signature: string | Uint8Array, userID: Uint8Array): boolean;
|
|
157
|
+
/** 多槽公钥缓存的配置。 */
|
|
158
|
+
export interface PreparedKeyCacheOptions {
|
|
159
|
+
/** 槽位上限,1..8,默认 8。 */
|
|
160
|
+
capacity?: number;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 配置多槽公钥缓存(默认 8 槽,满了按 LRU 淘汰)。
|
|
164
|
+
*
|
|
165
|
+
* 缩小容量不会立即清除高槽位,只是之后的查找与 `getPreparedPublicKeys` 不再考虑它们;
|
|
166
|
+
* 需要释放时调用 `clearPreparedPublicKeys()`。wasm 未加载时只记录,首次
|
|
167
|
+
* `preparePublicKey` 会应用。
|
|
168
|
+
*/
|
|
169
|
+
export declare function configurePreparedKeyCache(opts?: PreparedKeyCacheOptions): void;
|
|
170
|
+
/**
|
|
171
|
+
* 为一对**固定公钥**预计算 comb 表(默认 8 槽,满了 LRU 淘汰)。
|
|
172
|
+
*
|
|
173
|
+
* 之后 `encrypt` / `kemEncrypt` / `hybridEncrypt` 对该公钥会把 `k·P` 从 wNAF
|
|
174
|
+
* (256 次倍点)切到 comb(52 次倍点):本机端到端约 486 → 260 µs。建表一次性
|
|
175
|
+
* 约 280 µs,**约 1.2 次加密回本**。
|
|
176
|
+
*
|
|
177
|
+
* **只有显式 prepare 才会建表**:`encrypt` 命中就走 comb,不命中直接回退 wNAF,
|
|
178
|
+
* 绝不隐式建表 —— 否则外部可控的公钥能反复触发建表,形成放大面。
|
|
179
|
+
*
|
|
180
|
+
* 公钥是公开信息,缓存它不引入新的密钥材料驻留;8 槽共占 wasm 永久区固定 24 KiB。
|
|
181
|
+
*
|
|
182
|
+
* @param publicKey 64/65 字节公钥(hex / Uint8Array / PEM)。
|
|
183
|
+
* @throws {InvalidPublicKeyError} 公钥不在 SM2 曲线上。
|
|
184
|
+
*/
|
|
185
|
+
export declare function preparePublicKey(publicKey: string | Uint8Array): Promise<void>;
|
|
186
|
+
/** 清空所有公钥预计算槽,之后的 `encrypt` 全部回退到 wNAF。未加载 wasm 时也安全。 */
|
|
187
|
+
export declare function clearPreparedPublicKeys(): void;
|
|
188
|
+
/** @deprecated 用 `clearPreparedPublicKeys()`(保留单数别名)。 */
|
|
189
|
+
export declare function clearPreparedPublicKey(): void;
|
|
190
|
+
/**
|
|
191
|
+
* 当前槽位中的公钥(hex,按槽序)。
|
|
192
|
+
*
|
|
193
|
+
* wasm 未加载时返回空数组。
|
|
194
|
+
*/
|
|
195
|
+
export declare function getPreparedPublicKeys(): string[];
|
|
196
|
+
/** 最后一次 `preparePublicKey` 的公钥(hex);没有则为 `null`。 */
|
|
197
|
+
export declare function getPreparedPublicKey(): string | null;
|
|
129
198
|
/**
|
|
130
199
|
* SM2 加密。
|
|
131
200
|
*
|
package/dist/sm2/index.js
CHANGED
|
@@ -128,6 +128,14 @@ function derivePublicKeyRaw(d) {
|
|
|
128
128
|
* "派生公钥"这个操作的语义,以及 `bench` 里这一项的可测性。
|
|
129
129
|
*/
|
|
130
130
|
const PUB_CACHE = new Map();
|
|
131
|
+
/**
|
|
132
|
+
* 最后一次 `preparePublicKey` 的公钥(hex,x || y),没有则为 `null`。
|
|
133
|
+
*
|
|
134
|
+
* 只用于单数查询接口;多槽的真实状态在 wasm 侧(指纹 + LRU),正确性不依赖它。
|
|
135
|
+
*/
|
|
136
|
+
let PREPARED_PUB = null;
|
|
137
|
+
/** 多槽公钥缓存的槽位上限(1..8,默认 8)。wasm 未加载时先记在这里。 */
|
|
138
|
+
let preparedCapacity = 8;
|
|
131
139
|
let cacheEnabled = true;
|
|
132
140
|
let cacheCapacity = 32;
|
|
133
141
|
/**
|
|
@@ -345,6 +353,52 @@ export function sign(privateKey, message, opts = {}) {
|
|
|
345
353
|
exports.freeAll();
|
|
346
354
|
}
|
|
347
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* SM2 签名(自定义用户标识)。
|
|
358
|
+
*
|
|
359
|
+
* 与 `sign` 的区别仅在于 Z 值使用 `userID` 计算。X.509 签发需要它:
|
|
360
|
+
* OpenSSL 的 X.509 路径默认使用空标识,GM/T 证书使用 `1234567812345678`。
|
|
361
|
+
*
|
|
362
|
+
* 不支持 `pointPool`(签发场景不需要;需要时用 `sign` 的默认标识路径)。
|
|
363
|
+
*
|
|
364
|
+
* @param privateKey 32 字节私钥(hex / Uint8Array / PEM)。
|
|
365
|
+
* @param message 待签名消息。
|
|
366
|
+
* @param userID 用户标识(可为空数组)。
|
|
367
|
+
* @returns 64 字节签名 `r ‖ s` 的 hex。
|
|
368
|
+
*/
|
|
369
|
+
export function signWithID(privateKey, message, userID) {
|
|
370
|
+
const d = toPrivateKeyBytes(privateKey);
|
|
371
|
+
const m = toBytes(message, 'message');
|
|
372
|
+
const id = userID instanceof Uint8Array ? userID : new Uint8Array(0);
|
|
373
|
+
const pub = derivePublicKeyBytes(d);
|
|
374
|
+
const { exports, memory } = getModule();
|
|
375
|
+
const dp = exports.alloc(32);
|
|
376
|
+
const pxp = exports.alloc(32);
|
|
377
|
+
const pyp = exports.alloc(32);
|
|
378
|
+
const idp = exports.alloc(id.length === 0 ? 1 : id.length);
|
|
379
|
+
const mp = exports.alloc(m.length === 0 ? 1 : m.length);
|
|
380
|
+
const kp = exports.alloc(32);
|
|
381
|
+
const op = exports.alloc(64);
|
|
382
|
+
try {
|
|
383
|
+
putBytes(d, dp);
|
|
384
|
+
putBytes(pub.subarray(0, 32), pxp);
|
|
385
|
+
putBytes(pub.subarray(32, 64), pyp);
|
|
386
|
+
if (id.length > 0)
|
|
387
|
+
putBytes(id, idp);
|
|
388
|
+
putBytes(m, mp);
|
|
389
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
390
|
+
putBytes(randomK(), kp);
|
|
391
|
+
const rc = exports.signWithIDOut(dp, pxp, pyp, idp, id.length, mp, m.length, kp, op);
|
|
392
|
+
if (rc === 0)
|
|
393
|
+
return fromBytes(readBytes(op, 64), 'hex');
|
|
394
|
+
}
|
|
395
|
+
throw new Error('sm2.signWithID: failed to produce a valid signature after retries');
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
new Uint8Array(memory.buffer, kp, 32).fill(0);
|
|
399
|
+
exports.freeAll();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
348
402
|
/**
|
|
349
403
|
* SM2 验签。
|
|
350
404
|
*
|
|
@@ -375,6 +429,140 @@ export function verify(publicKey, message, signature) {
|
|
|
375
429
|
exports.freeAll();
|
|
376
430
|
}
|
|
377
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* SM2 验签(自定义用户标识)。
|
|
434
|
+
*
|
|
435
|
+
* 与 `verify` 的区别仅在于 Z 值使用 `userID` 计算。X.509 证书签名需要它:
|
|
436
|
+
* GM/T 证书使用默认标识 `1234567812345678`,而 OpenSSL 的 X.509 路径
|
|
437
|
+
* 默认使用空标识。
|
|
438
|
+
*
|
|
439
|
+
* @param publicKey 64/65 字节公钥。
|
|
440
|
+
* @param message 消息。
|
|
441
|
+
* @param signature 64 字节 `r ‖ s`。
|
|
442
|
+
* @param userID 用户标识(可为空数组)。
|
|
443
|
+
* @returns true 有效。
|
|
444
|
+
*/
|
|
445
|
+
export function verifyWithID(publicKey, message, signature, userID) {
|
|
446
|
+
const { x, y } = splitPub(publicKey);
|
|
447
|
+
if (!isOnCurve(x, y))
|
|
448
|
+
return false;
|
|
449
|
+
const m = toBytes(message, 'message');
|
|
450
|
+
const sig = toBytes(signature, 'signature');
|
|
451
|
+
if (sig.length !== 64)
|
|
452
|
+
throw new InvalidCiphertextError('sm2: signature must be 64 bytes');
|
|
453
|
+
const id = userID instanceof Uint8Array ? userID : new Uint8Array(0);
|
|
454
|
+
const { exports } = getModule();
|
|
455
|
+
const pxp = exports.alloc(32);
|
|
456
|
+
const pyp = exports.alloc(32);
|
|
457
|
+
const idp = exports.alloc(id.length === 0 ? 1 : id.length);
|
|
458
|
+
const mp = exports.alloc(m.length === 0 ? 1 : m.length);
|
|
459
|
+
const sigp = exports.alloc(64);
|
|
460
|
+
const zp = exports.alloc(32);
|
|
461
|
+
try {
|
|
462
|
+
putBytes(x, pxp);
|
|
463
|
+
putBytes(y, pyp);
|
|
464
|
+
if (id.length > 0)
|
|
465
|
+
putBytes(id, idp);
|
|
466
|
+
putBytes(m, mp);
|
|
467
|
+
putBytes(sig, sigp);
|
|
468
|
+
exports.computeZIdOut(pxp, pyp, idp, id.length, zp);
|
|
469
|
+
return exports.verifyWithZOut(pxp, pyp, zp, mp, m.length, sigp) === 1;
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
exports.freeAll();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* 配置多槽公钥缓存(默认 8 槽,满了按 LRU 淘汰)。
|
|
477
|
+
*
|
|
478
|
+
* 缩小容量不会立即清除高槽位,只是之后的查找与 `getPreparedPublicKeys` 不再考虑它们;
|
|
479
|
+
* 需要释放时调用 `clearPreparedPublicKeys()`。wasm 未加载时只记录,首次
|
|
480
|
+
* `preparePublicKey` 会应用。
|
|
481
|
+
*/
|
|
482
|
+
export function configurePreparedKeyCache(opts = {}) {
|
|
483
|
+
const capacity = opts.capacity ?? 8;
|
|
484
|
+
if (!Number.isInteger(capacity) || capacity < 1 || capacity > 8) {
|
|
485
|
+
throw new RangeError(`sm2.configurePreparedKeyCache: capacity 必须是 1..8 的整数,收到 ${String(capacity)}`);
|
|
486
|
+
}
|
|
487
|
+
preparedCapacity = capacity;
|
|
488
|
+
const mod = globalThis.__sm2Wasm;
|
|
489
|
+
if (mod)
|
|
490
|
+
mod.exports.setCombCapacityOut(capacity);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* 为一对**固定公钥**预计算 comb 表(默认 8 槽,满了 LRU 淘汰)。
|
|
494
|
+
*
|
|
495
|
+
* 之后 `encrypt` / `kemEncrypt` / `hybridEncrypt` 对该公钥会把 `k·P` 从 wNAF
|
|
496
|
+
* (256 次倍点)切到 comb(52 次倍点):本机端到端约 486 → 260 µs。建表一次性
|
|
497
|
+
* 约 280 µs,**约 1.2 次加密回本**。
|
|
498
|
+
*
|
|
499
|
+
* **只有显式 prepare 才会建表**:`encrypt` 命中就走 comb,不命中直接回退 wNAF,
|
|
500
|
+
* 绝不隐式建表 —— 否则外部可控的公钥能反复触发建表,形成放大面。
|
|
501
|
+
*
|
|
502
|
+
* 公钥是公开信息,缓存它不引入新的密钥材料驻留;8 槽共占 wasm 永久区固定 24 KiB。
|
|
503
|
+
*
|
|
504
|
+
* @param publicKey 64/65 字节公钥(hex / Uint8Array / PEM)。
|
|
505
|
+
* @throws {InvalidPublicKeyError} 公钥不在 SM2 曲线上。
|
|
506
|
+
*/
|
|
507
|
+
export async function preparePublicKey(publicKey) {
|
|
508
|
+
await ensureLoaded();
|
|
509
|
+
const { x, y } = splitPub(publicKey);
|
|
510
|
+
if (!isOnCurve(x, y)) {
|
|
511
|
+
throw new InvalidPublicKeyError('sm2.preparePublicKey: public key is not a point on the SM2 curve');
|
|
512
|
+
}
|
|
513
|
+
const hexPub = bytesToHex(x) + bytesToHex(y);
|
|
514
|
+
const { exports } = getModule();
|
|
515
|
+
const pxp = exports.alloc(32);
|
|
516
|
+
const pyp = exports.alloc(32);
|
|
517
|
+
try {
|
|
518
|
+
putBytes(x, pxp);
|
|
519
|
+
putBytes(y, pyp);
|
|
520
|
+
exports.setCombCapacityOut(preparedCapacity);
|
|
521
|
+
exports.prepareCombOut(pxp, pyp, preparedCapacity);
|
|
522
|
+
PREPARED_PUB = hexPub;
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
exports.freeAll();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/** 清空所有公钥预计算槽,之后的 `encrypt` 全部回退到 wNAF。未加载 wasm 时也安全。 */
|
|
529
|
+
export function clearPreparedPublicKeys() {
|
|
530
|
+
PREPARED_PUB = null;
|
|
531
|
+
const mod = globalThis.__sm2Wasm;
|
|
532
|
+
if (mod)
|
|
533
|
+
mod.exports.clearComb();
|
|
534
|
+
}
|
|
535
|
+
/** @deprecated 用 `clearPreparedPublicKeys()`(保留单数别名)。 */
|
|
536
|
+
export function clearPreparedPublicKey() {
|
|
537
|
+
clearPreparedPublicKeys();
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* 当前槽位中的公钥(hex,按槽序)。
|
|
541
|
+
*
|
|
542
|
+
* wasm 未加载时返回空数组。
|
|
543
|
+
*/
|
|
544
|
+
export function getPreparedPublicKeys() {
|
|
545
|
+
const mod = globalThis.__sm2Wasm;
|
|
546
|
+
if (!mod)
|
|
547
|
+
return [];
|
|
548
|
+
const out = mod.exports.alloc(64);
|
|
549
|
+
const keys = [];
|
|
550
|
+
try {
|
|
551
|
+
for (let s = 0; s < preparedCapacity; s++) {
|
|
552
|
+
if (mod.exports.combSlotKeyOut(s, out) === 1) {
|
|
553
|
+
keys.push(bytesToHex(new Uint8Array(mod.memory.buffer, out, 64).slice()));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
mod.exports.freeAll();
|
|
559
|
+
}
|
|
560
|
+
return keys;
|
|
561
|
+
}
|
|
562
|
+
/** 最后一次 `preparePublicKey` 的公钥(hex);没有则为 `null`。 */
|
|
563
|
+
export function getPreparedPublicKey() {
|
|
564
|
+
return PREPARED_PUB;
|
|
565
|
+
}
|
|
378
566
|
/**
|
|
379
567
|
* SM2 加密。
|
|
380
568
|
*
|
package/dist/sm2/sm2.wasm
CHANGED
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 算法名归一化与参数校验。
|
|
3
|
+
*
|
|
4
|
+
* 算法名大小写不敏感;对外只暴露 `sm-crypto-wasm/webcrypto` 的一个精选子集:
|
|
5
|
+
* SM3、HMAC(SM3)、SM2、SM4 的 ECB/CBC/CTR/CFB/OFB/GCM。
|
|
6
|
+
*/
|
|
7
|
+
/** 算法输入:字符串或带 `name` 的对象。 */
|
|
8
|
+
export type AlgorithmInput = string | {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly [key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
/** 归一化后的算法。 */
|
|
13
|
+
export interface NormalizedAlgorithm {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly [key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 归一化算法名并校验支持性。
|
|
19
|
+
*
|
|
20
|
+
* @throws DOMException `NotSupportedError` / `TypeError`
|
|
21
|
+
*/
|
|
22
|
+
export declare function normalizeAlgorithm(input: AlgorithmInput): NormalizedAlgorithm;
|
|
23
|
+
/** 是否为 SM4 系列算法。 */
|
|
24
|
+
export declare function isSm4(name: string): boolean;
|
|
25
|
+
/** 是否为 AES 风格的对称算法(SM4)。 */
|
|
26
|
+
export declare function isSymmetric(name: string): boolean;
|
|
27
|
+
/** BufferSource → 独立 Uint8Array(拷贝)。 */
|
|
28
|
+
export declare function bufferSourceBytes(value: ArrayBuffer | ArrayBufferView | undefined, label: string, required?: boolean): Uint8Array;
|
|
29
|
+
/** 必需的 IV。 */
|
|
30
|
+
export declare function requireIv(algorithm: NormalizedAlgorithm): Uint8Array;
|
|
31
|
+
/** 可选的 additionalData。 */
|
|
32
|
+
export declare function optionalAad(algorithm: NormalizedAlgorithm): Uint8Array;
|
|
33
|
+
/** GCM 的 tagLength:只支持 128。 */
|
|
34
|
+
export declare function requireGcmTagLength(algorithm: NormalizedAlgorithm): 128;
|
|
35
|
+
/** SM4-CTR 的 length:只支持 128。 */
|
|
36
|
+
export declare function requireCtrLength(algorithm: NormalizedAlgorithm): 128;
|
|
37
|
+
/** SM4 密钥长度:固定 128。 */
|
|
38
|
+
export declare function requireAesLength(algorithm: NormalizedAlgorithm): 128;
|
|
39
|
+
/** HMAC 的 hash 参数归一化为 SM3。 */
|
|
40
|
+
export declare function hashName(hash: unknown): 'SM3';
|
|
41
|
+
/** SM2 的可选 userID。 */
|
|
42
|
+
export declare function optionalUserId(algorithm: NormalizedAlgorithm): Uint8Array | undefined;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 算法名归一化与参数校验。
|
|
3
|
+
*
|
|
4
|
+
* 算法名大小写不敏感;对外只暴露 `sm-crypto-wasm/webcrypto` 的一个精选子集:
|
|
5
|
+
* SM3、HMAC(SM3)、SM2、SM4 的 ECB/CBC/CTR/CFB/OFB/GCM。
|
|
6
|
+
*/
|
|
7
|
+
import { dataError, notSupported } from './errors.js';
|
|
8
|
+
const SUPPORTED = new Set([
|
|
9
|
+
'SM3',
|
|
10
|
+
'HMAC',
|
|
11
|
+
'SM2',
|
|
12
|
+
'SM4-ECB',
|
|
13
|
+
'SM4-CBC',
|
|
14
|
+
'SM4-CTR',
|
|
15
|
+
'SM4-CFB',
|
|
16
|
+
'SM4-OFB',
|
|
17
|
+
'SM4-GCM',
|
|
18
|
+
]);
|
|
19
|
+
const SM4_NAMES = new Set(['SM4-ECB', 'SM4-CBC', 'SM4-CTR', 'SM4-CFB', 'SM4-OFB', 'SM4-GCM']);
|
|
20
|
+
/**
|
|
21
|
+
* 归一化算法名并校验支持性。
|
|
22
|
+
*
|
|
23
|
+
* @throws DOMException `NotSupportedError` / `TypeError`
|
|
24
|
+
*/
|
|
25
|
+
export function normalizeAlgorithm(input) {
|
|
26
|
+
if (typeof input === 'string') {
|
|
27
|
+
const name = input.toUpperCase();
|
|
28
|
+
if (!SUPPORTED.has(name)) {
|
|
29
|
+
throw notSupported(`unsupported algorithm "${input}"`);
|
|
30
|
+
}
|
|
31
|
+
return { name };
|
|
32
|
+
}
|
|
33
|
+
if (input === null || typeof input !== 'object' || typeof input.name !== 'string') {
|
|
34
|
+
throw new TypeError('algorithm must be a string or an object with a name');
|
|
35
|
+
}
|
|
36
|
+
const name = input.name.toUpperCase();
|
|
37
|
+
if (!SUPPORTED.has(name)) {
|
|
38
|
+
throw notSupported(`unsupported algorithm "${input.name}"`);
|
|
39
|
+
}
|
|
40
|
+
return { ...input, name };
|
|
41
|
+
}
|
|
42
|
+
/** 是否为 SM4 系列算法。 */
|
|
43
|
+
export function isSm4(name) {
|
|
44
|
+
return SM4_NAMES.has(name);
|
|
45
|
+
}
|
|
46
|
+
/** 是否为 AES 风格的对称算法(SM4)。 */
|
|
47
|
+
export function isSymmetric(name) {
|
|
48
|
+
return SM4_NAMES.has(name);
|
|
49
|
+
}
|
|
50
|
+
/** BufferSource → 独立 Uint8Array(拷贝)。 */
|
|
51
|
+
export function bufferSourceBytes(value, label, required = true) {
|
|
52
|
+
if (value === undefined) {
|
|
53
|
+
if (required)
|
|
54
|
+
throw dataError(`${label} is required`);
|
|
55
|
+
return new Uint8Array(0);
|
|
56
|
+
}
|
|
57
|
+
if (value instanceof ArrayBuffer)
|
|
58
|
+
return new Uint8Array(value.slice(0));
|
|
59
|
+
if (ArrayBuffer.isView(value)) {
|
|
60
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength).slice();
|
|
61
|
+
}
|
|
62
|
+
throw dataError(`${label} must be an ArrayBuffer or ArrayBufferView`);
|
|
63
|
+
}
|
|
64
|
+
/** 必需的 IV。 */
|
|
65
|
+
export function requireIv(algorithm) {
|
|
66
|
+
const iv = bufferSourceBytes(algorithm.iv, 'iv', true);
|
|
67
|
+
if (iv.length === 0)
|
|
68
|
+
throw dataError('iv must not be empty');
|
|
69
|
+
return iv;
|
|
70
|
+
}
|
|
71
|
+
/** 可选的 additionalData。 */
|
|
72
|
+
export function optionalAad(algorithm) {
|
|
73
|
+
return bufferSourceBytes(algorithm.additionalData, 'additionalData', false);
|
|
74
|
+
}
|
|
75
|
+
/** GCM 的 tagLength:只支持 128。 */
|
|
76
|
+
export function requireGcmTagLength(algorithm) {
|
|
77
|
+
const tagLength = algorithm.tagLength ?? 128;
|
|
78
|
+
if (!Number.isInteger(tagLength) || tagLength !== 128) {
|
|
79
|
+
throw notSupported(`SM4-GCM tagLength ${String(tagLength)} is not supported (only 128)`);
|
|
80
|
+
}
|
|
81
|
+
return 128;
|
|
82
|
+
}
|
|
83
|
+
/** SM4-CTR 的 length:只支持 128。 */
|
|
84
|
+
export function requireCtrLength(algorithm) {
|
|
85
|
+
const length = algorithm.length ?? 128;
|
|
86
|
+
if (!Number.isInteger(length) || length !== 128) {
|
|
87
|
+
throw notSupported(`SM4-CTR length ${String(length)} is not supported (only 128)`);
|
|
88
|
+
}
|
|
89
|
+
return 128;
|
|
90
|
+
}
|
|
91
|
+
/** SM4 密钥长度:固定 128。 */
|
|
92
|
+
export function requireAesLength(algorithm) {
|
|
93
|
+
const length = algorithm.length ?? 128;
|
|
94
|
+
if (!Number.isInteger(length) || length !== 128) {
|
|
95
|
+
throw notSupported(`SM4 key length ${String(length)} is not supported (only 128)`);
|
|
96
|
+
}
|
|
97
|
+
return 128;
|
|
98
|
+
}
|
|
99
|
+
/** HMAC 的 hash 参数归一化为 SM3。 */
|
|
100
|
+
export function hashName(hash) {
|
|
101
|
+
if (hash === undefined)
|
|
102
|
+
throw dataError('HMAC requires a hash parameter');
|
|
103
|
+
const name = typeof hash === 'string'
|
|
104
|
+
? hash.toUpperCase()
|
|
105
|
+
: typeof hash === 'object' && hash !== null && 'name' in hash
|
|
106
|
+
? String(hash.name).toUpperCase()
|
|
107
|
+
: '';
|
|
108
|
+
if (name !== 'SM3')
|
|
109
|
+
throw notSupported(`HMAC hash "${name}" is not supported (only SM3)`);
|
|
110
|
+
return 'SM3';
|
|
111
|
+
}
|
|
112
|
+
/** SM2 的可选 userID。 */
|
|
113
|
+
export function optionalUserId(algorithm) {
|
|
114
|
+
const raw = algorithm.userID;
|
|
115
|
+
if (raw === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (!(raw instanceof Uint8Array))
|
|
118
|
+
throw dataError('SM2 userID must be a Uint8Array');
|
|
119
|
+
return new Uint8Array(raw);
|
|
120
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 风格的 `CryptoKey`。
|
|
3
|
+
*
|
|
4
|
+
* 密钥材料放在模块级 `WeakMap` 中,对象本身只暴露只读元数据;这样:
|
|
5
|
+
* - 无法通过序列化/枚举把私钥带出去;
|
|
6
|
+
* - 将来接入 HSM/RemoteSigner 时可把材料换成 `provider`,公共 API 不变。
|
|
7
|
+
*/
|
|
8
|
+
/** 密钥类型。 */
|
|
9
|
+
export type KeyType = 'secret' | 'public' | 'private';
|
|
10
|
+
/** WebCrypto 密钥用途。 */
|
|
11
|
+
export type KeyUsage = 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'deriveKey' | 'deriveBits' | 'wrapKey' | 'unwrapKey';
|
|
12
|
+
/** 密钥算法元数据(只读)。 */
|
|
13
|
+
export interface KeyAlgorithm {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly [key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 将来的外部密钥提供者(HSM / RemoteSigner)。
|
|
19
|
+
*
|
|
20
|
+
* v1 不实现;`KeyMaterial` 已按联合类型预留,后续无需改公共 API。
|
|
21
|
+
*/
|
|
22
|
+
export interface KeyProvider {
|
|
23
|
+
sign?(algorithm: KeyAlgorithm, data: Uint8Array): Uint8Array | Promise<Uint8Array>;
|
|
24
|
+
verify?(algorithm: KeyAlgorithm, signature: Uint8Array, data: Uint8Array): boolean | Promise<boolean>;
|
|
25
|
+
encrypt?(algorithm: KeyAlgorithm, data: Uint8Array): Uint8Array | Promise<Uint8Array>;
|
|
26
|
+
decrypt?(algorithm: KeyAlgorithm, data: Uint8Array): Uint8Array | Promise<Uint8Array>;
|
|
27
|
+
exportKey?(format: string): Uint8Array | Promise<Uint8Array>;
|
|
28
|
+
}
|
|
29
|
+
/** 密钥材料。v1 只创建 `bytes`;`provider` 为将来预留。 */
|
|
30
|
+
export type KeyMaterial = {
|
|
31
|
+
readonly kind: 'bytes';
|
|
32
|
+
readonly bytes: Uint8Array;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: 'provider';
|
|
35
|
+
readonly provider: KeyProvider;
|
|
36
|
+
};
|
|
37
|
+
/** WebCrypto 风格的密钥对象。 */
|
|
38
|
+
export declare class CryptoKey {
|
|
39
|
+
readonly type: KeyType;
|
|
40
|
+
readonly extractable: boolean;
|
|
41
|
+
readonly algorithm: KeyAlgorithm;
|
|
42
|
+
readonly usages: readonly KeyUsage[];
|
|
43
|
+
constructor(type: KeyType, algorithm: KeyAlgorithm, extractable: boolean, usages: readonly KeyUsage[]);
|
|
44
|
+
}
|
|
45
|
+
/** 创建带字节材料的密钥(内部使用)。 */
|
|
46
|
+
export declare function createBytesKey(type: KeyType, algorithm: KeyAlgorithm, extractable: boolean, usages: readonly KeyUsage[], bytes: Uint8Array): CryptoKey;
|
|
47
|
+
/** 创建带外部 provider 的密钥(v1 预留,内部使用)。 */
|
|
48
|
+
export declare function createProviderKey(type: KeyType, algorithm: KeyAlgorithm, extractable: boolean, usages: readonly KeyUsage[], provider: KeyProvider): CryptoKey;
|
|
49
|
+
/** 读取密钥材料(内部使用)。 */
|
|
50
|
+
export declare function getKeyMaterial(key: CryptoKey): KeyMaterial;
|
|
51
|
+
/** 读取字节材料(内部使用);provider 材料暂不支持同步读取。 */
|
|
52
|
+
export declare function getKeyBytes(key: CryptoKey): Uint8Array;
|
|
53
|
+
/** 校验密钥类型。 */
|
|
54
|
+
export declare function assertKeyType(key: unknown, expected: KeyType, operation: string): asserts key is CryptoKey;
|
|
55
|
+
/** 校验密钥用途。 */
|
|
56
|
+
export declare function assertUsage(key: CryptoKey, usage: KeyUsage, operation: string): void;
|
|
57
|
+
/** 校验密钥算法名。 */
|
|
58
|
+
export declare function assertKeyAlgorithm(key: CryptoKey, name: string, operation: string): void;
|
|
59
|
+
/** 校验 usage 列表合法(未知值抛 SyntaxError)。 */
|
|
60
|
+
export declare function normalizeUsages(usages: readonly string[] | undefined): KeyUsage[];
|
|
61
|
+
/** 检查 usage 是否属于允许集合;不允许时抛 SyntaxError。 */
|
|
62
|
+
export declare function assertUsagesAllowed(usages: readonly KeyUsage[], allowed: readonly KeyUsage[], algorithm: string): void;
|
|
63
|
+
/** 取交集(保持 requesting 顺序)。 */
|
|
64
|
+
export declare function filterUsages(usages: readonly KeyUsage[], allowed: readonly KeyUsage[]): KeyUsage[];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 风格的 `CryptoKey`。
|
|
3
|
+
*
|
|
4
|
+
* 密钥材料放在模块级 `WeakMap` 中,对象本身只暴露只读元数据;这样:
|
|
5
|
+
* - 无法通过序列化/枚举把私钥带出去;
|
|
6
|
+
* - 将来接入 HSM/RemoteSigner 时可把材料换成 `provider`,公共 API 不变。
|
|
7
|
+
*/
|
|
8
|
+
import { invalidAccess, notSupported } from './errors.js';
|
|
9
|
+
const ALL_USAGES = [
|
|
10
|
+
'encrypt',
|
|
11
|
+
'decrypt',
|
|
12
|
+
'sign',
|
|
13
|
+
'verify',
|
|
14
|
+
'deriveKey',
|
|
15
|
+
'deriveBits',
|
|
16
|
+
'wrapKey',
|
|
17
|
+
'unwrapKey',
|
|
18
|
+
];
|
|
19
|
+
const MATERIAL = new WeakMap();
|
|
20
|
+
/** WebCrypto 风格的密钥对象。 */
|
|
21
|
+
export class CryptoKey {
|
|
22
|
+
type;
|
|
23
|
+
extractable;
|
|
24
|
+
algorithm;
|
|
25
|
+
usages;
|
|
26
|
+
constructor(type, algorithm, extractable, usages) {
|
|
27
|
+
this.type = type;
|
|
28
|
+
this.extractable = extractable;
|
|
29
|
+
this.algorithm = Object.freeze({ ...algorithm });
|
|
30
|
+
this.usages = Object.freeze([...usages]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** 创建带字节材料的密钥(内部使用)。 */
|
|
34
|
+
export function createBytesKey(type, algorithm, extractable, usages, bytes) {
|
|
35
|
+
const key = new CryptoKey(type, algorithm, extractable, usages);
|
|
36
|
+
MATERIAL.set(key, { kind: 'bytes', bytes: new Uint8Array(bytes) });
|
|
37
|
+
return key;
|
|
38
|
+
}
|
|
39
|
+
/** 创建带外部 provider 的密钥(v1 预留,内部使用)。 */
|
|
40
|
+
export function createProviderKey(type, algorithm, extractable, usages, provider) {
|
|
41
|
+
const key = new CryptoKey(type, algorithm, extractable, usages);
|
|
42
|
+
MATERIAL.set(key, { kind: 'provider', provider });
|
|
43
|
+
return key;
|
|
44
|
+
}
|
|
45
|
+
/** 读取密钥材料(内部使用)。 */
|
|
46
|
+
export function getKeyMaterial(key) {
|
|
47
|
+
const material = MATERIAL.get(key);
|
|
48
|
+
if (material === undefined) {
|
|
49
|
+
throw invalidAccess('key has no material (was it created by this module?)');
|
|
50
|
+
}
|
|
51
|
+
return material;
|
|
52
|
+
}
|
|
53
|
+
/** 读取字节材料(内部使用);provider 材料暂不支持同步读取。 */
|
|
54
|
+
export function getKeyBytes(key) {
|
|
55
|
+
const material = getKeyMaterial(key);
|
|
56
|
+
if (material.kind !== 'bytes') {
|
|
57
|
+
throw notSupported('external key providers are not supported yet');
|
|
58
|
+
}
|
|
59
|
+
return material.bytes;
|
|
60
|
+
}
|
|
61
|
+
/** 校验密钥类型。 */
|
|
62
|
+
export function assertKeyType(key, expected, operation) {
|
|
63
|
+
if (!(key instanceof CryptoKey)) {
|
|
64
|
+
throw new TypeError(`${operation}: key must be a CryptoKey`);
|
|
65
|
+
}
|
|
66
|
+
if (key.type !== expected) {
|
|
67
|
+
throw invalidAccess(`${operation} requires a ${expected} key, got ${key.type}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** 校验密钥用途。 */
|
|
71
|
+
export function assertUsage(key, usage, operation) {
|
|
72
|
+
if (!key.usages.includes(usage)) {
|
|
73
|
+
throw invalidAccess(`${operation}: key does not allow "${usage}"`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** 校验密钥算法名。 */
|
|
77
|
+
export function assertKeyAlgorithm(key, name, operation) {
|
|
78
|
+
if (key.algorithm.name !== name) {
|
|
79
|
+
throw invalidAccess(`${operation}: key algorithm is ${key.algorithm.name}, expected ${name}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** 校验 usage 列表合法(未知值抛 SyntaxError)。 */
|
|
83
|
+
export function normalizeUsages(usages) {
|
|
84
|
+
if (usages === undefined)
|
|
85
|
+
return [];
|
|
86
|
+
if (!Array.isArray(usages))
|
|
87
|
+
throw new TypeError('keyUsages must be an array');
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const usage of usages) {
|
|
90
|
+
if (!ALL_USAGES.includes(usage)) {
|
|
91
|
+
throw new DOMException(`unknown key usage "${usage}"`, 'SyntaxError');
|
|
92
|
+
}
|
|
93
|
+
if (!out.includes(usage))
|
|
94
|
+
out.push(usage);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
/** 检查 usage 是否属于允许集合;不允许时抛 SyntaxError。 */
|
|
99
|
+
export function assertUsagesAllowed(usages, allowed, algorithm) {
|
|
100
|
+
for (const usage of usages) {
|
|
101
|
+
if (!allowed.includes(usage)) {
|
|
102
|
+
throw new DOMException(`usage "${usage}" is not valid for ${algorithm}`, 'SyntaxError');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** 取交集(保持 requesting 顺序)。 */
|
|
107
|
+
export function filterUsages(usages, allowed) {
|
|
108
|
+
return usages.filter((usage) => allowed.includes(usage));
|
|
109
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto 适配层的错误映射。
|
|
3
|
+
*
|
|
4
|
+
* 对外的所有失败都映射为 `DOMException`(WebCrypto 的约定):
|
|
5
|
+
* - `NotSupportedError`:未知算法/格式/不支持的参数;
|
|
6
|
+
* - `InvalidAccessError`:密钥用途或 `extractable` 不允许;
|
|
7
|
+
* - `DataError`:密钥/数据格式非法;
|
|
8
|
+
* - `OperationError`:加解密/验签失败。
|
|
9
|
+
*/
|
|
10
|
+
/** 构造 `DOMException` 的便捷函数。 */
|
|
11
|
+
export declare function notSupported(message: string): DOMException;
|
|
12
|
+
/** 构造 `InvalidAccessError`。 */
|
|
13
|
+
export declare function invalidAccess(message: string): DOMException;
|
|
14
|
+
/** 构造 `DataError`。 */
|
|
15
|
+
export declare function dataError(message: string): DOMException;
|
|
16
|
+
/** 构造 `OperationError`。 */
|
|
17
|
+
export declare function operationError(message: string): DOMException;
|
|
18
|
+
/** 把库内错误映射为 `DOMException`;已经是 `DOMException` 时原样返回。 */
|
|
19
|
+
export declare function toDOMException(err: unknown, fallback?: 'OperationError' | 'DataError'): DOMException;
|