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/pem/asn1.js
CHANGED
|
@@ -1,297 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 旧 ASN.1 入口(兼容层)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - OBJECT IDENTIFIER (0x06)、SEQUENCE (0x30, constructed)
|
|
4
|
+
* 实现已迁移到 `src/asn1/`(DER/BER 通用层)。本文件只保留历史导出名,
|
|
5
|
+
* 使 `src/pem/*` 与既有测试无需改动:
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - `parseDer` → `src/asn1/decode.ts`
|
|
8
|
+
* - `encodeDer` → `src/asn1/encode.ts` 的 `encode`
|
|
9
|
+
* - `asn1Int` / `asn1Octets` / `asn1Oid` / `asn1Null` / `asn1Seq` → 对应构造器
|
|
10
|
+
* - `encodeOid` / `decodeOid` / `asn1IntValue` → OID 与 INTEGER 解码
|
|
10
11
|
*
|
|
11
|
-
* spec/plan 对应:
|
|
12
|
+
* spec/plan 对应:X.509/CMS plan 阶段 0「迁移」。
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
name = 'Asn1Error';
|
|
16
|
-
}
|
|
17
|
-
const TAG_INTEGER = 0x02;
|
|
18
|
-
const _TAG_BIT_STRING = 0x03;
|
|
19
|
-
const TAG_OCTET_STRING = 0x04;
|
|
20
|
-
const TAG_NULL = 0x05;
|
|
21
|
-
const TAG_OID = 0x06;
|
|
22
|
-
const TAG_SEQUENCE = 0x30; // constructed
|
|
23
|
-
/**
|
|
24
|
-
* 解析一段 DER 字节。
|
|
25
|
-
*
|
|
26
|
-
* @param buf DER 字节。
|
|
27
|
-
* @returns 解析后的根节点(通常为 SEQUENCE)。
|
|
28
|
-
* @throws Asn1Error 输入截断、tag/length 不合法、不支持的构造。
|
|
29
|
-
*/
|
|
30
|
-
export function parseDer(buf) {
|
|
31
|
-
const reader = new Reader(buf);
|
|
32
|
-
const node = reader.readNode();
|
|
33
|
-
// 允许根节点之后留 0 字节尾巴;多余字节视为错误。
|
|
34
|
-
if (reader.remaining() !== 0) {
|
|
35
|
-
throw new Asn1Error(`parseDer: ${reader.remaining()} trailing bytes after root node`);
|
|
36
|
-
}
|
|
37
|
-
return node;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* 将 Asn1Node 编码回 DER 字节。**重新计算 length 与 raw**。
|
|
41
|
-
*
|
|
42
|
-
* @param node 待编码节点。
|
|
43
|
-
* @returns DER 字节。
|
|
44
|
-
*/
|
|
45
|
-
export function encodeDer(node) {
|
|
46
|
-
// 值字节:primitive 直接取 value;constructed 递归子节点。
|
|
47
|
-
const parts = [];
|
|
48
|
-
if (node.children) {
|
|
49
|
-
for (const child of node.children)
|
|
50
|
-
parts.push(encodeDer(child));
|
|
51
|
-
}
|
|
52
|
-
const value = node.children ? concat(parts) : node.value;
|
|
53
|
-
const lengthBytes = encodeLength(value.length);
|
|
54
|
-
const tagByte = node.tag & 0xff;
|
|
55
|
-
const out = new Uint8Array(1 + lengthBytes.length + value.length);
|
|
56
|
-
out[0] = tagByte;
|
|
57
|
-
for (let i = 0; i < lengthBytes.length; i++)
|
|
58
|
-
out[1 + i] = lengthBytes[i] ?? 0;
|
|
59
|
-
for (let i = 0; i < value.length; i++) {
|
|
60
|
-
out[1 + lengthBytes.length + i] = value[i] ?? 0;
|
|
61
|
-
}
|
|
62
|
-
return out;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* 便捷构造函数:构造一个 INTEGER 节点。
|
|
66
|
-
*
|
|
67
|
-
* @param value 大端整数(可为负:负数用二进制补码表示)。
|
|
68
|
-
*/
|
|
69
|
-
export function asn1Int(value) {
|
|
70
|
-
const bytes = bigintToBytes(BigInt(value));
|
|
71
|
-
const raw = concat([new Uint8Array([TAG_INTEGER]), encodeLength(bytes.length), bytes]);
|
|
72
|
-
return {
|
|
73
|
-
cls: 0,
|
|
74
|
-
tag: TAG_INTEGER,
|
|
75
|
-
length: bytes.length,
|
|
76
|
-
value: bytes,
|
|
77
|
-
raw,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
/** 便捷构造函数:构造一个 OCTET STRING 节点。 */
|
|
81
|
-
export function asn1Octets(value) {
|
|
82
|
-
const raw = concat([new Uint8Array([TAG_OCTET_STRING]), encodeLength(value.length), value]);
|
|
83
|
-
return {
|
|
84
|
-
cls: 0,
|
|
85
|
-
tag: TAG_OCTET_STRING,
|
|
86
|
-
length: value.length,
|
|
87
|
-
value,
|
|
88
|
-
raw,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
/** 便捷构造函数:构造一个 OID 节点(输入如 `"1.2.156.10197.1.100"`)。 */
|
|
92
|
-
export function asn1Oid(oid) {
|
|
93
|
-
const bytes = encodeOid(oid);
|
|
94
|
-
const raw = concat([new Uint8Array([TAG_OID]), encodeLength(bytes.length), bytes]);
|
|
95
|
-
return { cls: 0, tag: TAG_OID, length: bytes.length, value: bytes, raw };
|
|
96
|
-
}
|
|
97
|
-
/** 便捷构造函数:构造一个 NULL 节点。 */
|
|
98
|
-
export function asn1Null() {
|
|
99
|
-
const raw = new Uint8Array([TAG_NULL, 0x00]);
|
|
100
|
-
return { cls: 0, tag: TAG_NULL, length: 0, value: new Uint8Array(0), raw };
|
|
101
|
-
}
|
|
102
|
-
/** 便捷构造函数:构造一个 SEQUENCE 节点。 */
|
|
103
|
-
export function asn1Seq(children) {
|
|
104
|
-
const parts = children.map((c) => c.raw);
|
|
105
|
-
const value = concat(parts);
|
|
106
|
-
const raw = concat([new Uint8Array([TAG_SEQUENCE]), encodeLength(value.length), value]);
|
|
107
|
-
// tag 字段统一存 UNIVERSAL 编号 16(0x10),与 Reader 输出一致;
|
|
108
|
-
// raw 首字节保留完整 0x30 以便序列化。
|
|
109
|
-
return {
|
|
110
|
-
cls: 0,
|
|
111
|
-
tag: 0x10,
|
|
112
|
-
length: value.length,
|
|
113
|
-
value,
|
|
114
|
-
children,
|
|
115
|
-
raw,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
// ----------------- 内部:长度编码 -----------------
|
|
119
|
-
function encodeLength(len) {
|
|
120
|
-
if (len < 0 || !Number.isInteger(len)) {
|
|
121
|
-
throw new Asn1Error(`encodeLength: bad length ${len}`);
|
|
122
|
-
}
|
|
123
|
-
if (len < 0x80)
|
|
124
|
-
return new Uint8Array([len]);
|
|
125
|
-
// 长格式:首个字节 0x80|n(n = 后续字节数),后续 big-endian 长度。
|
|
126
|
-
const bytes = [];
|
|
127
|
-
let v = len;
|
|
128
|
-
while (v > 0) {
|
|
129
|
-
bytes.unshift(v & 0xff);
|
|
130
|
-
v >>= 8;
|
|
131
|
-
}
|
|
132
|
-
return new Uint8Array([0x80 | bytes.length, ...bytes]);
|
|
133
|
-
}
|
|
134
|
-
// ----------------- 内部:OID 编码 -----------------
|
|
135
|
-
/**
|
|
136
|
-
* OID 编码:第一个子标识符 = a*40 + b,后续每个子标识符按 7-bit 编码。
|
|
137
|
-
*
|
|
138
|
-
* 例:`1.2.156.10197.1.100` → [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x01, 0x64]
|
|
139
|
-
* 1*40+2 = 42 = 0x2a;156、10197、1、100 都用 base-128 编码。
|
|
140
|
-
*
|
|
141
|
-
* @param oid 点分 OID 字符串。
|
|
142
|
-
* @returns OID 内容字节(不含 tag 和 length)。
|
|
143
|
-
*/
|
|
144
|
-
export function encodeOid(oid) {
|
|
145
|
-
const parts = oid.split('.').map((s) => parseInt(s, 10));
|
|
146
|
-
if (parts.length < 2 || parts.some((n) => !Number.isFinite(n) || n < 0)) {
|
|
147
|
-
throw new Asn1Error(`encodeOid: bad oid "${oid}"`);
|
|
148
|
-
}
|
|
149
|
-
const first = (parts[0] ?? 0) * 40 + (parts[1] ?? 0);
|
|
150
|
-
const out = [first];
|
|
151
|
-
for (let i = 2; i < parts.length; i++) {
|
|
152
|
-
out.push(...encodeBase128(parts[i] ?? 0));
|
|
153
|
-
}
|
|
154
|
-
return new Uint8Array(out);
|
|
155
|
-
}
|
|
156
|
-
/** 反向解码。 */
|
|
157
|
-
export function decodeOid(bytes) {
|
|
158
|
-
if (bytes.length === 0)
|
|
159
|
-
throw new Asn1Error('decodeOid: empty');
|
|
160
|
-
const first = bytes[0] ?? 0;
|
|
161
|
-
const a = Math.floor(first / 40);
|
|
162
|
-
const b = first % 40;
|
|
163
|
-
const parts = [a, b];
|
|
164
|
-
let value = 0;
|
|
165
|
-
for (let i = 1; i < bytes.length; i++) {
|
|
166
|
-
const octet = bytes[i] ?? 0;
|
|
167
|
-
value = (value << 7) | (octet & 0x7f);
|
|
168
|
-
if ((octet & 0x80) === 0) {
|
|
169
|
-
parts.push(value);
|
|
170
|
-
value = 0;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (value !== 0)
|
|
174
|
-
throw new Asn1Error('decodeOid: truncated');
|
|
175
|
-
return parts.join('.');
|
|
176
|
-
}
|
|
177
|
-
function encodeBase128(n) {
|
|
178
|
-
if (n < 0 || !Number.isInteger(n)) {
|
|
179
|
-
throw new Asn1Error(`encodeBase128: bad ${n}`);
|
|
180
|
-
}
|
|
181
|
-
if (n < 0x80)
|
|
182
|
-
return [n];
|
|
183
|
-
const out = [];
|
|
184
|
-
let v = n;
|
|
185
|
-
do {
|
|
186
|
-
out.unshift((v & 0x7f) | 0x80);
|
|
187
|
-
v >>= 7;
|
|
188
|
-
} while (v > 0);
|
|
189
|
-
out[out.length - 1] = (out[out.length - 1] ?? 0) & 0x7f;
|
|
190
|
-
return out;
|
|
191
|
-
}
|
|
192
|
-
// ----------------- 内部:大端整数 -----------------
|
|
193
|
-
function bigintToBytes(n) {
|
|
194
|
-
if (n === 0n)
|
|
195
|
-
return new Uint8Array([0]);
|
|
196
|
-
const negative = n < 0n;
|
|
197
|
-
let v = negative ? -n : n;
|
|
198
|
-
const tmp = [];
|
|
199
|
-
while (v > 0n) {
|
|
200
|
-
tmp.unshift(Number(v & 0xffn));
|
|
201
|
-
v >>= 8n;
|
|
202
|
-
}
|
|
203
|
-
// 负数采用补码:高位加 0xff 让最高位为 1。
|
|
204
|
-
if (negative) {
|
|
205
|
-
// 简化处理:本仓库不需要负 INTEGER。
|
|
206
|
-
throw new Asn1Error('bigintToBytes: negative integers not supported');
|
|
207
|
-
}
|
|
208
|
-
// 正数:补 0x00 前缀以免被当成负数(最高位为 1 时必须)。
|
|
209
|
-
if ((tmp[0] ?? 0) & 0x80)
|
|
210
|
-
tmp.unshift(0);
|
|
211
|
-
return new Uint8Array(tmp);
|
|
212
|
-
}
|
|
213
|
-
/** 读一个 INTEGER 返回 bigint。 */
|
|
214
|
-
export function asn1IntValue(node) {
|
|
215
|
-
if (node.tag !== TAG_INTEGER) {
|
|
216
|
-
throw new Asn1Error(`asn1IntValue: tag ${node.tag}, expected INTEGER`);
|
|
217
|
-
}
|
|
218
|
-
let v = 0n;
|
|
219
|
-
for (let i = 0; i < node.value.length; i++) {
|
|
220
|
-
v = (v << 8n) | BigInt(node.value[i] ?? 0);
|
|
221
|
-
}
|
|
222
|
-
return v;
|
|
223
|
-
}
|
|
224
|
-
// ----------------- 内部:Reader -----------------
|
|
225
|
-
class Reader {
|
|
226
|
-
buf;
|
|
227
|
-
pos = 0;
|
|
228
|
-
constructor(buf) {
|
|
229
|
-
this.buf = buf;
|
|
230
|
-
}
|
|
231
|
-
remaining() {
|
|
232
|
-
return this.buf.length - this.pos;
|
|
233
|
-
}
|
|
234
|
-
take(n) {
|
|
235
|
-
if (this.pos + n > this.buf.length) {
|
|
236
|
-
throw new Asn1Error(`Reader: truncated, need ${n} bytes at pos ${this.pos}`);
|
|
237
|
-
}
|
|
238
|
-
const out = this.buf.subarray(this.pos, this.pos + n);
|
|
239
|
-
this.pos += n;
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
readTag() {
|
|
243
|
-
if (this.remaining() < 1)
|
|
244
|
-
throw new Asn1Error('Reader: empty tag');
|
|
245
|
-
return this.buf[this.pos++] ?? 0;
|
|
246
|
-
}
|
|
247
|
-
readLength() {
|
|
248
|
-
if (this.remaining() < 1)
|
|
249
|
-
throw new Asn1Error('Reader: empty length');
|
|
250
|
-
const first = this.buf[this.pos++] ?? 0;
|
|
251
|
-
if ((first & 0x80) === 0)
|
|
252
|
-
return first;
|
|
253
|
-
const n = first & 0x7f;
|
|
254
|
-
if (n === 0)
|
|
255
|
-
throw new Asn1Error('Reader: indefinite length not supported');
|
|
256
|
-
if (this.remaining() < n)
|
|
257
|
-
throw new Asn1Error('Reader: truncated long length');
|
|
258
|
-
let len = 0;
|
|
259
|
-
for (let i = 0; i < n; i++) {
|
|
260
|
-
len = (len << 8) | (this.buf[this.pos++] ?? 0);
|
|
261
|
-
}
|
|
262
|
-
return len;
|
|
263
|
-
}
|
|
264
|
-
readNode() {
|
|
265
|
-
const tagStart = this.pos;
|
|
266
|
-
const tag = this.readTag();
|
|
267
|
-
const length = this.readLength();
|
|
268
|
-
const cls = (tag & 0xc0) >> 6;
|
|
269
|
-
const tagNum = tag & 0x1f;
|
|
270
|
-
const valueStart = this.pos;
|
|
271
|
-
const value = this.take(length);
|
|
272
|
-
const raw = this.buf.subarray(tagStart, valueStart + length);
|
|
273
|
-
const constructed = (tag & 0x20) !== 0;
|
|
274
|
-
if (constructed) {
|
|
275
|
-
// SEQUENCE 等:递归子节点。
|
|
276
|
-
const inner = new Reader(value);
|
|
277
|
-
const children = [];
|
|
278
|
-
while (inner.remaining() > 0) {
|
|
279
|
-
children.push(inner.readNode());
|
|
280
|
-
}
|
|
281
|
-
return { cls, tag: tagNum, length, value, children, raw };
|
|
282
|
-
}
|
|
283
|
-
return { cls, tag: tagNum, length, value, raw };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
function concat(parts) {
|
|
287
|
-
let total = 0;
|
|
288
|
-
for (const p of parts)
|
|
289
|
-
total += p.length;
|
|
290
|
-
const out = new Uint8Array(total);
|
|
291
|
-
let off = 0;
|
|
292
|
-
for (const p of parts) {
|
|
293
|
-
out.set(p, off);
|
|
294
|
-
off += p.length;
|
|
295
|
-
}
|
|
296
|
-
return out;
|
|
297
|
-
}
|
|
14
|
+
export { decodeOid, encode as encodeDer, encodeOid, integer as asn1Int, nullValue as asn1Null, octetString as asn1Octets, oid as asn1Oid, parseDer, readInteger as asn1IntValue, seq as asn1Seq, } from '../asn1/index.js';
|
|
15
|
+
export { Asn1Error } from '../asn1/types.js';
|
package/dist/pem/pkcs8.js
CHANGED
|
@@ -155,7 +155,15 @@ function bitStringNode(value) {
|
|
|
155
155
|
for (let i = 0; i < value.length; i++) {
|
|
156
156
|
raw[1 + lengthBytes.length + i] = value[i] ?? 0;
|
|
157
157
|
}
|
|
158
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
cls: 0,
|
|
160
|
+
tag: 0x03,
|
|
161
|
+
constructed: false,
|
|
162
|
+
indefinite: false,
|
|
163
|
+
length: value.length,
|
|
164
|
+
value,
|
|
165
|
+
raw,
|
|
166
|
+
};
|
|
159
167
|
}
|
|
160
168
|
function encodeLen(n) {
|
|
161
169
|
if (n < 0x80)
|
package/dist/pem/sm2.d.ts
CHANGED
|
@@ -37,12 +37,24 @@ export declare const OID_SM2_CURVE = "1.2.156.10197.1.301";
|
|
|
37
37
|
* @throws InvalidKeyError PEM/DER 结构非法、或曲线不是 SM2。
|
|
38
38
|
*/
|
|
39
39
|
export declare function parseSm2PrivateKeyPem(pem: string): Uint8Array;
|
|
40
|
+
/**
|
|
41
|
+
* 解析 SM2 私钥 DER(PKCS#8 或 SEC1),返回 32 字节私钥 d。
|
|
42
|
+
*
|
|
43
|
+
* WebCrypto 的 `importKey('pkcs8', ...)` 使用这个入口。
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseSm2PrivateKeyDer(der: Uint8Array): Uint8Array;
|
|
40
46
|
/**
|
|
41
47
|
* 解析 SM2 公钥 PEM,返回 65 字节未压缩点 `04 || X || Y`。
|
|
42
48
|
*
|
|
43
49
|
* @throws InvalidKeyError PEM/DER 结构非法、曲线不是 SM2、或点不是未压缩形式。
|
|
44
50
|
*/
|
|
45
51
|
export declare function parseSm2PublicKeyPem(pem: string): Uint8Array;
|
|
52
|
+
/**
|
|
53
|
+
* 解析 SM2 公钥 DER(SPKI),返回 65 字节未压缩点。
|
|
54
|
+
*
|
|
55
|
+
* WebCrypto 的 `importKey('spki', ...)` 使用这个入口。
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseSm2PublicKeyDer(der: Uint8Array): Uint8Array;
|
|
46
58
|
/**
|
|
47
59
|
* 编码 SM2 私钥为 PKCS#8 PEM(`BEGIN PRIVATE KEY`)。
|
|
48
60
|
*
|
|
@@ -50,9 +62,13 @@ export declare function parseSm2PublicKeyPem(pem: string): Uint8Array;
|
|
|
50
62
|
* @param pub 可选公钥(64 或 65 字节);给出则内嵌到 ECPrivateKey 的 `[1]`。
|
|
51
63
|
*/
|
|
52
64
|
export declare function encodeSm2PrivateKeyPem(d: string | Uint8Array, pub?: string | Uint8Array): string;
|
|
65
|
+
/** 编码 SM2 私钥为 PKCS#8 DER。 */
|
|
66
|
+
export declare function encodeSm2PrivateKeyDer(d: string | Uint8Array, pub?: string | Uint8Array): Uint8Array;
|
|
53
67
|
/**
|
|
54
68
|
* 编码 SM2 公钥为 SPKI PEM(`BEGIN PUBLIC KEY`)。
|
|
55
69
|
*
|
|
56
70
|
* @param pub 64(X || Y)或 65(04 || X || Y)字节公钥。
|
|
57
71
|
*/
|
|
58
72
|
export declare function encodeSm2PublicKeyPem(pub: string | Uint8Array): string;
|
|
73
|
+
/** 编码 SM2 公钥为 SPKI DER。 */
|
|
74
|
+
export declare function encodeSm2PublicKeyDer(pub: string | Uint8Array): Uint8Array;
|
package/dist/pem/sm2.js
CHANGED
|
@@ -47,7 +47,14 @@ const CLS_CONTEXT = 2;
|
|
|
47
47
|
* @throws InvalidKeyError PEM/DER 结构非法、或曲线不是 SM2。
|
|
48
48
|
*/
|
|
49
49
|
export function parseSm2PrivateKeyPem(pem) {
|
|
50
|
-
|
|
50
|
+
return parseSm2PrivateKeyDer(pemToBytes(pem, 'sm2.privateKey'));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 解析 SM2 私钥 DER(PKCS#8 或 SEC1),返回 32 字节私钥 d。
|
|
54
|
+
*
|
|
55
|
+
* WebCrypto 的 `importKey('pkcs8', ...)` 使用这个入口。
|
|
56
|
+
*/
|
|
57
|
+
export function parseSm2PrivateKeyDer(der) {
|
|
51
58
|
const top = parseOrThrow(der, 'parseSm2PrivateKeyPem');
|
|
52
59
|
const kids = top.children;
|
|
53
60
|
if (top.tag !== TAG_SEQUENCE || !kids) {
|
|
@@ -92,7 +99,14 @@ export function parseSm2PrivateKeyPem(pem) {
|
|
|
92
99
|
* @throws InvalidKeyError PEM/DER 结构非法、曲线不是 SM2、或点不是未压缩形式。
|
|
93
100
|
*/
|
|
94
101
|
export function parseSm2PublicKeyPem(pem) {
|
|
95
|
-
|
|
102
|
+
return parseSm2PublicKeyDer(pemToBytes(pem, 'sm2.publicKey'));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 解析 SM2 公钥 DER(SPKI),返回 65 字节未压缩点。
|
|
106
|
+
*
|
|
107
|
+
* WebCrypto 的 `importKey('spki', ...)` 使用这个入口。
|
|
108
|
+
*/
|
|
109
|
+
export function parseSm2PublicKeyDer(der) {
|
|
96
110
|
const top = parseOrThrow(der, 'parseSm2PublicKeyPem');
|
|
97
111
|
const kids = top.children;
|
|
98
112
|
if (top.tag !== TAG_SEQUENCE || !kids || kids.length < 2) {
|
|
@@ -118,6 +132,10 @@ export function parseSm2PublicKeyPem(pem) {
|
|
|
118
132
|
* @param pub 可选公钥(64 或 65 字节);给出则内嵌到 ECPrivateKey 的 `[1]`。
|
|
119
133
|
*/
|
|
120
134
|
export function encodeSm2PrivateKeyPem(d, pub) {
|
|
135
|
+
return bytesToPem(encodeSm2PrivateKeyDer(d, pub), 'PRIVATE KEY');
|
|
136
|
+
}
|
|
137
|
+
/** 编码 SM2 私钥为 PKCS#8 DER。 */
|
|
138
|
+
export function encodeSm2PrivateKeyDer(d, pub) {
|
|
121
139
|
const scalar = normalizeScalar(d);
|
|
122
140
|
const ecChildren = [
|
|
123
141
|
asn1Int(1),
|
|
@@ -136,7 +154,7 @@ export function encodeSm2PrivateKeyPem(d, pub) {
|
|
|
136
154
|
asn1Seq([asn1Oid(OID_ID_EC_PUBLIC_KEY), asn1Oid(OID_SM2_CURVE)]),
|
|
137
155
|
asn1Octets(ec.raw),
|
|
138
156
|
]);
|
|
139
|
-
return
|
|
157
|
+
return pkcs8.raw;
|
|
140
158
|
}
|
|
141
159
|
/**
|
|
142
160
|
* 编码 SM2 公钥为 SPKI PEM(`BEGIN PUBLIC KEY`)。
|
|
@@ -144,6 +162,10 @@ export function encodeSm2PrivateKeyPem(d, pub) {
|
|
|
144
162
|
* @param pub 64(X || Y)或 65(04 || X || Y)字节公钥。
|
|
145
163
|
*/
|
|
146
164
|
export function encodeSm2PublicKeyPem(pub) {
|
|
165
|
+
return bytesToPem(encodeSm2PublicKeyDer(pub), 'PUBLIC KEY');
|
|
166
|
+
}
|
|
167
|
+
/** 编码 SM2 公钥为 SPKI DER。 */
|
|
168
|
+
export function encodeSm2PublicKeyDer(pub) {
|
|
147
169
|
const point = normalizeUncompressed(pub);
|
|
148
170
|
const bits = new Uint8Array(point.length + 1);
|
|
149
171
|
bits.set(point, 1);
|
|
@@ -151,7 +173,7 @@ export function encodeSm2PublicKeyPem(pub) {
|
|
|
151
173
|
asn1Seq([asn1Oid(OID_ID_EC_PUBLIC_KEY), asn1Oid(OID_SM2_CURVE)]),
|
|
152
174
|
bitStringNode(bits),
|
|
153
175
|
]);
|
|
154
|
-
return
|
|
176
|
+
return spki.raw;
|
|
155
177
|
}
|
|
156
178
|
// ---------- 内部 ----------
|
|
157
179
|
function parseOrThrow(der, where) {
|
|
@@ -229,7 +251,15 @@ function normalizeUncompressed(pub) {
|
|
|
229
251
|
/** 构造 BIT STRING 节点(value 需自带首字节「未使用位数」)。 */
|
|
230
252
|
function bitStringNode(value) {
|
|
231
253
|
const raw = concat([new Uint8Array([TAG_BIT_STRING]), encodeLen(value.length), value]);
|
|
232
|
-
return {
|
|
254
|
+
return {
|
|
255
|
+
cls: 0,
|
|
256
|
+
tag: TAG_BIT_STRING,
|
|
257
|
+
constructed: false,
|
|
258
|
+
indefinite: false,
|
|
259
|
+
length: value.length,
|
|
260
|
+
value,
|
|
261
|
+
raw,
|
|
262
|
+
};
|
|
233
263
|
}
|
|
234
264
|
/** 构造 [n] EXPLICIT 包装节点。 */
|
|
235
265
|
function explicit(tagNum, inner) {
|
|
@@ -237,6 +267,8 @@ function explicit(tagNum, inner) {
|
|
|
237
267
|
return {
|
|
238
268
|
cls: CLS_CONTEXT,
|
|
239
269
|
tag: tagNum,
|
|
270
|
+
constructed: true,
|
|
271
|
+
indefinite: false,
|
|
240
272
|
length: inner.raw.length,
|
|
241
273
|
value: inner.raw,
|
|
242
274
|
children: [inner],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* base64 纯 JS 实现(宿主既没有 `Buffer`、也没有 `btoa` / `atob` 时的兜底)。
|
|
3
|
+
*
|
|
4
|
+
* 存在理由:微信小程序等运行时没有 `btoa` / `atob`,而 SM2 / SM9 的 PEM 密钥
|
|
5
|
+
* 生成与解析需要 base64。base64 是确定性编码、不属于密码学原语,因此在库内兜底
|
|
6
|
+
* 不需要像 RNG 那样 fail-closed。
|
|
7
|
+
*
|
|
8
|
+
* 只在 `runtime/convert.ts` 的最后一级回退里使用;Node 有 `Buffer`、浏览器有
|
|
9
|
+
* `btoa` / `atob` 时都不会走到这里。零 `node:*` 引用,可安全进入浏览器/小程序产物。
|
|
10
|
+
*/
|
|
11
|
+
/** 标准 base64 编码(带 `=` padding)。 */
|
|
12
|
+
export declare function encodeBase64(bytes: Uint8Array): string;
|
|
13
|
+
/**
|
|
14
|
+
* 标准 base64 解码。
|
|
15
|
+
*
|
|
16
|
+
* 与 `atob` 的宽松度对齐:允许空白字符与缺失 padding;非法字符、非法长度抛
|
|
17
|
+
* `TypeError`(`convert.base64ToBytes` 的契约是失败抛 TypeError)。
|
|
18
|
+
*
|
|
19
|
+
* @param b64 base64 字符串。
|
|
20
|
+
* @param label 错误信息里的字段名。
|
|
21
|
+
*/
|
|
22
|
+
export declare function decodeBase64(b64: string, label?: string): Uint8Array;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* base64 纯 JS 实现(宿主既没有 `Buffer`、也没有 `btoa` / `atob` 时的兜底)。
|
|
3
|
+
*
|
|
4
|
+
* 存在理由:微信小程序等运行时没有 `btoa` / `atob`,而 SM2 / SM9 的 PEM 密钥
|
|
5
|
+
* 生成与解析需要 base64。base64 是确定性编码、不属于密码学原语,因此在库内兜底
|
|
6
|
+
* 不需要像 RNG 那样 fail-closed。
|
|
7
|
+
*
|
|
8
|
+
* 只在 `runtime/convert.ts` 的最后一级回退里使用;Node 有 `Buffer`、浏览器有
|
|
9
|
+
* `btoa` / `atob` 时都不会走到这里。零 `node:*` 引用,可安全进入浏览器/小程序产物。
|
|
10
|
+
*/
|
|
11
|
+
const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
12
|
+
/** 标准 base64 编码(带 `=` padding)。 */
|
|
13
|
+
export function encodeBase64(bytes) {
|
|
14
|
+
let out = '';
|
|
15
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
16
|
+
const a = bytes[i] ?? 0;
|
|
17
|
+
const hasB = i + 1 < bytes.length;
|
|
18
|
+
const hasC = i + 2 < bytes.length;
|
|
19
|
+
const b = hasB ? (bytes[i + 1] ?? 0) : 0;
|
|
20
|
+
const c = hasC ? (bytes[i + 2] ?? 0) : 0;
|
|
21
|
+
const n = (a << 16) | (b << 8) | c;
|
|
22
|
+
out += B64.charAt((n >> 18) & 63);
|
|
23
|
+
out += B64.charAt((n >> 12) & 63);
|
|
24
|
+
out += hasB ? B64.charAt((n >> 6) & 63) : '=';
|
|
25
|
+
out += hasC ? B64.charAt(n & 63) : '=';
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 标准 base64 解码。
|
|
31
|
+
*
|
|
32
|
+
* 与 `atob` 的宽松度对齐:允许空白字符与缺失 padding;非法字符、非法长度抛
|
|
33
|
+
* `TypeError`(`convert.base64ToBytes` 的契约是失败抛 TypeError)。
|
|
34
|
+
*
|
|
35
|
+
* @param b64 base64 字符串。
|
|
36
|
+
* @param label 错误信息里的字段名。
|
|
37
|
+
*/
|
|
38
|
+
export function decodeBase64(b64, label = 'base64') {
|
|
39
|
+
const stripped = b64.replace(/[\t\n\f\r ]/g, '');
|
|
40
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(stripped)) {
|
|
41
|
+
throw new TypeError(`${label}: invalid base64`);
|
|
42
|
+
}
|
|
43
|
+
const pad = stripped.endsWith('==') ? 2 : stripped.endsWith('=') ? 1 : 0;
|
|
44
|
+
if (pad > 0 && stripped.length % 4 !== 0) {
|
|
45
|
+
throw new TypeError(`${label}: invalid base64 padding`);
|
|
46
|
+
}
|
|
47
|
+
const body = pad > 0 ? stripped.slice(0, -pad) : stripped;
|
|
48
|
+
if (body.length % 4 === 1) {
|
|
49
|
+
throw new TypeError(`${label}: invalid base64 length`);
|
|
50
|
+
}
|
|
51
|
+
const out = new Uint8Array(Math.floor((body.length * 3) / 4));
|
|
52
|
+
let buffer = 0;
|
|
53
|
+
let bits = 0;
|
|
54
|
+
let pos = 0;
|
|
55
|
+
for (let i = 0; i < body.length; i++) {
|
|
56
|
+
const v = B64.indexOf(body.charAt(i));
|
|
57
|
+
if (v < 0)
|
|
58
|
+
throw new TypeError(`${label}: invalid base64 character at index ${i}`);
|
|
59
|
+
buffer = (buffer << 6) | v;
|
|
60
|
+
bits += 6;
|
|
61
|
+
if (bits >= 8) {
|
|
62
|
+
bits -= 8;
|
|
63
|
+
out[pos++] = (buffer >> bits) & 0xff;
|
|
64
|
+
buffer &= (1 << bits) - 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out.subarray(0, pos);
|
|
68
|
+
}
|
|
@@ -40,10 +40,19 @@ export declare function fromBytes(bytes: Uint8Array, format?: OutputFormat): str
|
|
|
40
40
|
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
41
41
|
/** hex 字符串 → bytes。支持 `0x` 前缀。奇数位长度或非法字符抛错。 */
|
|
42
42
|
export declare function hexToBytes(hex: string, label?: string): Uint8Array;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* bytes → base64。优先级:Node `Buffer` → 宿主 `btoa` → 纯 JS 兜底。
|
|
45
|
+
*
|
|
46
|
+
* 微信小程序既没有 `Buffer` 也没有 `btoa`,会走最后一级;不需要宿主再安装
|
|
47
|
+
* base64 polyfill。
|
|
48
|
+
*/
|
|
44
49
|
export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
45
|
-
/**
|
|
46
|
-
|
|
50
|
+
/**
|
|
51
|
+
* base64 字符串 → bytes。失败抛 TypeError。
|
|
52
|
+
*
|
|
53
|
+
* 优先级:Node `Buffer` → 宿主 `atob` → 纯 JS 兜底(微信小程序等)。
|
|
54
|
+
*/
|
|
55
|
+
export declare function base64ToBytes(b64: string, label?: string): Uint8Array;
|
|
47
56
|
/** PEM → bytes。剥去 BEGIN/END armor,base64 解码。 */
|
|
48
57
|
export declare function pemToBytes(pem: string, label?: string): Uint8Array;
|
|
49
58
|
/** bytes → PEM(带 BEGIN/END 头)。`kind` 例:`SM2 PRIVATE KEY`。 */
|
package/dist/runtime/convert.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* spec/plan 对应:plan Task 2;spec §6 API 默认 hex、§9 安全姿态。
|
|
18
18
|
*/
|
|
19
|
+
import { decodeBase64, encodeBase64 } from './base64.core.js';
|
|
19
20
|
/**
|
|
20
21
|
* 把任意 `Input` 归一化为 `Uint8Array`。
|
|
21
22
|
*
|
|
@@ -106,20 +107,32 @@ export function hexToBytes(hex, label = 'hex') {
|
|
|
106
107
|
// tryParseHex 已覆盖全部合法情形,走到这里说明上面漏判,兜底抛错。
|
|
107
108
|
throw new TypeError(`hexToBytes(${label}): invalid hex`);
|
|
108
109
|
}
|
|
109
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* bytes → base64。优先级:Node `Buffer` → 宿主 `btoa` → 纯 JS 兜底。
|
|
112
|
+
*
|
|
113
|
+
* 微信小程序既没有 `Buffer` 也没有 `btoa`,会走最后一级;不需要宿主再安装
|
|
114
|
+
* base64 polyfill。
|
|
115
|
+
*/
|
|
110
116
|
export function bytesToBase64(bytes) {
|
|
111
117
|
if (HAS_BUFFER) {
|
|
112
118
|
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64');
|
|
113
119
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
if (HAS_BTOA) {
|
|
121
|
+
let bin = '';
|
|
122
|
+
for (let i = 0; i < bytes.length; i++)
|
|
123
|
+
bin += String.fromCharCode(bytes[i] ?? 0);
|
|
124
|
+
return btoa(bin);
|
|
125
|
+
}
|
|
126
|
+
return encodeBase64(bytes);
|
|
118
127
|
}
|
|
119
128
|
/** base64 合法字符集(含结尾 padding)。 */
|
|
120
129
|
const B64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
121
|
-
/**
|
|
122
|
-
|
|
130
|
+
/**
|
|
131
|
+
* base64 字符串 → bytes。失败抛 TypeError。
|
|
132
|
+
*
|
|
133
|
+
* 优先级:Node `Buffer` → 宿主 `atob` → 纯 JS 兜底(微信小程序等)。
|
|
134
|
+
*/
|
|
135
|
+
export function base64ToBytes(b64, label = 'base64') {
|
|
123
136
|
// Node 快路径。`Buffer.from(..., 'base64')` 对非法字符是静默丢弃的,
|
|
124
137
|
// 先用正则挡掉,非法的落到 `atob` 去抛错,保持与浏览器一致的行为。
|
|
125
138
|
if (HAS_BUFFER && B64_RE.test(b64)) {
|
|
@@ -128,11 +141,14 @@ export function base64ToBytes(b64, _label = 'base64') {
|
|
|
128
141
|
out.set(buf);
|
|
129
142
|
return out;
|
|
130
143
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
if (HAS_ATOB) {
|
|
145
|
+
const bin = atob(b64);
|
|
146
|
+
const out = new Uint8Array(bin.length);
|
|
147
|
+
for (let i = 0; i < bin.length; i++)
|
|
148
|
+
out[i] = bin.charCodeAt(i);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
return decodeBase64(b64, label);
|
|
136
152
|
}
|
|
137
153
|
/** PEM → bytes。剥去 BEGIN/END armor,base64 解码。 */
|
|
138
154
|
export function pemToBytes(pem, label = 'pem') {
|
|
@@ -169,6 +185,9 @@ export function bytesToPem(bytes, kind) {
|
|
|
169
185
|
* (实测 4 KB 输入:hex 编码 52.6 µs → 1.9 µs)。
|
|
170
186
|
*/
|
|
171
187
|
const HAS_BUFFER = typeof Buffer !== 'undefined' && typeof Buffer.from === 'function';
|
|
188
|
+
/** 宿主是否提供 `btoa` / `atob`(现代浏览器有;微信小程序没有)。 */
|
|
189
|
+
const HAS_BTOA = typeof btoa === 'function';
|
|
190
|
+
const HAS_ATOB = typeof atob === 'function';
|
|
172
191
|
/** 16 进制数字查表(浏览器路径)。 */
|
|
173
192
|
const HEX_CHARS = '0123456789abcdef';
|
|
174
193
|
/**
|