@freedomofpress/sigstore-browser 0.1.0 → 0.1.2
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/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +1 -15
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js +2 -18
- package/dist/interfaces.js.map +1 -1
- package/dist/rfc3161/timestamp.d.ts +1 -1
- package/dist/rfc3161/timestamp.d.ts.map +1 -1
- package/dist/rfc3161/timestamp.js +3 -5
- package/dist/rfc3161/timestamp.js.map +1 -1
- package/dist/rfc3161/tstinfo.d.ts +1 -1
- package/dist/rfc3161/tstinfo.d.ts.map +1 -1
- package/dist/rfc3161/tstinfo.js +18 -4
- package/dist/rfc3161/tstinfo.js.map +1 -1
- package/dist/sigstore.d.ts +1 -1
- package/dist/sigstore.d.ts.map +1 -1
- package/dist/sigstore.js +6 -8
- package/dist/sigstore.js.map +1 -1
- package/dist/timestamp/tsa.d.ts +1 -1
- package/dist/timestamp/tsa.d.ts.map +1 -1
- package/dist/timestamp/tsa.js +4 -5
- package/dist/timestamp/tsa.js.map +1 -1
- package/dist/tlog/body.js +1 -1
- package/dist/tlog/body.js.map +1 -1
- package/dist/tlog/checkpoint.d.ts.map +1 -1
- package/dist/tlog/checkpoint.js +6 -7
- package/dist/tlog/checkpoint.js.map +1 -1
- package/dist/tlog/dsse.js +3 -3
- package/dist/tlog/dsse.js.map +1 -1
- package/dist/tlog/hashedrekord.js +1 -1
- package/dist/tlog/hashedrekord.js.map +1 -1
- package/dist/tlog/intoto.js +2 -2
- package/dist/tlog/intoto.js.map +1 -1
- package/dist/tlog/merkle.js +4 -4
- package/dist/tlog/merkle.js.map +1 -1
- package/dist/trust/tuf.d.ts +1 -1
- package/dist/trust/tuf.d.ts.map +1 -1
- package/dist/trust/tuf.js +4 -4
- package/dist/trust/tuf.js.map +1 -1
- package/dist/x509/cert.d.ts +1 -1
- package/dist/x509/cert.d.ts.map +1 -1
- package/dist/x509/cert.js +4 -7
- package/dist/x509/cert.js.map +1 -1
- package/dist/x509/chain.d.ts.map +1 -1
- package/dist/x509/chain.js +16 -5
- package/dist/x509/chain.js.map +1 -1
- package/dist/x509/ext.d.ts +1 -1
- package/dist/x509/ext.d.ts.map +1 -1
- package/dist/x509/ext.js +17 -3
- package/dist/x509/ext.js.map +1 -1
- package/dist/x509/sct.d.ts.map +1 -1
- package/dist/x509/sct.js +2 -4
- package/dist/x509/sct.js.map +1 -1
- package/package.json +8 -4
- package/src/cli.ts +6 -1
- package/src/index.ts +3 -0
- package/src/interfaces.ts +2 -17
- package/src/rfc3161/timestamp.ts +3 -5
- package/src/rfc3161/tstinfo.ts +3 -5
- package/src/sigstore.ts +17 -16
- package/src/timestamp/tsa.ts +5 -6
- package/src/tlog/body.ts +1 -1
- package/src/tlog/checkpoint.ts +13 -7
- package/src/tlog/dsse.ts +3 -3
- package/src/tlog/hashedrekord.ts +1 -1
- package/src/tlog/intoto.ts +2 -2
- package/src/tlog/merkle.ts +4 -4
- package/src/trust/tuf.ts +6 -6
- package/src/x509/cert.ts +11 -7
- package/src/x509/chain.ts +6 -6
- package/src/x509/ext.ts +2 -4
- package/src/x509/sct.ts +2 -4
- package/vite.config.playwright.ts +4 -16
- package/dist/asn1/error.d.ts +0 -5
- package/dist/asn1/error.d.ts.map +0 -1
- package/dist/asn1/error.js +0 -20
- package/dist/asn1/error.js.map +0 -1
- package/dist/asn1/index.d.ts +0 -2
- package/dist/asn1/index.d.ts.map +0 -1
- package/dist/asn1/index.js +0 -17
- package/dist/asn1/index.js.map +0 -1
- package/dist/asn1/length.d.ts +0 -4
- package/dist/asn1/length.d.ts.map +0 -1
- package/dist/asn1/length.js +0 -59
- package/dist/asn1/length.js.map +0 -1
- package/dist/asn1/obj.d.ts +0 -15
- package/dist/asn1/obj.d.ts.map +0 -1
- package/dist/asn1/obj.js +0 -153
- package/dist/asn1/obj.js.map +0 -1
- package/dist/asn1/parse.d.ts +0 -7
- package/dist/asn1/parse.d.ts.map +0 -1
- package/dist/asn1/parse.js +0 -118
- package/dist/asn1/parse.js.map +0 -1
- package/dist/asn1/tag.d.ts +0 -17
- package/dist/asn1/tag.d.ts.map +0 -1
- package/dist/asn1/tag.js +0 -86
- package/dist/asn1/tag.js.map +0 -1
- package/dist/canonicalize.d.ts +0 -2
- package/dist/canonicalize.d.ts.map +0 -1
- package/dist/canonicalize.js +0 -64
- package/dist/canonicalize.js.map +0 -1
- package/dist/crypto.d.ts +0 -5
- package/dist/crypto.d.ts.map +0 -1
- package/dist/crypto.js +0 -260
- package/dist/crypto.js.map +0 -1
- package/dist/encoding.d.ts +0 -17
- package/dist/encoding.d.ts.map +0 -1
- package/dist/encoding.js +0 -112
- package/dist/encoding.js.map +0 -1
- package/dist/pem.d.ts +0 -3
- package/dist/pem.d.ts.map +0 -1
- package/dist/pem.js +0 -41
- package/dist/pem.js.map +0 -1
- package/dist/stream.d.ts +0 -22
- package/dist/stream.d.ts.map +0 -1
- package/dist/stream.js +0 -114
- package/dist/stream.js.map +0 -1
- package/src/asn1/error.ts +0 -18
- package/src/asn1/index.ts +0 -16
- package/src/asn1/length.ts +0 -71
- package/src/asn1/obj.ts +0 -184
- package/src/asn1/parse.ts +0 -146
- package/src/asn1/tag.ts +0 -105
- package/src/canonicalize.ts +0 -61
- package/src/crypto.ts +0 -310
- package/src/encoding.ts +0 -141
- package/src/pem.ts +0 -48
- package/src/stream.ts +0 -141
- package/src/types/tuf-browser.d.ts +0 -13
package/src/interfaces.ts
CHANGED
|
@@ -99,20 +99,5 @@ export interface RawCA {
|
|
|
99
99
|
|
|
100
100
|
export type RawCAs = RawCA[];
|
|
101
101
|
|
|
102
|
-
export
|
|
103
|
-
|
|
104
|
-
Ed25519 = "Ed25519",
|
|
105
|
-
RSA = "RSA",
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export enum EcdsaTypes {
|
|
109
|
-
P256 = "P-256",
|
|
110
|
-
P384 = "P-384",
|
|
111
|
-
P521 = "P-521",
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export enum HashAlgorithms {
|
|
115
|
-
SHA256 = "SHA-256",
|
|
116
|
-
SHA384 = "SHA-384",
|
|
117
|
-
SHA512 = "SHA-512",
|
|
118
|
-
}
|
|
102
|
+
// Re-export crypto enums from crypto-browser (shared with tuf-browser)
|
|
103
|
+
export { KeyTypes, EcdsaTypes, HashAlgorithms } from "@freedomofpress/crypto-browser";
|
package/src/rfc3161/timestamp.ts
CHANGED
|
@@ -13,9 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { ASN1Obj } from "
|
|
17
|
-
import { bufferEqual, verifySignature } from "../crypto.js";
|
|
18
|
-
import { toArrayBuffer } from "../encoding.js";
|
|
16
|
+
import { ASN1Obj, uint8ArrayEqual, verifySignature } from "@freedomofpress/crypto-browser";
|
|
19
17
|
import { HashAlgorithms } from "../interfaces.js";
|
|
20
18
|
import { ECDSA_SIGNATURE_ALGOS, RSA_SIGNATURE_ALGOS, SHA2_HASH_ALGOS } from "../oid.js";
|
|
21
19
|
import { RFC3161TimestampVerificationError } from "./error.js";
|
|
@@ -123,11 +121,11 @@ export class RFC3161Timestamp {
|
|
|
123
121
|
|
|
124
122
|
const tstInfoDigest = await crypto.subtle.digest(
|
|
125
123
|
hashAlgName,
|
|
126
|
-
|
|
124
|
+
this.tstInfo.raw as Uint8Array<ArrayBuffer>,
|
|
127
125
|
);
|
|
128
126
|
const expectedDigest = this.messageDigestAttributeObj.subs[1].subs[0].value;
|
|
129
127
|
|
|
130
|
-
if (!
|
|
128
|
+
if (!uint8ArrayEqual(new Uint8Array(tstInfoDigest), expectedDigest)) {
|
|
131
129
|
throw new RFC3161TimestampVerificationError(
|
|
132
130
|
"signed data does not match tstInfo",
|
|
133
131
|
);
|
package/src/rfc3161/tstinfo.ts
CHANGED
|
@@ -13,9 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { ASN1Obj } from "
|
|
17
|
-
import { bufferEqual } from "../crypto.js";
|
|
18
|
-
import { toArrayBuffer } from "../encoding.js";
|
|
16
|
+
import { ASN1Obj, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
|
|
19
17
|
import { HashAlgorithms } from "../interfaces.js";
|
|
20
18
|
import { SHA2_HASH_ALGOS } from "../oid.js";
|
|
21
19
|
import { RFC3161TimestampVerificationError } from "./error.js";
|
|
@@ -62,10 +60,10 @@ export class TSTInfo {
|
|
|
62
60
|
|
|
63
61
|
const digest = await crypto.subtle.digest(
|
|
64
62
|
hashAlgName,
|
|
65
|
-
|
|
63
|
+
data as Uint8Array<ArrayBuffer>,
|
|
66
64
|
);
|
|
67
65
|
if (
|
|
68
|
-
!
|
|
66
|
+
!uint8ArrayEqual(new Uint8Array(digest), this.messageImprintHashedMessage)
|
|
69
67
|
) {
|
|
70
68
|
throw new RFC3161TimestampVerificationError(
|
|
71
69
|
"message imprint does not match artifact",
|
package/src/sigstore.ts
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
import { SigstoreBundle } from "./bundle.js";
|
|
2
|
-
import { canonicalize } from "./canonicalize.js";
|
|
3
|
-
import { importKey, verifySignature, verifySignatureOverDigest } from "./crypto.js";
|
|
4
|
-
import { preAuthEncoding } from "./dsse.js";
|
|
5
1
|
import {
|
|
6
2
|
base64ToUint8Array,
|
|
3
|
+
ByteStream,
|
|
4
|
+
canonicalize,
|
|
5
|
+
importKey,
|
|
7
6
|
stringToUint8Array,
|
|
8
|
-
toArrayBuffer,
|
|
9
7
|
uint8ArrayEqual,
|
|
10
8
|
Uint8ArrayToHex,
|
|
11
9
|
Uint8ArrayToString,
|
|
12
|
-
|
|
10
|
+
verifySignature,
|
|
11
|
+
verifySignatureOverDigest,
|
|
12
|
+
} from "@freedomofpress/crypto-browser";
|
|
13
|
+
import { HashAlgorithms } from "./interfaces.js";
|
|
14
|
+
import {
|
|
15
|
+
CertificateChainVerifier,
|
|
16
|
+
EXTENSION_OID_SCT,
|
|
17
|
+
X509Certificate,
|
|
18
|
+
X509SCTExtension,
|
|
19
|
+
} from "./x509/index.js";
|
|
20
|
+
import { SigstoreBundle } from "./bundle.js";
|
|
21
|
+
import { preAuthEncoding } from "./dsse.js";
|
|
13
22
|
import {
|
|
14
23
|
CertAuthority,
|
|
15
24
|
CTLog,
|
|
16
|
-
HashAlgorithms,
|
|
17
25
|
RawCAs,
|
|
18
26
|
RawLogs,
|
|
19
27
|
RawTimestampAuthorities,
|
|
@@ -22,13 +30,6 @@ import {
|
|
|
22
30
|
SigstoreRoots,
|
|
23
31
|
TrustedRoot,
|
|
24
32
|
} from "./interfaces.js";
|
|
25
|
-
import { ByteStream } from "./stream.js";
|
|
26
|
-
import {
|
|
27
|
-
CertificateChainVerifier,
|
|
28
|
-
EXTENSION_OID_SCT,
|
|
29
|
-
X509Certificate,
|
|
30
|
-
X509SCTExtension,
|
|
31
|
-
} from "./x509/index.js";
|
|
32
33
|
import { verifyMerkleInclusion } from "./tlog/merkle.js";
|
|
33
34
|
import { verifyCheckpoint } from "./tlog/checkpoint.js";
|
|
34
35
|
import { verifyTLogBody } from "./tlog/body.js";
|
|
@@ -302,7 +303,7 @@ export class SigstoreVerifier {
|
|
|
302
303
|
|
|
303
304
|
// Calculate hash of the issuer's public key
|
|
304
305
|
const issuerId = new Uint8Array(
|
|
305
|
-
await crypto.subtle.digest(HashAlgorithms.SHA256,
|
|
306
|
+
await crypto.subtle.digest(HashAlgorithms.SHA256, issuer.publicKey as Uint8Array<ArrayBuffer>),
|
|
306
307
|
);
|
|
307
308
|
preCert.appendView(issuerId);
|
|
308
309
|
|
|
@@ -675,7 +676,7 @@ export class SigstoreVerifier {
|
|
|
675
676
|
} else {
|
|
676
677
|
// data is the file content, compute the digest
|
|
677
678
|
artifactDigest = Uint8ArrayToHex(
|
|
678
|
-
new Uint8Array(await crypto.subtle.digest(HashAlgorithms.SHA256,
|
|
679
|
+
new Uint8Array(await crypto.subtle.digest(HashAlgorithms.SHA256, data as Uint8Array<ArrayBuffer>))
|
|
679
680
|
);
|
|
680
681
|
}
|
|
681
682
|
|
package/src/timestamp/tsa.ts
CHANGED
|
@@ -13,11 +13,10 @@
|
|
|
13
13
|
* - Adds verifyBundleTimestamp for Sigstore bundle integration (new functionality)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { base64ToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
|
|
17
|
+
import { CertificateChainVerifier, X509Certificate } from "../x509/index.js";
|
|
18
|
+
import { RawTimestampAuthority } from "../interfaces.js";
|
|
16
19
|
import { RFC3161Timestamp } from "../rfc3161/index.js";
|
|
17
|
-
import { X509Certificate, CertificateChainVerifier } from "../x509/index.js";
|
|
18
|
-
import { bufferEqual } from "../crypto.js";
|
|
19
|
-
import { base64ToUint8Array, Uint8ArrayToHex } from "../encoding.js";
|
|
20
|
-
import type { RawTimestampAuthority } from "../interfaces.js";
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* Verifies an RFC3161 timestamp against a set of timestamp authorities
|
|
@@ -110,8 +109,8 @@ function filterCAsBySerialAndIssuer(
|
|
|
110
109
|
base64ToUint8Array(ca.certChain.certificates[0].rawBytes)
|
|
111
110
|
);
|
|
112
111
|
|
|
113
|
-
return
|
|
114
|
-
|
|
112
|
+
return uint8ArrayEqual(leafCert.serialNumber, criteria.serialNumber) &&
|
|
113
|
+
uint8ArrayEqual(leafCert.issuer, criteria.issuer);
|
|
115
114
|
});
|
|
116
115
|
}
|
|
117
116
|
|
package/src/tlog/body.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Direct bundle comparison instead of SignatureContent abstraction
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { base64Decode } from "
|
|
12
|
+
import { base64Decode } from "@freedomofpress/crypto-browser";
|
|
13
13
|
import type { SigstoreBundle, TLogEntry } from "../bundle.js";
|
|
14
14
|
import { verifyHashedRekordBody } from "./hashedrekord.js";
|
|
15
15
|
import { verifyDSSEBody } from "./dsse.js";
|
package/src/tlog/checkpoint.ts
CHANGED
|
@@ -19,8 +19,14 @@ Follows the signed note format specification:
|
|
|
19
19
|
https://github.com/transparency-dev/formats/blob/main/log/README.md
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import {
|
|
23
|
-
|
|
22
|
+
import {
|
|
23
|
+
base64ToUint8Array,
|
|
24
|
+
importKey,
|
|
25
|
+
KeyTypes,
|
|
26
|
+
stringToUint8Array,
|
|
27
|
+
uint8ArrayEqual,
|
|
28
|
+
verifySignature,
|
|
29
|
+
} from "@freedomofpress/crypto-browser";
|
|
24
30
|
import type { TLogEntry } from "../bundle.js";
|
|
25
31
|
import type { RawLogs } from "../interfaces.js";
|
|
26
32
|
|
|
@@ -223,17 +229,17 @@ async function importTLogKey(tlog: RawLogs[0]): Promise<CryptoKey> {
|
|
|
223
229
|
|
|
224
230
|
if (keyDetails === "ecdsa-sha2-nistp256") {
|
|
225
231
|
// SSH-style ECDSA P-256 format used in tests
|
|
226
|
-
keyType =
|
|
232
|
+
keyType = KeyTypes.Ecdsa;
|
|
227
233
|
scheme = "P256-SHA256";
|
|
228
234
|
} else if (keyDetails.includes("ECDSA")) {
|
|
229
|
-
keyType =
|
|
235
|
+
keyType = KeyTypes.Ecdsa;
|
|
230
236
|
// Extract the curve and hash, e.g., "P256_SHA_256" from "PKIX_ECDSA_P256_SHA_256"
|
|
231
237
|
scheme = keyDetails.replace("PKIX_ECDSA_", "").replaceAll("_", "-");
|
|
232
238
|
} else if (keyDetails.includes("ED25519")) {
|
|
233
|
-
keyType =
|
|
234
|
-
scheme =
|
|
239
|
+
keyType = KeyTypes.Ed25519;
|
|
240
|
+
scheme = KeyTypes.Ed25519;
|
|
235
241
|
} else if (keyDetails.includes("RSA")) {
|
|
236
|
-
keyType =
|
|
242
|
+
keyType = KeyTypes.RSA;
|
|
237
243
|
// Extract RSA details, e.g., "PSS_SHA_256" from "PKIX_RSA_PSS_SHA_256"
|
|
238
244
|
scheme = keyDetails.replace("PKIX_RSA_", "").replaceAll("_", "-");
|
|
239
245
|
} else {
|
package/src/tlog/dsse.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Adds verifyDSSE002Body() - new functionality (reference only supports v0.0.1)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { base64ToUint8Array, hexToUint8Array,
|
|
14
|
+
import { base64ToUint8Array, hexToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
|
|
15
15
|
import { HashAlgorithms } from "../interfaces.js";
|
|
16
16
|
import type { SigstoreBundle } from "../bundle.js";
|
|
17
17
|
import type { RekorEntry } from "./body.js";
|
|
@@ -90,7 +90,7 @@ async function verifyDSSE001Body(
|
|
|
90
90
|
|
|
91
91
|
const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
|
|
92
92
|
const bundleHashBytes = new Uint8Array(
|
|
93
|
-
await crypto.subtle.digest(HashAlgorithms.SHA256,
|
|
93
|
+
await crypto.subtle.digest(HashAlgorithms.SHA256, payloadBytes as Uint8Array<ArrayBuffer>)
|
|
94
94
|
);
|
|
95
95
|
|
|
96
96
|
if (!uint8ArrayEqual(tlogHashBytes, bundleHashBytes)) {
|
|
@@ -138,7 +138,7 @@ async function verifyDSSE002Body(
|
|
|
138
138
|
|
|
139
139
|
const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
|
|
140
140
|
const bundleHashBytes = new Uint8Array(
|
|
141
|
-
await crypto.subtle.digest(HashAlgorithms.SHA256,
|
|
141
|
+
await crypto.subtle.digest(HashAlgorithms.SHA256, payloadBytes as Uint8Array<ArrayBuffer>)
|
|
142
142
|
);
|
|
143
143
|
|
|
144
144
|
if (!uint8ArrayEqual(tlogHashBytes, bundleHashBytes)) {
|
package/src/tlog/hashedrekord.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Adds verifyHashedRekordV002Body() - new functionality (reference only supports v0.0.1)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { base64ToUint8Array, hexToUint8Array, uint8ArrayEqual } from "
|
|
13
|
+
import { base64ToUint8Array, hexToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
|
|
14
14
|
import type { SigstoreBundle } from "../bundle.js";
|
|
15
15
|
import type { RekorEntry } from "./body.js";
|
|
16
16
|
|
package/src/tlog/intoto.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Uses crypto.subtle.digest for hash computation instead of Node.js crypto
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { base64Decode, base64ToUint8Array, hexToUint8Array,
|
|
13
|
+
import { base64Decode, base64ToUint8Array, hexToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
|
|
14
14
|
import { HashAlgorithms } from "../interfaces.js";
|
|
15
15
|
import type { SigstoreBundle } from "../bundle.js";
|
|
16
16
|
import type { RekorEntry } from "./body.js";
|
|
@@ -92,7 +92,7 @@ export async function verifyIntotoBody(
|
|
|
92
92
|
|
|
93
93
|
const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
|
|
94
94
|
const bundleHashBytes = new Uint8Array(
|
|
95
|
-
await crypto.subtle.digest(HashAlgorithms.SHA256,
|
|
95
|
+
await crypto.subtle.digest(HashAlgorithms.SHA256, payloadBytes as Uint8Array<ArrayBuffer>)
|
|
96
96
|
);
|
|
97
97
|
|
|
98
98
|
if (!uint8ArrayEqual(tlogHashBytes, bundleHashBytes)) {
|
package/src/tlog/merkle.ts
CHANGED
|
@@ -6,9 +6,9 @@ Key differences:
|
|
|
6
6
|
- Uses Uint8Array instead of Buffer
|
|
7
7
|
- Uses async Web Crypto API (crypto.subtle.digest) instead of sync Node.js crypto
|
|
8
8
|
- Base64 decoding via custom base64ToUint8Array instead of Buffer.from
|
|
9
|
-
-
|
|
9
|
+
- uint8ArrayEqual from crypto-browser instead of Node.js crypto.timingSafeEqual
|
|
10
10
|
*/
|
|
11
|
-
import { base64ToUint8Array,
|
|
11
|
+
import { base64ToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
|
|
12
12
|
import { HashAlgorithms } from "../interfaces.js";
|
|
13
13
|
import type { TLogEntry } from "../bundle.js";
|
|
14
14
|
|
|
@@ -138,7 +138,7 @@ async function hashChildren(
|
|
|
138
138
|
data.set(left, RFC6962_NODE_HASH_PREFIX.length);
|
|
139
139
|
data.set(right, RFC6962_NODE_HASH_PREFIX.length + left.length);
|
|
140
140
|
|
|
141
|
-
const hash = await crypto.subtle.digest(HashAlgorithms.SHA256,
|
|
141
|
+
const hash = await crypto.subtle.digest(HashAlgorithms.SHA256, data as Uint8Array<ArrayBuffer>);
|
|
142
142
|
return new Uint8Array(hash);
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -147,6 +147,6 @@ async function hashLeaf(leaf: Uint8Array): Promise<Uint8Array> {
|
|
|
147
147
|
data.set(RFC6962_LEAF_HASH_PREFIX, 0);
|
|
148
148
|
data.set(leaf, RFC6962_LEAF_HASH_PREFIX.length);
|
|
149
149
|
|
|
150
|
-
const hash = await crypto.subtle.digest(HashAlgorithms.SHA256,
|
|
150
|
+
const hash = await crypto.subtle.digest(HashAlgorithms.SHA256, data as Uint8Array<ArrayBuffer>);
|
|
151
151
|
return new Uint8Array(hash);
|
|
152
152
|
}
|
package/src/trust/tuf.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* Reference: https://github.com/freedomofpress/tuf-browser
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
10
|
+
import { Uint8ArrayToString } from "@freedomofpress/crypto-browser";
|
|
11
|
+
import type { TUFClient } from "@freedomofpress/tuf-browser";
|
|
12
|
+
import { TrustedRoot } from "../interfaces.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Options for TrustedRootProvider configuration
|
|
@@ -106,7 +106,7 @@ export class TrustedRootProvider {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
try {
|
|
109
|
-
const { TUFClient } = await import('tuf-browser
|
|
109
|
+
const { TUFClient } = await import('@freedomofpress/tuf-browser');
|
|
110
110
|
|
|
111
111
|
// Get initial root metadata
|
|
112
112
|
const rootMetadata = this.initialRoot || await this.getDefaultRoot();
|
|
@@ -179,10 +179,10 @@ export class TrustedRootProvider {
|
|
|
179
179
|
|
|
180
180
|
// Fetch the trusted root target via TUF
|
|
181
181
|
// TUF will handle all verification (signatures, rollback protection, etc.)
|
|
182
|
-
const
|
|
182
|
+
const trustedRootBuffer = await this.tufClient.getTarget(this.trustedRootTarget);
|
|
183
183
|
|
|
184
184
|
// Parse the trusted root JSON
|
|
185
|
-
const trustedRootJson = Uint8ArrayToString(
|
|
185
|
+
const trustedRootJson = Uint8ArrayToString(new Uint8Array(trustedRootBuffer));
|
|
186
186
|
const trustedRoot = JSON.parse(trustedRootJson) as TrustedRoot;
|
|
187
187
|
|
|
188
188
|
// Cache the result
|
package/src/x509/cert.ts
CHANGED
|
@@ -13,12 +13,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
import {
|
|
17
|
+
ASN1Obj,
|
|
18
|
+
importKey,
|
|
19
|
+
toDER,
|
|
20
|
+
Uint8ArrayToBase64,
|
|
21
|
+
uint8ArrayEqual,
|
|
22
|
+
verifySignature,
|
|
23
|
+
} from "@freedomofpress/crypto-browser";
|
|
19
24
|
import { KeyTypes } from "../interfaces.js";
|
|
20
25
|
import { ECDSA_CURVE_NAMES, ECDSA_SIGNATURE_ALGOS, RSA_SIGNATURE_ALGOS } from "../oid.js";
|
|
21
|
-
import * as pem from "../pem.js";
|
|
22
26
|
import {
|
|
23
27
|
X509AuthorityKeyIDExtension,
|
|
24
28
|
X509BasicConstraintsExtension,
|
|
@@ -50,7 +54,7 @@ export class X509Certificate {
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
public static parse(cert: Uint8Array | string): X509Certificate {
|
|
53
|
-
const der = typeof cert === "string" ?
|
|
57
|
+
const der = typeof cert === "string" ? toDER(cert) : cert;
|
|
54
58
|
const asn1 = ASN1Obj.parseBuffer(der);
|
|
55
59
|
return new X509Certificate(asn1);
|
|
56
60
|
}
|
|
@@ -112,7 +116,7 @@ export class X509Certificate {
|
|
|
112
116
|
} else {
|
|
113
117
|
scheme = "SHA256";
|
|
114
118
|
}
|
|
115
|
-
return importKey(
|
|
119
|
+
return importKey(KeyTypes.RSA, `RSA_PKCS1V5_${scheme}`, Uint8ArrayToBase64(publicKey));
|
|
116
120
|
} else {
|
|
117
121
|
// ECDSA key - the curve OID is in the second element
|
|
118
122
|
const curveOID = spki.subs[0].subs[1]?.toOID();
|
|
@@ -233,7 +237,7 @@ export class X509Certificate {
|
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
public equals(other: X509Certificate): boolean {
|
|
236
|
-
return
|
|
240
|
+
return uint8ArrayEqual(this.root.toDER(), other.root.toDER());
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
// Creates a copy of the certificate with a new buffer
|
package/src/x509/chain.ts
CHANGED
|
@@ -23,11 +23,11 @@ limitations under the License.
|
|
|
23
23
|
* Key differences from sigstore-js:
|
|
24
24
|
* - Browser-compatible: async verify() method for Web Crypto API
|
|
25
25
|
* - Uses Uint8Array instead of Buffer for binary data
|
|
26
|
-
* - Uses
|
|
26
|
+
* - Uses uint8ArrayEqual for Uint8Array comparisons
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
import { uint8ArrayEqual } from "@freedomofpress/crypto-browser";
|
|
29
30
|
import { X509Certificate } from "./cert.js";
|
|
30
|
-
import { bufferEqual } from "../crypto.js";
|
|
31
31
|
|
|
32
32
|
interface CertificateChainVerifierOptions {
|
|
33
33
|
trustedCerts: X509Certificate[];
|
|
@@ -123,7 +123,7 @@ export class CertificateChainVerifier {
|
|
|
123
123
|
let issuers: X509Certificate[] = [];
|
|
124
124
|
let keyIdentifier: Uint8Array | undefined;
|
|
125
125
|
|
|
126
|
-
if (
|
|
126
|
+
if (uint8ArrayEqual(certificate.subject, certificate.issuer)) {
|
|
127
127
|
if (await certificate.verify()) {
|
|
128
128
|
return [certificate];
|
|
129
129
|
}
|
|
@@ -137,7 +137,7 @@ export class CertificateChainVerifier {
|
|
|
137
137
|
if (keyIdentifier) {
|
|
138
138
|
if (possibleIssuer.extSubjectKeyID) {
|
|
139
139
|
if (
|
|
140
|
-
|
|
140
|
+
uint8ArrayEqual(
|
|
141
141
|
possibleIssuer.extSubjectKeyID.keyIdentifier,
|
|
142
142
|
keyIdentifier
|
|
143
143
|
)
|
|
@@ -148,7 +148,7 @@ export class CertificateChainVerifier {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
if (
|
|
151
|
+
if (uint8ArrayEqual(possibleIssuer.subject, certificate.issuer)) {
|
|
152
152
|
issuers.push(possibleIssuer);
|
|
153
153
|
}
|
|
154
154
|
});
|
|
@@ -180,7 +180,7 @@ export class CertificateChainVerifier {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
for (let i = path.length - 2; i >= 0; i--) {
|
|
183
|
-
if (!
|
|
183
|
+
if (!uint8ArrayEqual(path[i].issuer, path[i + 1].subject)) {
|
|
184
184
|
throw new Error("incorrect certificate name chaining");
|
|
185
185
|
}
|
|
186
186
|
}
|
package/src/x509/ext.ts
CHANGED
|
@@ -13,9 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { ASN1Obj } from "
|
|
17
|
-
import { toArrayBuffer, Uint8ArrayToString } from "../encoding.js";
|
|
18
|
-
import { ByteStream } from "../stream.js";
|
|
16
|
+
import { ASN1Obj, ByteStream, Uint8ArrayToString } from "@freedomofpress/crypto-browser";
|
|
19
17
|
import { SignedCertificateTimestamp } from "./sct.js";
|
|
20
18
|
|
|
21
19
|
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1
|
|
@@ -187,7 +185,7 @@ export class X509SCTExtension extends X509Extension {
|
|
|
187
185
|
|
|
188
186
|
get signedCertificateTimestamps(): SignedCertificateTimestamp[] {
|
|
189
187
|
const buf = this.extnValueObj.subs[0].value;
|
|
190
|
-
const stream = new ByteStream(
|
|
188
|
+
const stream = new ByteStream(buf);
|
|
191
189
|
|
|
192
190
|
// The overall list length is encoded in the first two bytes -- note this
|
|
193
191
|
// is the length of the list in bytes, NOT the number of SCTs in the list
|
package/src/x509/sct.ts
CHANGED
|
@@ -13,9 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { verifySignature } from "
|
|
17
|
-
import { readBigInt64BE, toArrayBuffer } from "../encoding.js";
|
|
18
|
-
import { ByteStream } from "../stream.js";
|
|
16
|
+
import { ByteStream, readBigInt64BE, verifySignature } from "@freedomofpress/crypto-browser";
|
|
19
17
|
|
|
20
18
|
interface SCTOptions {
|
|
21
19
|
version: number;
|
|
@@ -111,7 +109,7 @@ export class SignedCertificateTimestamp {
|
|
|
111
109
|
// https://www.rfc-editor.org/rfc/rfc6962#section-3.2
|
|
112
110
|
// https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.4.1
|
|
113
111
|
public static parse(buf: Uint8Array): SignedCertificateTimestamp {
|
|
114
|
-
const stream = new ByteStream(
|
|
112
|
+
const stream = new ByteStream(buf);
|
|
115
113
|
|
|
116
114
|
// Version - enum { v1(0), (255) }
|
|
117
115
|
const version = stream.getUint8();
|
|
@@ -2,25 +2,13 @@
|
|
|
2
2
|
import { defineConfig } from "vite";
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
-
build: {
|
|
6
|
-
minify: false,
|
|
7
|
-
outDir: "dist",
|
|
8
|
-
target: "esnext",
|
|
9
|
-
lib: {
|
|
10
|
-
entry: "src/index.ts",
|
|
11
|
-
formats: ["es"],
|
|
12
|
-
fileName: "index",
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
5
|
test: {
|
|
16
|
-
|
|
6
|
+
include: ["**/*.browser.test.ts"],
|
|
17
7
|
browser: {
|
|
18
|
-
provider: "playwright",
|
|
19
8
|
enabled: true,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
],
|
|
9
|
+
provider: "playwright",
|
|
10
|
+
headless: true,
|
|
11
|
+
instances: [{ browser: "chromium" }],
|
|
24
12
|
},
|
|
25
13
|
},
|
|
26
14
|
});
|
package/dist/asn1/error.d.ts
DELETED
package/dist/asn1/error.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/asn1/error.ts"],"names":[],"mappings":"AAeA,qBAAa,cAAe,SAAQ,KAAK;CAAG;AAE5C,qBAAa,aAAc,SAAQ,KAAK;CAAG"}
|
package/dist/asn1/error.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2023 The Sigstore Authors.
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
export class ASN1ParseError extends Error {
|
|
17
|
-
}
|
|
18
|
-
export class ASN1TypeError extends Error {
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=error.js.map
|
package/dist/asn1/error.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/asn1/error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;EAcE;AACF,MAAM,OAAO,cAAe,SAAQ,KAAK;CAAG;AAE5C,MAAM,OAAO,aAAc,SAAQ,KAAK;CAAG"}
|
package/dist/asn1/index.d.ts
DELETED
package/dist/asn1/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/asn1/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/asn1/index.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2023 The Sigstore Authors.
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
export { ASN1Obj } from "./obj.js";
|
|
17
|
-
//# sourceMappingURL=index.js.map
|
package/dist/asn1/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/asn1/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;EAcE;AACF,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/asn1/length.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"length.d.ts","sourceRoot":"","sources":["../../src/asn1/length.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK1C,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CA8BvD;AAGD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAgBpD"}
|
package/dist/asn1/length.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2023 The Sigstore Authors.
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
import { ASN1ParseError } from "./error.js";
|
|
17
|
-
// Decodes the length of a DER-encoded ANS.1 element from the supplied stream.
|
|
18
|
-
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-encoded-length-and-value-bytes
|
|
19
|
-
export function decodeLength(stream) {
|
|
20
|
-
const buf = stream.getUint8();
|
|
21
|
-
// If the most significant bit is UNSET the length is just the value of the
|
|
22
|
-
// byte.
|
|
23
|
-
if ((buf & 0x80) === 0x00) {
|
|
24
|
-
return buf;
|
|
25
|
-
}
|
|
26
|
-
// Otherwise, the lower 7 bits of the first byte indicate the number of bytes
|
|
27
|
-
// that follow to encode the length.
|
|
28
|
-
const byteCount = buf & 0x7f;
|
|
29
|
-
// Ensure the encoded length can safely fit in a JS number.
|
|
30
|
-
if (byteCount > 6) {
|
|
31
|
-
throw new ASN1ParseError("length exceeds 6 byte limit");
|
|
32
|
-
}
|
|
33
|
-
// Iterate over the bytes that encode the length.
|
|
34
|
-
let len = 0;
|
|
35
|
-
for (let i = 0; i < byteCount; i++) {
|
|
36
|
-
len = len * 256 + stream.getUint8();
|
|
37
|
-
}
|
|
38
|
-
// This is a valid ASN.1 length encoding, but we don't support it.
|
|
39
|
-
if (len === 0) {
|
|
40
|
-
throw new ASN1ParseError("indefinite length encoding not supported");
|
|
41
|
-
}
|
|
42
|
-
return len;
|
|
43
|
-
}
|
|
44
|
-
// Translates the supplied value to a DER-encoded length.
|
|
45
|
-
export function encodeLength(len) {
|
|
46
|
-
if (len < 128) {
|
|
47
|
-
return new Uint8Array([len]);
|
|
48
|
-
}
|
|
49
|
-
// Bitwise operations on large numbers are not supported in JS, so we need to
|
|
50
|
-
// use BigInts.
|
|
51
|
-
let val = BigInt(len);
|
|
52
|
-
const bytes = [];
|
|
53
|
-
while (val > 0n) {
|
|
54
|
-
bytes.unshift(Number(val & 255n));
|
|
55
|
-
val = val >> 8n;
|
|
56
|
-
}
|
|
57
|
-
return new Uint8Array([0x80 | bytes.length, ...bytes]);
|
|
58
|
-
}
|
|
59
|
-
//# sourceMappingURL=length.js.map
|
package/dist/asn1/length.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"length.js","sourceRoot":"","sources":["../../src/asn1/length.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;EAcE;AAGF,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,8EAA8E;AAC9E,qGAAqG;AACrG,MAAM,UAAU,YAAY,CAAC,MAAkB;IAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAE9B,2EAA2E;IAC3E,QAAQ;IACR,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,6EAA6E;IAC7E,oCAAoC;IACpC,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC;IAE7B,2DAA2D;IAC3D,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,cAAc,CAAC,6BAA6B,CAAC,CAAC;IAC1D,CAAC;IAED,iDAAiD;IACjD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IACtC,CAAC;IAED,kEAAkE;IAClE,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACd,MAAM,IAAI,cAAc,CAAC,0CAA0C,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;QACd,OAAO,IAAI,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,6EAA6E;IAC7E,eAAe;IACf,IAAI,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,EAAE,CAAC;IAEjB,OAAO,GAAG,GAAG,EAAE,EAAE,CAAC;QAChB,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC;QAClC,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC;IAClB,CAAC;IAED,OAAO,IAAI,UAAU,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;AACzD,CAAC"}
|