sm-crypto-wasm 0.3.1 → 0.7.1
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 +62 -834
- package/dist/asn1/decode.d.ts +165 -0
- package/dist/asn1/decode.js +457 -0
- package/dist/asn1/encode.d.ts +169 -0
- package/dist/asn1/encode.js +414 -0
- package/dist/asn1/index.d.ts +17 -0
- package/dist/asn1/index.js +17 -0
- package/dist/asn1/oid.d.ts +40 -0
- package/dist/asn1/oid.js +136 -0
- package/dist/asn1/time.d.ts +71 -0
- package/dist/asn1/time.js +165 -0
- package/dist/asn1/types.d.ts +160 -0
- package/dist/asn1/types.js +112 -0
- package/dist/cms/errors.d.ts +23 -0
- package/dist/cms/errors.js +17 -0
- package/dist/cms/index.d.ts +18 -0
- package/dist/cms/index.js +15 -0
- package/dist/cms/oids.d.ts +47 -0
- package/dist/cms/oids.js +47 -0
- package/dist/cms/parse.d.ts +217 -0
- package/dist/cms/parse.js +420 -0
- package/dist/cms/sign.d.ts +113 -0
- package/dist/cms/sign.js +194 -0
- package/dist/cms/verify.d.ts +117 -0
- package/dist/cms/verify.js +222 -0
- package/dist/common/hybrid.d.ts +25 -0
- package/dist/common/hybrid.js +25 -0
- package/dist/common/sealed.d.ts +119 -15
- package/dist/common/sealed.js +67 -9
- package/dist/esbuild-plugin.d.ts +74 -5
- package/dist/esbuild-plugin.js +69 -4
- package/dist/index.d.ts +22 -0
- package/dist/index.js +25 -0
- package/dist/pem/asn1.d.ts +19 -65
- package/dist/pem/asn1.js +19 -288
- package/dist/pem/index.d.ts +2 -0
- package/dist/pem/index.js +2 -0
- package/dist/pem/pkcs8.d.ts +64 -8
- package/dist/pem/pkcs8.js +75 -7
- package/dist/pem/sm2.d.ts +90 -2
- package/dist/pem/sm2.js +204 -15
- package/dist/pem/sm9.d.ts +42 -2
- package/dist/pem/sm9.js +42 -2
- package/dist/runtime/base64.core.d.ts +25 -1
- package/dist/runtime/base64.core.js +31 -1
- package/dist/runtime/convert.d.ts +70 -7
- package/dist/runtime/convert.js +129 -9
- package/dist/runtime/errors.d.ts +64 -8
- package/dist/runtime/errors.js +64 -8
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/loadWasm.browser.d.ts +33 -0
- package/dist/runtime/loadWasm.browser.js +46 -0
- package/dist/runtime/loadWasm.core.d.ts +59 -3
- package/dist/runtime/loadWasm.core.js +53 -3
- package/dist/runtime/loadWasm.d.ts +44 -0
- package/dist/runtime/loadWasm.js +79 -4
- package/dist/runtime/random.browser.d.ts +24 -0
- package/dist/runtime/random.browser.js +31 -1
- package/dist/runtime/random.core.d.ts +21 -0
- package/dist/runtime/random.core.js +27 -0
- package/dist/runtime/random.d.ts +28 -0
- package/dist/runtime/random.js +40 -1
- package/dist/sm2/exchange.d.ts +132 -16
- package/dist/sm2/exchange.js +120 -9
- package/dist/sm2/hybrid.d.ts +65 -3
- package/dist/sm2/hybrid.js +53 -1
- package/dist/sm2/index.d.ts +324 -16
- package/dist/sm2/index.js +530 -19
- package/dist/sm2/kem.d.ts +62 -5
- package/dist/sm2/kem.js +37 -2
- package/dist/sm2/keyInput.d.ts +36 -3
- package/dist/sm2/keyInput.js +36 -3
- package/dist/sm2/sm2.wasm +0 -0
- package/dist/sm3/index.d.ts +30 -0
- package/dist/sm3/index.js +55 -1
- package/dist/sm4/index.d.ts +125 -17
- package/dist/sm4/index.js +201 -14
- package/dist/sm9/core.d.ts +58 -8
- package/dist/sm9/core.js +36 -5
- package/dist/sm9/hybrid.d.ts +66 -3
- package/dist/sm9/hybrid.js +46 -1
- package/dist/sm9/index.d.ts +265 -19
- package/dist/sm9/index.js +175 -5
- package/dist/sm9/input.d.ts +37 -2
- package/dist/sm9/input.js +50 -1
- package/dist/sm9/kem.d.ts +57 -4
- package/dist/sm9/kem.js +47 -1
- package/dist/webcrypto/algorithms.d.ts +88 -0
- package/dist/webcrypto/algorithms.js +168 -0
- package/dist/webcrypto/cryptoKey.d.ts +130 -0
- package/dist/webcrypto/cryptoKey.js +167 -0
- package/dist/webcrypto/errors.d.ts +43 -0
- package/dist/webcrypto/errors.js +69 -0
- package/dist/webcrypto/index.d.ts +24 -0
- package/dist/webcrypto/index.js +21 -0
- package/dist/webcrypto/subtle.d.ts +118 -0
- package/dist/webcrypto/subtle.js +426 -0
- package/dist/x509/encode.d.ts +129 -0
- package/dist/x509/encode.js +248 -0
- package/dist/x509/errors.d.ts +29 -0
- package/dist/x509/errors.js +29 -0
- package/dist/x509/extensions.d.ts +66 -0
- package/dist/x509/extensions.js +273 -0
- package/dist/x509/index.d.ts +21 -0
- package/dist/x509/index.js +30 -0
- package/dist/x509/ip.d.ts +21 -0
- package/dist/x509/ip.js +122 -0
- package/dist/x509/issue.d.ts +87 -0
- package/dist/x509/issue.js +521 -0
- package/dist/x509/oids.d.ts +74 -0
- package/dist/x509/oids.js +103 -0
- package/dist/x509/parse.d.ts +45 -0
- package/dist/x509/parse.js +550 -0
- package/dist/x509/sha1.d.ts +20 -0
- package/dist/x509/sha1.js +89 -0
- package/dist/x509/sha256.d.ts +20 -0
- package/dist/x509/sha256.js +114 -0
- package/dist/x509/sig.d.ts +35 -0
- package/dist/x509/sig.js +104 -0
- package/dist/x509/types.d.ts +490 -0
- package/dist/x509/types.js +13 -0
- package/dist/x509/verify.d.ts +91 -0
- package/dist/x509/verify.js +105 -0
- package/dist/zuc/index.d.ts +128 -10
- package/dist/zuc/index.js +136 -7
- package/package.json +57 -28
package/dist/asn1/oid.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OBJECT IDENTIFIER 编解码。
|
|
3
|
+
*
|
|
4
|
+
* 与旧实现的差异:
|
|
5
|
+
* - 用 `bigint` 累加,不再受 32 位按位运算限制(`1.2.840.113549...` 这类
|
|
6
|
+
* 长组件不会溢出);
|
|
7
|
+
* - 首个子标识符也走 base128 编码,支持 `2.999` 这类第二组件 ≥ 40 的 OID;
|
|
8
|
+
* - 解码时拒绝截断、空输入与非最小(前导 0x80)编码。
|
|
9
|
+
*
|
|
10
|
+
* OBJECT IDENTIFIER encoding and decoding.
|
|
11
|
+
*
|
|
12
|
+
* Differences from the legacy implementation:
|
|
13
|
+
* - Accumulates in `bigint`, so 32-bit bitwise limits no longer apply (long arcs such
|
|
14
|
+
* as `1.2.840.113549...` cannot overflow);
|
|
15
|
+
* - The first subidentifier also goes through base128, so OIDs whose second arc is
|
|
16
|
+
* ≥ 40 (such as `2.999`) are supported;
|
|
17
|
+
* - Decoding rejects truncation, empty input and non-minimal (leading 0x80) encodings.
|
|
18
|
+
*/
|
|
19
|
+
import { Asn1Error } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* 点分 OID → 内容字节(不含 tag 与 length)。
|
|
22
|
+
*
|
|
23
|
+
* @param oid 形如 `"1.2.156.10197.1.100"`。
|
|
24
|
+
* @throws Asn1Error 格式非法或组件越界。
|
|
25
|
+
*
|
|
26
|
+
* Dotted OID → content bytes (without tag and length).
|
|
27
|
+
*
|
|
28
|
+
* @param oid Something like `"1.2.156.10197.1.100"`.
|
|
29
|
+
* @throws Asn1Error On malformed syntax or out-of-range components.
|
|
30
|
+
*/
|
|
31
|
+
export function encodeOid(oid) {
|
|
32
|
+
const parts = parseOidComponents(oid);
|
|
33
|
+
const first = parts[0] * 40n + parts[1];
|
|
34
|
+
const out = encodeBase128(first);
|
|
35
|
+
for (let i = 2; i < parts.length; i++) {
|
|
36
|
+
out.push(...encodeBase128(parts[i]));
|
|
37
|
+
}
|
|
38
|
+
return new Uint8Array(out);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 内容字节 → 点分 OID。
|
|
42
|
+
*
|
|
43
|
+
* @throws Asn1Error 空输入、截断或非法 base128 编码。
|
|
44
|
+
*
|
|
45
|
+
* Content bytes → dotted OID.
|
|
46
|
+
*
|
|
47
|
+
* @throws Asn1Error On empty input, truncation, or an invalid base128 encoding.
|
|
48
|
+
*/
|
|
49
|
+
export function decodeOid(bytes) {
|
|
50
|
+
if (bytes.length === 0)
|
|
51
|
+
throw new Asn1Error('decodeOid: empty');
|
|
52
|
+
const values = [];
|
|
53
|
+
let value = 0n;
|
|
54
|
+
let pending = false;
|
|
55
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
56
|
+
const octet = bytes[i] ?? 0;
|
|
57
|
+
// 每个子标识符的编码不得以 0x80 开头(非最小编码)。
|
|
58
|
+
//
|
|
59
|
+
// A subidentifier's encoding must not start with 0x80 (non-minimal encoding).
|
|
60
|
+
if (!pending && octet === 0x80) {
|
|
61
|
+
throw new Asn1Error('decodeOid: non-minimal base128 encoding');
|
|
62
|
+
}
|
|
63
|
+
value = (value << 7n) | BigInt(octet & 0x7f);
|
|
64
|
+
pending = (octet & 0x80) !== 0;
|
|
65
|
+
if (!pending) {
|
|
66
|
+
values.push(value);
|
|
67
|
+
value = 0n;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (pending)
|
|
71
|
+
throw new Asn1Error('decodeOid: truncated base128 component');
|
|
72
|
+
const first = values[0];
|
|
73
|
+
let a;
|
|
74
|
+
let b;
|
|
75
|
+
if (first < 40n) {
|
|
76
|
+
a = 0n;
|
|
77
|
+
b = first;
|
|
78
|
+
}
|
|
79
|
+
else if (first < 80n) {
|
|
80
|
+
a = 1n;
|
|
81
|
+
b = first - 40n;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
a = 2n;
|
|
85
|
+
b = first - 80n;
|
|
86
|
+
}
|
|
87
|
+
const parts = [a, b, ...values.slice(1)];
|
|
88
|
+
return parts.map((v) => v.toString(10)).join('.');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 校验并拆分点分 OID(每个组件为无符号十进制整数)。
|
|
92
|
+
*
|
|
93
|
+
* Validates and splits a dotted OID (each component is an unsigned decimal integer).
|
|
94
|
+
*/
|
|
95
|
+
function parseOidComponents(oid) {
|
|
96
|
+
if (typeof oid !== 'string' || oid.length === 0) {
|
|
97
|
+
throw new Asn1Error(`encodeOid: bad oid "${String(oid)}"`);
|
|
98
|
+
}
|
|
99
|
+
const raw = oid.split('.');
|
|
100
|
+
if (raw.length < 2)
|
|
101
|
+
throw new Asn1Error(`encodeOid: bad oid "${oid}"`);
|
|
102
|
+
const parts = [];
|
|
103
|
+
for (const item of raw) {
|
|
104
|
+
if (!/^(0|[1-9][0-9]*)$/.test(item)) {
|
|
105
|
+
throw new Asn1Error(`encodeOid: bad component "${item}" in "${oid}"`);
|
|
106
|
+
}
|
|
107
|
+
parts.push(BigInt(item));
|
|
108
|
+
}
|
|
109
|
+
const first = parts[0];
|
|
110
|
+
const second = parts[1];
|
|
111
|
+
if (first > 2n)
|
|
112
|
+
throw new Asn1Error(`encodeOid: first component must be 0..2, got ${first}`);
|
|
113
|
+
if (first < 2n && second > 39n) {
|
|
114
|
+
throw new Asn1Error(`encodeOid: second component must be 0..39 for arc ${first}, got ${second}`);
|
|
115
|
+
}
|
|
116
|
+
return parts;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 无符号 bigint → base128 字节(高位 bit 为续行标志)。
|
|
120
|
+
*
|
|
121
|
+
* Unsigned bigint → base128 bytes (the high bit marks a continuation).
|
|
122
|
+
*/
|
|
123
|
+
function encodeBase128(value) {
|
|
124
|
+
if (value < 0n)
|
|
125
|
+
throw new Asn1Error(`encodeBase128: negative value ${value}`);
|
|
126
|
+
const groups = [];
|
|
127
|
+
let v = value;
|
|
128
|
+
do {
|
|
129
|
+
groups.unshift(Number(v & 0x7fn));
|
|
130
|
+
v >>= 7n;
|
|
131
|
+
} while (v > 0n);
|
|
132
|
+
for (let i = 0; i < groups.length - 1; i++) {
|
|
133
|
+
groups[i] = groups[i] | 0x80;
|
|
134
|
+
}
|
|
135
|
+
return groups;
|
|
136
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
* X.509 time encoding/parsing (UTCTime / GeneralizedTime).
|
|
13
|
+
*
|
|
14
|
+
* Rules (RFC 5280):
|
|
15
|
+
* - 1950–2049 use UTCTime (`YYMMDDHHMMSSZ`);
|
|
16
|
+
* - Other years use GeneralizedTime (`YYYYMMDDHHMMSSZ`);
|
|
17
|
+
* - Certificate time has second precision: milliseconds are truncated when parsing,
|
|
18
|
+
* keeping parse → encode round-trips stable.
|
|
19
|
+
*
|
|
20
|
+
* Parsing is deliberately lenient (missing seconds, missing timezone, GeneralizedTime
|
|
21
|
+
* fractional seconds) because non-canonical encodings exist in the wild; encoding
|
|
22
|
+
* always emits the canonical form.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* 编码 UTCTime(仅接受 1950–2049 年)。
|
|
26
|
+
*
|
|
27
|
+
* @throws RangeError 日期无效或年份越界。
|
|
28
|
+
*
|
|
29
|
+
* Encodes a UTCTime (years 1950–2049 only).
|
|
30
|
+
*
|
|
31
|
+
* @throws RangeError When the Date is invalid or the year is out of range.
|
|
32
|
+
*/
|
|
33
|
+
export declare function encodeUtcTime(date: Date): string;
|
|
34
|
+
/**
|
|
35
|
+
* 编码 GeneralizedTime。
|
|
36
|
+
*
|
|
37
|
+
* @throws RangeError 日期无效。
|
|
38
|
+
*
|
|
39
|
+
* Encodes a GeneralizedTime.
|
|
40
|
+
*
|
|
41
|
+
* @throws RangeError When the Date is invalid.
|
|
42
|
+
*/
|
|
43
|
+
export declare function encodeGeneralizedTime(date: Date): string;
|
|
44
|
+
/**
|
|
45
|
+
* 按 RFC 5280 自动选择 UTCTime(1950–2049)或 GeneralizedTime。
|
|
46
|
+
*
|
|
47
|
+
* Picks UTCTime (1950–2049) or GeneralizedTime automatically as RFC 5280 requires.
|
|
48
|
+
*/
|
|
49
|
+
export declare function encodeTime(date: Date): string;
|
|
50
|
+
/**
|
|
51
|
+
* 解析 UTCTime 或 GeneralizedTime 字符串。
|
|
52
|
+
*
|
|
53
|
+
* 接受:
|
|
54
|
+
* - `YYMMDDHHMMSSZ` / `YYMMDDHHMMZ`(UTCTime,50–99 → 1950–1999,00–49 → 2000–2049);
|
|
55
|
+
* - `YYYYMMDDHHMMSSZ` / `YYYYMMDDHHMMZ` / `YYYYMMDDHHMMSS.sssZ`(GeneralizedTime);
|
|
56
|
+
* - 时间部分可带 `+HHMM` / `-HHMM` 偏移;省略时区按 UTC 处理。
|
|
57
|
+
*
|
|
58
|
+
* @returns 毫秒为 0 的 `Date`。
|
|
59
|
+
* @throws Asn1Error 格式或日期分量非法。
|
|
60
|
+
*
|
|
61
|
+
* Parses a UTCTime or GeneralizedTime string.
|
|
62
|
+
*
|
|
63
|
+
* Accepts:
|
|
64
|
+
* - `YYMMDDHHMMSSZ` / `YYMMDDHHMMZ` (UTCTime; 50–99 → 1950–1999, 00–49 → 2000–2049);
|
|
65
|
+
* - `YYYYMMDDHHMMSSZ` / `YYYYMMDDHHMMZ` / `YYYYMMDDHHMMSS.sssZ` (GeneralizedTime);
|
|
66
|
+
* - an optional `+HHMM` / `-HHMM` offset on the time part; a missing timezone means UTC.
|
|
67
|
+
*
|
|
68
|
+
* @returns A `Date` whose milliseconds are 0.
|
|
69
|
+
* @throws Asn1Error On illegal syntax or date components.
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseTime(text: string): Date;
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
* X.509 time encoding/parsing (UTCTime / GeneralizedTime).
|
|
13
|
+
*
|
|
14
|
+
* Rules (RFC 5280):
|
|
15
|
+
* - 1950–2049 use UTCTime (`YYMMDDHHMMSSZ`);
|
|
16
|
+
* - Other years use GeneralizedTime (`YYYYMMDDHHMMSSZ`);
|
|
17
|
+
* - Certificate time has second precision: milliseconds are truncated when parsing,
|
|
18
|
+
* keeping parse → encode round-trips stable.
|
|
19
|
+
*
|
|
20
|
+
* Parsing is deliberately lenient (missing seconds, missing timezone, GeneralizedTime
|
|
21
|
+
* fractional seconds) because non-canonical encodings exist in the wild; encoding
|
|
22
|
+
* always emits the canonical form.
|
|
23
|
+
*/
|
|
24
|
+
import { Asn1Error } from './types.js';
|
|
25
|
+
/**
|
|
26
|
+
* 编码 UTCTime(仅接受 1950–2049 年)。
|
|
27
|
+
*
|
|
28
|
+
* @throws RangeError 日期无效或年份越界。
|
|
29
|
+
*
|
|
30
|
+
* Encodes a UTCTime (years 1950–2049 only).
|
|
31
|
+
*
|
|
32
|
+
* @throws RangeError When the Date is invalid or the year is out of range.
|
|
33
|
+
*/
|
|
34
|
+
export function encodeUtcTime(date) {
|
|
35
|
+
const { year, month, day, hour, minute, second } = utcParts(date, 'encodeUtcTime');
|
|
36
|
+
if (year < 1950 || year > 2049) {
|
|
37
|
+
throw new RangeError(`encodeUtcTime: year ${year} outside 1950..2049`);
|
|
38
|
+
}
|
|
39
|
+
const yy = year % 100;
|
|
40
|
+
return `${pad2(yy)}${pad2(month)}${pad2(day)}${pad2(hour)}${pad2(minute)}${pad2(second)}Z`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 编码 GeneralizedTime。
|
|
44
|
+
*
|
|
45
|
+
* @throws RangeError 日期无效。
|
|
46
|
+
*
|
|
47
|
+
* Encodes a GeneralizedTime.
|
|
48
|
+
*
|
|
49
|
+
* @throws RangeError When the Date is invalid.
|
|
50
|
+
*/
|
|
51
|
+
export function encodeGeneralizedTime(date) {
|
|
52
|
+
const { year, month, day, hour, minute, second } = utcParts(date, 'encodeGeneralizedTime');
|
|
53
|
+
return `${pad4(year)}${pad2(month)}${pad2(day)}${pad2(hour)}${pad2(minute)}${pad2(second)}Z`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 按 RFC 5280 自动选择 UTCTime(1950–2049)或 GeneralizedTime。
|
|
57
|
+
*
|
|
58
|
+
* Picks UTCTime (1950–2049) or GeneralizedTime automatically as RFC 5280 requires.
|
|
59
|
+
*/
|
|
60
|
+
export function encodeTime(date) {
|
|
61
|
+
const year = utcParts(date, 'encodeTime').year;
|
|
62
|
+
return year >= 1950 && year <= 2049 ? encodeUtcTime(date) : encodeGeneralizedTime(date);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 解析 UTCTime 或 GeneralizedTime 字符串。
|
|
66
|
+
*
|
|
67
|
+
* 接受:
|
|
68
|
+
* - `YYMMDDHHMMSSZ` / `YYMMDDHHMMZ`(UTCTime,50–99 → 1950–1999,00–49 → 2000–2049);
|
|
69
|
+
* - `YYYYMMDDHHMMSSZ` / `YYYYMMDDHHMMZ` / `YYYYMMDDHHMMSS.sssZ`(GeneralizedTime);
|
|
70
|
+
* - 时间部分可带 `+HHMM` / `-HHMM` 偏移;省略时区按 UTC 处理。
|
|
71
|
+
*
|
|
72
|
+
* @returns 毫秒为 0 的 `Date`。
|
|
73
|
+
* @throws Asn1Error 格式或日期分量非法。
|
|
74
|
+
*
|
|
75
|
+
* Parses a UTCTime or GeneralizedTime string.
|
|
76
|
+
*
|
|
77
|
+
* Accepts:
|
|
78
|
+
* - `YYMMDDHHMMSSZ` / `YYMMDDHHMMZ` (UTCTime; 50–99 → 1950–1999, 00–49 → 2000–2049);
|
|
79
|
+
* - `YYYYMMDDHHMMSSZ` / `YYYYMMDDHHMMZ` / `YYYYMMDDHHMMSS.sssZ` (GeneralizedTime);
|
|
80
|
+
* - an optional `+HHMM` / `-HHMM` offset on the time part; a missing timezone means UTC.
|
|
81
|
+
*
|
|
82
|
+
* @returns A `Date` whose milliseconds are 0.
|
|
83
|
+
* @throws Asn1Error On illegal syntax or date components.
|
|
84
|
+
*/
|
|
85
|
+
export function parseTime(text) {
|
|
86
|
+
const s = text.trim();
|
|
87
|
+
const m = /^(\d{2}|\d{4})(\d{2})(\d{2})(\d{2})(\d{2})?(\d{2})?([.,]\d+)?(Z|[+-]\d{4})?$/.exec(s);
|
|
88
|
+
if (!m)
|
|
89
|
+
throw new Asn1Error(`parseTime: unsupported time syntax "${text}"`);
|
|
90
|
+
const yearToken = m[1];
|
|
91
|
+
const year = yearToken.length === 2 ? utcYear(Number(yearToken)) : Number(yearToken);
|
|
92
|
+
const month = Number(m[2]);
|
|
93
|
+
const day = Number(m[3]);
|
|
94
|
+
const hour = Number(m[4]);
|
|
95
|
+
const minute = m[5] === undefined ? 0 : Number(m[5]);
|
|
96
|
+
const second = m[6] === undefined ? 0 : Number(m[6]);
|
|
97
|
+
const zone = m[8];
|
|
98
|
+
let offsetMinutes = 0;
|
|
99
|
+
if (zone !== undefined && zone !== 'Z') {
|
|
100
|
+
const sign = zone.startsWith('-') ? -1 : 1;
|
|
101
|
+
const oh = Number(zone.slice(1, 3));
|
|
102
|
+
const om = Number(zone.slice(3, 5));
|
|
103
|
+
if (oh > 23 || om > 59)
|
|
104
|
+
throw new Asn1Error(`parseTime: bad timezone offset "${zone}"`);
|
|
105
|
+
offsetMinutes = sign * (oh * 60 + om);
|
|
106
|
+
}
|
|
107
|
+
const localMs = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
108
|
+
// 先在「输入的本地分量」上校验日期合法性,再应用时区偏移。
|
|
109
|
+
//
|
|
110
|
+
// Validate the date against the input's local components first, then apply the
|
|
111
|
+
// timezone offset.
|
|
112
|
+
const local = new Date(localMs);
|
|
113
|
+
if (Number.isNaN(local.getTime()) ||
|
|
114
|
+
local.getUTCFullYear() !== year ||
|
|
115
|
+
local.getUTCMonth() !== month - 1 ||
|
|
116
|
+
local.getUTCDate() !== day ||
|
|
117
|
+
local.getUTCHours() !== hour ||
|
|
118
|
+
local.getUTCMinutes() !== minute ||
|
|
119
|
+
local.getUTCSeconds() !== second) {
|
|
120
|
+
throw new Asn1Error(`parseTime: invalid date components in "${text}"`);
|
|
121
|
+
}
|
|
122
|
+
return new Date(localMs - offsetMinutes * 60_000);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* UTCTime 两位年份展开。
|
|
126
|
+
*
|
|
127
|
+
* Expands a two-digit UTCTime year.
|
|
128
|
+
*/
|
|
129
|
+
function utcYear(yy) {
|
|
130
|
+
return yy >= 50 ? 1900 + yy : 2000 + yy;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 提取日期的 UTC 分量并校验日期有效性。
|
|
134
|
+
*
|
|
135
|
+
* Extracts the UTC components of a Date and validates it.
|
|
136
|
+
*/
|
|
137
|
+
function utcParts(date, where) {
|
|
138
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
139
|
+
throw new RangeError(`${where}: invalid Date`);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
year: date.getUTCFullYear(),
|
|
143
|
+
month: date.getUTCMonth() + 1,
|
|
144
|
+
day: date.getUTCDate(),
|
|
145
|
+
hour: date.getUTCHours(),
|
|
146
|
+
minute: date.getUTCMinutes(),
|
|
147
|
+
second: date.getUTCSeconds(),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 十进制补零到 2 位。
|
|
152
|
+
*
|
|
153
|
+
* Zero-pads a decimal number to 2 digits.
|
|
154
|
+
*/
|
|
155
|
+
function pad2(n) {
|
|
156
|
+
return n.toString(10).padStart(2, '0');
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 十进制补零到 4 位。
|
|
160
|
+
*
|
|
161
|
+
* Zero-pads a decimal number to 4 digits.
|
|
162
|
+
*/
|
|
163
|
+
function pad4(n) {
|
|
164
|
+
return n.toString(10).padStart(4, '0');
|
|
165
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
* Shared ASN.1 types and constants (a DER/BER subset).
|
|
17
|
+
*
|
|
18
|
+
* Relationship to the legacy `src/pem/asn1.ts`:
|
|
19
|
+
* - The old implementation only covered the minimal types used by PKCS#8 and did
|
|
20
|
+
* not preserve the `constructed` bit;
|
|
21
|
+
* - This layer is the shared infrastructure for X.509 / CMS / WebCrypto: it covers
|
|
22
|
+
* BOOLEAN, SET, string types, time, and context/application/private tags.
|
|
23
|
+
*
|
|
24
|
+
* Design notes:
|
|
25
|
+
* - Each node keeps `raw` (the original slice including tag + length + value).
|
|
26
|
+
* X.509 TBS digests and CMS `signedAttrs` verification must hash the original
|
|
27
|
+
* bytes, never a re-encoding;
|
|
28
|
+
* - `raw` is a **view** onto the input buffer (no copy); callers must not assume
|
|
29
|
+
* its lifetime and should `slice()` when they need to keep it;
|
|
30
|
+
* - Parse errors are always `Asn1Error`; upper layers (X.509/CMS) map them to
|
|
31
|
+
* their own error types.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* ASN.1 标签类别。
|
|
35
|
+
*
|
|
36
|
+
* ASN.1 tag class.
|
|
37
|
+
*/
|
|
38
|
+
export type Asn1Class = 0 | 1 | 2 | 3;
|
|
39
|
+
/**
|
|
40
|
+
* ASN.1 标签类别常量。
|
|
41
|
+
*
|
|
42
|
+
* ASN.1 tag class constants.
|
|
43
|
+
*/
|
|
44
|
+
export declare const Asn1Class: {
|
|
45
|
+
readonly Universal: 0;
|
|
46
|
+
readonly Application: 1;
|
|
47
|
+
readonly ContextSpecific: 2;
|
|
48
|
+
readonly Private: 3;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* UNIVERSAL 类常用标签编号。
|
|
52
|
+
*
|
|
53
|
+
* Frequently used UNIVERSAL tag numbers.
|
|
54
|
+
*/
|
|
55
|
+
export declare const UniversalTag: {
|
|
56
|
+
readonly EndOfContent: 0;
|
|
57
|
+
readonly Boolean: 1;
|
|
58
|
+
readonly Integer: 2;
|
|
59
|
+
readonly BitString: 3;
|
|
60
|
+
readonly OctetString: 4;
|
|
61
|
+
readonly Null: 5;
|
|
62
|
+
readonly ObjectIdentifier: 6;
|
|
63
|
+
readonly Enumerated: 10;
|
|
64
|
+
readonly Utf8String: 12;
|
|
65
|
+
readonly Sequence: 16;
|
|
66
|
+
readonly Set: 17;
|
|
67
|
+
readonly NumericString: 18;
|
|
68
|
+
readonly PrintableString: 19;
|
|
69
|
+
readonly TeletexString: 20;
|
|
70
|
+
readonly VideotexString: 21;
|
|
71
|
+
readonly Ia5String: 22;
|
|
72
|
+
readonly UtcTime: 23;
|
|
73
|
+
readonly GeneralizedTime: 24;
|
|
74
|
+
readonly UniversalString: 28;
|
|
75
|
+
readonly BmpString: 30;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* ASN.1 节点。
|
|
79
|
+
*
|
|
80
|
+
* An ASN.1 node.
|
|
81
|
+
*/
|
|
82
|
+
export interface Asn1Node {
|
|
83
|
+
/**
|
|
84
|
+
* 标签类别:UNIVERSAL=0、APPLICATION=1、CONTEXT=2、PRIVATE=3。
|
|
85
|
+
*
|
|
86
|
+
* Tag class: UNIVERSAL=0, APPLICATION=1, CONTEXT=2, PRIVATE=3.
|
|
87
|
+
*/
|
|
88
|
+
readonly cls: Asn1Class;
|
|
89
|
+
/**
|
|
90
|
+
* 标签编号(高 tag number 已展开为数值,不含 0x1f 标志位)。
|
|
91
|
+
*
|
|
92
|
+
* Tag number (high tag numbers are already decoded to a plain number, without
|
|
93
|
+
* the 0x1f marker bit).
|
|
94
|
+
*/
|
|
95
|
+
readonly tag: number;
|
|
96
|
+
/**
|
|
97
|
+
* 是否为 constructed 编码。
|
|
98
|
+
*
|
|
99
|
+
* Whether the encoding is constructed.
|
|
100
|
+
*/
|
|
101
|
+
readonly constructed: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* 是否使用 BER indefinite-length(`0x80` + EOC)。DER 节点恒为 false。
|
|
104
|
+
*
|
|
105
|
+
* Whether BER indefinite-length (`0x80` + EOC) is used; always false for DER.
|
|
106
|
+
*/
|
|
107
|
+
readonly indefinite: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* 内容字节数(indefinite 时为实际内容长度,不含 EOC)。
|
|
110
|
+
*
|
|
111
|
+
* Content length in bytes (for indefinite nodes this is the real content length,
|
|
112
|
+
* excluding the EOC).
|
|
113
|
+
*/
|
|
114
|
+
readonly length: number;
|
|
115
|
+
/**
|
|
116
|
+
* primitive 的 value;constructed 为子节点编码拼接后的内容字节。
|
|
117
|
+
*
|
|
118
|
+
* The value of a primitive node; for a constructed node, the concatenated
|
|
119
|
+
* encodings of its children.
|
|
120
|
+
*/
|
|
121
|
+
readonly value: Uint8Array;
|
|
122
|
+
/**
|
|
123
|
+
* constructed 节点的子节点;primitive 为 `undefined`。
|
|
124
|
+
*
|
|
125
|
+
* Children of a constructed node; `undefined` for primitive nodes.
|
|
126
|
+
*/
|
|
127
|
+
readonly children?: readonly Asn1Node[];
|
|
128
|
+
/**
|
|
129
|
+
* 节点原始字节(含 tag + length + value;indefinite 时含 EOC)。
|
|
130
|
+
*
|
|
131
|
+
* Raw node bytes (tag + length + value; including the EOC when indefinite).
|
|
132
|
+
*/
|
|
133
|
+
readonly raw: Uint8Array;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* ASN.1 解析/编码错误。
|
|
137
|
+
*
|
|
138
|
+
* ASN.1 parse/encode error.
|
|
139
|
+
*/
|
|
140
|
+
export declare class Asn1Error extends Error {
|
|
141
|
+
readonly name = "Asn1Error";
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 判断节点是否为指定 UNIVERSAL 类型。
|
|
145
|
+
*
|
|
146
|
+
* Tests whether a node is the given UNIVERSAL type.
|
|
147
|
+
*/
|
|
148
|
+
export declare function isUniversal(node: Asn1Node, tag: number): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* 断言节点为指定 UNIVERSAL 类型,否则抛 `Asn1Error`。
|
|
151
|
+
*
|
|
152
|
+
* Asserts that a node is the given UNIVERSAL type, otherwise throws `Asn1Error`.
|
|
153
|
+
*/
|
|
154
|
+
export declare function expectUniversal(node: Asn1Node | undefined, tag: number, what: string): Asn1Node;
|
|
155
|
+
/**
|
|
156
|
+
* 断言节点为 constructed SEQUENCE 并返回子节点。
|
|
157
|
+
*
|
|
158
|
+
* Asserts that a node is a constructed SEQUENCE and returns its children.
|
|
159
|
+
*/
|
|
160
|
+
export declare function expectSequence(node: Asn1Node | undefined, what: string): readonly Asn1Node[];
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
* Shared ASN.1 types and constants (a DER/BER subset).
|
|
17
|
+
*
|
|
18
|
+
* Relationship to the legacy `src/pem/asn1.ts`:
|
|
19
|
+
* - The old implementation only covered the minimal types used by PKCS#8 and did
|
|
20
|
+
* not preserve the `constructed` bit;
|
|
21
|
+
* - This layer is the shared infrastructure for X.509 / CMS / WebCrypto: it covers
|
|
22
|
+
* BOOLEAN, SET, string types, time, and context/application/private tags.
|
|
23
|
+
*
|
|
24
|
+
* Design notes:
|
|
25
|
+
* - Each node keeps `raw` (the original slice including tag + length + value).
|
|
26
|
+
* X.509 TBS digests and CMS `signedAttrs` verification must hash the original
|
|
27
|
+
* bytes, never a re-encoding;
|
|
28
|
+
* - `raw` is a **view** onto the input buffer (no copy); callers must not assume
|
|
29
|
+
* its lifetime and should `slice()` when they need to keep it;
|
|
30
|
+
* - Parse errors are always `Asn1Error`; upper layers (X.509/CMS) map them to
|
|
31
|
+
* their own error types.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* ASN.1 标签类别常量。
|
|
35
|
+
*
|
|
36
|
+
* ASN.1 tag class constants.
|
|
37
|
+
*/
|
|
38
|
+
export const Asn1Class = {
|
|
39
|
+
Universal: 0,
|
|
40
|
+
Application: 1,
|
|
41
|
+
ContextSpecific: 2,
|
|
42
|
+
Private: 3,
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* UNIVERSAL 类常用标签编号。
|
|
46
|
+
*
|
|
47
|
+
* Frequently used UNIVERSAL tag numbers.
|
|
48
|
+
*/
|
|
49
|
+
export const UniversalTag = {
|
|
50
|
+
EndOfContent: 0,
|
|
51
|
+
Boolean: 1,
|
|
52
|
+
Integer: 2,
|
|
53
|
+
BitString: 3,
|
|
54
|
+
OctetString: 4,
|
|
55
|
+
Null: 5,
|
|
56
|
+
ObjectIdentifier: 6,
|
|
57
|
+
Enumerated: 10,
|
|
58
|
+
Utf8String: 12,
|
|
59
|
+
Sequence: 16,
|
|
60
|
+
Set: 17,
|
|
61
|
+
NumericString: 18,
|
|
62
|
+
PrintableString: 19,
|
|
63
|
+
TeletexString: 20,
|
|
64
|
+
VideotexString: 21,
|
|
65
|
+
Ia5String: 22,
|
|
66
|
+
UtcTime: 23,
|
|
67
|
+
GeneralizedTime: 24,
|
|
68
|
+
UniversalString: 28,
|
|
69
|
+
BmpString: 30,
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* ASN.1 解析/编码错误。
|
|
73
|
+
*
|
|
74
|
+
* ASN.1 parse/encode error.
|
|
75
|
+
*/
|
|
76
|
+
export class Asn1Error extends Error {
|
|
77
|
+
name = 'Asn1Error';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 判断节点是否为指定 UNIVERSAL 类型。
|
|
81
|
+
*
|
|
82
|
+
* Tests whether a node is the given UNIVERSAL type.
|
|
83
|
+
*/
|
|
84
|
+
export function isUniversal(node, tag) {
|
|
85
|
+
return node.cls === Asn1Class.Universal && node.tag === tag;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 断言节点为指定 UNIVERSAL 类型,否则抛 `Asn1Error`。
|
|
89
|
+
*
|
|
90
|
+
* Asserts that a node is the given UNIVERSAL type, otherwise throws `Asn1Error`.
|
|
91
|
+
*/
|
|
92
|
+
export function expectUniversal(node, tag, what) {
|
|
93
|
+
if (node === undefined) {
|
|
94
|
+
throw new Asn1Error(`${what}: missing element (expected tag ${tag})`);
|
|
95
|
+
}
|
|
96
|
+
if (!isUniversal(node, tag)) {
|
|
97
|
+
throw new Asn1Error(`${what}: expected UNIVERSAL tag ${tag}, got class ${node.cls} tag ${node.tag}`);
|
|
98
|
+
}
|
|
99
|
+
return node;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 断言节点为 constructed SEQUENCE 并返回子节点。
|
|
103
|
+
*
|
|
104
|
+
* Asserts that a node is a constructed SEQUENCE and returns its children.
|
|
105
|
+
*/
|
|
106
|
+
export function expectSequence(node, what) {
|
|
107
|
+
const seq = expectUniversal(node, UniversalTag.Sequence, what);
|
|
108
|
+
if (!seq.children) {
|
|
109
|
+
throw new Asn1Error(`${what}: SEQUENCE is not constructed`);
|
|
110
|
+
}
|
|
111
|
+
return seq.children;
|
|
112
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CMS(PKCS#7 SignedData)错误类型。
|
|
3
|
+
*
|
|
4
|
+
* CMS (PKCS#7 SignedData) error types.
|
|
5
|
+
*/
|
|
6
|
+
import { SmCryptoError } from '../runtime/errors.js';
|
|
7
|
+
/**
|
|
8
|
+
* CMS 解析/签名/验签失败。
|
|
9
|
+
*
|
|
10
|
+
* CMS parse/sign/verify failure.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CmsError extends SmCryptoError {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
constructor(message: string, options?: {
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `verifySignedData` 的结构化失败原因。
|
|
20
|
+
*
|
|
21
|
+
* Structured failure reasons returned by `verifySignedData`.
|
|
22
|
+
*/
|
|
23
|
+
export type CmsVerifyErrorCode = 'NO_CERTIFICATE' | 'DIGEST_MISMATCH' | 'SIGNATURE_INVALID' | 'UNSUPPORTED_ALGORITHM' | 'MALFORMED';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CMS(PKCS#7 SignedData)错误类型。
|
|
3
|
+
*
|
|
4
|
+
* CMS (PKCS#7 SignedData) error types.
|
|
5
|
+
*/
|
|
6
|
+
import { SmCryptoError } from '../runtime/errors.js';
|
|
7
|
+
/**
|
|
8
|
+
* CMS 解析/签名/验签失败。
|
|
9
|
+
*
|
|
10
|
+
* CMS parse/sign/verify failure.
|
|
11
|
+
*/
|
|
12
|
+
export class CmsError extends SmCryptoError {
|
|
13
|
+
name = 'CmsError';
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
}
|
|
17
|
+
}
|