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.
Files changed (76) hide show
  1. package/README.md +76 -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/runtime/base64.core.d.ts +22 -0
  34. package/dist/runtime/base64.core.js +68 -0
  35. package/dist/runtime/convert.d.ts +12 -3
  36. package/dist/runtime/convert.js +31 -12
  37. package/dist/sm2/index.d.ts +69 -0
  38. package/dist/sm2/index.js +188 -0
  39. package/dist/sm2/sm2.wasm +0 -0
  40. package/dist/webcrypto/algorithms.d.ts +42 -0
  41. package/dist/webcrypto/algorithms.js +120 -0
  42. package/dist/webcrypto/cryptoKey.d.ts +64 -0
  43. package/dist/webcrypto/cryptoKey.js +109 -0
  44. package/dist/webcrypto/errors.d.ts +19 -0
  45. package/dist/webcrypto/errors.js +45 -0
  46. package/dist/webcrypto/index.d.ts +14 -0
  47. package/dist/webcrypto/index.js +11 -0
  48. package/dist/webcrypto/subtle.d.ts +68 -0
  49. package/dist/webcrypto/subtle.js +358 -0
  50. package/dist/x509/encode.d.ts +59 -0
  51. package/dist/x509/encode.js +175 -0
  52. package/dist/x509/errors.d.ts +14 -0
  53. package/dist/x509/errors.js +14 -0
  54. package/dist/x509/extensions.d.ts +27 -0
  55. package/dist/x509/extensions.js +215 -0
  56. package/dist/x509/index.d.ts +14 -0
  57. package/dist/x509/index.js +11 -0
  58. package/dist/x509/ip.d.ts +12 -0
  59. package/dist/x509/ip.js +101 -0
  60. package/dist/x509/issue.d.ts +44 -0
  61. package/dist/x509/issue.js +414 -0
  62. package/dist/x509/oids.d.ts +40 -0
  63. package/dist/x509/oids.js +52 -0
  64. package/dist/x509/parse.d.ts +24 -0
  65. package/dist/x509/parse.js +436 -0
  66. package/dist/x509/sha1.d.ts +9 -0
  67. package/dist/x509/sha1.js +72 -0
  68. package/dist/x509/sha256.d.ts +9 -0
  69. package/dist/x509/sha256.js +84 -0
  70. package/dist/x509/sig.d.ts +20 -0
  71. package/dist/x509/sig.js +77 -0
  72. package/dist/x509/types.d.ts +235 -0
  73. package/dist/x509/types.js +7 -0
  74. package/dist/x509/verify.d.ts +44 -0
  75. package/dist/x509/verify.js +66 -0
  76. package/package.json +16 -3
