@gjsify/crypto 0.1.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 +27 -0
- package/lib/esm/asn1.js +504 -0
- package/lib/esm/bigint-math.js +34 -0
- package/lib/esm/cipher.js +1272 -0
- package/lib/esm/constants.js +15 -0
- package/lib/esm/crypto-utils.js +47 -0
- package/lib/esm/dh.js +411 -0
- package/lib/esm/ecdh.js +356 -0
- package/lib/esm/ecdsa.js +125 -0
- package/lib/esm/hash.js +100 -0
- package/lib/esm/hkdf.js +58 -0
- package/lib/esm/hmac.js +93 -0
- package/lib/esm/index.js +158 -0
- package/lib/esm/key-object.js +330 -0
- package/lib/esm/mgf1.js +27 -0
- package/lib/esm/pbkdf2.js +68 -0
- package/lib/esm/public-encrypt.js +175 -0
- package/lib/esm/random.js +138 -0
- package/lib/esm/rsa-oaep.js +95 -0
- package/lib/esm/rsa-pss.js +100 -0
- package/lib/esm/scrypt.js +134 -0
- package/lib/esm/sign.js +248 -0
- package/lib/esm/timing-safe-equal.js +13 -0
- package/lib/esm/x509.js +214 -0
- package/lib/types/asn1.d.ts +87 -0
- package/lib/types/bigint-math.d.ts +13 -0
- package/lib/types/cipher.d.ts +84 -0
- package/lib/types/constants.d.ts +10 -0
- package/lib/types/crypto-utils.d.ts +22 -0
- package/lib/types/dh.d.ts +79 -0
- package/lib/types/ecdh.d.ts +96 -0
- package/lib/types/ecdsa.d.ts +21 -0
- package/lib/types/hash.d.ts +25 -0
- package/lib/types/hkdf.d.ts +9 -0
- package/lib/types/hmac.d.ts +20 -0
- package/lib/types/index.d.ts +105 -0
- package/lib/types/key-object.d.ts +36 -0
- package/lib/types/mgf1.d.ts +5 -0
- package/lib/types/pbkdf2.d.ts +9 -0
- package/lib/types/public-encrypt.d.ts +42 -0
- package/lib/types/random.d.ts +22 -0
- package/lib/types/rsa-oaep.d.ts +8 -0
- package/lib/types/rsa-pss.d.ts +8 -0
- package/lib/types/scrypt.d.ts +11 -0
- package/lib/types/sign.d.ts +61 -0
- package/lib/types/timing-safe-equal.d.ts +6 -0
- package/lib/types/x509.d.ts +72 -0
- package/package.json +45 -0
- package/src/asn1.ts +797 -0
- package/src/bigint-math.ts +45 -0
- package/src/cipher.spec.ts +332 -0
- package/src/cipher.ts +952 -0
- package/src/constants.ts +16 -0
- package/src/crypto-utils.ts +64 -0
- package/src/dh.spec.ts +111 -0
- package/src/dh.ts +761 -0
- package/src/ecdh.spec.ts +116 -0
- package/src/ecdh.ts +624 -0
- package/src/ecdsa.ts +243 -0
- package/src/extended.spec.ts +444 -0
- package/src/gcm.spec.ts +141 -0
- package/src/hash.spec.ts +86 -0
- package/src/hash.ts +119 -0
- package/src/hkdf.ts +99 -0
- package/src/hmac.spec.ts +64 -0
- package/src/hmac.ts +123 -0
- package/src/index.ts +93 -0
- package/src/key-object.spec.ts +202 -0
- package/src/key-object.ts +401 -0
- package/src/mgf1.ts +37 -0
- package/src/pbkdf2.spec.ts +76 -0
- package/src/pbkdf2.ts +106 -0
- package/src/public-encrypt.ts +288 -0
- package/src/random.spec.ts +133 -0
- package/src/random.ts +183 -0
- package/src/rsa-oaep.ts +167 -0
- package/src/rsa-pss.ts +190 -0
- package/src/scrypt.spec.ts +90 -0
- package/src/scrypt.ts +191 -0
- package/src/sign.spec.ts +160 -0
- package/src/sign.ts +319 -0
- package/src/test.mts +19 -0
- package/src/timing-safe-equal.ts +21 -0
- package/src/x509.spec.ts +210 -0
- package/src/x509.ts +262 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @gjsify/crypto
|
|
2
|
+
|
|
3
|
+
GJS partial implementation of the Node.js `crypto` module using GLib.Checksum and GLib.Hmac. Supports Hash, Hmac, randomBytes, and UUID.
|
|
4
|
+
|
|
5
|
+
Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gjsify/crypto
|
|
11
|
+
# or
|
|
12
|
+
yarn add @gjsify/crypto
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createHash, randomBytes, randomUUID } from '@gjsify/crypto';
|
|
19
|
+
|
|
20
|
+
const hash = createHash('sha256').update('hello').digest('hex');
|
|
21
|
+
const bytes = randomBytes(16);
|
|
22
|
+
const uuid = randomUUID();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
MIT
|
package/lib/esm/asn1.js
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
function pemToDer(pem) {
|
|
3
|
+
const lines = pem.trim().split(/\r?\n/);
|
|
4
|
+
const headerIdx = lines.findIndex((l) => l.startsWith("-----BEGIN "));
|
|
5
|
+
if (headerIdx === -1) {
|
|
6
|
+
throw new Error("Invalid PEM: no BEGIN line found");
|
|
7
|
+
}
|
|
8
|
+
const headerLine = lines[headerIdx];
|
|
9
|
+
const headerMatch = headerLine.match(/^-----BEGIN (.+)-----$/);
|
|
10
|
+
if (!headerMatch) {
|
|
11
|
+
throw new Error("Invalid PEM header format");
|
|
12
|
+
}
|
|
13
|
+
const type = headerMatch[1];
|
|
14
|
+
const footerIdx = lines.findIndex((l, i) => i > headerIdx && l.startsWith("-----END "));
|
|
15
|
+
if (footerIdx === -1) {
|
|
16
|
+
throw new Error("Invalid PEM: no END line found");
|
|
17
|
+
}
|
|
18
|
+
const base64Body = lines.slice(headerIdx + 1, footerIdx).join("");
|
|
19
|
+
const der = Buffer.from(base64Body, "base64");
|
|
20
|
+
return { type, der: new Uint8Array(der.buffer, der.byteOffset, der.byteLength) };
|
|
21
|
+
}
|
|
22
|
+
const ASN1_INTEGER = 2;
|
|
23
|
+
const ASN1_BIT_STRING = 3;
|
|
24
|
+
const ASN1_OCTET_STRING = 4;
|
|
25
|
+
const ASN1_NULL = 5;
|
|
26
|
+
const ASN1_OID = 6;
|
|
27
|
+
const ASN1_SEQUENCE = 48;
|
|
28
|
+
function parseTlv(buf, offset) {
|
|
29
|
+
if (offset >= buf.length) {
|
|
30
|
+
throw new Error("ASN.1 parse error: unexpected end of data");
|
|
31
|
+
}
|
|
32
|
+
const tag = buf[offset++];
|
|
33
|
+
let length;
|
|
34
|
+
const firstLenByte = buf[offset++];
|
|
35
|
+
if (firstLenByte < 128) {
|
|
36
|
+
length = firstLenByte;
|
|
37
|
+
} else {
|
|
38
|
+
const numLenBytes = firstLenByte & 127;
|
|
39
|
+
if (numLenBytes === 0) {
|
|
40
|
+
throw new Error("ASN.1 parse error: indefinite length not supported");
|
|
41
|
+
}
|
|
42
|
+
length = 0;
|
|
43
|
+
for (let i = 0; i < numLenBytes; i++) {
|
|
44
|
+
length = length << 8 | buf[offset++];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const data = buf.slice(offset, offset + length);
|
|
48
|
+
const next = offset + length;
|
|
49
|
+
const result = { tag, data };
|
|
50
|
+
if (tag === ASN1_SEQUENCE) {
|
|
51
|
+
result.children = parseSequenceChildren(data);
|
|
52
|
+
}
|
|
53
|
+
return { value: result, next };
|
|
54
|
+
}
|
|
55
|
+
function parseSequenceChildren(data) {
|
|
56
|
+
const children = [];
|
|
57
|
+
let pos = 0;
|
|
58
|
+
while (pos < data.length) {
|
|
59
|
+
const { value, next } = parseTlv(data, pos);
|
|
60
|
+
children.push(value);
|
|
61
|
+
pos = next;
|
|
62
|
+
}
|
|
63
|
+
return children;
|
|
64
|
+
}
|
|
65
|
+
function parseDer(buf) {
|
|
66
|
+
const { value } = parseTlv(buf, 0);
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function integerToBigInt(data) {
|
|
70
|
+
let start = 0;
|
|
71
|
+
while (start < data.length - 1 && data[start] === 0) {
|
|
72
|
+
start++;
|
|
73
|
+
}
|
|
74
|
+
let result = 0n;
|
|
75
|
+
for (let i = start; i < data.length; i++) {
|
|
76
|
+
result = result << 8n | BigInt(data[i]);
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
const RSA_OID = new Uint8Array([42, 134, 72, 134, 247, 13, 1, 1, 1]);
|
|
81
|
+
function oidsEqual(a, b) {
|
|
82
|
+
if (a.length !== b.length) return false;
|
|
83
|
+
for (let i = 0; i < a.length; i++) {
|
|
84
|
+
if (a[i] !== b[i]) return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
function parseRsaPublicKeyPkcs1(seq) {
|
|
89
|
+
const children = seq.children;
|
|
90
|
+
if (!children || children.length < 2) {
|
|
91
|
+
throw new Error("Invalid PKCS#1 RSAPublicKey structure");
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
n: integerToBigInt(children[0].data),
|
|
95
|
+
e: integerToBigInt(children[1].data)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function parseRsaPrivateKeyPkcs1(seq) {
|
|
99
|
+
const children = seq.children;
|
|
100
|
+
if (!children || children.length < 6) {
|
|
101
|
+
throw new Error("Invalid PKCS#1 RSAPrivateKey structure");
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
n: integerToBigInt(children[1].data),
|
|
105
|
+
e: integerToBigInt(children[2].data),
|
|
106
|
+
d: integerToBigInt(children[3].data),
|
|
107
|
+
p: integerToBigInt(children[4].data),
|
|
108
|
+
q: integerToBigInt(children[5].data)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function parseSubjectPublicKeyInfo(seq) {
|
|
112
|
+
const children = seq.children;
|
|
113
|
+
if (!children || children.length < 2) {
|
|
114
|
+
throw new Error("Invalid SubjectPublicKeyInfo structure");
|
|
115
|
+
}
|
|
116
|
+
const algIdSeq = children[0];
|
|
117
|
+
if (!algIdSeq.children || algIdSeq.children.length < 1) {
|
|
118
|
+
throw new Error("Invalid AlgorithmIdentifier");
|
|
119
|
+
}
|
|
120
|
+
const oid = algIdSeq.children[0];
|
|
121
|
+
if (oid.tag !== ASN1_OID || !oidsEqual(oid.data, RSA_OID)) {
|
|
122
|
+
throw new Error("Unsupported algorithm: only RSA is supported");
|
|
123
|
+
}
|
|
124
|
+
const bitString = children[1];
|
|
125
|
+
if (bitString.tag !== ASN1_BIT_STRING) {
|
|
126
|
+
throw new Error("Expected BIT STRING for public key data");
|
|
127
|
+
}
|
|
128
|
+
const innerDer = bitString.data.slice(1);
|
|
129
|
+
const innerSeq = parseDer(innerDer);
|
|
130
|
+
return parseRsaPublicKeyPkcs1(innerSeq);
|
|
131
|
+
}
|
|
132
|
+
function parsePrivateKeyInfo(seq) {
|
|
133
|
+
const children = seq.children;
|
|
134
|
+
if (!children || children.length < 3) {
|
|
135
|
+
throw new Error("Invalid PrivateKeyInfo structure");
|
|
136
|
+
}
|
|
137
|
+
const algIdSeq = children[1];
|
|
138
|
+
if (!algIdSeq.children || algIdSeq.children.length < 1) {
|
|
139
|
+
throw new Error("Invalid AlgorithmIdentifier");
|
|
140
|
+
}
|
|
141
|
+
const oid = algIdSeq.children[0];
|
|
142
|
+
if (oid.tag !== ASN1_OID || !oidsEqual(oid.data, RSA_OID)) {
|
|
143
|
+
throw new Error("Unsupported algorithm: only RSA is supported");
|
|
144
|
+
}
|
|
145
|
+
const octetString = children[2];
|
|
146
|
+
if (octetString.tag !== ASN1_OCTET_STRING) {
|
|
147
|
+
throw new Error("Expected OCTET STRING for private key data");
|
|
148
|
+
}
|
|
149
|
+
const innerSeq = parseDer(octetString.data);
|
|
150
|
+
return parseRsaPrivateKeyPkcs1(innerSeq);
|
|
151
|
+
}
|
|
152
|
+
function encodeLength(length) {
|
|
153
|
+
if (length < 128) {
|
|
154
|
+
return new Uint8Array([length]);
|
|
155
|
+
}
|
|
156
|
+
const bytes = [];
|
|
157
|
+
let val = length;
|
|
158
|
+
while (val > 0) {
|
|
159
|
+
bytes.unshift(val & 255);
|
|
160
|
+
val >>= 8;
|
|
161
|
+
}
|
|
162
|
+
return new Uint8Array([128 | bytes.length, ...bytes]);
|
|
163
|
+
}
|
|
164
|
+
function encodeTlv(tag, data) {
|
|
165
|
+
const len = encodeLength(data.length);
|
|
166
|
+
const result = new Uint8Array(1 + len.length + data.length);
|
|
167
|
+
result[0] = tag;
|
|
168
|
+
result.set(len, 1);
|
|
169
|
+
result.set(data, 1 + len.length);
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
function bigintToAsn1Integer(value) {
|
|
173
|
+
if (value === 0n) {
|
|
174
|
+
return encodeTlv(ASN1_INTEGER, new Uint8Array([0]));
|
|
175
|
+
}
|
|
176
|
+
const hex = value.toString(16);
|
|
177
|
+
const paddedHex = hex.length % 2 ? "0" + hex : hex;
|
|
178
|
+
const bytes = [];
|
|
179
|
+
for (let i = 0; i < paddedHex.length; i += 2) {
|
|
180
|
+
bytes.push(parseInt(paddedHex.substring(i, i + 2), 16));
|
|
181
|
+
}
|
|
182
|
+
if (bytes[0] & 128) {
|
|
183
|
+
bytes.unshift(0);
|
|
184
|
+
}
|
|
185
|
+
return encodeTlv(ASN1_INTEGER, new Uint8Array(bytes));
|
|
186
|
+
}
|
|
187
|
+
function encodeSequence(children) {
|
|
188
|
+
let totalLen = 0;
|
|
189
|
+
for (const child of children) totalLen += child.length;
|
|
190
|
+
const data = new Uint8Array(totalLen);
|
|
191
|
+
let offset = 0;
|
|
192
|
+
for (const child of children) {
|
|
193
|
+
data.set(child, offset);
|
|
194
|
+
offset += child.length;
|
|
195
|
+
}
|
|
196
|
+
return encodeTlv(ASN1_SEQUENCE, data);
|
|
197
|
+
}
|
|
198
|
+
function encodeBitString(data) {
|
|
199
|
+
const inner = new Uint8Array(1 + data.length);
|
|
200
|
+
inner[0] = 0;
|
|
201
|
+
inner.set(data, 1);
|
|
202
|
+
return encodeTlv(ASN1_BIT_STRING, inner);
|
|
203
|
+
}
|
|
204
|
+
function encodeOctetString(data) {
|
|
205
|
+
return encodeTlv(ASN1_OCTET_STRING, data);
|
|
206
|
+
}
|
|
207
|
+
function encodeOid(oidBytes) {
|
|
208
|
+
return encodeTlv(ASN1_OID, oidBytes);
|
|
209
|
+
}
|
|
210
|
+
function encodeNull() {
|
|
211
|
+
return new Uint8Array([ASN1_NULL, 0]);
|
|
212
|
+
}
|
|
213
|
+
function encodeRsaPublicKeyPkcs1(components) {
|
|
214
|
+
return encodeSequence([
|
|
215
|
+
bigintToAsn1Integer(components.n),
|
|
216
|
+
bigintToAsn1Integer(components.e)
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
function encodeSubjectPublicKeyInfo(components) {
|
|
220
|
+
const algorithmId = encodeSequence([encodeOid(RSA_OID), encodeNull()]);
|
|
221
|
+
const rsaPublicKey = encodeRsaPublicKeyPkcs1(components);
|
|
222
|
+
const bitString = encodeBitString(rsaPublicKey);
|
|
223
|
+
return encodeSequence([algorithmId, bitString]);
|
|
224
|
+
}
|
|
225
|
+
function encodeRsaPrivateKeyPkcs1(components) {
|
|
226
|
+
const dp = components.d % (components.p - 1n);
|
|
227
|
+
const dq = components.d % (components.q - 1n);
|
|
228
|
+
const qi = modInverse(components.q, components.p);
|
|
229
|
+
return encodeSequence([
|
|
230
|
+
bigintToAsn1Integer(0n),
|
|
231
|
+
// version
|
|
232
|
+
bigintToAsn1Integer(components.n),
|
|
233
|
+
bigintToAsn1Integer(components.e),
|
|
234
|
+
bigintToAsn1Integer(components.d),
|
|
235
|
+
bigintToAsn1Integer(components.p),
|
|
236
|
+
bigintToAsn1Integer(components.q),
|
|
237
|
+
bigintToAsn1Integer(dp),
|
|
238
|
+
bigintToAsn1Integer(dq),
|
|
239
|
+
bigintToAsn1Integer(qi)
|
|
240
|
+
]);
|
|
241
|
+
}
|
|
242
|
+
function encodePrivateKeyInfo(components) {
|
|
243
|
+
const algorithmId = encodeSequence([encodeOid(RSA_OID), encodeNull()]);
|
|
244
|
+
const rsaPrivateKey = encodeRsaPrivateKeyPkcs1(components);
|
|
245
|
+
const octetString = encodeOctetString(rsaPrivateKey);
|
|
246
|
+
return encodeSequence([
|
|
247
|
+
bigintToAsn1Integer(0n),
|
|
248
|
+
// version
|
|
249
|
+
algorithmId,
|
|
250
|
+
octetString
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
function derToPem(der, type) {
|
|
254
|
+
const base64 = Buffer.from(der).toString("base64");
|
|
255
|
+
const lines = [`-----BEGIN ${type}-----`];
|
|
256
|
+
for (let i = 0; i < base64.length; i += 64) {
|
|
257
|
+
lines.push(base64.substring(i, i + 64));
|
|
258
|
+
}
|
|
259
|
+
lines.push(`-----END ${type}-----`);
|
|
260
|
+
return lines.join("\n");
|
|
261
|
+
}
|
|
262
|
+
function modInverse(a, m) {
|
|
263
|
+
let [old_r, r] = [a % m, m];
|
|
264
|
+
let [old_s, s] = [1n, 0n];
|
|
265
|
+
while (r !== 0n) {
|
|
266
|
+
const q = old_r / r;
|
|
267
|
+
[old_r, r] = [r, old_r - q * r];
|
|
268
|
+
[old_s, s] = [s, old_s - q * s];
|
|
269
|
+
}
|
|
270
|
+
return (old_s % m + m) % m;
|
|
271
|
+
}
|
|
272
|
+
function parseX509(pem) {
|
|
273
|
+
const { der } = pemToDer(pem);
|
|
274
|
+
return parseX509Der(der);
|
|
275
|
+
}
|
|
276
|
+
function parseX509Der(der) {
|
|
277
|
+
const root = parseDer(der);
|
|
278
|
+
if (root.tag !== ASN1_SEQUENCE || !root.children || root.children.length < 3) {
|
|
279
|
+
throw new Error("Invalid X.509 certificate structure");
|
|
280
|
+
}
|
|
281
|
+
const tbsCertificate = root.children[0];
|
|
282
|
+
const signatureAlgorithm = root.children[1];
|
|
283
|
+
const signatureBitString = root.children[2];
|
|
284
|
+
if (!tbsCertificate.children || tbsCertificate.children.length < 6) {
|
|
285
|
+
throw new Error("Invalid TBSCertificate structure");
|
|
286
|
+
}
|
|
287
|
+
let idx = 0;
|
|
288
|
+
if (tbsCertificate.children[0].tag === 160) {
|
|
289
|
+
idx++;
|
|
290
|
+
}
|
|
291
|
+
const serialNumber = integerToBigInt(tbsCertificate.children[idx].data);
|
|
292
|
+
idx++;
|
|
293
|
+
idx++;
|
|
294
|
+
const issuer = parseDN(tbsCertificate.children[idx]);
|
|
295
|
+
idx++;
|
|
296
|
+
const validity = tbsCertificate.children[idx];
|
|
297
|
+
const validFrom = parseAsn1Time(validity.children[0]);
|
|
298
|
+
const validTo = parseAsn1Time(validity.children[1]);
|
|
299
|
+
idx++;
|
|
300
|
+
const subject = parseDN(tbsCertificate.children[idx]);
|
|
301
|
+
idx++;
|
|
302
|
+
let publicKey = null;
|
|
303
|
+
let publicKeyAlgorithm = "unknown";
|
|
304
|
+
if (idx < tbsCertificate.children.length) {
|
|
305
|
+
const spki = tbsCertificate.children[idx];
|
|
306
|
+
try {
|
|
307
|
+
if (spki.children && spki.children.length >= 2) {
|
|
308
|
+
const algId = spki.children[0];
|
|
309
|
+
if (algId.children && algId.children.length >= 1) {
|
|
310
|
+
const oid = algId.children[0];
|
|
311
|
+
if (oid.tag === ASN1_OID && oidsEqual(oid.data, RSA_OID)) {
|
|
312
|
+
publicKeyAlgorithm = "rsa";
|
|
313
|
+
publicKey = parseSubjectPublicKeyInfo(spki);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
idx++;
|
|
320
|
+
}
|
|
321
|
+
let subjectAltName;
|
|
322
|
+
let extensions;
|
|
323
|
+
for (let i = idx; i < tbsCertificate.children.length; i++) {
|
|
324
|
+
const child = tbsCertificate.children[i];
|
|
325
|
+
if (child.tag === 163 && child.data.length > 0) {
|
|
326
|
+
const extSeq = parseDer(child.data);
|
|
327
|
+
if (extSeq.children) {
|
|
328
|
+
extensions = extSeq.children;
|
|
329
|
+
const SAN_OID = new Uint8Array([85, 29, 17]);
|
|
330
|
+
for (const ext of extSeq.children) {
|
|
331
|
+
if (ext.children && ext.children.length >= 2) {
|
|
332
|
+
const extOid = ext.children[0];
|
|
333
|
+
if (extOid.tag === ASN1_OID && oidsEqual(extOid.data, SAN_OID)) {
|
|
334
|
+
const extValue = ext.children[ext.children.length - 1];
|
|
335
|
+
subjectAltName = parseSAN(extValue.data);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
let sigAlg = "unknown";
|
|
343
|
+
if (signatureAlgorithm.children && signatureAlgorithm.children.length >= 1) {
|
|
344
|
+
sigAlg = oidToName(signatureAlgorithm.children[0].data);
|
|
345
|
+
}
|
|
346
|
+
const signature = signatureBitString.tag === ASN1_BIT_STRING ? signatureBitString.data.slice(1) : signatureBitString.data;
|
|
347
|
+
return {
|
|
348
|
+
raw: der,
|
|
349
|
+
tbsCertificate: tbsCertificate.data,
|
|
350
|
+
serialNumber,
|
|
351
|
+
issuer,
|
|
352
|
+
subject,
|
|
353
|
+
validFrom,
|
|
354
|
+
validTo,
|
|
355
|
+
publicKey,
|
|
356
|
+
publicKeyAlgorithm,
|
|
357
|
+
signatureAlgorithm: sigAlg,
|
|
358
|
+
signature,
|
|
359
|
+
subjectAltName,
|
|
360
|
+
extensions
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const OID_NAMES = {
|
|
364
|
+
"2.5.4.3": "CN",
|
|
365
|
+
"2.5.4.6": "C",
|
|
366
|
+
"2.5.4.7": "L",
|
|
367
|
+
"2.5.4.8": "ST",
|
|
368
|
+
"2.5.4.10": "O",
|
|
369
|
+
"2.5.4.11": "OU",
|
|
370
|
+
"1.2.840.113549.1.9.1": "emailAddress"
|
|
371
|
+
};
|
|
372
|
+
const SIG_ALG_NAMES = {
|
|
373
|
+
"1.2.840.113549.1.1.1": "rsaEncryption",
|
|
374
|
+
"1.2.840.113549.1.1.5": "sha1WithRSAEncryption",
|
|
375
|
+
"1.2.840.113549.1.1.11": "sha256WithRSAEncryption",
|
|
376
|
+
"1.2.840.113549.1.1.12": "sha384WithRSAEncryption",
|
|
377
|
+
"1.2.840.113549.1.1.13": "sha512WithRSAEncryption"
|
|
378
|
+
};
|
|
379
|
+
function decodeOidString(data) {
|
|
380
|
+
if (data.length === 0) return "";
|
|
381
|
+
const components = [];
|
|
382
|
+
components.push(Math.floor(data[0] / 40));
|
|
383
|
+
components.push(data[0] % 40);
|
|
384
|
+
let value = 0;
|
|
385
|
+
for (let i = 1; i < data.length; i++) {
|
|
386
|
+
value = value << 7 | data[i] & 127;
|
|
387
|
+
if (!(data[i] & 128)) {
|
|
388
|
+
components.push(value);
|
|
389
|
+
value = 0;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return components.join(".");
|
|
393
|
+
}
|
|
394
|
+
function oidToName(data) {
|
|
395
|
+
const oidStr = decodeOidString(data);
|
|
396
|
+
return SIG_ALG_NAMES[oidStr] || oidStr;
|
|
397
|
+
}
|
|
398
|
+
function parseDN(seq) {
|
|
399
|
+
if (!seq.children) return "";
|
|
400
|
+
const parts = [];
|
|
401
|
+
for (const rdn of seq.children) {
|
|
402
|
+
const rdnChildren = rdn.tag === 49 ? parseSequenceChildren(rdn.data) : rdn.children || [];
|
|
403
|
+
for (const atv of rdnChildren) {
|
|
404
|
+
if (atv.children && atv.children.length >= 2) {
|
|
405
|
+
const oidData = atv.children[0].data;
|
|
406
|
+
const oidStr = decodeOidString(oidData);
|
|
407
|
+
const name = OID_NAMES[oidStr] || oidStr;
|
|
408
|
+
const valueBytes = atv.children[1].data;
|
|
409
|
+
const value = new TextDecoder().decode(valueBytes);
|
|
410
|
+
parts.push(`${name}=${value}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return parts.join(", ");
|
|
415
|
+
}
|
|
416
|
+
function parseAsn1Time(tlv) {
|
|
417
|
+
const str = new TextDecoder().decode(tlv.data);
|
|
418
|
+
if (tlv.tag === 23) {
|
|
419
|
+
let year = parseInt(str.substring(0, 2), 10);
|
|
420
|
+
year = year >= 50 ? 1900 + year : 2e3 + year;
|
|
421
|
+
const month = parseInt(str.substring(2, 4), 10) - 1;
|
|
422
|
+
const day = parseInt(str.substring(4, 6), 10);
|
|
423
|
+
const hour = parseInt(str.substring(6, 8), 10);
|
|
424
|
+
const minute = parseInt(str.substring(8, 10), 10);
|
|
425
|
+
const second = parseInt(str.substring(10, 12), 10);
|
|
426
|
+
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
427
|
+
}
|
|
428
|
+
if (tlv.tag === 24) {
|
|
429
|
+
const year = parseInt(str.substring(0, 4), 10);
|
|
430
|
+
const month = parseInt(str.substring(4, 6), 10) - 1;
|
|
431
|
+
const day = parseInt(str.substring(6, 8), 10);
|
|
432
|
+
const hour = parseInt(str.substring(8, 10), 10);
|
|
433
|
+
const minute = parseInt(str.substring(10, 12), 10);
|
|
434
|
+
const second = parseInt(str.substring(12, 14), 10);
|
|
435
|
+
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
436
|
+
}
|
|
437
|
+
throw new Error(`Unsupported time tag: 0x${tlv.tag.toString(16)}`);
|
|
438
|
+
}
|
|
439
|
+
function parseSAN(data) {
|
|
440
|
+
const names = [];
|
|
441
|
+
try {
|
|
442
|
+
const seq = parseDer(data);
|
|
443
|
+
if (seq.tag === ASN1_SEQUENCE && seq.children) {
|
|
444
|
+
for (const child of seq.children) {
|
|
445
|
+
if (child.tag === 130) {
|
|
446
|
+
names.push("DNS:" + new TextDecoder().decode(child.data));
|
|
447
|
+
} else if (child.tag === 135) {
|
|
448
|
+
if (child.data.length === 4) {
|
|
449
|
+
names.push("IP Address:" + child.data.join("."));
|
|
450
|
+
} else if (child.data.length === 16) {
|
|
451
|
+
const parts = [];
|
|
452
|
+
for (let i = 0; i < 16; i += 2) {
|
|
453
|
+
parts.push((child.data[i] << 8 | child.data[i + 1]).toString(16));
|
|
454
|
+
}
|
|
455
|
+
names.push("IP Address:" + parts.join(":"));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
return names;
|
|
463
|
+
}
|
|
464
|
+
function parsePemKey(pem) {
|
|
465
|
+
const { type, der } = pemToDer(pem);
|
|
466
|
+
const root = parseDer(der);
|
|
467
|
+
if (root.tag !== ASN1_SEQUENCE) {
|
|
468
|
+
throw new Error("Invalid key format: expected top-level SEQUENCE");
|
|
469
|
+
}
|
|
470
|
+
switch (type) {
|
|
471
|
+
case "RSA PUBLIC KEY":
|
|
472
|
+
return { type: "rsa-public", components: parseRsaPublicKeyPkcs1(root) };
|
|
473
|
+
case "PUBLIC KEY":
|
|
474
|
+
return { type: "rsa-public", components: parseSubjectPublicKeyInfo(root) };
|
|
475
|
+
case "RSA PRIVATE KEY":
|
|
476
|
+
return { type: "rsa-private", components: parseRsaPrivateKeyPkcs1(root) };
|
|
477
|
+
case "PRIVATE KEY":
|
|
478
|
+
return { type: "rsa-private", components: parsePrivateKeyInfo(root) };
|
|
479
|
+
default:
|
|
480
|
+
throw new Error(`Unsupported PEM type: ${type}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function rsaKeySize(n) {
|
|
484
|
+
let bits = 0;
|
|
485
|
+
let val = n;
|
|
486
|
+
while (val > 0n) {
|
|
487
|
+
bits++;
|
|
488
|
+
val >>= 1n;
|
|
489
|
+
}
|
|
490
|
+
return Math.ceil(bits / 8);
|
|
491
|
+
}
|
|
492
|
+
export {
|
|
493
|
+
bigintToAsn1Integer,
|
|
494
|
+
derToPem,
|
|
495
|
+
encodePrivateKeyInfo,
|
|
496
|
+
encodeRsaPrivateKeyPkcs1,
|
|
497
|
+
encodeRsaPublicKeyPkcs1,
|
|
498
|
+
encodeSequence,
|
|
499
|
+
encodeSubjectPublicKeyInfo,
|
|
500
|
+
parsePemKey,
|
|
501
|
+
parseX509,
|
|
502
|
+
parseX509Der,
|
|
503
|
+
rsaKeySize
|
|
504
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function modPow(base, exp, mod) {
|
|
2
|
+
if (mod === 1n) return 0n;
|
|
3
|
+
base = (base % mod + mod) % mod;
|
|
4
|
+
let result = 1n;
|
|
5
|
+
while (exp > 0n) {
|
|
6
|
+
if (exp & 1n) {
|
|
7
|
+
result = result * base % mod;
|
|
8
|
+
}
|
|
9
|
+
exp >>= 1n;
|
|
10
|
+
base = base * base % mod;
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
function bigIntToBytes(value, length) {
|
|
15
|
+
const result = new Uint8Array(length);
|
|
16
|
+
let v = value;
|
|
17
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
18
|
+
result[i] = Number(v & 0xffn);
|
|
19
|
+
v >>= 8n;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
function bytesToBigInt(bytes) {
|
|
24
|
+
let result = 0n;
|
|
25
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
26
|
+
result = result << 8n | BigInt(bytes[i]);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
bigIntToBytes,
|
|
32
|
+
bytesToBigInt,
|
|
33
|
+
modPow
|
|
34
|
+
};
|