usertrust 1.4.0 → 2.0.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 +17 -1
- package/dist/anomaly/detector.d.ts.map +1 -1
- package/dist/anomaly/detector.js +36 -11
- package/dist/anomaly/detector.js.map +1 -1
- package/dist/anomaly/index.d.ts +2 -2
- package/dist/anomaly/index.d.ts.map +1 -1
- package/dist/anomaly/index.js +1 -1
- package/dist/anomaly/index.js.map +1 -1
- package/dist/anomaly/signals/spend-velocity.d.ts +39 -12
- package/dist/anomaly/signals/spend-velocity.d.ts.map +1 -1
- package/dist/anomaly/signals/spend-velocity.js +66 -43
- package/dist/anomaly/signals/spend-velocity.js.map +1 -1
- package/dist/anomaly/signals/token-rate.d.ts +33 -13
- package/dist/anomaly/signals/token-rate.d.ts.map +1 -1
- package/dist/anomaly/signals/token-rate.js +124 -38
- package/dist/anomaly/signals/token-rate.js.map +1 -1
- package/dist/anomaly/types.d.ts +28 -6
- package/dist/anomaly/types.d.ts.map +1 -1
- package/dist/audit/anchor-doctor.d.ts +42 -0
- package/dist/audit/anchor-doctor.d.ts.map +1 -0
- package/dist/audit/anchor-doctor.js +285 -0
- package/dist/audit/anchor-doctor.js.map +1 -0
- package/dist/audit/anchor-verify.d.ts +229 -0
- package/dist/audit/anchor-verify.d.ts.map +1 -0
- package/dist/audit/anchor-verify.js +809 -0
- package/dist/audit/anchor-verify.js.map +1 -0
- package/dist/audit/anchor.d.ts +177 -0
- package/dist/audit/anchor.d.ts.map +1 -0
- package/dist/audit/anchor.js +922 -0
- package/dist/audit/anchor.js.map +1 -0
- package/dist/audit/canonical.js +1 -1
- package/dist/audit/canonical.js.map +1 -1
- package/dist/audit/chain.d.ts.map +1 -1
- package/dist/audit/chain.js +21 -7
- package/dist/audit/chain.js.map +1 -1
- package/dist/audit/read.d.ts +19 -0
- package/dist/audit/read.d.ts.map +1 -0
- package/dist/audit/read.js +94 -0
- package/dist/audit/read.js.map +1 -0
- package/dist/audit/rekor-verify.d.ts +116 -0
- package/dist/audit/rekor-verify.d.ts.map +1 -0
- package/dist/audit/rekor-verify.js +533 -0
- package/dist/audit/rekor-verify.js.map +1 -0
- package/dist/audit/rekor.d.ts +71 -0
- package/dist/audit/rekor.d.ts.map +1 -0
- package/dist/audit/rekor.js +556 -0
- package/dist/audit/rekor.js.map +1 -0
- package/dist/audit/rotation.js +1 -1
- package/dist/audit/rotation.js.map +1 -1
- package/dist/audit/sigv4.d.ts +44 -0
- package/dist/audit/sigv4.d.ts.map +1 -0
- package/dist/audit/sigv4.js +134 -0
- package/dist/audit/sigv4.js.map +1 -0
- package/dist/audit/verify.d.ts +78 -0
- package/dist/audit/verify.d.ts.map +1 -1
- package/dist/audit/verify.js +195 -1
- package/dist/audit/verify.js.map +1 -1
- package/dist/cli/anchor.d.ts +3 -0
- package/dist/cli/anchor.d.ts.map +1 -0
- package/dist/cli/anchor.js +626 -0
- package/dist/cli/anchor.js.map +1 -0
- package/dist/cli/completions.js +1 -1
- package/dist/cli/export.d.ts +3 -0
- package/dist/cli/export.d.ts.map +1 -0
- package/dist/cli/export.js +58 -0
- package/dist/cli/export.js.map +1 -0
- package/dist/cli/health.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +99 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +4 -34
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/main.d.ts +2 -2
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +20 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/pricing.js +1 -1
- package/dist/cli/pricing.js.map +1 -1
- package/dist/cli/ui.d.ts +3 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +35 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +342 -3
- package/dist/cli/verify.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/detect.d.ts +71 -16
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +141 -6
- package/dist/detect.js.map +1 -1
- package/dist/export/markdown.d.ts +12 -0
- package/dist/export/markdown.d.ts.map +1 -0
- package/dist/export/markdown.js +142 -0
- package/dist/export/markdown.js.map +1 -0
- package/dist/govern.d.ts +67 -3
- package/dist/govern.d.ts.map +1 -1
- package/dist/govern.js +1011 -219
- package/dist/govern.js.map +1 -1
- package/dist/headless.d.ts +41 -2
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +58 -7
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +33 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -23
- package/dist/index.js.map +1 -1
- package/dist/ledger/client.d.ts.map +1 -1
- package/dist/ledger/client.js +57 -46
- package/dist/ledger/client.js.map +1 -1
- package/dist/ledger/engine.d.ts.map +1 -1
- package/dist/ledger/engine.js +22 -13
- package/dist/ledger/engine.js.map +1 -1
- package/dist/ledger/pricing.d.ts +54 -0
- package/dist/ledger/pricing.d.ts.map +1 -1
- package/dist/ledger/pricing.js +128 -8
- package/dist/ledger/pricing.js.map +1 -1
- package/dist/shared/types.d.ts +154 -340
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +67 -17
- package/dist/shared/types.js.map +1 -1
- package/dist/snapshot/checkpoint.d.ts.map +1 -1
- package/dist/snapshot/checkpoint.js +20 -2
- package/dist/snapshot/checkpoint.js.map +1 -1
- package/dist/streaming.d.ts +13 -3
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +88 -30
- package/dist/streaming.js.map +1 -1
- package/dist/supply-chain/sign.d.ts.map +1 -1
- package/dist/supply-chain/sign.js +3 -0
- package/dist/supply-chain/sign.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Usertools, Inc.
|
|
3
|
+
/**
|
|
4
|
+
* Rekor Transparency-Log Receipt Verification — Zero-dependency standalone
|
|
5
|
+
*
|
|
6
|
+
* INTENTIONAL DUPLICATION: this file exists byte-for-byte in BOTH
|
|
7
|
+
* packages/verify/src/rekor-verify.ts and
|
|
8
|
+
* packages/core/src/audit/rekor-verify.ts — here even the import paths match,
|
|
9
|
+
* since the module's only sibling import is anchor-verify.js. The parity test
|
|
10
|
+
* pins the two together; any change must land in both packages.
|
|
11
|
+
*
|
|
12
|
+
* A receipt is EVIDENCE SUPPLIED BY THE PARTY UNDER AUDIT: it proves nothing on
|
|
13
|
+
* its own, and every field in it is attacker-shaped. What verification
|
|
14
|
+
* establishes, end to end, is a single chain of bindings:
|
|
15
|
+
*
|
|
16
|
+
* our signed anchor record -> the bytes the log stored -> that entry's
|
|
17
|
+
* position in a tree -> a checkpoint over that tree -> a log key the AUDITOR
|
|
18
|
+
* pinned (never one the receipt names).
|
|
19
|
+
*
|
|
20
|
+
* Break any link and the receipt is worthless, so each is checked in order and
|
|
21
|
+
* the first failure is fatal (fail-closed). Rekor data deliberately lives in
|
|
22
|
+
* SEPARATE receipt files: the anchor record schema is frozen, and a
|
|
23
|
+
* transparency log must never become a field inside the thing it witnesses.
|
|
24
|
+
*
|
|
25
|
+
* Offline scope: a verified receipt proves the entry was included in the log
|
|
26
|
+
* under a pinned key. WHEN it was included is a separate, weaker claim:
|
|
27
|
+
* `integratedTime` is covered by no signature of its own, so it counts as
|
|
28
|
+
* attested only when the log's signed entry timestamp (SET) over it verifies
|
|
29
|
+
* under the same pinned keyring — otherwise the receipt proves inclusion and
|
|
30
|
+
* says nothing about time. Neither claim proves the log's current head is
|
|
31
|
+
* consistent with that checkpoint; that needs a witness/consistency query,
|
|
32
|
+
* which stays out of the zero-dependency verifier by design.
|
|
33
|
+
*/
|
|
34
|
+
import { createHash } from "node:crypto";
|
|
35
|
+
import { anchorPayloadHash, publicKeyFromPem, verifySignatureRaw, } from "./anchor-verify.js";
|
|
36
|
+
// ── Constants ──
|
|
37
|
+
/**
|
|
38
|
+
* The rekor.sigstore.dev v1 log key, as served by
|
|
39
|
+
* https://rekor.sigstore.dev/api/v1/log/publicKey — ECDSA P-256, SPKI sha256
|
|
40
|
+
* c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d (the log's
|
|
41
|
+
* published logID). This is DATA, not a dependency, and it is not a blanket
|
|
42
|
+
* trust root: it is consulted ONLY for receipts whose log URL host IS
|
|
43
|
+
* rekor.sigstore.dev. Any other log must be pinned by the operator with
|
|
44
|
+
* --rekor-pubkey.
|
|
45
|
+
*/
|
|
46
|
+
export const REKOR_PROD_PUBKEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
47
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr
|
|
48
|
+
kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==
|
|
49
|
+
-----END PUBLIC KEY-----
|
|
50
|
+
`;
|
|
51
|
+
/** The one host the embedded key above is allowed to speak for. */
|
|
52
|
+
const REKOR_PROD_HOST = "rekor.sigstore.dev";
|
|
53
|
+
const ERR = "rekor-receipt-invalid";
|
|
54
|
+
// Caps on untrusted input. A receipt is a small artifact; anything near these
|
|
55
|
+
// bounds is an attempt to make the verifier do work, not to prove inclusion.
|
|
56
|
+
const MAX_RECEIPT_BYTES = 256 * 1024;
|
|
57
|
+
const MAX_ENTRY_BODY_BYTES = 64 * 1024;
|
|
58
|
+
const MAX_CHECKPOINT_BYTES = 8 * 1024;
|
|
59
|
+
const MAX_INCLUSION_PATH = 64;
|
|
60
|
+
const MAX_PEM_BYTES = 16 * 1024;
|
|
61
|
+
const MAX_SET_BYTES = 1024;
|
|
62
|
+
/**
|
|
63
|
+
* Exclusive upper bound on integratedTime, in SECONDS: `* 1000` must stay
|
|
64
|
+
* inside the ±8.64e15 ms ECMAScript Date range, or every downstream
|
|
65
|
+
* `new Date(ms).toISOString()` throws RangeError on attacker-chosen input.
|
|
66
|
+
*/
|
|
67
|
+
const MAX_INTEGRATED_TIME = 8_640_000_000_000;
|
|
68
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
69
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
70
|
+
const UINT = /^(0|[1-9][0-9]*)$/;
|
|
71
|
+
// Matching control characters is the entire point here — they are what gets
|
|
72
|
+
// removed from anything echoed back to a terminal.
|
|
73
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the intent
|
|
74
|
+
const CONTROL_CHARS = /[\x00-\x1f\x7f]/g;
|
|
75
|
+
const LEAF_PREFIX = Buffer.from([0x00]);
|
|
76
|
+
const NODE_PREFIX = Buffer.from([0x01]);
|
|
77
|
+
const RECEIPT_KEYS = new Set(["v", "vaultId", "anchorSeq", "artifactHash", "entryBody", "log"]);
|
|
78
|
+
const LOG_KEYS = new Set([
|
|
79
|
+
"url",
|
|
80
|
+
"logIndex",
|
|
81
|
+
"treeSize",
|
|
82
|
+
"rootHash",
|
|
83
|
+
"hashes",
|
|
84
|
+
"checkpoint",
|
|
85
|
+
"integratedTime",
|
|
86
|
+
"signedEntryTimestamp",
|
|
87
|
+
"logID",
|
|
88
|
+
]);
|
|
89
|
+
// ── Small helpers ──
|
|
90
|
+
/**
|
|
91
|
+
* Untrusted values are never echoed whole into an error string. Control
|
|
92
|
+
* characters are stripped BEFORE truncation, because these strings are printed
|
|
93
|
+
* to a terminal: an escape sequence or a bare CR inside a field name would let
|
|
94
|
+
* the party under audit repaint the line its own verdict is printed on, and
|
|
95
|
+
* truncating first would leave a half-consumed escape behind.
|
|
96
|
+
*/
|
|
97
|
+
function clip(value) {
|
|
98
|
+
const scrubbed = value.replace(CONTROL_CHARS, "");
|
|
99
|
+
return scrubbed.length <= 80 ? scrubbed : `${scrubbed.slice(0, 80)}...`;
|
|
100
|
+
}
|
|
101
|
+
function asObject(value) {
|
|
102
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
103
|
+
? value
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
function isSafeIntAtLeast(value, min) {
|
|
107
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= min;
|
|
108
|
+
}
|
|
109
|
+
/** Lowercased host (INCLUDING port) of an http(s) URL, or null. */
|
|
110
|
+
function logHost(url) {
|
|
111
|
+
let parsed;
|
|
112
|
+
try {
|
|
113
|
+
parsed = new URL(url);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:")
|
|
119
|
+
return null;
|
|
120
|
+
return parsed.host.toLowerCase();
|
|
121
|
+
}
|
|
122
|
+
function nodeHash(left, right) {
|
|
123
|
+
return createHash("sha256").update(NODE_PREFIX).update(left).update(right).digest();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Sorted-key JSON of a FLAT object of strings and safe integers — the exact
|
|
127
|
+
* bytes a Rekor SET is computed over. Over this charset (base64, lowercase hex,
|
|
128
|
+
* integers) it is byte-identical to RFC 8785 JCS; a general JCS implementation
|
|
129
|
+
* would buy nothing here and cost the verifier its zero-dependency property.
|
|
130
|
+
*/
|
|
131
|
+
function sortedKeyStringify(value) {
|
|
132
|
+
const fields = Object.keys(value)
|
|
133
|
+
.sort()
|
|
134
|
+
.map((key) => `${JSON.stringify(key)}:${JSON.stringify(value[key])}`);
|
|
135
|
+
return `{${fields.join(",")}}`;
|
|
136
|
+
}
|
|
137
|
+
// ── Signed notes (checkpoints) ──
|
|
138
|
+
/**
|
|
139
|
+
* Parse a signed note:
|
|
140
|
+
* <origin>\n<treeSize>\n<base64 rootHash>\n\n— <keyname> <base64 sig>\n...
|
|
141
|
+
* Strictly LF-only and strictly this shape — a note is a signed artifact, so
|
|
142
|
+
* accepting variants would mean verifying a signature over bytes we did not
|
|
143
|
+
* actually parse. The signature payload is `4-byte keyhint || DER signature`;
|
|
144
|
+
* the hint identifies which key the log used and is ADVISORY only, because the
|
|
145
|
+
* auditor pins the key out-of-band. Only its 4 bytes are skipped.
|
|
146
|
+
*
|
|
147
|
+
* ONE OR MORE signature lines are accepted: a production checkpoint is signed by
|
|
148
|
+
* the log and co-signed by its witnesses, and every line must still be
|
|
149
|
+
* well-formed. Which of them (if any) speaks for the auditor is decided later,
|
|
150
|
+
* against the pinned keyring — never here.
|
|
151
|
+
*/
|
|
152
|
+
export function parseSignedNote(note) {
|
|
153
|
+
if (note.length === 0 || Buffer.byteLength(note, "utf8") > MAX_CHECKPOINT_BYTES)
|
|
154
|
+
return null;
|
|
155
|
+
if (note.includes("\r"))
|
|
156
|
+
return null;
|
|
157
|
+
const lines = note.split("\n");
|
|
158
|
+
if (lines.length > 5 && lines[lines.length - 1] === "")
|
|
159
|
+
lines.pop();
|
|
160
|
+
if (lines.length < 5)
|
|
161
|
+
return null;
|
|
162
|
+
const origin = lines[0];
|
|
163
|
+
const sizeLine = lines[1];
|
|
164
|
+
const rootB64 = lines[2];
|
|
165
|
+
if (origin.length === 0 || lines[3] !== "")
|
|
166
|
+
return null;
|
|
167
|
+
if (!UINT.test(sizeLine))
|
|
168
|
+
return null;
|
|
169
|
+
const treeSize = Number(sizeLine);
|
|
170
|
+
if (!Number.isSafeInteger(treeSize))
|
|
171
|
+
return null;
|
|
172
|
+
if (!BASE64.test(rootB64))
|
|
173
|
+
return null;
|
|
174
|
+
const root = Buffer.from(rootB64, "base64");
|
|
175
|
+
if (root.length !== 32)
|
|
176
|
+
return null;
|
|
177
|
+
const sigs = [];
|
|
178
|
+
for (const line of lines.slice(4)) {
|
|
179
|
+
const parts = line.split(" ");
|
|
180
|
+
if (parts.length !== 3 || parts[0] !== "—" || parts[1].length === 0)
|
|
181
|
+
return null;
|
|
182
|
+
const sigField = parts[2];
|
|
183
|
+
if (!BASE64.test(sigField))
|
|
184
|
+
return null;
|
|
185
|
+
const raw = Buffer.from(sigField, "base64");
|
|
186
|
+
if (raw.length <= 4)
|
|
187
|
+
return null;
|
|
188
|
+
sigs.push(raw.subarray(4));
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
origin,
|
|
192
|
+
treeSize,
|
|
193
|
+
rootHashHex: root.toString("hex"),
|
|
194
|
+
body: `${origin}\n${sizeLine}\n${rootB64}\n`,
|
|
195
|
+
sigs,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Does a checkpoint's signed origin name the host the receipt says it came from?
|
|
200
|
+
* A production Rekor checkpoint's origin is `"<host> - <treeID>"`, while the
|
|
201
|
+
* signed-note format also permits the bare host; both identify the same log, so
|
|
202
|
+
* only the part before the first " - " is compared. The comparison stays exact
|
|
203
|
+
* on that part — a suffix rule would let evil.example.org.attacker.net pass.
|
|
204
|
+
*/
|
|
205
|
+
function originNamesHost(origin, host) {
|
|
206
|
+
const separator = origin.indexOf(" - ");
|
|
207
|
+
const named = separator === -1 ? origin : origin.slice(0, separator);
|
|
208
|
+
return named.toLowerCase() === host;
|
|
209
|
+
}
|
|
210
|
+
// ── RFC 9162 inclusion ──
|
|
211
|
+
/**
|
|
212
|
+
* RFC 9162 §2.1.3.2 inclusion verification: walk the audit path from the leaf
|
|
213
|
+
* to the root, deciding sibling order from the leaf index rather than from any
|
|
214
|
+
* hint the proof supplies. `fn`/`sn` track the leaf's and the last leaf's
|
|
215
|
+
* positions within the current subtree; the right-shift after a left-sibling
|
|
216
|
+
* merge is what makes right-edge (incomplete) subtrees come out correct.
|
|
217
|
+
*
|
|
218
|
+
* Never throws: malformed hex, out-of-range indices, and over-long paths are
|
|
219
|
+
* rejections, because this runs on attacker-supplied input.
|
|
220
|
+
*/
|
|
221
|
+
export function verifyIndexInclusion(leafHex, index, treeSize, pathHex, rootHex) {
|
|
222
|
+
if (!Number.isSafeInteger(index) || !Number.isSafeInteger(treeSize))
|
|
223
|
+
return false;
|
|
224
|
+
if (index < 0 || treeSize < 1 || index >= treeSize)
|
|
225
|
+
return false;
|
|
226
|
+
if (!HEX64.test(leafHex) || !HEX64.test(rootHex))
|
|
227
|
+
return false;
|
|
228
|
+
if (pathHex.length > MAX_INCLUSION_PATH)
|
|
229
|
+
return false;
|
|
230
|
+
let fn = index;
|
|
231
|
+
let sn = treeSize - 1;
|
|
232
|
+
let r = Buffer.from(leafHex, "hex");
|
|
233
|
+
for (const pHex of pathHex) {
|
|
234
|
+
if (!HEX64.test(pHex))
|
|
235
|
+
return false;
|
|
236
|
+
if (sn === 0)
|
|
237
|
+
return false;
|
|
238
|
+
const p = Buffer.from(pHex, "hex");
|
|
239
|
+
if (fn % 2 === 1 || fn === sn) {
|
|
240
|
+
r = nodeHash(p, r);
|
|
241
|
+
// LSB(fn) not set (the fn === sn right-edge case): shift both equally
|
|
242
|
+
// until LSB(fn) is set or fn is 0.
|
|
243
|
+
while (fn % 2 === 0 && fn !== 0) {
|
|
244
|
+
fn = Math.floor(fn / 2);
|
|
245
|
+
sn = Math.floor(sn / 2);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
r = nodeHash(r, p);
|
|
250
|
+
}
|
|
251
|
+
fn = Math.floor(fn / 2);
|
|
252
|
+
sn = Math.floor(sn / 2);
|
|
253
|
+
}
|
|
254
|
+
return sn === 0 && r.toString("hex") === rootHex;
|
|
255
|
+
}
|
|
256
|
+
// ── Strict receipt parsing ──
|
|
257
|
+
/**
|
|
258
|
+
* Strict parse of one receipt. Unknown fields, wrong types, and range
|
|
259
|
+
* violations are all rejected before any crypto runs — the same fail-closed
|
|
260
|
+
* posture as parseAnchorRecord, for the same reason: a field the verifier
|
|
261
|
+
* silently ignores is a field an attacker can use.
|
|
262
|
+
*/
|
|
263
|
+
export function parseRekorReceipt(raw) {
|
|
264
|
+
const bad = (message) => ({
|
|
265
|
+
receipt: null,
|
|
266
|
+
error: `${ERR}: ${message}`,
|
|
267
|
+
});
|
|
268
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_RECEIPT_BYTES)
|
|
269
|
+
return bad("receipt exceeds 256 KiB");
|
|
270
|
+
let parsed;
|
|
271
|
+
try {
|
|
272
|
+
parsed = JSON.parse(raw);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return bad("not valid JSON");
|
|
276
|
+
}
|
|
277
|
+
const obj = asObject(parsed);
|
|
278
|
+
if (obj === null)
|
|
279
|
+
return bad("not an object");
|
|
280
|
+
for (const key of Object.keys(obj)) {
|
|
281
|
+
if (!RECEIPT_KEYS.has(key))
|
|
282
|
+
return bad(`unknown field "${clip(key)}"`);
|
|
283
|
+
}
|
|
284
|
+
if (obj.v !== 1)
|
|
285
|
+
return bad("unsupported version");
|
|
286
|
+
if (typeof obj.vaultId !== "string" || obj.vaultId.length === 0)
|
|
287
|
+
return bad("missing vaultId");
|
|
288
|
+
if (!isSafeIntAtLeast(obj.anchorSeq, 1)) {
|
|
289
|
+
return bad("anchorSeq must be a safe integer >= 1");
|
|
290
|
+
}
|
|
291
|
+
if (typeof obj.artifactHash !== "string" || !HEX64.test(obj.artifactHash)) {
|
|
292
|
+
return bad("artifactHash must be 64 lowercase hex characters");
|
|
293
|
+
}
|
|
294
|
+
if (typeof obj.entryBody !== "string" || obj.entryBody.length === 0) {
|
|
295
|
+
return bad("missing entryBody");
|
|
296
|
+
}
|
|
297
|
+
if (!BASE64.test(obj.entryBody))
|
|
298
|
+
return bad("entryBody must be base64");
|
|
299
|
+
if (Buffer.from(obj.entryBody, "base64").length > MAX_ENTRY_BODY_BYTES) {
|
|
300
|
+
return bad("entryBody exceeds 64 KiB decoded");
|
|
301
|
+
}
|
|
302
|
+
const log = asObject(obj.log);
|
|
303
|
+
if (log === null)
|
|
304
|
+
return bad("log must be an object");
|
|
305
|
+
for (const key of Object.keys(log)) {
|
|
306
|
+
if (!LOG_KEYS.has(key))
|
|
307
|
+
return bad(`unknown log field "${clip(key)}"`);
|
|
308
|
+
}
|
|
309
|
+
if (typeof log.url !== "string" || logHost(log.url) === null) {
|
|
310
|
+
return bad("log.url must be an http(s) URL");
|
|
311
|
+
}
|
|
312
|
+
if (!isSafeIntAtLeast(log.logIndex, 0))
|
|
313
|
+
return bad("log.logIndex must be a safe integer >= 0");
|
|
314
|
+
if (!isSafeIntAtLeast(log.treeSize, 1))
|
|
315
|
+
return bad("log.treeSize must be a safe integer >= 1");
|
|
316
|
+
if (log.logIndex >= log.treeSize)
|
|
317
|
+
return bad("log.logIndex must be < log.treeSize");
|
|
318
|
+
if (typeof log.rootHash !== "string" || !HEX64.test(log.rootHash)) {
|
|
319
|
+
return bad("log.rootHash must be 64 lowercase hex characters");
|
|
320
|
+
}
|
|
321
|
+
if (!Array.isArray(log.hashes) || log.hashes.length > MAX_INCLUSION_PATH) {
|
|
322
|
+
return bad(`log.hashes must be an array of at most ${MAX_INCLUSION_PATH} hashes`);
|
|
323
|
+
}
|
|
324
|
+
for (const h of log.hashes) {
|
|
325
|
+
if (typeof h !== "string" || !HEX64.test(h)) {
|
|
326
|
+
return bad("log.hashes entries must be 64 lowercase hex characters");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (typeof log.checkpoint !== "string" ||
|
|
330
|
+
log.checkpoint.length === 0 ||
|
|
331
|
+
Buffer.byteLength(log.checkpoint, "utf8") > MAX_CHECKPOINT_BYTES) {
|
|
332
|
+
return bad("log.checkpoint must be a non-empty string of at most 8 KiB");
|
|
333
|
+
}
|
|
334
|
+
// Bounded in SECONDS so `integratedTime * 1000` cannot leave the Date range
|
|
335
|
+
// and turn a printed verdict into a RangeError on attacker-chosen input.
|
|
336
|
+
if (!isSafeIntAtLeast(log.integratedTime, 1) || log.integratedTime >= MAX_INTEGRATED_TIME) {
|
|
337
|
+
return bad(`log.integratedTime must be a safe integer > 0 and < ${MAX_INTEGRATED_TIME}`);
|
|
338
|
+
}
|
|
339
|
+
if (log.logID !== undefined && (typeof log.logID !== "string" || !HEX64.test(log.logID))) {
|
|
340
|
+
return bad("log.logID must be 64 lowercase hex characters");
|
|
341
|
+
}
|
|
342
|
+
if (log.signedEntryTimestamp !== undefined) {
|
|
343
|
+
if (typeof log.signedEntryTimestamp !== "string" ||
|
|
344
|
+
log.signedEntryTimestamp.length === 0 ||
|
|
345
|
+
!BASE64.test(log.signedEntryTimestamp) ||
|
|
346
|
+
Buffer.byteLength(log.signedEntryTimestamp, "utf8") > MAX_SET_BYTES) {
|
|
347
|
+
return bad("log.signedEntryTimestamp must be base64 of at most 1 KiB");
|
|
348
|
+
}
|
|
349
|
+
// The logID is part of the payload the SET signs, so a SET without one
|
|
350
|
+
// is unverifiable by construction — and unverifiable supplied evidence
|
|
351
|
+
// is a rejection, never a receipt that quietly proves less than it looks.
|
|
352
|
+
if (log.logID === undefined) {
|
|
353
|
+
return bad("log.signedEntryTimestamp requires log.logID");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return { receipt: obj, error: null };
|
|
357
|
+
}
|
|
358
|
+
// ── Entry binding ──
|
|
359
|
+
/**
|
|
360
|
+
* The logged entry must commit to OUR artifact AND OUR signature. Without the
|
|
361
|
+
* signature binding, anyone could log the (public) payload hash of someone
|
|
362
|
+
* else's anchor and present the resulting receipt as their own.
|
|
363
|
+
*/
|
|
364
|
+
function checkHashedRekordEntry(entryBytes, artifactHash, recordSigBase64) {
|
|
365
|
+
if (entryBytes.length === 0)
|
|
366
|
+
return "entryBody decodes to no bytes";
|
|
367
|
+
if (entryBytes.length > MAX_ENTRY_BODY_BYTES)
|
|
368
|
+
return "entryBody exceeds 64 KiB decoded";
|
|
369
|
+
let parsed;
|
|
370
|
+
try {
|
|
371
|
+
parsed = JSON.parse(entryBytes.toString("utf8"));
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return "entryBody is not valid JSON";
|
|
375
|
+
}
|
|
376
|
+
const entry = asObject(parsed);
|
|
377
|
+
if (entry === null)
|
|
378
|
+
return "entryBody is not a JSON object";
|
|
379
|
+
if (entry.kind !== "hashedrekord")
|
|
380
|
+
return "entryBody kind is not hashedrekord";
|
|
381
|
+
const spec = asObject(entry.spec);
|
|
382
|
+
if (spec === null)
|
|
383
|
+
return "entryBody spec is missing";
|
|
384
|
+
const hash = asObject(asObject(spec.data)?.hash);
|
|
385
|
+
if (hash === null)
|
|
386
|
+
return "entryBody spec.data.hash is missing";
|
|
387
|
+
if (hash.algorithm !== "sha256")
|
|
388
|
+
return "entryBody spec.data.hash.algorithm is not sha256";
|
|
389
|
+
if (hash.value !== artifactHash) {
|
|
390
|
+
return "entryBody spec.data.hash.value does not match the receipt artifactHash";
|
|
391
|
+
}
|
|
392
|
+
const signature = asObject(spec.signature);
|
|
393
|
+
if (signature === null || typeof signature.content !== "string") {
|
|
394
|
+
return "entryBody spec.signature.content is missing";
|
|
395
|
+
}
|
|
396
|
+
// Compare DECODED bytes: base64 spelling is not canonical, so string
|
|
397
|
+
// equality would reject honest receipts and is not what "same signature"
|
|
398
|
+
// means.
|
|
399
|
+
const logged = Buffer.from(signature.content, "base64");
|
|
400
|
+
const ours = Buffer.from(recordSigBase64, "base64");
|
|
401
|
+
if (ours.length === 0 || !logged.equals(ours)) {
|
|
402
|
+
return "entryBody spec.signature.content is not the anchor record signature";
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Which key(s) may speak for this log. Supplying no key is only meaningful for
|
|
408
|
+
* the one log whose key ships with the verifier; for anything else, an unpinned
|
|
409
|
+
* receipt would be self-certifying, so it is refused rather than trusted.
|
|
410
|
+
*
|
|
411
|
+
* A caller who supplied material that ALL got discarded (empty file, oversized
|
|
412
|
+
* blob) is not a caller who supplied nothing: silently falling back to the
|
|
413
|
+
* embedded key there would verify a rekor.sigstore.dev receipt under a key the
|
|
414
|
+
* auditor never chose, and pass a merge gate the auditor thought they had
|
|
415
|
+
* pinned. Discarded-everything is refused instead.
|
|
416
|
+
*/
|
|
417
|
+
function resolveLogKeyring(host, logPubkeysPem) {
|
|
418
|
+
const supplied = logPubkeysPem.filter((pem) => pem.length > 0 && Buffer.byteLength(pem, "utf8") <= MAX_PEM_BYTES);
|
|
419
|
+
if (supplied.length > 0)
|
|
420
|
+
return { keys: supplied, error: null };
|
|
421
|
+
if (logPubkeysPem.length > 0) {
|
|
422
|
+
return {
|
|
423
|
+
keys: [],
|
|
424
|
+
error: "supplied --rekor-pubkey material is empty or invalid — refusing to fall back to the embedded key",
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (host === REKOR_PROD_HOST)
|
|
428
|
+
return { keys: [REKOR_PROD_PUBKEY_PEM], error: null };
|
|
429
|
+
return {
|
|
430
|
+
keys: [],
|
|
431
|
+
error: `custom log requires --rekor-pubkey (no pinned key for log host ${clip(host)})`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
// ── Receipt verification ──
|
|
435
|
+
/**
|
|
436
|
+
* Verify one receipt against the anchor record it claims to witness, under a
|
|
437
|
+
* caller-pinned keyring (any key in the ring may verify the checkpoint).
|
|
438
|
+
*
|
|
439
|
+
* The order below is the binding chain, and it is deliberate: cheap identity
|
|
440
|
+
* and hash checks first, then the entry decode, then the inclusion walk, and
|
|
441
|
+
* only then the signatures — so a receipt for the wrong record can never cost
|
|
442
|
+
* an ECDSA verification, and a failure always names the earliest broken link.
|
|
443
|
+
* `ok` is the INCLUSION verdict; `attestedTimeMs` is the strictly weaker time
|
|
444
|
+
* claim, and only a verifying signed entry timestamp produces it.
|
|
445
|
+
*/
|
|
446
|
+
export function verifyRekorReceipt(receipt, record, logPubkeysPem) {
|
|
447
|
+
const errors = [];
|
|
448
|
+
const fail = (message) => {
|
|
449
|
+
errors.push(`${ERR}: ${message}`);
|
|
450
|
+
return { ok: false, attestedTimeMs: null, errors };
|
|
451
|
+
};
|
|
452
|
+
// 1 — the receipt must name THIS record.
|
|
453
|
+
if (receipt.vaultId !== record.vaultId || receipt.anchorSeq !== record.anchorSeq) {
|
|
454
|
+
return fail(`record mismatch: receipt claims vault ${clip(receipt.vaultId)} anchorSeq ${receipt.anchorSeq}`);
|
|
455
|
+
}
|
|
456
|
+
// 2 — and must carry that record's signing-payload hash.
|
|
457
|
+
const payloadHash = anchorPayloadHash(record);
|
|
458
|
+
if (receipt.artifactHash !== payloadHash) {
|
|
459
|
+
return fail("artifactHash is not the anchor payload hash of this record");
|
|
460
|
+
}
|
|
461
|
+
// 3 — the bytes the log stored must commit to the artifact and our signature.
|
|
462
|
+
const entryBytes = Buffer.from(receipt.entryBody, "base64");
|
|
463
|
+
const entryError = checkHashedRekordEntry(entryBytes, payloadHash, record.sig);
|
|
464
|
+
if (entryError !== null)
|
|
465
|
+
return fail(entryError);
|
|
466
|
+
// 4/5 — sha256(0x00 || stored bytes) must sit at logIndex under log.rootHash.
|
|
467
|
+
const leafHex = createHash("sha256").update(LEAF_PREFIX).update(entryBytes).digest("hex");
|
|
468
|
+
if (!verifyIndexInclusion(leafHex, receipt.log.logIndex, receipt.log.treeSize, receipt.log.hashes, receipt.log.rootHash)) {
|
|
469
|
+
return fail(`inclusion proof does not reconstruct log.rootHash from logIndex ${receipt.log.logIndex} of ${receipt.log.treeSize}`);
|
|
470
|
+
}
|
|
471
|
+
// 6 — the checkpoint must be this log's, at this size, over this root.
|
|
472
|
+
const host = logHost(receipt.log.url);
|
|
473
|
+
if (host === null)
|
|
474
|
+
return fail("log.url is not an http(s) URL");
|
|
475
|
+
const note = parseSignedNote(receipt.log.checkpoint);
|
|
476
|
+
if (note === null)
|
|
477
|
+
return fail("log.checkpoint is not a parseable signed note");
|
|
478
|
+
if (!originNamesHost(note.origin, host)) {
|
|
479
|
+
return fail(`log.checkpoint origin ${clip(note.origin)} is not the log.url host ${clip(host)}`);
|
|
480
|
+
}
|
|
481
|
+
if (note.treeSize !== receipt.log.treeSize) {
|
|
482
|
+
return fail(`log.checkpoint treeSize ${note.treeSize} does not match log.treeSize ${receipt.log.treeSize}`);
|
|
483
|
+
}
|
|
484
|
+
if (note.rootHashHex !== receipt.log.rootHash) {
|
|
485
|
+
return fail("log.checkpoint root hash does not match log.rootHash");
|
|
486
|
+
}
|
|
487
|
+
// 7 — and must be signed by a key the AUDITOR pinned. A co-signed checkpoint
|
|
488
|
+
// carries one signature per witness, and the auditor pins only the parties
|
|
489
|
+
// they trust: ONE line verifying under ONE pinned key is the whole claim.
|
|
490
|
+
const keyring = resolveLogKeyring(host, logPubkeysPem);
|
|
491
|
+
if (keyring.error !== null)
|
|
492
|
+
return fail(keyring.error);
|
|
493
|
+
const sigsBase64 = note.sigs.map((sig) => sig.toString("base64"));
|
|
494
|
+
const signed = keyring.keys.some((pem) => {
|
|
495
|
+
const key = publicKeyFromPem(pem);
|
|
496
|
+
if (key === null)
|
|
497
|
+
return false;
|
|
498
|
+
return sigsBase64.some((sig) => verifySignatureRaw("ecdsa-p256", note.body, key, sig));
|
|
499
|
+
});
|
|
500
|
+
if (!signed) {
|
|
501
|
+
return fail("log.checkpoint signature does not verify under any pinned log public key");
|
|
502
|
+
}
|
|
503
|
+
// 8 — inclusion holds. TIME is a separate claim: the checkpoint signs the
|
|
504
|
+
// tree, not when this entry entered it, so integratedTime is worth exactly
|
|
505
|
+
// as much as the signed entry timestamp over it. No SET (or no logID to
|
|
506
|
+
// build its payload from) means an honest "included, time unknown" — the
|
|
507
|
+
// caller then falls back to the operator-claimed timestamp knowing it is
|
|
508
|
+
// operator-claimed. A SET that is PRESENT and does not verify is different
|
|
509
|
+
// in kind: supplied evidence that fails is a forgery attempt, not an
|
|
510
|
+
// absence, so it fails the whole receipt closed.
|
|
511
|
+
const set = receipt.log.signedEntryTimestamp;
|
|
512
|
+
const logID = receipt.log.logID;
|
|
513
|
+
if (set === undefined || logID === undefined) {
|
|
514
|
+
return { ok: true, attestedTimeMs: null, errors };
|
|
515
|
+
}
|
|
516
|
+
const setPayload = sortedKeyStringify({
|
|
517
|
+
body: receipt.entryBody,
|
|
518
|
+
integratedTime: receipt.log.integratedTime,
|
|
519
|
+
logID,
|
|
520
|
+
logIndex: receipt.log.logIndex,
|
|
521
|
+
});
|
|
522
|
+
const setSigned = keyring.keys.some((pem) => {
|
|
523
|
+
const key = publicKeyFromPem(pem);
|
|
524
|
+
if (key === null)
|
|
525
|
+
return false;
|
|
526
|
+
return verifySignatureRaw("ecdsa-p256", setPayload, key, set);
|
|
527
|
+
});
|
|
528
|
+
if (!setSigned)
|
|
529
|
+
return fail("signedEntryTimestamp does not verify");
|
|
530
|
+
// 9 — every link held AND the log signed the time, so it is witness-attested.
|
|
531
|
+
return { ok: true, attestedTimeMs: receipt.log.integratedTime * 1000, errors };
|
|
532
|
+
}
|
|
533
|
+
//# sourceMappingURL=rekor-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rekor-verify.js","sourceRoot":"","sources":["../../src/audit/rekor-verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type AnchorSink } from "./anchor.js";
|
|
2
|
+
import { type AnchorRecord } from "./anchor-verify.js";
|
|
3
|
+
export declare const DEFAULT_REKOR_URL = "https://rekor.sigstore.dev";
|
|
4
|
+
/**
|
|
5
|
+
* Injectable HTTP transport — the sinks' only impure edge. Structurally
|
|
6
|
+
* identical to anchor-doctor's, so a caller may share one transport across
|
|
7
|
+
* both without either module importing the other.
|
|
8
|
+
*/
|
|
9
|
+
export type HttpTransport = (opts: {
|
|
10
|
+
method: string;
|
|
11
|
+
url: string;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
body: Buffer;
|
|
14
|
+
}) => Promise<{
|
|
15
|
+
status: number;
|
|
16
|
+
body: string;
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
}>;
|
|
19
|
+
export interface S3SinkConfig {
|
|
20
|
+
bucket: string;
|
|
21
|
+
region: string;
|
|
22
|
+
prefix?: string;
|
|
23
|
+
endpoint?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Endpoint rule for BOTH sinks. Plaintext is refused off-loopback: for S3,
|
|
27
|
+
* SigV4 authenticates the request but does not encrypt it, so an http endpoint
|
|
28
|
+
* on the network would put the credential-bearing headers on the wire; for
|
|
29
|
+
* Rekor, an http log URL lets anyone on the path answer for the witness — and
|
|
30
|
+
* a sink that accepts a forged 201 files forged evidence. Loopback is allowed
|
|
31
|
+
* for dev MinIO/LocalStack and local log instances.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseEndpoint(endpoint: string, sinkName?: string): URL;
|
|
34
|
+
/**
|
|
35
|
+
* PUT one canonical record per object, keyed by vault and anchorSeq. Immutability
|
|
36
|
+
* is the STORE's job (Object Lock retention / a deny-delete bucket policy);
|
|
37
|
+
* `usertrust anchor doctor` reports whether this identity's permissions match
|
|
38
|
+
* that intent.
|
|
39
|
+
*/
|
|
40
|
+
export declare function s3Sink(cfg: S3SinkConfig, transport?: HttpTransport): AnchorSink;
|
|
41
|
+
/**
|
|
42
|
+
* The hashedrekord entry we PROPOSE for a record: the anchor payload hash as the
|
|
43
|
+
* artifact, the record's Ed25519 signature, and the vault's public key. The log
|
|
44
|
+
* stores its own serialization of this; the receipt carries the log's bytes, so
|
|
45
|
+
* these are the bytes we sign nothing over and merely offer.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildHashedRekordEntry(record: AnchorRecord, publicKeySpkiBase64: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* `<seq12>.<8 hex of sha256(log url)>.json` — one receipt per (anchor, LOG).
|
|
50
|
+
* Keying by anchorSeq alone made two rekor sinks silently overwrite each
|
|
51
|
+
* other's evidence: the second witness's receipt replaced the first's, and the
|
|
52
|
+
* operator ended up with fewer independent attestations than they configured.
|
|
53
|
+
* Readers glob `*.json` in this directory, so pre-existing `<seq12>.json`
|
|
54
|
+
* receipts keep being found.
|
|
55
|
+
*/
|
|
56
|
+
export declare function rekorReceiptPath(rootDir: string, anchorSeq: number, logUrl: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Publish a record's payload hash to a Rekor transparency log and persist the
|
|
59
|
+
* inclusion receipt next to the mirror.
|
|
60
|
+
*
|
|
61
|
+
* A 409 means the log already holds an entry for this proposal, which happens
|
|
62
|
+
* on exactly one honest path: a crash between writing the receipt and clearing
|
|
63
|
+
* the outbox, so the emitter redelivers a record that IS already witnessed.
|
|
64
|
+
* That case is resolved from the receipt we already hold — never from the 409
|
|
65
|
+
* itself, which carries no evidence. A 409 with no such receipt is a duplicate
|
|
66
|
+
* we cannot account for, and fabricating one from a Location header would mean
|
|
67
|
+
* filing evidence about bytes we never saw (delta D1), so it rejects and the
|
|
68
|
+
* outbox keeps the record for a human.
|
|
69
|
+
*/
|
|
70
|
+
export declare function rekorSink(rootDir: string, url?: string, transport?: HttpTransport): AnchorSink;
|
|
71
|
+
//# sourceMappingURL=rekor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rekor.d.ts","sourceRoot":"","sources":["../../src/audit/rekor.ts"],"names":[],"mappings":"AA2CA,OAAO,EAAuB,KAAK,UAAU,EAAkC,MAAM,aAAa,CAAC;AACnG,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AAK1E,eAAO,MAAM,iBAAiB,+BAA+B,CAAC;AAe9D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACb,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAElF,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAwCD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,GAAG,CAczE;AAyBD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,EAAE,SAAS,GAAE,aAA6B,GAAG,UAAU,CA0C9F;AAkBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAchG;AA4ID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG3F;AA8GD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACxB,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAA0B,EAC/B,SAAS,GAAE,aAA6B,GACtC,UAAU,CAwEZ"}
|