@noz-ele/edgca 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/LICENSE +21 -0
- package/README.md +270 -0
- package/SECURITY.md +34 -0
- package/dist/bytes.d.ts +9 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +58 -0
- package/dist/ca.d.ts +6 -0
- package/dist/ca.d.ts.map +1 -0
- package/dist/ca.js +193 -0
- package/dist/crypto.d.ts +16 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +104 -0
- package/dist/der.d.ts +45 -0
- package/dist/der.d.ts.map +1 -0
- package/dist/der.js +269 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/ip.d.ts +2 -0
- package/dist/ip.d.ts.map +1 -0
- package/dist/ip.js +55 -0
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +71 -0
- package/dist/oids.d.ts +15 -0
- package/dist/oids.d.ts.map +1 -0
- package/dist/oids.js +48 -0
- package/dist/parser.d.ts +17 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +146 -0
- package/dist/pem.d.ts +7 -0
- package/dist/pem.d.ts.map +1 -0
- package/dist/pem.js +52 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/verify.d.ts +46 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +73 -0
- package/dist/x509.d.ts +34 -0
- package/dist/x509.d.ts.map +1 -0
- package/dist/x509.js +197 -0
- package/package.json +66 -0
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { arrayBufferFromBytes, concatBytes } from "./bytes.js";
|
|
2
|
+
import { integer, readChildren, readElement, readSequenceChildren, sequence, TAG } from "./der.js";
|
|
3
|
+
import { pemToDerWithLabel, privateKeyDerToPem, publicKeyDerToPem } from "./pem.js";
|
|
4
|
+
const EC_ALGORITHM = {
|
|
5
|
+
name: "ECDSA",
|
|
6
|
+
namedCurve: "P-256"
|
|
7
|
+
};
|
|
8
|
+
const ECDSA_SIGN_ALGORITHM = {
|
|
9
|
+
name: "ECDSA",
|
|
10
|
+
hash: "SHA-256"
|
|
11
|
+
};
|
|
12
|
+
export async function generateKeyPair() {
|
|
13
|
+
return crypto.subtle.generateKey(EC_ALGORITHM, true, ["sign", "verify"]);
|
|
14
|
+
}
|
|
15
|
+
export async function signDer(privateKey, data) {
|
|
16
|
+
const raw = new Uint8Array(await crypto.subtle.sign(ECDSA_SIGN_ALGORITHM, privateKey, arrayBufferFromBytes(data)));
|
|
17
|
+
return ecdsaRawToDer(raw);
|
|
18
|
+
}
|
|
19
|
+
export async function verifyDer(publicKey, signatureDer, data) {
|
|
20
|
+
return crypto.subtle.verify(ECDSA_SIGN_ALGORITHM, publicKey, arrayBufferFromBytes(ecdsaDerToRaw(signatureDer)), arrayBufferFromBytes(data));
|
|
21
|
+
}
|
|
22
|
+
export async function digestSha256(data) {
|
|
23
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", arrayBufferFromBytes(data)));
|
|
24
|
+
}
|
|
25
|
+
export async function digestSha1(data) {
|
|
26
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-1", arrayBufferFromBytes(data)));
|
|
27
|
+
}
|
|
28
|
+
// RFC 5280 §4.2.1.2 method (1): SHA-1 of the BIT STRING subjectPublicKey value,
|
|
29
|
+
// excluding the tag, length, and number of unused bits.
|
|
30
|
+
export async function keyIdentifierFromSpki(spki) {
|
|
31
|
+
const root = readElement(spki);
|
|
32
|
+
if (root.tag !== TAG.SEQUENCE) {
|
|
33
|
+
throw new Error("Invalid SubjectPublicKeyInfo");
|
|
34
|
+
}
|
|
35
|
+
const children = readSequenceChildren(root);
|
|
36
|
+
const subjectPublicKey = children[1];
|
|
37
|
+
if (!subjectPublicKey || subjectPublicKey.tag !== TAG.BIT_STRING || subjectPublicKey.value.length < 1) {
|
|
38
|
+
throw new Error("Invalid SubjectPublicKeyInfo subjectPublicKey");
|
|
39
|
+
}
|
|
40
|
+
return digestSha1(subjectPublicKey.value.subarray(1));
|
|
41
|
+
}
|
|
42
|
+
export async function privateKeyToPem(key) {
|
|
43
|
+
const der = new Uint8Array(await crypto.subtle.exportKey("pkcs8", key));
|
|
44
|
+
return privateKeyDerToPem(der);
|
|
45
|
+
}
|
|
46
|
+
export async function publicKeyToPem(key) {
|
|
47
|
+
const der = new Uint8Array(await crypto.subtle.exportKey("spki", key));
|
|
48
|
+
return publicKeyDerToPem(der);
|
|
49
|
+
}
|
|
50
|
+
export async function exportSpki(key) {
|
|
51
|
+
return new Uint8Array(await crypto.subtle.exportKey("spki", key));
|
|
52
|
+
}
|
|
53
|
+
export async function importPrivateKeyPem(pem) {
|
|
54
|
+
return crypto.subtle.importKey("pkcs8", arrayBufferFromBytes(pemToDerWithLabel(pem, "PRIVATE KEY")), EC_ALGORITHM, true, ["sign"]);
|
|
55
|
+
}
|
|
56
|
+
export async function keyPairFromPrivateKeyPem(pem) {
|
|
57
|
+
const privateKey = await importPrivateKeyPem(pem);
|
|
58
|
+
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
|
|
59
|
+
delete jwk.d;
|
|
60
|
+
jwk.key_ops = ["verify"];
|
|
61
|
+
const publicKey = await crypto.subtle.importKey("jwk", jwk, EC_ALGORITHM, true, ["verify"]);
|
|
62
|
+
return { privateKey, publicKey };
|
|
63
|
+
}
|
|
64
|
+
export async function importPublicKeySpki(spki) {
|
|
65
|
+
return crypto.subtle.importKey("spki", arrayBufferFromBytes(spki), EC_ALGORITHM, true, ["verify"]);
|
|
66
|
+
}
|
|
67
|
+
export async function assertKeyPairMatches(privateKey, publicKey) {
|
|
68
|
+
const data = new TextEncoder().encode("edgca-key-pair-check");
|
|
69
|
+
const signature = await signDer(privateKey, data);
|
|
70
|
+
const ok = await verifyDer(publicKey, signature, data);
|
|
71
|
+
if (!ok) {
|
|
72
|
+
throw new Error("Private key does not match CA certificate public key");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function ecdsaRawToDer(raw) {
|
|
76
|
+
if (raw.length !== 64) {
|
|
77
|
+
throw new Error("P-256 ECDSA raw signature must be 64 bytes");
|
|
78
|
+
}
|
|
79
|
+
return sequence(integer(raw.subarray(0, 32)), integer(raw.subarray(32)));
|
|
80
|
+
}
|
|
81
|
+
export function ecdsaDerToRaw(signature) {
|
|
82
|
+
const root = readElement(signature);
|
|
83
|
+
if (root.tag !== TAG.SEQUENCE || root.end !== signature.length) {
|
|
84
|
+
throw new Error("Invalid DER ECDSA signature");
|
|
85
|
+
}
|
|
86
|
+
const [r, s] = readChildren(root.value);
|
|
87
|
+
if (!r || !s || r.tag !== TAG.INTEGER || s.tag !== TAG.INTEGER) {
|
|
88
|
+
throw new Error("Invalid DER ECDSA signature integers");
|
|
89
|
+
}
|
|
90
|
+
return concatBytes([integerToFixedWidth(r.value), integerToFixedWidth(s.value)]);
|
|
91
|
+
}
|
|
92
|
+
function integerToFixedWidth(value) {
|
|
93
|
+
let start = 0;
|
|
94
|
+
while (start < value.length - 1 && value[start] === 0) {
|
|
95
|
+
start += 1;
|
|
96
|
+
}
|
|
97
|
+
const trimmed = value.subarray(start);
|
|
98
|
+
if (trimmed.length > 32) {
|
|
99
|
+
throw new Error("ECDSA integer is wider than P-256");
|
|
100
|
+
}
|
|
101
|
+
const out = new Uint8Array(32);
|
|
102
|
+
out.set(trimmed, 32 - trimmed.length);
|
|
103
|
+
return out;
|
|
104
|
+
}
|
package/dist/der.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const TAG: {
|
|
2
|
+
readonly BOOLEAN: 1;
|
|
3
|
+
readonly INTEGER: 2;
|
|
4
|
+
readonly BIT_STRING: 3;
|
|
5
|
+
readonly OCTET_STRING: 4;
|
|
6
|
+
readonly NULL: 5;
|
|
7
|
+
readonly OBJECT_IDENTIFIER: 6;
|
|
8
|
+
readonly UTF8_STRING: 12;
|
|
9
|
+
readonly SEQUENCE: 48;
|
|
10
|
+
readonly SET: 49;
|
|
11
|
+
readonly PRINTABLE_STRING: 19;
|
|
12
|
+
readonly IA5_STRING: 22;
|
|
13
|
+
readonly UTC_TIME: 23;
|
|
14
|
+
readonly GENERALIZED_TIME: 24;
|
|
15
|
+
};
|
|
16
|
+
export interface DerElement {
|
|
17
|
+
tag: number;
|
|
18
|
+
headerLength: number;
|
|
19
|
+
length: number;
|
|
20
|
+
start: number;
|
|
21
|
+
end: number;
|
|
22
|
+
value: Uint8Array;
|
|
23
|
+
raw: Uint8Array;
|
|
24
|
+
}
|
|
25
|
+
export declare function der(tag: number, value: Uint8Array): Uint8Array;
|
|
26
|
+
export declare function sequence(...children: Uint8Array[]): Uint8Array;
|
|
27
|
+
export declare function set(...children: Uint8Array[]): Uint8Array;
|
|
28
|
+
export declare function explicit(tagNumber: number, value: Uint8Array): Uint8Array;
|
|
29
|
+
export declare function oid(value: string): Uint8Array;
|
|
30
|
+
export declare function boolean(value: boolean): Uint8Array;
|
|
31
|
+
export declare function integer(value: bigint | number | Uint8Array): Uint8Array;
|
|
32
|
+
export declare function bitString(bytes: Uint8Array, unusedBits?: number): Uint8Array;
|
|
33
|
+
export declare function octetString(bytes: Uint8Array): Uint8Array;
|
|
34
|
+
export declare function utf8String(value: string): Uint8Array;
|
|
35
|
+
export declare function printableString(value: string): Uint8Array;
|
|
36
|
+
export declare function ia5String(value: string): Uint8Array;
|
|
37
|
+
export declare function contextPrimitive(tagNumber: number, value: Uint8Array): Uint8Array;
|
|
38
|
+
export declare function utcTime(date: Date): Uint8Array;
|
|
39
|
+
export declare function generalizedTime(date: Date): Uint8Array;
|
|
40
|
+
export declare function readElement(input: Uint8Array, offset?: number): DerElement;
|
|
41
|
+
export declare function readSequenceChildren(element: DerElement): DerElement[];
|
|
42
|
+
export declare function readChildren(input: Uint8Array): DerElement[];
|
|
43
|
+
export declare function decodeOid(input: Uint8Array): string;
|
|
44
|
+
export declare function decodeInteger(input: Uint8Array): bigint;
|
|
45
|
+
//# sourceMappingURL=der.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"der.d.ts","sourceRoot":"","sources":["../src/der.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;;;;;;;;;;;;;;CAcN,CAAC;AAEX,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,EAAE,UAAU,CAAC;CACjB;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAE9D;AAED,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAE9D;AAED,wBAAgB,GAAG,CAAC,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAEzD;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAEzE;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAmB7C;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAElD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CA4BvE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,SAAI,GAAG,UAAU,CAMvE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAEzD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAMzD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEnD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAEjF;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAO9C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAMtD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,SAAI,GAAG,UAAU,CAuBrE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,EAAE,CAMtE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,CAW5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CA4CnD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAevD"}
|
package/dist/der.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { asciiBytes, concatBytes, utf8Bytes } from "./bytes.js";
|
|
2
|
+
export const TAG = {
|
|
3
|
+
BOOLEAN: 0x01,
|
|
4
|
+
INTEGER: 0x02,
|
|
5
|
+
BIT_STRING: 0x03,
|
|
6
|
+
OCTET_STRING: 0x04,
|
|
7
|
+
NULL: 0x05,
|
|
8
|
+
OBJECT_IDENTIFIER: 0x06,
|
|
9
|
+
UTF8_STRING: 0x0c,
|
|
10
|
+
SEQUENCE: 0x30,
|
|
11
|
+
SET: 0x31,
|
|
12
|
+
PRINTABLE_STRING: 0x13,
|
|
13
|
+
IA5_STRING: 0x16,
|
|
14
|
+
UTC_TIME: 0x17,
|
|
15
|
+
GENERALIZED_TIME: 0x18
|
|
16
|
+
};
|
|
17
|
+
export function der(tag, value) {
|
|
18
|
+
return concatBytes([new Uint8Array([tag]), encodeLength(value.length), value]);
|
|
19
|
+
}
|
|
20
|
+
export function sequence(...children) {
|
|
21
|
+
return der(TAG.SEQUENCE, concatBytes(children));
|
|
22
|
+
}
|
|
23
|
+
export function set(...children) {
|
|
24
|
+
return der(TAG.SET, concatBytes(children));
|
|
25
|
+
}
|
|
26
|
+
export function explicit(tagNumber, value) {
|
|
27
|
+
return der(0xa0 + tagNumber, value);
|
|
28
|
+
}
|
|
29
|
+
export function oid(value) {
|
|
30
|
+
const parts = value.split(".").map((part) => {
|
|
31
|
+
if (!/^(0|[1-9]\d*)$/.test(part)) {
|
|
32
|
+
throw new Error(`Invalid OID: ${value}`);
|
|
33
|
+
}
|
|
34
|
+
return Number(part);
|
|
35
|
+
});
|
|
36
|
+
if (parts.length < 2) {
|
|
37
|
+
throw new Error(`Invalid OID: ${value}`);
|
|
38
|
+
}
|
|
39
|
+
const [first, second, ...rest] = parts;
|
|
40
|
+
if (first === undefined || second === undefined || first > 2 || second > 39 && first < 2) {
|
|
41
|
+
throw new Error(`Invalid OID: ${value}`);
|
|
42
|
+
}
|
|
43
|
+
const body = [first * 40 + second, ...rest].flatMap(encodeBase128);
|
|
44
|
+
return der(TAG.OBJECT_IDENTIFIER, new Uint8Array(body));
|
|
45
|
+
}
|
|
46
|
+
export function boolean(value) {
|
|
47
|
+
return der(TAG.BOOLEAN, new Uint8Array([value ? 0xff : 0x00]));
|
|
48
|
+
}
|
|
49
|
+
export function integer(value) {
|
|
50
|
+
if (value instanceof Uint8Array) {
|
|
51
|
+
return der(TAG.INTEGER, normalizeIntegerBytes(value));
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === "number") {
|
|
54
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
55
|
+
throw new Error("INTEGER number must be a non-negative safe integer");
|
|
56
|
+
}
|
|
57
|
+
return integer(BigInt(value));
|
|
58
|
+
}
|
|
59
|
+
if (value < 0n) {
|
|
60
|
+
throw new Error("INTEGER bigint must be non-negative");
|
|
61
|
+
}
|
|
62
|
+
if (value === 0n) {
|
|
63
|
+
return der(TAG.INTEGER, new Uint8Array([0]));
|
|
64
|
+
}
|
|
65
|
+
const bytes = [];
|
|
66
|
+
let current = value;
|
|
67
|
+
while (current > 0n) {
|
|
68
|
+
bytes.unshift(Number(current & 0xffn));
|
|
69
|
+
current >>= 8n;
|
|
70
|
+
}
|
|
71
|
+
return integer(new Uint8Array(bytes));
|
|
72
|
+
}
|
|
73
|
+
export function bitString(bytes, unusedBits = 0) {
|
|
74
|
+
if (unusedBits < 0 || unusedBits > 7) {
|
|
75
|
+
throw new Error("BIT STRING unused bits must be between 0 and 7");
|
|
76
|
+
}
|
|
77
|
+
return der(TAG.BIT_STRING, concatBytes([new Uint8Array([unusedBits]), bytes]));
|
|
78
|
+
}
|
|
79
|
+
export function octetString(bytes) {
|
|
80
|
+
return der(TAG.OCTET_STRING, bytes);
|
|
81
|
+
}
|
|
82
|
+
export function utf8String(value) {
|
|
83
|
+
return der(TAG.UTF8_STRING, utf8Bytes(value));
|
|
84
|
+
}
|
|
85
|
+
export function printableString(value) {
|
|
86
|
+
if (!/^[A-Za-z0-9 '()+,\-./:=?]*$/.test(value)) {
|
|
87
|
+
throw new Error("PrintableString contains an unsupported character");
|
|
88
|
+
}
|
|
89
|
+
return der(TAG.PRINTABLE_STRING, asciiBytes(value));
|
|
90
|
+
}
|
|
91
|
+
export function ia5String(value) {
|
|
92
|
+
return der(TAG.IA5_STRING, asciiBytes(value));
|
|
93
|
+
}
|
|
94
|
+
export function contextPrimitive(tagNumber, value) {
|
|
95
|
+
return der(0x80 + tagNumber, value);
|
|
96
|
+
}
|
|
97
|
+
export function utcTime(date) {
|
|
98
|
+
const year = date.getUTCFullYear();
|
|
99
|
+
if (year < 1950 || year > 2049) {
|
|
100
|
+
return generalizedTime(date);
|
|
101
|
+
}
|
|
102
|
+
return der(TAG.UTC_TIME, asciiBytes(`${two(year % 100)}${timeTail(date)}`));
|
|
103
|
+
}
|
|
104
|
+
export function generalizedTime(date) {
|
|
105
|
+
const year = date.getUTCFullYear();
|
|
106
|
+
if (year < 1 || year > 9999) {
|
|
107
|
+
throw new Error("GeneralizedTime year must be between 0001 and 9999");
|
|
108
|
+
}
|
|
109
|
+
return der(TAG.GENERALIZED_TIME, asciiBytes(`${year.toString().padStart(4, "0")}${timeTail(date)}`));
|
|
110
|
+
}
|
|
111
|
+
export function readElement(input, offset = 0) {
|
|
112
|
+
if (offset >= input.length) {
|
|
113
|
+
throw new Error("Unexpected end of DER input");
|
|
114
|
+
}
|
|
115
|
+
const tag = input[offset];
|
|
116
|
+
const lengthInfo = decodeLength(input, offset + 1);
|
|
117
|
+
const start = lengthInfo.offset;
|
|
118
|
+
const end = start + lengthInfo.length;
|
|
119
|
+
if (end > input.length) {
|
|
120
|
+
throw new Error("DER length exceeds input size");
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
tag,
|
|
124
|
+
headerLength: start - offset,
|
|
125
|
+
length: lengthInfo.length,
|
|
126
|
+
start,
|
|
127
|
+
end,
|
|
128
|
+
value: input.subarray(start, end),
|
|
129
|
+
raw: input.subarray(offset, end)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export function readSequenceChildren(element) {
|
|
133
|
+
if (element.tag !== TAG.SEQUENCE) {
|
|
134
|
+
throw new Error("Expected SEQUENCE");
|
|
135
|
+
}
|
|
136
|
+
return readChildren(element.value);
|
|
137
|
+
}
|
|
138
|
+
export function readChildren(input) {
|
|
139
|
+
const out = [];
|
|
140
|
+
let offset = 0;
|
|
141
|
+
while (offset < input.length) {
|
|
142
|
+
const element = readElement(input, offset);
|
|
143
|
+
out.push(element);
|
|
144
|
+
offset = element.end;
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
export function decodeOid(input) {
|
|
149
|
+
if (input.length === 0) {
|
|
150
|
+
throw new Error("Invalid empty OID");
|
|
151
|
+
}
|
|
152
|
+
// Decode the first base-128 sub-identifier (which may be multi-byte for
|
|
153
|
+
// joint-iso-itu-t OIDs whose joint value 2*40+second exceeds 127).
|
|
154
|
+
let firstSubId = 0;
|
|
155
|
+
let i = 0;
|
|
156
|
+
let firstComplete = false;
|
|
157
|
+
while (i < input.length) {
|
|
158
|
+
const byte = input[i];
|
|
159
|
+
firstSubId = firstSubId * 128 + (byte & 0x7f);
|
|
160
|
+
i += 1;
|
|
161
|
+
if ((byte & 0x80) === 0) {
|
|
162
|
+
firstComplete = true;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!firstComplete) {
|
|
167
|
+
throw new Error("Truncated OID");
|
|
168
|
+
}
|
|
169
|
+
const parts = firstSubId < 40 ? [0, firstSubId]
|
|
170
|
+
: firstSubId < 80 ? [1, firstSubId - 40]
|
|
171
|
+
: [2, firstSubId - 80];
|
|
172
|
+
let value = 0;
|
|
173
|
+
for (; i < input.length; i += 1) {
|
|
174
|
+
const byte = input[i];
|
|
175
|
+
value = value * 128 + (byte & 0x7f);
|
|
176
|
+
if ((byte & 0x80) === 0) {
|
|
177
|
+
parts.push(value);
|
|
178
|
+
value = 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (value !== 0) {
|
|
182
|
+
throw new Error("Truncated OID");
|
|
183
|
+
}
|
|
184
|
+
return parts.join(".");
|
|
185
|
+
}
|
|
186
|
+
export function decodeInteger(input) {
|
|
187
|
+
if (input.length === 0) {
|
|
188
|
+
throw new Error("Invalid empty INTEGER");
|
|
189
|
+
}
|
|
190
|
+
if ((input[0] & 0x80) !== 0) {
|
|
191
|
+
throw new Error("Negative INTEGER is unsupported");
|
|
192
|
+
}
|
|
193
|
+
let value = 0n;
|
|
194
|
+
for (const byte of input) {
|
|
195
|
+
value = (value << 8n) | BigInt(byte);
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
function encodeLength(length) {
|
|
200
|
+
if (length < 0x80) {
|
|
201
|
+
return new Uint8Array([length]);
|
|
202
|
+
}
|
|
203
|
+
const bytes = [];
|
|
204
|
+
let current = length;
|
|
205
|
+
while (current > 0) {
|
|
206
|
+
bytes.unshift(current & 0xff);
|
|
207
|
+
current >>= 8;
|
|
208
|
+
}
|
|
209
|
+
return new Uint8Array([0x80 | bytes.length, ...bytes]);
|
|
210
|
+
}
|
|
211
|
+
function decodeLength(input, offset) {
|
|
212
|
+
const first = input[offset];
|
|
213
|
+
if (first === undefined) {
|
|
214
|
+
throw new Error("Missing DER length");
|
|
215
|
+
}
|
|
216
|
+
if ((first & 0x80) === 0) {
|
|
217
|
+
return { length: first, offset: offset + 1 };
|
|
218
|
+
}
|
|
219
|
+
const count = first & 0x7f;
|
|
220
|
+
if (count === 0) {
|
|
221
|
+
throw new Error("Indefinite DER length is not allowed");
|
|
222
|
+
}
|
|
223
|
+
if (offset + 1 + count > input.length) {
|
|
224
|
+
throw new Error("Truncated DER length");
|
|
225
|
+
}
|
|
226
|
+
let length = 0;
|
|
227
|
+
for (let i = 0; i < count; i += 1) {
|
|
228
|
+
length = (length << 8) | input[offset + 1 + i];
|
|
229
|
+
}
|
|
230
|
+
return { length, offset: offset + 1 + count };
|
|
231
|
+
}
|
|
232
|
+
function encodeBase128(value) {
|
|
233
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
234
|
+
throw new Error("OID component must be a non-negative safe integer");
|
|
235
|
+
}
|
|
236
|
+
if (value === 0) {
|
|
237
|
+
return [0];
|
|
238
|
+
}
|
|
239
|
+
const bytes = [];
|
|
240
|
+
let current = value;
|
|
241
|
+
while (current > 0) {
|
|
242
|
+
bytes.unshift(current & 0x7f);
|
|
243
|
+
current >>= 7;
|
|
244
|
+
}
|
|
245
|
+
for (let i = 0; i < bytes.length - 1; i += 1) {
|
|
246
|
+
bytes[i] = bytes[i] | 0x80;
|
|
247
|
+
}
|
|
248
|
+
return bytes;
|
|
249
|
+
}
|
|
250
|
+
function normalizeIntegerBytes(bytes) {
|
|
251
|
+
let offset = 0;
|
|
252
|
+
while (offset < bytes.length - 1 && bytes[offset] === 0) {
|
|
253
|
+
offset += 1;
|
|
254
|
+
}
|
|
255
|
+
const trimmed = bytes.subarray(offset);
|
|
256
|
+
if (trimmed.length === 0) {
|
|
257
|
+
return new Uint8Array([0]);
|
|
258
|
+
}
|
|
259
|
+
if ((trimmed[0] & 0x80) !== 0) {
|
|
260
|
+
return concatBytes([new Uint8Array([0]), trimmed]);
|
|
261
|
+
}
|
|
262
|
+
return new Uint8Array(trimmed);
|
|
263
|
+
}
|
|
264
|
+
function timeTail(date) {
|
|
265
|
+
return `${two(date.getUTCMonth() + 1)}${two(date.getUTCDate())}${two(date.getUTCHours())}${two(date.getUTCMinutes())}${two(date.getUTCSeconds())}Z`;
|
|
266
|
+
}
|
|
267
|
+
function two(value) {
|
|
268
|
+
return value.toString().padStart(2, "0");
|
|
269
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createRootCA, importCertificateAuthority, issueClientCert, issueIntermediateCA } from "./ca.js";
|
|
2
|
+
export { pemToDer, certificateToPem } from "./pem.js";
|
|
3
|
+
export { privateKeyToPem, publicKeyToPem } from "./crypto.js";
|
|
4
|
+
export { verifyClientCertificateIssuedBy, type VerifyClientCertificateIssuedByOptions, type VerifyClientCertificateValidity } from "./verify.js";
|
|
5
|
+
export type { CertificateAuthority, CreateRootCAOptions, ImportCertificateAuthorityOptions, IssueClientCertOptions, IssueIntermediateCAOptions, IssuedClientCertificate, SerialNumber, ShortSubjectAttributeType, Subject, SubjectAttribute, SubjectAttributeType } from "./types.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC1B,eAAe,EACf,mBAAmB,EACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,+BAA+B,EAC/B,KAAK,sCAAsC,EAC3C,KAAK,+BAA+B,EACrC,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iCAAiC,EACjC,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,EACvB,YAAY,EACZ,yBAAyB,EACzB,OAAO,EACP,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createRootCA, importCertificateAuthority, issueClientCert, issueIntermediateCA } from "./ca.js";
|
|
2
|
+
export { pemToDer, certificateToPem } from "./pem.js";
|
|
3
|
+
export { privateKeyToPem, publicKeyToPem } from "./crypto.js";
|
|
4
|
+
export { verifyClientCertificateIssuedBy } from "./verify.js";
|
package/dist/ip.d.ts
ADDED
package/dist/ip.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip.d.ts","sourceRoot":"","sources":["../src/ip.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEzD"}
|
package/dist/ip.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function encodeIpAddress(value) {
|
|
2
|
+
return value.includes(":") ? encodeIpv6(value) : encodeIpv4(value);
|
|
3
|
+
}
|
|
4
|
+
function encodeIpv4(value) {
|
|
5
|
+
const parts = value.split(".");
|
|
6
|
+
if (parts.length !== 4) {
|
|
7
|
+
throw new Error(`Invalid IPv4 address: ${value}`);
|
|
8
|
+
}
|
|
9
|
+
return new Uint8Array(parts.map((part) => {
|
|
10
|
+
if (!/^(0|[1-9]\d*)$/.test(part)) {
|
|
11
|
+
throw new Error(`Invalid IPv4 address: ${value}`);
|
|
12
|
+
}
|
|
13
|
+
const octet = Number(part);
|
|
14
|
+
if (octet < 0 || octet > 255) {
|
|
15
|
+
throw new Error(`Invalid IPv4 address: ${value}`);
|
|
16
|
+
}
|
|
17
|
+
return octet;
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
function encodeIpv6(value) {
|
|
21
|
+
const segments = value.split("::");
|
|
22
|
+
if (segments.length > 2) {
|
|
23
|
+
throw new Error(`Invalid IPv6 address: ${value}`);
|
|
24
|
+
}
|
|
25
|
+
const left = parseIpv6Groups(segments[0], value);
|
|
26
|
+
const right = parseIpv6Groups(segments[1] ?? "", value);
|
|
27
|
+
const missing = 8 - left.length - right.length;
|
|
28
|
+
if (segments.length === 2) {
|
|
29
|
+
// RFC 5952 §4.2.2: "::" must compress at least two zero groups.
|
|
30
|
+
if (missing < 2) {
|
|
31
|
+
throw new Error(`Invalid IPv6 address: ${value}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (missing !== 0) {
|
|
35
|
+
throw new Error(`Invalid IPv6 address: ${value}`);
|
|
36
|
+
}
|
|
37
|
+
const groups = [...left, ...new Array(missing).fill(0), ...right];
|
|
38
|
+
const out = new Uint8Array(16);
|
|
39
|
+
groups.forEach((group, index) => {
|
|
40
|
+
out[index * 2] = group >> 8;
|
|
41
|
+
out[index * 2 + 1] = group & 0xff;
|
|
42
|
+
});
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function parseIpv6Groups(part, original) {
|
|
46
|
+
if (part === "") {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return part.split(":").map((group) => {
|
|
50
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(group)) {
|
|
51
|
+
throw new Error(`Invalid IPv6 address: ${original}`);
|
|
52
|
+
}
|
|
53
|
+
return Number.parseInt(group, 16);
|
|
54
|
+
});
|
|
55
|
+
}
|
package/dist/name.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.d.ts","sourceRoot":"","sources":["../src/name.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAgB3F,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAkCvD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,CAUtE"}
|
package/dist/name.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { sequence, set, oid, ia5String, printableString, utf8String } from "./der.js";
|
|
2
|
+
import { MAX_SUBJECT_VALUE_LENGTH, SUBJECT_ATTRIBUTE_OIDS, SUBJECT_VALUE_LENGTH_LIMITS } from "./oids.js";
|
|
3
|
+
const SHORT_TYPES = new Set(Object.keys(SUBJECT_ATTRIBUTE_OIDS));
|
|
4
|
+
const DOTTED_OID_PATTERN = /^(?:0|1|2)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))+$/;
|
|
5
|
+
// RFC 5280 §A.1: AttributeValue string type per attribute OID.
|
|
6
|
+
// C → PrintableString, emailAddress (E) → IA5String, others → UTF8String.
|
|
7
|
+
const PRINTABLE_STRING_OIDS = new Set([SUBJECT_ATTRIBUTE_OIDS.C]);
|
|
8
|
+
const IA5_STRING_OIDS = new Set([SUBJECT_ATTRIBUTE_OIDS.E]);
|
|
9
|
+
function encodeAttributeValue(attributeOid, value) {
|
|
10
|
+
if (PRINTABLE_STRING_OIDS.has(attributeOid))
|
|
11
|
+
return printableString(value);
|
|
12
|
+
if (IA5_STRING_OIDS.has(attributeOid))
|
|
13
|
+
return ia5String(value);
|
|
14
|
+
return utf8String(value);
|
|
15
|
+
}
|
|
16
|
+
export function encodeName(subject) {
|
|
17
|
+
if (!Array.isArray(subject) || subject.length === 0) {
|
|
18
|
+
throw new Error("subject must be a non-empty array");
|
|
19
|
+
}
|
|
20
|
+
return sequence(...subject.map((attribute, index) => {
|
|
21
|
+
if (!attribute || typeof attribute !== "object") {
|
|
22
|
+
throw new Error(`subject[${index}] must be an object with type and value`);
|
|
23
|
+
}
|
|
24
|
+
if (typeof attribute.type !== "string") {
|
|
25
|
+
throw new Error(`subject[${index}].type must be a string`);
|
|
26
|
+
}
|
|
27
|
+
if (typeof attribute.value !== "string") {
|
|
28
|
+
throw new Error(`subject[${index}].value must be a string`);
|
|
29
|
+
}
|
|
30
|
+
if (attribute.value.length === 0) {
|
|
31
|
+
throw new Error(`subject[${index}].value must not be empty`);
|
|
32
|
+
}
|
|
33
|
+
if (containsForbiddenChar(attribute.value)) {
|
|
34
|
+
throw new Error(`subject[${index}].value contains forbidden control or bidi character`);
|
|
35
|
+
}
|
|
36
|
+
const limit = SUBJECT_VALUE_LENGTH_LIMITS[attribute.type] ?? MAX_SUBJECT_VALUE_LENGTH;
|
|
37
|
+
const codepointLength = [...attribute.value].length;
|
|
38
|
+
if (codepointLength > limit) {
|
|
39
|
+
throw new Error(`subject[${index}].value exceeds ${limit} character limit for type "${attribute.type}"`);
|
|
40
|
+
}
|
|
41
|
+
const attributeOid = resolveAttributeOid(attribute.type);
|
|
42
|
+
const value = encodeAttributeValue(attributeOid, attribute.value);
|
|
43
|
+
return set(sequence(oid(attributeOid), value));
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
export function resolveAttributeOid(type) {
|
|
47
|
+
if (SHORT_TYPES.has(type)) {
|
|
48
|
+
return SUBJECT_ATTRIBUTE_OIDS[type];
|
|
49
|
+
}
|
|
50
|
+
if (DOTTED_OID_PATTERN.test(type)) {
|
|
51
|
+
return type;
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`Unsupported subject attribute type: ${type}`);
|
|
54
|
+
}
|
|
55
|
+
// Reject C0 controls (U+0000-U+001F), DEL (U+007F), LTR/RTL marks (U+200E, U+200F),
|
|
56
|
+
// bidi embedding/override (U+202A-U+202E), and bidi isolates (U+2066-U+2069).
|
|
57
|
+
// Avoids embedding raw control characters in source for review safety.
|
|
58
|
+
function containsForbiddenChar(value) {
|
|
59
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
60
|
+
const code = value.charCodeAt(i);
|
|
61
|
+
if (code < 0x20 || code === 0x7f)
|
|
62
|
+
return true;
|
|
63
|
+
if (code === 0x200e || code === 0x200f)
|
|
64
|
+
return true;
|
|
65
|
+
if (code >= 0x202a && code <= 0x202e)
|
|
66
|
+
return true;
|
|
67
|
+
if (code >= 0x2066 && code <= 0x2069)
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
package/dist/oids.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ShortSubjectAttributeType } from "./types.js";
|
|
2
|
+
export declare const OID: {
|
|
3
|
+
readonly ecdsaWithSha256: "1.2.840.10045.4.3.2";
|
|
4
|
+
readonly basicConstraints: "2.5.29.19";
|
|
5
|
+
readonly keyUsage: "2.5.29.15";
|
|
6
|
+
readonly extendedKeyUsage: "2.5.29.37";
|
|
7
|
+
readonly subjectAltName: "2.5.29.17";
|
|
8
|
+
readonly subjectKeyIdentifier: "2.5.29.14";
|
|
9
|
+
readonly authorityKeyIdentifier: "2.5.29.35";
|
|
10
|
+
readonly clientAuth: "1.3.6.1.5.5.7.3.2";
|
|
11
|
+
};
|
|
12
|
+
export declare const SUBJECT_ATTRIBUTE_OIDS: Record<ShortSubjectAttributeType, string>;
|
|
13
|
+
export declare const SUBJECT_VALUE_LENGTH_LIMITS: Record<ShortSubjectAttributeType, number>;
|
|
14
|
+
export declare const MAX_SUBJECT_VALUE_LENGTH = 256;
|
|
15
|
+
//# sourceMappingURL=oids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oids.d.ts","sourceRoot":"","sources":["../src/oids.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,eAAO,MAAM,GAAG;;;;;;;;;CASN,CAAC;AAEX,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAgB5E,CAAC;AAGF,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAgBjF,CAAC;AAIF,eAAO,MAAM,wBAAwB,MAAM,CAAC"}
|
package/dist/oids.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const OID = {
|
|
2
|
+
ecdsaWithSha256: "1.2.840.10045.4.3.2",
|
|
3
|
+
basicConstraints: "2.5.29.19",
|
|
4
|
+
keyUsage: "2.5.29.15",
|
|
5
|
+
extendedKeyUsage: "2.5.29.37",
|
|
6
|
+
subjectAltName: "2.5.29.17",
|
|
7
|
+
subjectKeyIdentifier: "2.5.29.14",
|
|
8
|
+
authorityKeyIdentifier: "2.5.29.35",
|
|
9
|
+
clientAuth: "1.3.6.1.5.5.7.3.2"
|
|
10
|
+
};
|
|
11
|
+
export const SUBJECT_ATTRIBUTE_OIDS = {
|
|
12
|
+
CN: "2.5.4.3",
|
|
13
|
+
O: "2.5.4.10",
|
|
14
|
+
OU: "2.5.4.11",
|
|
15
|
+
C: "2.5.4.6",
|
|
16
|
+
ST: "2.5.4.8",
|
|
17
|
+
L: "2.5.4.7",
|
|
18
|
+
E: "1.2.840.113549.1.9.1",
|
|
19
|
+
DC: "0.9.2342.19200300.100.1.25",
|
|
20
|
+
SERIALNUMBER: "2.5.4.5",
|
|
21
|
+
STREET: "2.5.4.9",
|
|
22
|
+
POSTALCODE: "2.5.4.17",
|
|
23
|
+
TITLE: "2.5.4.12",
|
|
24
|
+
GIVENNAME: "2.5.4.42",
|
|
25
|
+
SURNAME: "2.5.4.4",
|
|
26
|
+
UID: "0.9.2342.19200300.100.1.1"
|
|
27
|
+
};
|
|
28
|
+
// RFC 5280 Appendix A.1 ub-* upper bounds (character counts).
|
|
29
|
+
export const SUBJECT_VALUE_LENGTH_LIMITS = {
|
|
30
|
+
CN: 64,
|
|
31
|
+
O: 64,
|
|
32
|
+
OU: 64,
|
|
33
|
+
C: 4,
|
|
34
|
+
ST: 128,
|
|
35
|
+
L: 128,
|
|
36
|
+
E: 255,
|
|
37
|
+
DC: 63,
|
|
38
|
+
SERIALNUMBER: 64,
|
|
39
|
+
STREET: 128,
|
|
40
|
+
POSTALCODE: 16,
|
|
41
|
+
TITLE: 64,
|
|
42
|
+
GIVENNAME: 16,
|
|
43
|
+
SURNAME: 40,
|
|
44
|
+
UID: 256
|
|
45
|
+
};
|
|
46
|
+
// Default cap for unknown (dotted-OID) attributes. Matches the largest known
|
|
47
|
+
// ub-* limit (UID = 256). Callers needing more must use a known short type.
|
|
48
|
+
export const MAX_SUBJECT_VALUE_LENGTH = 256;
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ParsedCertificate {
|
|
2
|
+
der: Uint8Array;
|
|
3
|
+
tbsCertificateDer: Uint8Array;
|
|
4
|
+
signatureDer: Uint8Array;
|
|
5
|
+
issuerNameDer: Uint8Array;
|
|
6
|
+
subjectNameDer: Uint8Array;
|
|
7
|
+
subjectPublicKeyInfoDer: Uint8Array;
|
|
8
|
+
publicKey: CryptoKey;
|
|
9
|
+
isCA: boolean;
|
|
10
|
+
pathLenConstraint?: number;
|
|
11
|
+
keyCertSign: boolean;
|
|
12
|
+
subjectKeyIdentifier?: Uint8Array;
|
|
13
|
+
authorityKeyIdentifier?: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseCertificateDer(der: Uint8Array): Promise<ParsedCertificate>;
|
|
16
|
+
export declare function assertIssuerSubjectMatches(issuer: ParsedCertificate, issued: ParsedCertificate): void;
|
|
17
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,UAAU,CAAC;IAChB,iBAAiB,EAAE,UAAU,CAAC;IAC9B,YAAY,EAAE,UAAU,CAAC;IACzB,aAAa,EAAE,UAAU,CAAC;IAC1B,cAAc,EAAE,UAAU,CAAC;IAC3B,uBAAuB,EAAE,UAAU,CAAC;IACpC,SAAS,EAAE,SAAS,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,oBAAoB,CAAC,EAAE,UAAU,CAAC;IAClC,sBAAsB,CAAC,EAAE,UAAU,CAAC;CACrC;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkErF;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAIrG"}
|