@@ -0,0 +1,275 @@
1
+ /**
2
+ * ASN.1 DER 编码器与构造器。
3
+ *
4
+ * 规则:
5
+ * - `encode()` 按 `cls + constructed + tag` 重建首字节(修复旧实现丢掉
6
+ * constructed 位的问题),对 constructed 节点递归编码子节点;
7
+ * - SET OF 按编码字节排序(DER 要求);
8
+ * - BOOLEAN TRUE 编码为 0xFF;
9
+ * - INTEGER 使用最小补码编码;
10
+ * - 输出始终是 definite-length(BER indefinite 只在解析侧接受)。
11
+ */
12
+ import { encodeOid } from './oid.js';
13
+ import { encodeGeneralizedTime, encodeTime, encodeUtcTime } from './time.js';
14
+ import { Asn1Class, Asn1Error, UniversalTag } from './types.js';
15
+ // ----------------- 基础编码 -----------------
16
+ /** 编码单个节点为 DER 字节。 */
17
+ export function encode(node) {
18
+ const constructed = node.children !== undefined ? true : node.constructed;
19
+ const value = constructed
20
+ ? node.children
21
+ ? concat(node.children.map((child) => encode(child)))
22
+ : node.value
23
+ : node.value;
24
+ const header = concat([encodeTag(node.cls, constructed, node.tag), encodeLength(value.length)]);
25
+ return concat([header, value]);
26
+ }
27
+ /**
28
+ * `encode` 的旧名(兼容 `src/pem/asn1.ts` 的历史导出)。
29
+ *
30
+ * @deprecated 使用 `encode`。
31
+ */
32
+ export const encodeDer = encode;
33
+ /** 编码 tag 首字节(含高 tag number 的 base128 展开)。 */
34
+ export function encodeTag(cls, constructed, tag) {
35
+ if (!Number.isInteger(tag) || tag < 0) {
36
+ throw new Asn1Error(`encodeTag: invalid tag ${tag}`);
37
+ }
38
+ const base = (cls << 6) | (constructed ? 0x20 : 0);
39
+ if (tag < 0x1f)
40
+ return new Uint8Array([base | tag]);
41
+ const out = [base | 0x1f];
42
+ let v = tag;
43
+ const groups = [];
44
+ do {
45
+ groups.unshift(v % 128);
46
+ v = Math.floor(v / 128);
47
+ } while (v > 0);
48
+ for (let i = 0; i < groups.length - 1; i++)
49
+ groups[i] = groups[i] | 0x80;
50
+ out.push(...groups);
51
+ return new Uint8Array(out);
52
+ }
53
+ /** 编码 definite-length。 */
54
+ export function encodeLength(length) {
55
+ if (!Number.isInteger(length) || length < 0) {
56
+ throw new Asn1Error(`encodeLength: bad length ${length}`);
57
+ }
58
+ if (length < 0x80)
59
+ return new Uint8Array([length]);
60
+ const bytes = [];
61
+ let v = length;
62
+ while (v > 0) {
63
+ bytes.unshift(v % 256);
64
+ v = Math.floor(v / 256);
65
+ }
66
+ return new Uint8Array([0x80 | bytes.length, ...bytes]);
67
+ }
68
+ // ----------------- 构造器 -----------------
69
+ /** 构造 SEQUENCE。 */
70
+ export function seq(children) {
71
+ return makeConstructed(Asn1Class.Universal, UniversalTag.Sequence, children);
72
+ }
73
+ /** 构造 SET(保持给定顺序;ASN.1 SET 的排序语义由调用方负责)。 */
74
+ export function set(children) {
75
+ return makeConstructed(Asn1Class.Universal, UniversalTag.Set, children);
76
+ }
77
+ /** 构造 SET OF(按 DER 编码字节排序)。 */
78
+ export function setOf(children) {
79
+ const sorted = children
80
+ .map((node) => ({ node, bytes: encode(node) }))
81
+ .sort((a, b) => compareBytes(a.bytes, b.bytes))
82
+ .map((entry) => entry.node);
83
+ return makeConstructed(Asn1Class.Universal, UniversalTag.Set, sorted);
84
+ }
85
+ /** 构造 `[tag] EXPLICIT` 包装。 */
86
+ export function explicit(tag, inner) {
87
+ const value = encode(inner);
88
+ return {
89
+ cls: Asn1Class.ContextSpecific,
90
+ tag,
91
+ constructed: true,
92
+ indefinite: false,
93
+ length: value.length,
94
+ value,
95
+ children: [inner],
96
+ raw: concat([
97
+ encodeTag(Asn1Class.ContextSpecific, true, tag),
98
+ encodeLength(value.length),
99
+ value,
100
+ ]),
101
+ };
102
+ }
103
+ /**
104
+ * 构造 `[tag] IMPLICIT` 包装:只替换外层标签,内容保持不变。
105
+ */
106
+ export function implicit(tag, inner, cls = Asn1Class.ContextSpecific) {
107
+ const constructed = inner.children !== undefined ? true : inner.constructed;
108
+ const value = inner.children ? concat(inner.children.map((child) => encode(child))) : inner.value;
109
+ return {
110
+ cls,
111
+ tag,
112
+ constructed,
113
+ indefinite: false,
114
+ length: value.length,
115
+ value,
116
+ children: inner.children,
117
+ raw: concat([encodeTag(cls, constructed, tag), encodeLength(value.length), value]),
118
+ };
119
+ }
120
+ /** 构造 BIT STRING。`unusedBits` 为末尾未使用位数(0..7)。 */
121
+ export function bitString(bytes, unusedBits = 0) {
122
+ if (!Number.isInteger(unusedBits) || unusedBits < 0 || unusedBits > 7) {
123
+ throw new RangeError(`bitString: unusedBits must be 0..7, got ${unusedBits}`);
124
+ }
125
+ const value = new Uint8Array(bytes.length + 1);
126
+ value[0] = unusedBits;
127
+ value.set(bytes, 1);
128
+ return makePrimitive(UniversalTag.BitString, value);
129
+ }
130
+ /** 构造 OCTET STRING。 */
131
+ export function octetString(bytes) {
132
+ return makePrimitive(UniversalTag.OctetString, bytes);
133
+ }
134
+ /** 构造 BOOLEAN。 */
135
+ export function boolean(value) {
136
+ return makePrimitive(UniversalTag.Boolean, new Uint8Array([value ? 0xff : 0x00]));
137
+ }
138
+ /** 构造 NULL。 */
139
+ export function nullValue() {
140
+ return makePrimitive(UniversalTag.Null, new Uint8Array(0));
141
+ }
142
+ /** 构造 INTEGER(最小补码编码)。 */
143
+ export function integer(value) {
144
+ const v = typeof value === 'number' ? BigInt(value) : value;
145
+ if (typeof v !== 'bigint')
146
+ throw new Asn1Error('integer: value must be bigint or number');
147
+ return makePrimitive(UniversalTag.Integer, encodeIntegerValue(v));
148
+ }
149
+ /** 构造 OBJECT IDENTIFIER。 */
150
+ export function oid(value) {
151
+ return makePrimitive(UniversalTag.ObjectIdentifier, encodeOid(value));
152
+ }
153
+ /** 构造 UTF8String。 */
154
+ export function utf8String(value) {
155
+ return makePrimitive(UniversalTag.Utf8String, new TextEncoder().encode(value));
156
+ }
157
+ /** 构造 PrintableString(仅允许 A-Z a-z 0-9 与 `'()+,-./:=?` 和空格)。 */
158
+ export function printableString(value) {
159
+ if (!/^[A-Za-z0-9 '()+,\-./:=?]*$/.test(value)) {
160
+ throw new Asn1Error(`printableString: unsupported character in "${value}"`);
161
+ }
162
+ return makePrimitive(UniversalTag.PrintableString, asciiBytes(value));
163
+ }
164
+ /** 构造 IA5String(仅 ASCII)。 */
165
+ export function ia5String(value) {
166
+ for (let i = 0; i < value.length; i++) {
167
+ if (value.charCodeAt(i) > 0x7f) {
168
+ throw new Asn1Error('ia5String: non-ASCII character');
169
+ }
170
+ }
171
+ return makePrimitive(UniversalTag.Ia5String, asciiBytes(value));
172
+ }
173
+ /** 构造 UTCTime(1950–2049)。 */
174
+ export function utcTime(date) {
175
+ return makePrimitive(UniversalTag.UtcTime, asciiBytes(encodeUtcTime(date)));
176
+ }
177
+ /** 构造 GeneralizedTime。 */
178
+ export function generalizedTime(date) {
179
+ return makePrimitive(UniversalTag.GeneralizedTime, asciiBytes(encodeGeneralizedTime(date)));
180
+ }
181
+ /** 按 RFC 5280 自动选择 UTCTime / GeneralizedTime。 */
182
+ export function time(date) {
183
+ return makePrimitive(isUtcYear(date) ? UniversalTag.UtcTime : UniversalTag.GeneralizedTime, asciiBytes(encodeTime(date)));
184
+ }
185
+ // ----------------- 内部 -----------------
186
+ function makePrimitive(tag, value) {
187
+ return {
188
+ cls: Asn1Class.Universal,
189
+ tag,
190
+ constructed: false,
191
+ indefinite: false,
192
+ length: value.length,
193
+ value,
194
+ raw: concat([encodeTag(Asn1Class.Universal, false, tag), encodeLength(value.length), value]),
195
+ };
196
+ }
197
+ function makeConstructed(cls, tag, children) {
198
+ const value = concat(children.map((child) => encode(child)));
199
+ return {
200
+ cls,
201
+ tag,
202
+ constructed: true,
203
+ indefinite: false,
204
+ length: value.length,
205
+ value,
206
+ children,
207
+ raw: concat([encodeTag(cls, true, tag), encodeLength(value.length), value]),
208
+ };
209
+ }
210
+ function isUtcYear(date) {
211
+ if (!(date instanceof Date) || Number.isNaN(date.getTime()))
212
+ return false;
213
+ const year = date.getUTCFullYear();
214
+ return year >= 1950 && year <= 2049;
215
+ }
216
+ /**
217
+ * INTEGER 最小补码编码。
218
+ *
219
+ * - 0 → `00`;
220
+ * - 正数:最高位为 1 时前置 `00`;
221
+ * - 负数:选择最少字节数 n 使 `-2^(8n-1) <= v`,按 2^(8n) 取模。
222
+ */
223
+ export function encodeIntegerValue(v) {
224
+ if (v === 0n)
225
+ return new Uint8Array([0]);
226
+ if (v > 0n) {
227
+ let n = 1;
228
+ while (v >= 1n << BigInt(8 * n - 1))
229
+ n++;
230
+ const out = new Uint8Array(n);
231
+ let x = v;
232
+ for (let i = n - 1; i >= 0; i--) {
233
+ out[i] = Number(x & 0xffn);
234
+ x >>= 8n;
235
+ }
236
+ return out;
237
+ }
238
+ let n = 1;
239
+ while (v < -(1n << BigInt(8 * n - 1)))
240
+ n++;
241
+ let x = (1n << BigInt(8 * n)) + v;
242
+ const out = new Uint8Array(n);
243
+ for (let i = n - 1; i >= 0; i--) {
244
+ out[i] = Number(x & 0xffn);
245
+ x >>= 8n;
246
+ }
247
+ return out;
248
+ }
249
+ function asciiBytes(text) {
250
+ const out = new Uint8Array(text.length);
251
+ for (let i = 0; i < text.length; i++)
252
+ out[i] = text.charCodeAt(i) & 0x7f;
253
+ return out;
254
+ }
255
+ function compareBytes(a, b) {
256
+ const n = Math.min(a.length, b.length);
257
+ for (let i = 0; i < n; i++) {
258
+ const d = a[i] - b[i];
259
+ if (d !== 0)
260
+ return d;
261
+ }
262
+ return a.length - b.length;
263
+ }
264
+ function concat(parts) {
265
+ let total = 0;
266
+ for (const p of parts)
267
+ total += p.length;
268
+ const out = new Uint8Array(total);
269
+ let offset = 0;
270
+ for (const p of parts) {
271
+ out.set(p, offset);
272
+ offset += p.length;
273
+ }
274
+ return out;
275
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ASN.1 内部层 barrel。
3
+ *
4
+ * **不进入主入口导出**(spec Anti-Goal:不公开底层 DER/大数接口)。
5
+ * `src/pem/asn1.ts` 仍按旧符号名 re-export 本层,供 PEM/PKCS#8 代码与测试使用。
6
+ */
7
+ export * from './decode.js';
8
+ export * from './encode.js';
9
+ export * from './oid.js';
10
+ export * from './time.js';
11
+ export * from './types.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ASN.1 内部层 barrel。
3
+ *
4
+ * **不进入主入口导出**(spec Anti-Goal:不公开底层 DER/大数接口)。
5
+ * `src/pem/asn1.ts` 仍按旧符号名 re-export 本层,供 PEM/PKCS#8 代码与测试使用。
6
+ */
7
+ export * from './decode.js';
8
+ export * from './encode.js';
9
+ export * from './oid.js';
10
+ export * from './time.js';
11
+ export * from './types.js';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * OBJECT IDENTIFIER 编解码。
3
+ *
4
+ * 与旧实现的差异:
5
+ * - 用 `bigint` 累加,不再受 32 位按位运算限制(`1.2.840.113549...` 这类
6
+ * 长组件不会溢出);
7
+ * - 首个子标识符也走 base128 编码,支持 `2.999` 这类第二组件 ≥ 40 的 OID;
8
+ * - 解码时拒绝截断、空输入与非最小(前导 0x80)编码。
9
+ */
10
+ /**
11
+ * 点分 OID → 内容字节(不含 tag 与 length)。
12
+ *
13
+ * @param oid 形如 `"1.2.156.10197.1.100"`。
14
+ * @throws Asn1Error 格式非法或组件越界。
15
+ */
16
+ export declare function encodeOid(oid: string): Uint8Array;
17
+ /**
18
+ * 内容字节 → 点分 OID。
19
+ *
20
+ * @throws Asn1Error 空输入、截断或非法 base128 编码。
21
+ */
22
+ export declare function decodeOid(bytes: Uint8Array): string;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * OBJECT IDENTIFIER 编解码。
3
+ *
4
+ * 与旧实现的差异:
5
+ * - 用 `bigint` 累加,不再受 32 位按位运算限制(`1.2.840.113549...` 这类
6
+ * 长组件不会溢出);
7
+ * - 首个子标识符也走 base128 编码,支持 `2.999` 这类第二组件 ≥ 40 的 OID;
8
+ * - 解码时拒绝截断、空输入与非最小(前导 0x80)编码。
9
+ */
10
+ import { Asn1Error } from './types.js';
11
+ /**
12
+ * 点分 OID → 内容字节(不含 tag 与 length)。
13
+ *
14
+ * @param oid 形如 `"1.2.156.10197.1.100"`。
15
+ * @throws Asn1Error 格式非法或组件越界。
16
+ */
17
+ export function encodeOid(oid) {
18
+ const parts = parseOidComponents(oid);
19
+ const first = parts[0] * 40n + parts[1];
20
+ const out = encodeBase128(first);
21
+ for (let i = 2; i < parts.length; i++) {
22
+ out.push(...encodeBase128(parts[i]));
23
+ }
24
+ return new Uint8Array(out);
25
+ }
26
+ /**
27
+ * 内容字节 → 点分 OID。
28
+ *
29
+ * @throws Asn1Error 空输入、截断或非法 base128 编码。
30
+ */
31
+ export function decodeOid(bytes) {
32
+ if (bytes.length === 0)
33
+ throw new Asn1Error('decodeOid: empty');
34
+ const values = [];
35
+ let value = 0n;
36
+ let pending = false;
37
+ for (let i = 0; i < bytes.length; i++) {
38
+ const octet = bytes[i] ?? 0;
39
+ // 每个子标识符的编码不得以 0x80 开头(非最小编码)。
40
+ if (!pending && octet === 0x80) {
41
+ throw new Asn1Error('decodeOid: non-minimal base128 encoding');
42
+ }
43
+ value = (value << 7n) | BigInt(octet & 0x7f);
44
+ pending = (octet & 0x80) !== 0;
45
+ if (!pending) {
46
+ values.push(value);
47
+ value = 0n;
48
+ }
49
+ }
50
+ if (pending)
51
+ throw new Asn1Error('decodeOid: truncated base128 component');
52
+ const first = values[0];
53
+ let a;
54
+ let b;
55
+ if (first < 40n) {
56
+ a = 0n;
57
+ b = first;
58
+ }
59
+ else if (first < 80n) {
60
+ a = 1n;
61
+ b = first - 40n;
62
+ }
63
+ else {
64
+ a = 2n;
65
+ b = first - 80n;
66
+ }
67
+ const parts = [a, b, ...values.slice(1)];
68
+ return parts.map((v) => v.toString(10)).join('.');
69
+ }
70
+ function parseOidComponents(oid) {
71
+ if (typeof oid !== 'string' || oid.length === 0) {
72
+ throw new Asn1Error(`encodeOid: bad oid "${String(oid)}"`);
73
+ }
74
+ const raw = oid.split('.');
75
+ if (raw.length < 2)
76
+ throw new Asn1Error(`encodeOid: bad oid "${oid}"`);
77
+ const parts = [];
78
+ for (const item of raw) {
79
+ if (!/^(0|[1-9][0-9]*)$/.test(item)) {
80
+ throw new Asn1Error(`encodeOid: bad component "${item}" in "${oid}"`);
81
+ }
82
+ parts.push(BigInt(item));
83
+ }
84
+ const first = parts[0];
85
+ const second = parts[1];
86
+ if (first > 2n)
87
+ throw new Asn1Error(`encodeOid: first component must be 0..2, got ${first}`);
88
+ if (first < 2n && second > 39n) {
89
+ throw new Asn1Error(`encodeOid: second component must be 0..39 for arc ${first}, got ${second}`);
90
+ }
91
+ return parts;
92
+ }
93
+ /** 无符号 bigint → base128 字节(高位 bit 为续行标志)。 */
94
+ function encodeBase128(value) {
95
+ if (value < 0n)
96
+ throw new Asn1Error(`encodeBase128: negative value ${value}`);
97
+ const groups = [];
98
+ let v = value;
99
+ do {
100
+ groups.unshift(Number(v & 0x7fn));
101
+ v >>= 7n;
102
+ } while (v > 0n);
103
+ for (let i = 0; i < groups.length - 1; i++) {
104
+ groups[i] = groups[i] | 0x80;
105
+ }
106
+ return groups;
107
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * X.509 时间编码/解析(UTCTime / GeneralizedTime)。
3
+ *
4
+ * 规则(RFC 5280):
5
+ * - 1950–2049 用 UTCTime(`YYMMDDHHMMSSZ`);
6
+ * - 其余年份用 GeneralizedTime(`YYYYMMDDHHMMSSZ`);
7
+ * - 证书时间精度到秒:解析时毫秒一律截断,保证 parse → encode round-trip。
8
+ *
9
+ * 解析刻意宽松(接受无秒、无时区、GeneralizedTime 的小数秒),因为真实现场
10
+ * 存在非规范编码;编码始终输出规范形式。
11
+ */
12
+ /**
13
+ * 编码 UTCTime(仅接受 1950–2049 年)。
14
+ *
15
+ * @throws RangeError 日期无效或年份越界。
16
+ */
17
+ export declare function encodeUtcTime(date: Date): string;
18
+ /**
19
+ * 编码 GeneralizedTime。
20
+ *
21
+ * @throws RangeError 日期无效。
22
+ */
23
+ export declare function encodeGeneralizedTime(date: Date): string;
24
+ /**
25
+ * 按 RFC 5280 自动选择 UTCTime(1950–2049)或 GeneralizedTime。
26
+ */
27
+ export declare function encodeTime(date: Date): string;
28
+ /**
29
+ * 解析 UTCTime 或 GeneralizedTime 字符串。
30
+ *
31
+ * 接受:
32
+ * - `YYMMDDHHMMSSZ` / `YYMMDDHHMMZ`(UTCTime,50–99 → 1950–1999,00–49 → 2000–2049);
33
+ * - `YYYYMMDDHHMMSSZ` / `YYYYMMDDHHMMZ` / `YYYYMMDDHHMMSS.sssZ`(GeneralizedTime);
34
+ * - 时间部分可带 `+HHMM` / `-HHMM` 偏移;省略时区按 UTC 处理。
35
+ *
36
+ * @returns 毫秒为 0 的 `Date`。
37
+ * @throws Asn1Error 格式或日期分量非法。
38
+ */
39
+ export declare function parseTime(text: string): Date;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * X.509 时间编码/解析(UTCTime / GeneralizedTime)。
3
+ *
4
+ * 规则(RFC 5280):
5
+ * - 1950–2049 用 UTCTime(`YYMMDDHHMMSSZ`);
6
+ * - 其余年份用 GeneralizedTime(`YYYYMMDDHHMMSSZ`);
7
+ * - 证书时间精度到秒:解析时毫秒一律截断,保证 parse → encode round-trip。
8
+ *
9
+ * 解析刻意宽松(接受无秒、无时区、GeneralizedTime 的小数秒),因为真实现场
10
+ * 存在非规范编码;编码始终输出规范形式。
11
+ */
12
+ import { Asn1Error } from './types.js';
13
+ /**
14
+ * 编码 UTCTime(仅接受 1950–2049 年)。
15
+ *
16
+ * @throws RangeError 日期无效或年份越界。
17
+ */
18
+ export function encodeUtcTime(date) {
19
+ const { year, month, day, hour, minute, second } = utcParts(date, 'encodeUtcTime');
20
+ if (year < 1950 || year > 2049) {
21
+ throw new RangeError(`encodeUtcTime: year ${year} outside 1950..2049`);
22
+ }
23
+ const yy = year % 100;
24
+ return `${pad2(yy)}${pad2(month)}${pad2(day)}${pad2(hour)}${pad2(minute)}${pad2(second)}Z`;
25
+ }
26
+ /**
27
+ * 编码 GeneralizedTime。
28
+ *
29
+ * @throws RangeError 日期无效。
30
+ */
31
+ export function encodeGeneralizedTime(date) {
32
+ const { year, month, day, hour, minute, second } = utcParts(date, 'encodeGeneralizedTime');
33
+ return `${pad4(year)}${pad2(month)}${pad2(day)}${pad2(hour)}${pad2(minute)}${pad2(second)}Z`;
34
+ }
35
+ /**
36
+ * 按 RFC 5280 自动选择 UTCTime(1950–2049)或 GeneralizedTime。
37
+ */
38
+ export function encodeTime(date) {
39
+ const year = utcParts(date, 'encodeTime').year;
40
+ return year >= 1950 && year <= 2049 ? encodeUtcTime(date) : encodeGeneralizedTime(date);
41
+ }
42
+ /**
43
+ * 解析 UTCTime 或 GeneralizedTime 字符串。
44
+ *
45
+ * 接受:
46
+ * - `YYMMDDHHMMSSZ` / `YYMMDDHHMMZ`(UTCTime,50–99 → 1950–1999,00–49 → 2000–2049);
47
+ * - `YYYYMMDDHHMMSSZ` / `YYYYMMDDHHMMZ` / `YYYYMMDDHHMMSS.sssZ`(GeneralizedTime);
48
+ * - 时间部分可带 `+HHMM` / `-HHMM` 偏移;省略时区按 UTC 处理。
49
+ *
50
+ * @returns 毫秒为 0 的 `Date`。
51
+ * @throws Asn1Error 格式或日期分量非法。
52
+ */
53
+ export function parseTime(text) {
54
+ const s = text.trim();
55
+ const m = /^(\d{2}|\d{4})(\d{2})(\d{2})(\d{2})(\d{2})?(\d{2})?([.,]\d+)?(Z|[+-]\d{4})?$/.exec(s);
56
+ if (!m)
57
+ throw new Asn1Error(`parseTime: unsupported time syntax "${text}"`);
58
+ const yearToken = m[1];
59
+ const year = yearToken.length === 2 ? utcYear(Number(yearToken)) : Number(yearToken);
60
+ const month = Number(m[2]);
61
+ const day = Number(m[3]);
62
+ const hour = Number(m[4]);
63
+ const minute = m[5] === undefined ? 0 : Number(m[5]);
64
+ const second = m[6] === undefined ? 0 : Number(m[6]);
65
+ const zone = m[8];
66
+ let offsetMinutes = 0;
67
+ if (zone !== undefined && zone !== 'Z') {
68
+ const sign = zone.startsWith('-') ? -1 : 1;
69
+ const oh = Number(zone.slice(1, 3));
70
+ const om = Number(zone.slice(3, 5));
71
+ if (oh > 23 || om > 59)
72
+ throw new Asn1Error(`parseTime: bad timezone offset "${zone}"`);
73
+ offsetMinutes = sign * (oh * 60 + om);
74
+ }
75
+ const localMs = Date.UTC(year, month - 1, day, hour, minute, second);
76
+ // 先在「输入的本地分量」上校验日期合法性,再应用时区偏移。
77
+ const local = new Date(localMs);
78
+ if (Number.isNaN(local.getTime()) ||
79
+ local.getUTCFullYear() !== year ||
80
+ local.getUTCMonth() !== month - 1 ||
81
+ local.getUTCDate() !== day ||
82
+ local.getUTCHours() !== hour ||
83
+ local.getUTCMinutes() !== minute ||
84
+ local.getUTCSeconds() !== second) {
85
+ throw new Asn1Error(`parseTime: invalid date components in "${text}"`);
86
+ }
87
+ return new Date(localMs - offsetMinutes * 60_000);
88
+ }
89
+ /** UTCTime 两位年份展开。 */
90
+ function utcYear(yy) {
91
+ return yy >= 50 ? 1900 + yy : 2000 + yy;
92
+ }
93
+ function utcParts(date, where) {
94
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
95
+ throw new RangeError(`${where}: invalid Date`);
96
+ }
97
+ return {
98
+ year: date.getUTCFullYear(),
99
+ month: date.getUTCMonth() + 1,
100
+ day: date.getUTCDate(),
101
+ hour: date.getUTCHours(),
102
+ minute: date.getUTCMinutes(),
103
+ second: date.getUTCSeconds(),
104
+ };
105
+ }
106
+ function pad2(n) {
107
+ return n.toString(10).padStart(2, '0');
108
+ }
109
+ function pad4(n) {
110
+ return n.toString(10).padStart(4, '0');
111
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ASN.1 通用类型与常量(DER/BER 子集)。
3
+ *
4
+ * 与旧 `src/pem/asn1.ts` 的关系:
5
+ * - 旧实现只覆盖 PKCS#8 用到的最小类型,且不保留 `constructed` 位;
6
+ * - 本层是 X.509 / CMS / WebCrypto 共用的通用基础设施,覆盖
7
+ * BOOLEAN、SET、字符串、时间、context/application/private 标签等。
8
+ *
9
+ * 设计要点:
10
+ * - 每个节点保留 `raw`(含 tag + length + value 的原始切片),X.509 的 TBS
11
+ * 与 CMS 的 `signedAttrs` 验签必须对原始字节做摘要,不能重新编码;
12
+ * - `raw` 是对输入缓冲的**视图**(不拷贝),调用方不应假定其生命周期;
13
+ * 需要长期持有时自行 `slice()`;
14
+ * - 解析错误统一为 `Asn1Error`;上层(X.509/CMS)负责转换为各自的错误类型。
15
+ */
16
+ /** ASN.1 标签类别。 */
17
+ export type Asn1Class = 0 | 1 | 2 | 3;
18
+ /** ASN.1 标签类别常量。 */
19
+ export declare const Asn1Class: {
20
+ readonly Universal: 0;
21
+ readonly Application: 1;
22
+ readonly ContextSpecific: 2;
23
+ readonly Private: 3;
24
+ };
25
+ /** UNIVERSAL 类常用标签编号。 */
26
+ export declare const UniversalTag: {
27
+ readonly EndOfContent: 0;
28
+ readonly Boolean: 1;
29
+ readonly Integer: 2;
30
+ readonly BitString: 3;
31
+ readonly OctetString: 4;
32
+ readonly Null: 5;
33
+ readonly ObjectIdentifier: 6;
34
+ readonly Enumerated: 10;
35
+ readonly Utf8String: 12;
36
+ readonly Sequence: 16;
37
+ readonly Set: 17;
38
+ readonly NumericString: 18;
39
+ readonly PrintableString: 19;
40
+ readonly TeletexString: 20;
41
+ readonly VideotexString: 21;
42
+ readonly Ia5String: 22;
43
+ readonly UtcTime: 23;
44
+ readonly GeneralizedTime: 24;
45
+ readonly UniversalString: 28;
46
+ readonly BmpString: 30;
47
+ };
48
+ /** ASN.1 节点。 */
49
+ export interface Asn1Node {
50
+ /** 标签类别:UNIVERSAL=0、APPLICATION=1、CONTEXT=2、PRIVATE=3。 */
51
+ readonly cls: Asn1Class;
52
+ /** 标签编号(高 tag number 已展开为数值,不含 0x1f 标志位)。 */
53
+ readonly tag: number;
54
+ /** 是否为 constructed 编码。 */
55
+ readonly constructed: boolean;
56
+ /** 是否使用 BER indefinite-length(`0x80` + EOC)。DER 节点恒为 false。 */
57
+ readonly indefinite: boolean;
58
+ /** 内容字节数(indefinite 时为实际内容长度,不含 EOC)。 */
59
+ readonly length: number;
60
+ /** primitive 的 value;constructed 为子节点编码拼接后的内容字节。 */
61
+ readonly value: Uint8Array;
62
+ /** constructed 节点的子节点;primitive 为 `undefined`。 */
63
+ readonly children?: readonly Asn1Node[];
64
+ /** 节点原始字节(含 tag + length + value;indefinite 时含 EOC)。 */
65
+ readonly raw: Uint8Array;
66
+ }
67
+ /** ASN.1 解析/编码错误。 */
68
+ export declare class Asn1Error extends Error {
69
+ readonly name = "Asn1Error";
70
+ }
71
+ /** 判断节点是否为指定 UNIVERSAL 类型。 */
72
+ export declare function isUniversal(node: Asn1Node, tag: number): boolean;
73
+ /** 断言节点为指定 UNIVERSAL 类型,否则抛 `Asn1Error`。 */
74
+ export declare function expectUniversal(node: Asn1Node | undefined, tag: number, what: string): Asn1Node;
75
+ /** 断言节点为 constructed SEQUENCE 并返回子节点。 */
76
+ export declare function expectSequence(node: Asn1Node | undefined, what: string): readonly Asn1Node[];