@gjsify/crypto 0.3.12 → 0.3.14
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/lib/esm/asn1.js +576 -450
- package/lib/esm/bigint-math.js +37 -28
- package/lib/esm/cipher.js +1252 -1229
- package/lib/esm/constants.js +12 -13
- package/lib/esm/crypto-utils.js +54 -36
- package/lib/esm/dh.js +408 -368
- package/lib/esm/ecdh.js +403 -321
- package/lib/esm/ecdsa.js +138 -111
- package/lib/esm/hash.js +100 -89
- package/lib/esm/hkdf.js +65 -47
- package/lib/esm/hmac.js +95 -90
- package/lib/esm/index.js +75 -148
- package/lib/esm/key-object.js +348 -307
- package/lib/esm/mgf1.js +30 -24
- package/lib/esm/pbkdf2.js +66 -59
- package/lib/esm/public-encrypt.js +203 -156
- package/lib/esm/random.js +137 -124
- package/lib/esm/rsa-oaep.js +94 -87
- package/lib/esm/rsa-pss.js +95 -88
- package/lib/esm/scrypt.js +116 -115
- package/lib/esm/sign.js +267 -237
- package/lib/esm/timing-safe-equal.js +16 -11
- package/lib/esm/x509.js +215 -206
- package/package.json +7 -7
package/lib/esm/asn1.js
CHANGED
|
@@ -1,504 +1,630 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
//#region src/asn1.ts
|
|
4
|
+
/**
|
|
5
|
+
* Strip PEM armor (header/footer lines), base64-decode the body to DER bytes.
|
|
6
|
+
*/
|
|
2
7
|
function pemToDer(pem) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
const lines = pem.trim().split(/\r?\n/);
|
|
9
|
+
const headerIdx = lines.findIndex((l) => l.startsWith("-----BEGIN "));
|
|
10
|
+
if (headerIdx === -1) {
|
|
11
|
+
throw new Error("Invalid PEM: no BEGIN line found");
|
|
12
|
+
}
|
|
13
|
+
const headerLine = lines[headerIdx];
|
|
14
|
+
const headerMatch = headerLine.match(/^-----BEGIN (.+)-----$/);
|
|
15
|
+
if (!headerMatch) {
|
|
16
|
+
throw new Error("Invalid PEM header format");
|
|
17
|
+
}
|
|
18
|
+
const type = headerMatch[1];
|
|
19
|
+
const footerIdx = lines.findIndex((l, i) => i > headerIdx && l.startsWith("-----END "));
|
|
20
|
+
if (footerIdx === -1) {
|
|
21
|
+
throw new Error("Invalid PEM: no END line found");
|
|
22
|
+
}
|
|
23
|
+
const base64Body = lines.slice(headerIdx + 1, footerIdx).join("");
|
|
24
|
+
const der = Buffer.from(base64Body, "base64");
|
|
25
|
+
return {
|
|
26
|
+
type,
|
|
27
|
+
der: new Uint8Array(der.buffer, der.byteOffset, der.byteLength)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** ASN.1 tag constants */
|
|
22
31
|
const ASN1_INTEGER = 2;
|
|
23
32
|
const ASN1_BIT_STRING = 3;
|
|
24
33
|
const ASN1_OCTET_STRING = 4;
|
|
25
34
|
const ASN1_NULL = 5;
|
|
26
35
|
const ASN1_OID = 6;
|
|
27
36
|
const ASN1_SEQUENCE = 48;
|
|
37
|
+
/**
|
|
38
|
+
* Parse one TLV (tag-length-value) from the DER buffer starting at `offset`.
|
|
39
|
+
* Returns the parsed value and the new offset past it.
|
|
40
|
+
*/
|
|
28
41
|
function parseTlv(buf, offset) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
if (offset >= buf.length) {
|
|
43
|
+
throw new Error("ASN.1 parse error: unexpected end of data");
|
|
44
|
+
}
|
|
45
|
+
const tag = buf[offset++];
|
|
46
|
+
let length;
|
|
47
|
+
const firstLenByte = buf[offset++];
|
|
48
|
+
if (firstLenByte < 128) {
|
|
49
|
+
length = firstLenByte;
|
|
50
|
+
} else {
|
|
51
|
+
const numLenBytes = firstLenByte & 127;
|
|
52
|
+
if (numLenBytes === 0) {
|
|
53
|
+
throw new Error("ASN.1 parse error: indefinite length not supported");
|
|
54
|
+
}
|
|
55
|
+
length = 0;
|
|
56
|
+
for (let i = 0; i < numLenBytes; i++) {
|
|
57
|
+
length = length << 8 | buf[offset++];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const data = buf.slice(offset, offset + length);
|
|
61
|
+
const next = offset + length;
|
|
62
|
+
const result = {
|
|
63
|
+
tag,
|
|
64
|
+
data
|
|
65
|
+
};
|
|
66
|
+
if (tag === ASN1_SEQUENCE) {
|
|
67
|
+
result.children = parseSequenceChildren(data);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
value: result,
|
|
71
|
+
next
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse all TLV children within a SEQUENCE's data bytes.
|
|
76
|
+
*/
|
|
55
77
|
function parseSequenceChildren(data) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
78
|
+
const children = [];
|
|
79
|
+
let pos = 0;
|
|
80
|
+
while (pos < data.length) {
|
|
81
|
+
const { value, next } = parseTlv(data, pos);
|
|
82
|
+
children.push(value);
|
|
83
|
+
pos = next;
|
|
84
|
+
}
|
|
85
|
+
return children;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse the entire DER buffer as a single top-level TLV.
|
|
89
|
+
*/
|
|
65
90
|
function parseDer(buf) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
91
|
+
const { value } = parseTlv(buf, 0);
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Convert an ASN.1 INTEGER's raw data bytes to a non-negative BigInt.
|
|
96
|
+
* ASN.1 INTEGERs are big-endian two's-complement. For RSA keys all values
|
|
97
|
+
* are positive, so we just strip any leading 0x00 padding byte.
|
|
98
|
+
*/
|
|
69
99
|
function integerToBigInt(data) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
100
|
+
let start = 0;
|
|
101
|
+
while (start < data.length - 1 && data[start] === 0) {
|
|
102
|
+
start++;
|
|
103
|
+
}
|
|
104
|
+
let result = 0n;
|
|
105
|
+
for (let i = start; i < data.length; i++) {
|
|
106
|
+
result = result << 8n | BigInt(data[i]);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
/** RSA encryption OID: 1.2.840.113549.1.1.1 */
|
|
111
|
+
const RSA_OID = new Uint8Array([
|
|
112
|
+
42,
|
|
113
|
+
134,
|
|
114
|
+
72,
|
|
115
|
+
134,
|
|
116
|
+
247,
|
|
117
|
+
13,
|
|
118
|
+
1,
|
|
119
|
+
1,
|
|
120
|
+
1
|
|
121
|
+
]);
|
|
81
122
|
function oidsEqual(a, b) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
123
|
+
if (a.length !== b.length) return false;
|
|
124
|
+
for (let i = 0; i < a.length; i++) {
|
|
125
|
+
if (a[i] !== b[i]) return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Parse PKCS#1 RSAPublicKey:
|
|
131
|
+
* SEQUENCE { INTEGER n, INTEGER e }
|
|
132
|
+
*/
|
|
88
133
|
function parseRsaPublicKeyPkcs1(seq) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
134
|
+
const children = seq.children;
|
|
135
|
+
if (!children || children.length < 2) {
|
|
136
|
+
throw new Error("Invalid PKCS#1 RSAPublicKey structure");
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
n: integerToBigInt(children[0].data),
|
|
140
|
+
e: integerToBigInt(children[1].data)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Parse PKCS#1 RSAPrivateKey:
|
|
145
|
+
* SEQUENCE {
|
|
146
|
+
* INTEGER version,
|
|
147
|
+
* INTEGER n,
|
|
148
|
+
* INTEGER e,
|
|
149
|
+
* INTEGER d,
|
|
150
|
+
* INTEGER p,
|
|
151
|
+
* INTEGER q,
|
|
152
|
+
* INTEGER dp,
|
|
153
|
+
* INTEGER dq,
|
|
154
|
+
* INTEGER qi
|
|
155
|
+
* }
|
|
156
|
+
*/
|
|
98
157
|
function parseRsaPrivateKeyPkcs1(seq) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
158
|
+
const children = seq.children;
|
|
159
|
+
if (!children || children.length < 6) {
|
|
160
|
+
throw new Error("Invalid PKCS#1 RSAPrivateKey structure");
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
n: integerToBigInt(children[1].data),
|
|
164
|
+
e: integerToBigInt(children[2].data),
|
|
165
|
+
d: integerToBigInt(children[3].data),
|
|
166
|
+
p: integerToBigInt(children[4].data),
|
|
167
|
+
q: integerToBigInt(children[5].data)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Parse PKCS#8 SubjectPublicKeyInfo:
|
|
172
|
+
* SEQUENCE {
|
|
173
|
+
* SEQUENCE { OID algorithm, NULL } -- AlgorithmIdentifier
|
|
174
|
+
* BIT STRING -- wraps PKCS#1 RSAPublicKey
|
|
175
|
+
* }
|
|
176
|
+
*/
|
|
111
177
|
function parseSubjectPublicKeyInfo(seq) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
178
|
+
const children = seq.children;
|
|
179
|
+
if (!children || children.length < 2) {
|
|
180
|
+
throw new Error("Invalid SubjectPublicKeyInfo structure");
|
|
181
|
+
}
|
|
182
|
+
const algIdSeq = children[0];
|
|
183
|
+
if (!algIdSeq.children || algIdSeq.children.length < 1) {
|
|
184
|
+
throw new Error("Invalid AlgorithmIdentifier");
|
|
185
|
+
}
|
|
186
|
+
const oid = algIdSeq.children[0];
|
|
187
|
+
if (oid.tag !== ASN1_OID || !oidsEqual(oid.data, RSA_OID)) {
|
|
188
|
+
throw new Error("Unsupported algorithm: only RSA is supported");
|
|
189
|
+
}
|
|
190
|
+
const bitString = children[1];
|
|
191
|
+
if (bitString.tag !== ASN1_BIT_STRING) {
|
|
192
|
+
throw new Error("Expected BIT STRING for public key data");
|
|
193
|
+
}
|
|
194
|
+
const innerDer = bitString.data.slice(1);
|
|
195
|
+
const innerSeq = parseDer(innerDer);
|
|
196
|
+
return parseRsaPublicKeyPkcs1(innerSeq);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Parse PKCS#8 PrivateKeyInfo:
|
|
200
|
+
* SEQUENCE {
|
|
201
|
+
* INTEGER version,
|
|
202
|
+
* SEQUENCE { OID algorithm, NULL } -- AlgorithmIdentifier
|
|
203
|
+
* OCTET STRING -- wraps PKCS#1 RSAPrivateKey
|
|
204
|
+
* }
|
|
205
|
+
*/
|
|
132
206
|
function parsePrivateKeyInfo(seq) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
207
|
+
const children = seq.children;
|
|
208
|
+
if (!children || children.length < 3) {
|
|
209
|
+
throw new Error("Invalid PrivateKeyInfo structure");
|
|
210
|
+
}
|
|
211
|
+
const algIdSeq = children[1];
|
|
212
|
+
if (!algIdSeq.children || algIdSeq.children.length < 1) {
|
|
213
|
+
throw new Error("Invalid AlgorithmIdentifier");
|
|
214
|
+
}
|
|
215
|
+
const oid = algIdSeq.children[0];
|
|
216
|
+
if (oid.tag !== ASN1_OID || !oidsEqual(oid.data, RSA_OID)) {
|
|
217
|
+
throw new Error("Unsupported algorithm: only RSA is supported");
|
|
218
|
+
}
|
|
219
|
+
const octetString = children[2];
|
|
220
|
+
if (octetString.tag !== ASN1_OCTET_STRING) {
|
|
221
|
+
throw new Error("Expected OCTET STRING for private key data");
|
|
222
|
+
}
|
|
223
|
+
const innerSeq = parseDer(octetString.data);
|
|
224
|
+
return parseRsaPrivateKeyPkcs1(innerSeq);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Encode a length value in DER format.
|
|
228
|
+
*/
|
|
152
229
|
function encodeLength(length) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
230
|
+
if (length < 128) {
|
|
231
|
+
return new Uint8Array([length]);
|
|
232
|
+
}
|
|
233
|
+
const bytes = [];
|
|
234
|
+
let val = length;
|
|
235
|
+
while (val > 0) {
|
|
236
|
+
bytes.unshift(val & 255);
|
|
237
|
+
val >>= 8;
|
|
238
|
+
}
|
|
239
|
+
return new Uint8Array([128 | bytes.length, ...bytes]);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Encode a TLV (tag-length-value).
|
|
243
|
+
*/
|
|
164
244
|
function encodeTlv(tag, data) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
245
|
+
const len = encodeLength(data.length);
|
|
246
|
+
const result = new Uint8Array(1 + len.length + data.length);
|
|
247
|
+
result[0] = tag;
|
|
248
|
+
result.set(len, 1);
|
|
249
|
+
result.set(data, 1 + len.length);
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Encode a BigInt as an ASN.1 INTEGER.
|
|
254
|
+
*/
|
|
172
255
|
function bigintToAsn1Integer(value) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
256
|
+
if (value === 0n) {
|
|
257
|
+
return encodeTlv(ASN1_INTEGER, new Uint8Array([0]));
|
|
258
|
+
}
|
|
259
|
+
const hex = value.toString(16);
|
|
260
|
+
const paddedHex = hex.length % 2 ? "0" + hex : hex;
|
|
261
|
+
const bytes = [];
|
|
262
|
+
for (let i = 0; i < paddedHex.length; i += 2) {
|
|
263
|
+
bytes.push(parseInt(paddedHex.substring(i, i + 2), 16));
|
|
264
|
+
}
|
|
265
|
+
if (bytes[0] & 128) {
|
|
266
|
+
bytes.unshift(0);
|
|
267
|
+
}
|
|
268
|
+
return encodeTlv(ASN1_INTEGER, new Uint8Array(bytes));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Encode an ASN.1 SEQUENCE from its children.
|
|
272
|
+
*/
|
|
187
273
|
function encodeSequence(children) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
274
|
+
let totalLen = 0;
|
|
275
|
+
for (const child of children) totalLen += child.length;
|
|
276
|
+
const data = new Uint8Array(totalLen);
|
|
277
|
+
let offset = 0;
|
|
278
|
+
for (const child of children) {
|
|
279
|
+
data.set(child, offset);
|
|
280
|
+
offset += child.length;
|
|
281
|
+
}
|
|
282
|
+
return encodeTlv(ASN1_SEQUENCE, data);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Encode an ASN.1 BIT STRING (with 0 unused bits).
|
|
286
|
+
*/
|
|
198
287
|
function encodeBitString(data) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
288
|
+
const inner = new Uint8Array(1 + data.length);
|
|
289
|
+
inner[0] = 0;
|
|
290
|
+
inner.set(data, 1);
|
|
291
|
+
return encodeTlv(ASN1_BIT_STRING, inner);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Encode an ASN.1 OCTET STRING.
|
|
295
|
+
*/
|
|
204
296
|
function encodeOctetString(data) {
|
|
205
|
-
|
|
297
|
+
return encodeTlv(ASN1_OCTET_STRING, data);
|
|
206
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Encode an ASN.1 OID.
|
|
301
|
+
*/
|
|
207
302
|
function encodeOid(oidBytes) {
|
|
208
|
-
|
|
303
|
+
return encodeTlv(ASN1_OID, oidBytes);
|
|
209
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Encode ASN.1 NULL.
|
|
307
|
+
*/
|
|
210
308
|
function encodeNull() {
|
|
211
|
-
|
|
309
|
+
return new Uint8Array([ASN1_NULL, 0]);
|
|
212
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Encode RSA public key components as PKCS#1 RSAPublicKey DER.
|
|
313
|
+
*/
|
|
213
314
|
function encodeRsaPublicKeyPkcs1(components) {
|
|
214
|
-
|
|
215
|
-
bigintToAsn1Integer(components.n),
|
|
216
|
-
bigintToAsn1Integer(components.e)
|
|
217
|
-
]);
|
|
315
|
+
return encodeSequence([bigintToAsn1Integer(components.n), bigintToAsn1Integer(components.e)]);
|
|
218
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Encode RSA public key as PKCS#8 SubjectPublicKeyInfo DER.
|
|
319
|
+
*/
|
|
219
320
|
function encodeSubjectPublicKeyInfo(components) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
321
|
+
const algorithmId = encodeSequence([encodeOid(RSA_OID), encodeNull()]);
|
|
322
|
+
const rsaPublicKey = encodeRsaPublicKeyPkcs1(components);
|
|
323
|
+
const bitString = encodeBitString(rsaPublicKey);
|
|
324
|
+
return encodeSequence([algorithmId, bitString]);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Encode RSA private key components as PKCS#1 RSAPrivateKey DER.
|
|
328
|
+
*/
|
|
225
329
|
function encodeRsaPrivateKeyPkcs1(components) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
330
|
+
const dp = components.d % (components.p - 1n);
|
|
331
|
+
const dq = components.d % (components.q - 1n);
|
|
332
|
+
const qi = modInverse(components.q, components.p);
|
|
333
|
+
return encodeSequence([
|
|
334
|
+
bigintToAsn1Integer(0n),
|
|
335
|
+
bigintToAsn1Integer(components.n),
|
|
336
|
+
bigintToAsn1Integer(components.e),
|
|
337
|
+
bigintToAsn1Integer(components.d),
|
|
338
|
+
bigintToAsn1Integer(components.p),
|
|
339
|
+
bigintToAsn1Integer(components.q),
|
|
340
|
+
bigintToAsn1Integer(dp),
|
|
341
|
+
bigintToAsn1Integer(dq),
|
|
342
|
+
bigintToAsn1Integer(qi)
|
|
343
|
+
]);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Encode RSA private key as PKCS#8 PrivateKeyInfo DER.
|
|
347
|
+
*/
|
|
242
348
|
function encodePrivateKeyInfo(components) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
349
|
+
const algorithmId = encodeSequence([encodeOid(RSA_OID), encodeNull()]);
|
|
350
|
+
const rsaPrivateKey = encodeRsaPrivateKeyPkcs1(components);
|
|
351
|
+
const octetString = encodeOctetString(rsaPrivateKey);
|
|
352
|
+
return encodeSequence([
|
|
353
|
+
bigintToAsn1Integer(0n),
|
|
354
|
+
algorithmId,
|
|
355
|
+
octetString
|
|
356
|
+
]);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Convert DER bytes to PEM string.
|
|
360
|
+
*/
|
|
253
361
|
function derToPem(der, type) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
362
|
+
const base64 = Buffer.from(der).toString("base64");
|
|
363
|
+
const lines = [`-----BEGIN ${type}-----`];
|
|
364
|
+
for (let i = 0; i < base64.length; i += 64) {
|
|
365
|
+
lines.push(base64.substring(i, i + 64));
|
|
366
|
+
}
|
|
367
|
+
lines.push(`-----END ${type}-----`);
|
|
368
|
+
return lines.join("\n");
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Modular inverse: a^(-1) mod m using extended Euclidean algorithm.
|
|
372
|
+
*/
|
|
262
373
|
function modInverse(a, m) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
374
|
+
let [old_r, r] = [a % m, m];
|
|
375
|
+
let [old_s, s] = [1n, 0n];
|
|
376
|
+
while (r !== 0n) {
|
|
377
|
+
const q = old_r / r;
|
|
378
|
+
[old_r, r] = [r, old_r - q * r];
|
|
379
|
+
[old_s, s] = [s, old_s - q * s];
|
|
380
|
+
}
|
|
381
|
+
return (old_s % m + m) % m;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Parse an X.509 certificate from PEM or DER.
|
|
385
|
+
*/
|
|
272
386
|
function parseX509(pem) {
|
|
273
|
-
|
|
274
|
-
|
|
387
|
+
const { der } = pemToDer(pem);
|
|
388
|
+
return parseX509Der(der);
|
|
275
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Parse an X.509 certificate from DER bytes.
|
|
392
|
+
*/
|
|
276
393
|
function parseX509Der(der) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
394
|
+
const root = parseDer(der);
|
|
395
|
+
if (root.tag !== ASN1_SEQUENCE || !root.children || root.children.length < 3) {
|
|
396
|
+
throw new Error("Invalid X.509 certificate structure");
|
|
397
|
+
}
|
|
398
|
+
const tbsCertificate = root.children[0];
|
|
399
|
+
const signatureAlgorithm = root.children[1];
|
|
400
|
+
const signatureBitString = root.children[2];
|
|
401
|
+
if (!tbsCertificate.children || tbsCertificate.children.length < 6) {
|
|
402
|
+
throw new Error("Invalid TBSCertificate structure");
|
|
403
|
+
}
|
|
404
|
+
let idx = 0;
|
|
405
|
+
if (tbsCertificate.children[0].tag === 160) {
|
|
406
|
+
idx++;
|
|
407
|
+
}
|
|
408
|
+
const serialNumber = integerToBigInt(tbsCertificate.children[idx].data);
|
|
409
|
+
idx++;
|
|
410
|
+
idx++;
|
|
411
|
+
const issuer = parseDN(tbsCertificate.children[idx]);
|
|
412
|
+
idx++;
|
|
413
|
+
const validity = tbsCertificate.children[idx];
|
|
414
|
+
const validFrom = parseAsn1Time(validity.children[0]);
|
|
415
|
+
const validTo = parseAsn1Time(validity.children[1]);
|
|
416
|
+
idx++;
|
|
417
|
+
const subject = parseDN(tbsCertificate.children[idx]);
|
|
418
|
+
idx++;
|
|
419
|
+
let publicKey = null;
|
|
420
|
+
let publicKeyAlgorithm = "unknown";
|
|
421
|
+
if (idx < tbsCertificate.children.length) {
|
|
422
|
+
const spki = tbsCertificate.children[idx];
|
|
423
|
+
try {
|
|
424
|
+
if (spki.children && spki.children.length >= 2) {
|
|
425
|
+
const algId = spki.children[0];
|
|
426
|
+
if (algId.children && algId.children.length >= 1) {
|
|
427
|
+
const oid = algId.children[0];
|
|
428
|
+
if (oid.tag === ASN1_OID && oidsEqual(oid.data, RSA_OID)) {
|
|
429
|
+
publicKeyAlgorithm = "rsa";
|
|
430
|
+
publicKey = parseSubjectPublicKeyInfo(spki);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
} catch {}
|
|
435
|
+
idx++;
|
|
436
|
+
}
|
|
437
|
+
let subjectAltName;
|
|
438
|
+
let extensions;
|
|
439
|
+
for (let i = idx; i < tbsCertificate.children.length; i++) {
|
|
440
|
+
const child = tbsCertificate.children[i];
|
|
441
|
+
if (child.tag === 163 && child.data.length > 0) {
|
|
442
|
+
const extSeq = parseDer(child.data);
|
|
443
|
+
if (extSeq.children) {
|
|
444
|
+
extensions = extSeq.children;
|
|
445
|
+
const SAN_OID = new Uint8Array([
|
|
446
|
+
85,
|
|
447
|
+
29,
|
|
448
|
+
17
|
|
449
|
+
]);
|
|
450
|
+
for (const ext of extSeq.children) {
|
|
451
|
+
if (ext.children && ext.children.length >= 2) {
|
|
452
|
+
const extOid = ext.children[0];
|
|
453
|
+
if (extOid.tag === ASN1_OID && oidsEqual(extOid.data, SAN_OID)) {
|
|
454
|
+
const extValue = ext.children[ext.children.length - 1];
|
|
455
|
+
subjectAltName = parseSAN(extValue.data);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
let sigAlg = "unknown";
|
|
463
|
+
if (signatureAlgorithm.children && signatureAlgorithm.children.length >= 1) {
|
|
464
|
+
sigAlg = oidToName(signatureAlgorithm.children[0].data);
|
|
465
|
+
}
|
|
466
|
+
const signature = signatureBitString.tag === ASN1_BIT_STRING ? signatureBitString.data.slice(1) : signatureBitString.data;
|
|
467
|
+
return {
|
|
468
|
+
raw: der,
|
|
469
|
+
tbsCertificate: tbsCertificate.data,
|
|
470
|
+
serialNumber,
|
|
471
|
+
issuer,
|
|
472
|
+
subject,
|
|
473
|
+
validFrom,
|
|
474
|
+
validTo,
|
|
475
|
+
publicKey,
|
|
476
|
+
publicKeyAlgorithm,
|
|
477
|
+
signatureAlgorithm: sigAlg,
|
|
478
|
+
signature,
|
|
479
|
+
subjectAltName,
|
|
480
|
+
extensions
|
|
481
|
+
};
|
|
362
482
|
}
|
|
363
483
|
const OID_NAMES = {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
484
|
+
"2.5.4.3": "CN",
|
|
485
|
+
"2.5.4.6": "C",
|
|
486
|
+
"2.5.4.7": "L",
|
|
487
|
+
"2.5.4.8": "ST",
|
|
488
|
+
"2.5.4.10": "O",
|
|
489
|
+
"2.5.4.11": "OU",
|
|
490
|
+
"1.2.840.113549.1.9.1": "emailAddress"
|
|
371
491
|
};
|
|
372
492
|
const SIG_ALG_NAMES = {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
493
|
+
"1.2.840.113549.1.1.1": "rsaEncryption",
|
|
494
|
+
"1.2.840.113549.1.1.5": "sha1WithRSAEncryption",
|
|
495
|
+
"1.2.840.113549.1.1.11": "sha256WithRSAEncryption",
|
|
496
|
+
"1.2.840.113549.1.1.12": "sha384WithRSAEncryption",
|
|
497
|
+
"1.2.840.113549.1.1.13": "sha512WithRSAEncryption"
|
|
378
498
|
};
|
|
379
499
|
function decodeOidString(data) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
500
|
+
if (data.length === 0) return "";
|
|
501
|
+
const components = [];
|
|
502
|
+
components.push(Math.floor(data[0] / 40));
|
|
503
|
+
components.push(data[0] % 40);
|
|
504
|
+
let value = 0;
|
|
505
|
+
for (let i = 1; i < data.length; i++) {
|
|
506
|
+
value = value << 7 | data[i] & 127;
|
|
507
|
+
if (!(data[i] & 128)) {
|
|
508
|
+
components.push(value);
|
|
509
|
+
value = 0;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return components.join(".");
|
|
393
513
|
}
|
|
394
514
|
function oidToName(data) {
|
|
395
|
-
|
|
396
|
-
|
|
515
|
+
const oidStr = decodeOidString(data);
|
|
516
|
+
return SIG_ALG_NAMES[oidStr] || oidStr;
|
|
397
517
|
}
|
|
398
518
|
function parseDN(seq) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
519
|
+
if (!seq.children) return "";
|
|
520
|
+
const parts = [];
|
|
521
|
+
for (const rdn of seq.children) {
|
|
522
|
+
const rdnChildren = rdn.tag === 49 ? parseSequenceChildren(rdn.data) : rdn.children || [];
|
|
523
|
+
for (const atv of rdnChildren) {
|
|
524
|
+
if (atv.children && atv.children.length >= 2) {
|
|
525
|
+
const oidData = atv.children[0].data;
|
|
526
|
+
const oidStr = decodeOidString(oidData);
|
|
527
|
+
const name = OID_NAMES[oidStr] || oidStr;
|
|
528
|
+
const valueBytes = atv.children[1].data;
|
|
529
|
+
const value = new TextDecoder().decode(valueBytes);
|
|
530
|
+
parts.push(`${name}=${value}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return parts.join(", ");
|
|
415
535
|
}
|
|
416
536
|
function parseAsn1Time(tlv) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
537
|
+
const str = new TextDecoder().decode(tlv.data);
|
|
538
|
+
if (tlv.tag === 23) {
|
|
539
|
+
let year = parseInt(str.substring(0, 2), 10);
|
|
540
|
+
year = year >= 50 ? 1900 + year : 2e3 + year;
|
|
541
|
+
const month = parseInt(str.substring(2, 4), 10) - 1;
|
|
542
|
+
const day = parseInt(str.substring(4, 6), 10);
|
|
543
|
+
const hour = parseInt(str.substring(6, 8), 10);
|
|
544
|
+
const minute = parseInt(str.substring(8, 10), 10);
|
|
545
|
+
const second = parseInt(str.substring(10, 12), 10);
|
|
546
|
+
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
547
|
+
}
|
|
548
|
+
if (tlv.tag === 24) {
|
|
549
|
+
const year = parseInt(str.substring(0, 4), 10);
|
|
550
|
+
const month = parseInt(str.substring(4, 6), 10) - 1;
|
|
551
|
+
const day = parseInt(str.substring(6, 8), 10);
|
|
552
|
+
const hour = parseInt(str.substring(8, 10), 10);
|
|
553
|
+
const minute = parseInt(str.substring(10, 12), 10);
|
|
554
|
+
const second = parseInt(str.substring(12, 14), 10);
|
|
555
|
+
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
556
|
+
}
|
|
557
|
+
throw new Error(`Unsupported time tag: 0x${tlv.tag.toString(16)}`);
|
|
438
558
|
}
|
|
439
559
|
function parseSAN(data) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
560
|
+
const names = [];
|
|
561
|
+
try {
|
|
562
|
+
const seq = parseDer(data);
|
|
563
|
+
if (seq.tag === ASN1_SEQUENCE && seq.children) {
|
|
564
|
+
for (const child of seq.children) {
|
|
565
|
+
if (child.tag === 130) {
|
|
566
|
+
names.push("DNS:" + new TextDecoder().decode(child.data));
|
|
567
|
+
} else if (child.tag === 135) {
|
|
568
|
+
if (child.data.length === 4) {
|
|
569
|
+
names.push("IP Address:" + child.data.join("."));
|
|
570
|
+
} else if (child.data.length === 16) {
|
|
571
|
+
const parts = [];
|
|
572
|
+
for (let i = 0; i < 16; i += 2) {
|
|
573
|
+
parts.push((child.data[i] << 8 | child.data[i + 1]).toString(16));
|
|
574
|
+
}
|
|
575
|
+
names.push("IP Address:" + parts.join(":"));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
} catch {}
|
|
581
|
+
return names;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Parse a PEM-encoded RSA key. Supports:
|
|
585
|
+
* - RSA PUBLIC KEY (PKCS#1)
|
|
586
|
+
* - PUBLIC KEY (PKCS#8 SubjectPublicKeyInfo)
|
|
587
|
+
* - RSA PRIVATE KEY (PKCS#1)
|
|
588
|
+
* - PRIVATE KEY (PKCS#8 PrivateKeyInfo)
|
|
589
|
+
*/
|
|
464
590
|
function parsePemKey(pem) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
591
|
+
const { type, der } = pemToDer(pem);
|
|
592
|
+
const root = parseDer(der);
|
|
593
|
+
if (root.tag !== ASN1_SEQUENCE) {
|
|
594
|
+
throw new Error("Invalid key format: expected top-level SEQUENCE");
|
|
595
|
+
}
|
|
596
|
+
switch (type) {
|
|
597
|
+
case "RSA PUBLIC KEY": return {
|
|
598
|
+
type: "rsa-public",
|
|
599
|
+
components: parseRsaPublicKeyPkcs1(root)
|
|
600
|
+
};
|
|
601
|
+
case "PUBLIC KEY": return {
|
|
602
|
+
type: "rsa-public",
|
|
603
|
+
components: parseSubjectPublicKeyInfo(root)
|
|
604
|
+
};
|
|
605
|
+
case "RSA PRIVATE KEY": return {
|
|
606
|
+
type: "rsa-private",
|
|
607
|
+
components: parseRsaPrivateKeyPkcs1(root)
|
|
608
|
+
};
|
|
609
|
+
case "PRIVATE KEY": return {
|
|
610
|
+
type: "rsa-private",
|
|
611
|
+
components: parsePrivateKeyInfo(root)
|
|
612
|
+
};
|
|
613
|
+
default: throw new Error(`Unsupported PEM type: ${type}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get the key size in bytes (byte length of modulus n).
|
|
618
|
+
*/
|
|
483
619
|
function rsaKeySize(n) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
encodePrivateKeyInfo,
|
|
496
|
-
encodeRsaPrivateKeyPkcs1,
|
|
497
|
-
encodeRsaPublicKeyPkcs1,
|
|
498
|
-
encodeSequence,
|
|
499
|
-
encodeSubjectPublicKeyInfo,
|
|
500
|
-
parsePemKey,
|
|
501
|
-
parseX509,
|
|
502
|
-
parseX509Der,
|
|
503
|
-
rsaKeySize
|
|
504
|
-
};
|
|
620
|
+
let bits = 0;
|
|
621
|
+
let val = n;
|
|
622
|
+
while (val > 0n) {
|
|
623
|
+
bits++;
|
|
624
|
+
val >>= 1n;
|
|
625
|
+
}
|
|
626
|
+
return Math.ceil(bits / 8);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
//#endregion
|
|
630
|
+
export { bigintToAsn1Integer, derToPem, encodePrivateKeyInfo, encodeRsaPrivateKeyPkcs1, encodeRsaPublicKeyPkcs1, encodeSequence, encodeSubjectPublicKeyInfo, parsePemKey, parseX509, parseX509Der, rsaKeySize };
|