sm-crypto-wasm 0.3.1 → 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 +70 -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/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 +15 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 错误类型。
|
|
3
|
+
*
|
|
4
|
+
* 所有证书结构/校验错误都是 `CertificateError`(`SmCryptoError` 的子类),
|
|
5
|
+
* 便于调用方与其它库错误一起捕获。
|
|
6
|
+
*/
|
|
7
|
+
import { SmCryptoError } from '../runtime/errors.js';
|
|
8
|
+
/** 证书解析或校验失败。 */
|
|
9
|
+
export class CertificateError extends SmCryptoError {
|
|
10
|
+
name = 'CertificateError';
|
|
11
|
+
constructor(message, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 扩展解析。
|
|
3
|
+
*
|
|
4
|
+
* 原则(红线 #9):未知扩展、未知值类型一律保留原始字节,不抛错;
|
|
5
|
+
* 已知扩展内容损坏时返回 `undefined`,由调用方按“扩展缺失”处理。
|
|
6
|
+
*/
|
|
7
|
+
import type { AuthorityKeyIdentifier, BasicConstraints, GeneralName, KeyUsage } from './types.js';
|
|
8
|
+
/** 解析 KeyUsage(BIT STRING)。 */
|
|
9
|
+
export declare function parseKeyUsage(value: Uint8Array): KeyUsage;
|
|
10
|
+
/** 解析 BasicConstraints。 */
|
|
11
|
+
export declare function parseBasicConstraints(value: Uint8Array): BasicConstraints;
|
|
12
|
+
/** 解析 SubjectAltName(SEQUENCE OF GeneralName)。 */
|
|
13
|
+
export declare function parseSubjectAltName(value: Uint8Array): GeneralName[];
|
|
14
|
+
/** 解析 SubjectKeyIdentifier(OCTET STRING)。 */
|
|
15
|
+
export declare function parseSubjectKeyIdentifier(value: Uint8Array): Uint8Array;
|
|
16
|
+
/** 解析 AuthorityKeyIdentifier。 */
|
|
17
|
+
export declare function parseAuthorityKeyIdentifier(value: Uint8Array): AuthorityKeyIdentifier;
|
|
18
|
+
/** 解析 ExtendedKeyUsage(SEQUENCE OF OID)。 */
|
|
19
|
+
export declare function parseExtendedKeyUsage(value: Uint8Array): string[];
|
|
20
|
+
/**
|
|
21
|
+
* 按扩展 OID 解析已知扩展;未知 OID 返回 `undefined`,不抛错。
|
|
22
|
+
*
|
|
23
|
+
* @internal 供 `parse.ts` 与签发逻辑复用。
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseKnownExtension(oid: string, value: Uint8Array): KeyUsage | BasicConstraints | GeneralName[] | Uint8Array | AuthorityKeyIdentifier | string[] | undefined;
|
|
26
|
+
/** 4 字节 IPv4 / 16 字节 IPv6 → 文本。 */
|
|
27
|
+
export declare function formatIp(bytes: Uint8Array): string;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 扩展解析。
|
|
3
|
+
*
|
|
4
|
+
* 原则(红线 #9):未知扩展、未知值类型一律保留原始字节,不抛错;
|
|
5
|
+
* 已知扩展内容损坏时返回 `undefined`,由调用方按“扩展缺失”处理。
|
|
6
|
+
*/
|
|
7
|
+
import { Asn1Class, expectSequence, parseDer, readBitString, readOctetString, readOid, readString, UniversalTag, } from '../asn1/index.js';
|
|
8
|
+
import { bytesToHex } from '../runtime/convert.js';
|
|
9
|
+
import { OID_AUTHORITY_KEY_IDENTIFIER, OID_BASIC_CONSTRAINTS, OID_EXTENDED_KEY_USAGE, OID_KEY_USAGE, OID_SUBJECT_ALT_NAME, OID_SUBJECT_KEY_IDENTIFIER, } from './oids.js';
|
|
10
|
+
/** 解析 KeyUsage(BIT STRING)。 */
|
|
11
|
+
export function parseKeyUsage(value) {
|
|
12
|
+
const node = parseExtensionValue(value, 'KeyUsage');
|
|
13
|
+
const bits = readBitString(node, 'KeyUsage');
|
|
14
|
+
const get = (index) => {
|
|
15
|
+
const byte = bits.bytes[Math.floor(index / 8)];
|
|
16
|
+
if (byte === undefined)
|
|
17
|
+
return false;
|
|
18
|
+
return ((byte >> (7 - (index % 8))) & 1) === 1;
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
digitalSignature: get(0),
|
|
22
|
+
nonRepudiation: get(1),
|
|
23
|
+
keyEncipherment: get(2),
|
|
24
|
+
dataEncipherment: get(3),
|
|
25
|
+
keyAgreement: get(4),
|
|
26
|
+
keyCertSign: get(5),
|
|
27
|
+
cRLSign: get(6),
|
|
28
|
+
encipherOnly: get(7),
|
|
29
|
+
decipherOnly: get(8),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** 解析 BasicConstraints。 */
|
|
33
|
+
export function parseBasicConstraints(value) {
|
|
34
|
+
const node = parseExtensionValue(value, 'BasicConstraints');
|
|
35
|
+
const children = expectSequence(node, 'BasicConstraints');
|
|
36
|
+
let ca = false;
|
|
37
|
+
let pathLen;
|
|
38
|
+
let index = 0;
|
|
39
|
+
const first = children[0];
|
|
40
|
+
if (first !== undefined && first.cls === Asn1Class.Universal && first.tag === 1) {
|
|
41
|
+
if (first.value.length !== 1)
|
|
42
|
+
throw new Error('BasicConstraints: bad BOOLEAN');
|
|
43
|
+
ca = first.value[0] !== 0;
|
|
44
|
+
index = 1;
|
|
45
|
+
}
|
|
46
|
+
const lenNode = children[index];
|
|
47
|
+
if (lenNode !== undefined) {
|
|
48
|
+
if (lenNode.cls !== Asn1Class.Universal || lenNode.tag !== UniversalTag.Integer) {
|
|
49
|
+
throw new Error('BasicConstraints: pathLenConstraint is not INTEGER');
|
|
50
|
+
}
|
|
51
|
+
let n = 0;
|
|
52
|
+
for (let i = 0; i < lenNode.value.length; i++) {
|
|
53
|
+
n = n * 256 + lenNode.value[i];
|
|
54
|
+
if (!Number.isSafeInteger(n))
|
|
55
|
+
throw new Error('BasicConstraints: pathLen too large');
|
|
56
|
+
}
|
|
57
|
+
pathLen = n;
|
|
58
|
+
}
|
|
59
|
+
return pathLen === undefined ? { ca } : { ca, pathLen };
|
|
60
|
+
}
|
|
61
|
+
/** 解析 SubjectAltName(SEQUENCE OF GeneralName)。 */
|
|
62
|
+
export function parseSubjectAltName(value) {
|
|
63
|
+
const node = parseExtensionValue(value, 'SubjectAltName');
|
|
64
|
+
const children = expectSequence(node, 'SubjectAltName');
|
|
65
|
+
return children.map(parseGeneralName);
|
|
66
|
+
}
|
|
67
|
+
/** 解析 SubjectKeyIdentifier(OCTET STRING)。 */
|
|
68
|
+
export function parseSubjectKeyIdentifier(value) {
|
|
69
|
+
const node = parseExtensionValue(value, 'SubjectKeyIdentifier');
|
|
70
|
+
return new Uint8Array(readOctetString(node, 'SubjectKeyIdentifier'));
|
|
71
|
+
}
|
|
72
|
+
/** 解析 AuthorityKeyIdentifier。 */
|
|
73
|
+
export function parseAuthorityKeyIdentifier(value) {
|
|
74
|
+
const node = parseExtensionValue(value, 'AuthorityKeyIdentifier');
|
|
75
|
+
const children = expectSequence(node, 'AuthorityKeyIdentifier');
|
|
76
|
+
let keyIdentifier;
|
|
77
|
+
let authorityCertSerialNumber;
|
|
78
|
+
for (const child of children) {
|
|
79
|
+
if (child.cls !== Asn1Class.ContextSpecific)
|
|
80
|
+
continue;
|
|
81
|
+
if (child.tag === 0) {
|
|
82
|
+
keyIdentifier = new Uint8Array(child.value);
|
|
83
|
+
}
|
|
84
|
+
else if (child.tag === 2) {
|
|
85
|
+
authorityCertSerialNumber = positiveIntegerFromBytes(child.value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return authorityCertSerialNumber === undefined
|
|
89
|
+
? keyIdentifier === undefined
|
|
90
|
+
? {}
|
|
91
|
+
: { keyIdentifier }
|
|
92
|
+
: keyIdentifier === undefined
|
|
93
|
+
? { authorityCertSerialNumber }
|
|
94
|
+
: { keyIdentifier, authorityCertSerialNumber };
|
|
95
|
+
}
|
|
96
|
+
/** 解析 ExtendedKeyUsage(SEQUENCE OF OID)。 */
|
|
97
|
+
export function parseExtendedKeyUsage(value) {
|
|
98
|
+
const node = parseExtensionValue(value, 'ExtendedKeyUsage');
|
|
99
|
+
const children = expectSequence(node, 'ExtendedKeyUsage');
|
|
100
|
+
return children.map((child) => readOid(child, 'ExtendedKeyUsage'));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 按扩展 OID 解析已知扩展;未知 OID 返回 `undefined`,不抛错。
|
|
104
|
+
*
|
|
105
|
+
* @internal 供 `parse.ts` 与签发逻辑复用。
|
|
106
|
+
*/
|
|
107
|
+
export function parseKnownExtension(oid, value) {
|
|
108
|
+
try {
|
|
109
|
+
switch (oid) {
|
|
110
|
+
case OID_KEY_USAGE:
|
|
111
|
+
return parseKeyUsage(value);
|
|
112
|
+
case OID_BASIC_CONSTRAINTS:
|
|
113
|
+
return parseBasicConstraints(value);
|
|
114
|
+
case OID_SUBJECT_ALT_NAME:
|
|
115
|
+
return parseSubjectAltName(value);
|
|
116
|
+
case OID_SUBJECT_KEY_IDENTIFIER:
|
|
117
|
+
return parseSubjectKeyIdentifier(value);
|
|
118
|
+
case OID_AUTHORITY_KEY_IDENTIFIER:
|
|
119
|
+
return parseAuthorityKeyIdentifier(value);
|
|
120
|
+
case OID_EXTENDED_KEY_USAGE:
|
|
121
|
+
return parseExtendedKeyUsage(value);
|
|
122
|
+
default:
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// 已知扩展损坏:按“扩展缺失”处理,绝不让整张证书解析失败。
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function parseExtensionValue(value, what) {
|
|
132
|
+
try {
|
|
133
|
+
return parseDer(value);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
throw new Error(`${what}: invalid extension value`, { cause: err });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function parseGeneralName(node) {
|
|
140
|
+
if (node.cls !== Asn1Class.ContextSpecific) {
|
|
141
|
+
throw new Error(`GeneralName: unexpected class ${node.cls}`);
|
|
142
|
+
}
|
|
143
|
+
const raw = node.raw;
|
|
144
|
+
switch (node.tag) {
|
|
145
|
+
case 1:
|
|
146
|
+
return { type: 'email', value: readString(asStringNode(node), 'GeneralName'), raw };
|
|
147
|
+
case 2:
|
|
148
|
+
return { type: 'dns', value: readString(asStringNode(node), 'GeneralName'), raw };
|
|
149
|
+
case 6:
|
|
150
|
+
return { type: 'uri', value: readString(asStringNode(node), 'GeneralName'), raw };
|
|
151
|
+
case 7:
|
|
152
|
+
return { type: 'ip', value: formatIp(node.value), raw };
|
|
153
|
+
default:
|
|
154
|
+
return { type: 'other', tag: node.tag, value: `#${bytesToHex(node.value)}`, raw };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** IMPLICIT IA5String:把内容字节包装成 UNIVERSAL IA5String 节点供 readString 使用。 */
|
|
158
|
+
function asStringNode(node) {
|
|
159
|
+
return {
|
|
160
|
+
cls: Asn1Class.Universal,
|
|
161
|
+
tag: UniversalTag.Ia5String,
|
|
162
|
+
constructed: false,
|
|
163
|
+
indefinite: false,
|
|
164
|
+
length: node.value.length,
|
|
165
|
+
value: node.value,
|
|
166
|
+
raw: node.raw,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** 4 字节 IPv4 / 16 字节 IPv6 → 文本。 */
|
|
170
|
+
export function formatIp(bytes) {
|
|
171
|
+
if (bytes.length === 4) {
|
|
172
|
+
return `${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}`;
|
|
173
|
+
}
|
|
174
|
+
if (bytes.length !== 16)
|
|
175
|
+
return `#${bytesToHex(bytes)}`;
|
|
176
|
+
const groups = [];
|
|
177
|
+
for (let i = 0; i < 16; i += 2) {
|
|
178
|
+
groups.push((bytes[i] << 8) | bytes[i + 1]);
|
|
179
|
+
}
|
|
180
|
+
// 找出最长的一段连续 0(长度 >= 2)压缩为 `::`。
|
|
181
|
+
let bestStart = -1;
|
|
182
|
+
let bestLen = 0;
|
|
183
|
+
for (let i = 0; i < 8;) {
|
|
184
|
+
if (groups[i] !== 0) {
|
|
185
|
+
i++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
let j = i;
|
|
189
|
+
while (j < 8 && groups[j] === 0)
|
|
190
|
+
j++;
|
|
191
|
+
if (j - i > bestLen) {
|
|
192
|
+
bestStart = i;
|
|
193
|
+
bestLen = j - i;
|
|
194
|
+
}
|
|
195
|
+
i = j;
|
|
196
|
+
}
|
|
197
|
+
if (bestLen < 2)
|
|
198
|
+
return groups.map((g) => g.toString(16)).join(':');
|
|
199
|
+
const left = groups
|
|
200
|
+
.slice(0, bestStart)
|
|
201
|
+
.map((g) => g.toString(16))
|
|
202
|
+
.join(':');
|
|
203
|
+
const right = groups
|
|
204
|
+
.slice(bestStart + bestLen)
|
|
205
|
+
.map((g) => g.toString(16))
|
|
206
|
+
.join(':');
|
|
207
|
+
return `${left}::${right}`;
|
|
208
|
+
}
|
|
209
|
+
/** 正 INTEGER 内容字节 → bigint(用于 [2] IMPLICIT INTEGER)。 */
|
|
210
|
+
function positiveIntegerFromBytes(bytes) {
|
|
211
|
+
let v = 0n;
|
|
212
|
+
for (let i = 0; i < bytes.length; i++)
|
|
213
|
+
v = (v << 8n) | BigInt(bytes[i]);
|
|
214
|
+
return v;
|
|
215
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 子模块(`sm-crypto-wasm/x509`)。
|
|
3
|
+
*
|
|
4
|
+
* 范围:证书解析、单证书签名验证、自签/CA 签发。**不做证书链路径校验**、
|
|
5
|
+
* 不拉 CRL/OCSP;调用方需要按 issuer 逐级调用 `verifySignature`。
|
|
6
|
+
*/
|
|
7
|
+
export { CertificateError } from './errors.js';
|
|
8
|
+
export type { SelfSignedCertificateOptions } from './issue.js';
|
|
9
|
+
export { createSelfSignedCertificate, issueCertificate, parseDistinguishedName, } from './issue.js';
|
|
10
|
+
export * from './oids.js';
|
|
11
|
+
export { parseCertificate, parseCertificates } from './parse.js';
|
|
12
|
+
export type { AlgorithmIdentifier, AuthorityKeyIdentifier, BasicConstraints, BasicConstraintsInput, Certificate, CertificateInput, CertificateSigner, DistinguishedNameInput, Extension, GeneralName, IssueCertificateOptions, IssueCertificateResult, KeyUsage, KeyUsageName, Name, NameAttribute, SubjectAltNameInput, SubjectPublicKeyInfo, Validity, } from './types.js';
|
|
13
|
+
export type { VerifySignatureOptions } from './verify.js';
|
|
14
|
+
export { DEFAULT_SM2_USER_ID, isWithinValidity, verifyCertificateSignature } from './verify.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 子模块(`sm-crypto-wasm/x509`)。
|
|
3
|
+
*
|
|
4
|
+
* 范围:证书解析、单证书签名验证、自签/CA 签发。**不做证书链路径校验**、
|
|
5
|
+
* 不拉 CRL/OCSP;调用方需要按 issuer 逐级调用 `verifySignature`。
|
|
6
|
+
*/
|
|
7
|
+
export { CertificateError } from './errors.js';
|
|
8
|
+
export { createSelfSignedCertificate, issueCertificate, parseDistinguishedName, } from './issue.js';
|
|
9
|
+
export * from './oids.js';
|
|
10
|
+
export { parseCertificate, parseCertificates } from './parse.js';
|
|
11
|
+
export { DEFAULT_SM2_USER_ID, isWithinValidity, verifyCertificateSignature } from './verify.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP 地址解析(用于 SubjectAltName 的 iPAddress)。
|
|
3
|
+
*
|
|
4
|
+
* 输出 4 字节(IPv4)或 16 字节(IPv6)。IPv6 支持 `::` 压缩与
|
|
5
|
+
* 末尾内嵌 IPv4(如 `::ffff:192.168.0.1`)。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 把 IPv4 / IPv6 文本解析为地址字节。
|
|
9
|
+
*
|
|
10
|
+
* @throws CertificateError 格式非法。
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseIpAddress(value: string): Uint8Array;
|
package/dist/x509/ip.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP 地址解析(用于 SubjectAltName 的 iPAddress)。
|
|
3
|
+
*
|
|
4
|
+
* 输出 4 字节(IPv4)或 16 字节(IPv6)。IPv6 支持 `::` 压缩与
|
|
5
|
+
* 末尾内嵌 IPv4(如 `::ffff:192.168.0.1`)。
|
|
6
|
+
*/
|
|
7
|
+
import { CertificateError } from './errors.js';
|
|
8
|
+
/**
|
|
9
|
+
* 把 IPv4 / IPv6 文本解析为地址字节。
|
|
10
|
+
*
|
|
11
|
+
* @throws CertificateError 格式非法。
|
|
12
|
+
*/
|
|
13
|
+
export function parseIpAddress(value) {
|
|
14
|
+
const text = value.trim();
|
|
15
|
+
if (text.length === 0)
|
|
16
|
+
throw new CertificateError('subjectAltName: empty IP address');
|
|
17
|
+
if (text.includes(':'))
|
|
18
|
+
return parseIpv6(text);
|
|
19
|
+
return parseIpv4(text);
|
|
20
|
+
}
|
|
21
|
+
function parseIpv4(text) {
|
|
22
|
+
const parts = text.split('.');
|
|
23
|
+
if (parts.length !== 4)
|
|
24
|
+
throw new CertificateError(`invalid IPv4 address "${text}"`);
|
|
25
|
+
const out = new Uint8Array(4);
|
|
26
|
+
for (let i = 0; i < 4; i++) {
|
|
27
|
+
const part = parts[i];
|
|
28
|
+
if (!/^(0|[1-9][0-9]{0,2})$/.test(part)) {
|
|
29
|
+
throw new CertificateError(`invalid IPv4 address "${text}"`);
|
|
30
|
+
}
|
|
31
|
+
const n = Number(part);
|
|
32
|
+
if (n > 255)
|
|
33
|
+
throw new CertificateError(`invalid IPv4 address "${text}"`);
|
|
34
|
+
out[i] = n;
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function parseIpv6(text) {
|
|
39
|
+
const doubleColon = text.indexOf('::');
|
|
40
|
+
let headParts;
|
|
41
|
+
let tailParts;
|
|
42
|
+
let compressed = false;
|
|
43
|
+
if (doubleColon >= 0) {
|
|
44
|
+
if (text.indexOf('::', doubleColon + 2) >= 0) {
|
|
45
|
+
throw new CertificateError(`invalid IPv6 address "${text}"`);
|
|
46
|
+
}
|
|
47
|
+
compressed = true;
|
|
48
|
+
headParts = splitPart(text.slice(0, doubleColon));
|
|
49
|
+
tailParts = splitPart(text.slice(doubleColon + 2));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
headParts = splitPart(text);
|
|
53
|
+
tailParts = [];
|
|
54
|
+
}
|
|
55
|
+
const head = groupsOf(headParts, false, text);
|
|
56
|
+
const tail = groupsOf(tailParts, true, text);
|
|
57
|
+
let groups;
|
|
58
|
+
if (!compressed) {
|
|
59
|
+
if (head.length !== 8)
|
|
60
|
+
throw new CertificateError(`invalid IPv6 address "${text}"`);
|
|
61
|
+
groups = head;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const total = head.length + tail.length;
|
|
65
|
+
if (total > 7)
|
|
66
|
+
throw new CertificateError(`invalid IPv6 address "${text}"`);
|
|
67
|
+
groups = [...head, ...new Array(8 - total).fill(0), ...tail];
|
|
68
|
+
}
|
|
69
|
+
const out = new Uint8Array(16);
|
|
70
|
+
for (let i = 0; i < 8; i++) {
|
|
71
|
+
const group = groups[i];
|
|
72
|
+
out[i * 2] = (group >> 8) & 0xff;
|
|
73
|
+
out[i * 2 + 1] = group & 0xff;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function splitPart(part) {
|
|
78
|
+
if (part.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
return part.split(':').filter((item) => item !== '');
|
|
81
|
+
}
|
|
82
|
+
function groupsOf(parts, allowIpv4AtEnd, text) {
|
|
83
|
+
const out = [];
|
|
84
|
+
for (let i = 0; i < parts.length; i++) {
|
|
85
|
+
const part = parts[i];
|
|
86
|
+
if (part.includes('.')) {
|
|
87
|
+
if (!allowIpv4AtEnd || i !== parts.length - 1) {
|
|
88
|
+
throw new CertificateError(`invalid IPv6 address "${text}"`);
|
|
89
|
+
}
|
|
90
|
+
const v4 = parseIpv4(part);
|
|
91
|
+
out.push((v4[0] << 8) | v4[1]);
|
|
92
|
+
out.push((v4[2] << 8) | v4[3]);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(part)) {
|
|
96
|
+
throw new CertificateError(`invalid IPv6 address "${text}"`);
|
|
97
|
+
}
|
|
98
|
+
out.push(Number.parseInt(part, 16));
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 证书签发(自签 + 用 CA 证书签发终端证书)。
|
|
3
|
+
*
|
|
4
|
+
* 安全边界(红线 #4 / #5 / #6):
|
|
5
|
+
* - 证书版本固定 v3;
|
|
6
|
+
* - `basicConstraints.ca=false` 的证书不得用于签发子证书,且该证书不能带
|
|
7
|
+
* `keyUsage: keyCertSign`;
|
|
8
|
+
* - 用作签发者的证书必须是 CA(`basicConstraints.ca === true`),若带 KeyUsage
|
|
9
|
+
* 必须包含 `keyCertSign`;
|
|
10
|
+
* - CA 证书的 KeyUsage(若提供)必须包含 `keyCertSign`;
|
|
11
|
+
* - 序列号为正整数;默认生成 16 字节随机数并把最高位清零;
|
|
12
|
+
* - SAN 的 dNSName / rfc822Name / URI 必须为 ASCII,iPAddress 必须是合法 4/16 字节。
|
|
13
|
+
*
|
|
14
|
+
* 本模块**不生成密钥、不管理私钥**:私钥由调用方提供,签名后不驻留。
|
|
15
|
+
*/
|
|
16
|
+
import type { Certificate, DistinguishedNameInput, IssueCertificateOptions, IssueCertificateResult } from './types.js';
|
|
17
|
+
/** `createSelfSignedCertificate` 的选项。 */
|
|
18
|
+
export type SelfSignedCertificateOptions = Omit<IssueCertificateOptions, 'issuer' | 'signer'> & {
|
|
19
|
+
readonly privateKey: string | Uint8Array;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 签发一张 SM2 证书。
|
|
23
|
+
*
|
|
24
|
+
* @throws CertificateError 输入不满足证书约束。
|
|
25
|
+
* @throws RangeError 序列号/有效期/路径长度越界。
|
|
26
|
+
*/
|
|
27
|
+
export declare function issueCertificate(opts: IssueCertificateOptions): IssueCertificateResult;
|
|
28
|
+
/**
|
|
29
|
+
* 生成自签证书(issuer = subject)。
|
|
30
|
+
*
|
|
31
|
+
* @throws CertificateError / RangeError 同 `issueCertificate`。
|
|
32
|
+
*/
|
|
33
|
+
export declare function createSelfSignedCertificate(opts: SelfSignedCertificateOptions): IssueCertificateResult;
|
|
34
|
+
/**
|
|
35
|
+
* 解析 RFC 4514 子集形式的 DN 字符串,如 `"C=CN,O=Example,CN=Example CA"`。
|
|
36
|
+
*
|
|
37
|
+
* 支持反斜杠转义(`\,`、`\=`、`\\`);不做完整 RFC 4514(多值 RDN、
|
|
38
|
+
* `#hex` 编码、十六进制转义等)。
|
|
39
|
+
*
|
|
40
|
+
* @throws CertificateError 格式非法或属性名未知。
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseDistinguishedName(input: string): DistinguishedNameInput;
|
|
43
|
+
/** 保留类型引用,供生成的声明文件使用。 */
|
|
44
|
+
export type { Certificate };
|