burnledger 0.4.0 → 0.6.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 +11 -4
- package/dist/cjs/anchor.d.ts +71 -0
- package/dist/cjs/anchor.d.ts.map +1 -0
- package/dist/cjs/anchor.js +279 -0
- package/dist/cjs/anchor.js.map +1 -0
- package/dist/cjs/client.d.ts +26 -2
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +27 -5
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/enclave-seal.d.ts +101 -0
- package/dist/cjs/enclave-seal.d.ts.map +1 -0
- package/dist/cjs/enclave-seal.js +479 -0
- package/dist/cjs/enclave-seal.js.map +1 -0
- package/dist/cjs/errors.d.ts +42 -1
- package/dist/cjs/errors.d.ts.map +1 -1
- package/dist/cjs/errors.js +44 -2
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/http.d.ts +21 -1
- package/dist/cjs/http.d.ts.map +1 -1
- package/dist/cjs/http.js +76 -12
- package/dist/cjs/http.js.map +1 -1
- package/dist/cjs/index.browser.d.ts +2 -4
- package/dist/cjs/index.browser.d.ts.map +1 -1
- package/dist/cjs/index.browser.js.map +1 -1
- package/dist/cjs/index.d.ts +16 -11
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +20 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/keys.d.ts +66 -0
- package/dist/cjs/keys.d.ts.map +1 -1
- package/dist/cjs/keys.js +128 -3
- package/dist/cjs/keys.js.map +1 -1
- package/dist/cjs/models.d.ts +41 -3
- package/dist/cjs/models.d.ts.map +1 -1
- package/dist/cjs/models.js +25 -4
- package/dist/cjs/models.js.map +1 -1
- package/dist/cjs/verify.d.ts +72 -6
- package/dist/cjs/verify.d.ts.map +1 -1
- package/dist/cjs/verify.js +392 -62
- package/dist/cjs/verify.js.map +1 -1
- package/dist/cjs/web-verifier.d.ts +54 -5
- package/dist/cjs/web-verifier.d.ts.map +1 -1
- package/dist/cjs/web-verifier.js +36 -3
- package/dist/cjs/web-verifier.js.map +1 -1
- package/dist/esm/anchor.d.ts +71 -0
- package/dist/esm/anchor.d.ts.map +1 -0
- package/dist/esm/anchor.js +275 -0
- package/dist/esm/anchor.js.map +1 -0
- package/dist/esm/cli.d.ts +45 -14
- package/dist/esm/cli.d.ts.map +1 -1
- package/dist/esm/cli.js +357 -50
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/client.d.ts +26 -2
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +27 -5
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/enclave-seal.d.ts +101 -0
- package/dist/esm/enclave-seal.d.ts.map +1 -0
- package/dist/esm/enclave-seal.js +472 -0
- package/dist/esm/enclave-seal.js.map +1 -0
- package/dist/esm/errors.d.ts +42 -1
- package/dist/esm/errors.d.ts.map +1 -1
- package/dist/esm/errors.js +43 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/http.d.ts +21 -1
- package/dist/esm/http.d.ts.map +1 -1
- package/dist/esm/http.js +75 -12
- package/dist/esm/http.js.map +1 -1
- package/dist/esm/index.browser.d.ts +2 -4
- package/dist/esm/index.browser.d.ts.map +1 -1
- package/dist/esm/index.browser.js.map +1 -1
- package/dist/esm/index.d.ts +16 -11
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +11 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/keys.d.ts +66 -0
- package/dist/esm/keys.d.ts.map +1 -1
- package/dist/esm/keys.js +125 -3
- package/dist/esm/keys.js.map +1 -1
- package/dist/esm/models.d.ts +41 -3
- package/dist/esm/models.d.ts.map +1 -1
- package/dist/esm/models.js +24 -4
- package/dist/esm/models.js.map +1 -1
- package/dist/esm/verify.d.ts +72 -6
- package/dist/esm/verify.d.ts.map +1 -1
- package/dist/esm/verify.js +390 -63
- package/dist/esm/verify.js.map +1 -1
- package/dist/esm/web-verifier.d.ts +54 -5
- package/dist/esm/web-verifier.d.ts.map +1 -1
- package/dist/esm/web-verifier.js +40 -3
- package/dist/esm/web-verifier.js.map +1 -1
- package/package.json +1 -1
- package/src/anchor.ts +330 -0
- package/src/cli.ts +389 -54
- package/src/client.ts +46 -4
- package/src/enclave-seal.ts +582 -0
- package/src/errors.ts +47 -2
- package/src/http.ts +79 -12
- package/src/index.browser.ts +2 -2
- package/src/index.ts +28 -9
- package/src/keys.ts +176 -3
- package/src/models.ts +72 -7
- package/src/verify.ts +467 -72
- package/src/web-verifier.ts +58 -5
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/** The statement's log anchor, tested against the log's own signed head.
|
|
2
|
+
*
|
|
3
|
+
* A certificate status statement is an OFF-LOG document. Nothing stops the
|
|
4
|
+
* issuer signing two of them, at the same instant, saying different things
|
|
5
|
+
* about the same record to two different readers — that is the split-view
|
|
6
|
+
* attack on revocation status, and no signature by the issuer can rule out a
|
|
7
|
+
* second signature by the issuer.
|
|
8
|
+
*
|
|
9
|
+
* What the statement DOES carry is an anchor: sth_tree_size and sth_root_hash,
|
|
10
|
+
* signed, naming the log head the issuer had in front of it when it spoke.
|
|
11
|
+
* Until now this CLI PRINTED that anchor and never checked it, so the field was
|
|
12
|
+
* decoration. Checking it does not make the statement append-only, and this
|
|
13
|
+
* file must not be read as claiming it does. What it buys is narrower and real:
|
|
14
|
+
*
|
|
15
|
+
* - A statement can no longer name a tree the log does not have. The anchor
|
|
16
|
+
* must be a PREFIX of the head the log publishes now, proved by an RFC 6962
|
|
17
|
+
* consistency proof, or this CLI refuses the statement.
|
|
18
|
+
* - So an issuer wanting to show two readers different revocation status has
|
|
19
|
+
* two options, and both leave marks. Anchor both statements to the same
|
|
20
|
+
* head, and the two statements are directly comparable signed evidence of
|
|
21
|
+
* equivocation. Anchor them to different heads, and the log itself has
|
|
22
|
+
* forked — which is what the witness's published record exists to catch,
|
|
23
|
+
* and which no amount of re-signing can hide.
|
|
24
|
+
*
|
|
25
|
+
* What it does NOT close: a reader who checks one statement, alone, against a
|
|
26
|
+
* log the issuer also controls learns only that the issuer is self-consistent.
|
|
27
|
+
* Split view is detected by comparing notes — between readers, or against the
|
|
28
|
+
* witness — and this check is what makes the note worth comparing.
|
|
29
|
+
*
|
|
30
|
+
* This is a port of cmd/cli/anchor.go, verdict for verdict and word for word.
|
|
31
|
+
* Three CLIs that disagree about the same record are worse than one that never
|
|
32
|
+
* checked, so the detail strings are copied rather than paraphrased: a reader
|
|
33
|
+
* diffing the Go and the npm output must see the same line.
|
|
34
|
+
*/
|
|
35
|
+
import { bytesToHex, buildTreeHeadPayload, formatTimestamp, hexToBytes, verifyConsistency, } from "./verify.js";
|
|
36
|
+
export const ANCHOR_CONFIRMED = "CONFIRMED";
|
|
37
|
+
export const ANCHOR_UNVERIFIED = "UNVERIFIED";
|
|
38
|
+
export const ANCHOR_INCONSISTENT = "INCONSISTENT";
|
|
39
|
+
/** How much of a log response this will buffer, matching cmd/cli/online.go.
|
|
40
|
+
* A head is a few hundred bytes and a consistency proof is at most 64 hashes;
|
|
41
|
+
* anything near this size is a misbehaving or hostile server, not an answer. */
|
|
42
|
+
const MAX_RESPONSE_BYTES = 1 << 20;
|
|
43
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
44
|
+
/** Test the statement's signed anchor against the log's own signed head.
|
|
45
|
+
*
|
|
46
|
+
* `key` is the key that signed the STATEMENT, taken from the reader's --keys
|
|
47
|
+
* file. The head is verified against it before a single field of the head is
|
|
48
|
+
* used: an unverified head is an unattributed one, and letting it decide
|
|
49
|
+
* whether the anchor is consistent would hand that decision to whoever answered
|
|
50
|
+
* the HTTP request.
|
|
51
|
+
*
|
|
52
|
+
* `statement` must already have passed unwrapStatusStatement, which is what
|
|
53
|
+
* guarantees `sth_root_hash` is 32 bytes of hex. That is the boundary check;
|
|
54
|
+
* this function does not repeat it.
|
|
55
|
+
*/
|
|
56
|
+
export async function checkStatementAnchor(crypto, apiUrl, statement, key) {
|
|
57
|
+
const treeSize = statement.sth_tree_size;
|
|
58
|
+
if (typeof treeSize !== "number" || !Number.isSafeInteger(treeSize) || treeSize < 0) {
|
|
59
|
+
// Go's uint64 field would have failed to unmarshal, taking the whole
|
|
60
|
+
// document with it; here the document already parsed, so the anchor is what
|
|
61
|
+
// is unusable. Either way nothing was established, which is UNVERIFIED.
|
|
62
|
+
return unverified("the statement's anchored tree size is not a usable integer");
|
|
63
|
+
}
|
|
64
|
+
if (treeSize === 0)
|
|
65
|
+
return unverified("the statement names no log head to anchor to");
|
|
66
|
+
let head;
|
|
67
|
+
try {
|
|
68
|
+
head = await fetchLogHead(apiUrl);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return unverified(`could not read the log head: ${message(e)}`);
|
|
72
|
+
}
|
|
73
|
+
let headPayload;
|
|
74
|
+
try {
|
|
75
|
+
headPayload = buildTreeHeadPayload(head);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
return unverified(`could not rebuild the log head payload: ${message(e)}`);
|
|
79
|
+
}
|
|
80
|
+
const headSignature = hexToBytes(head.signature);
|
|
81
|
+
if (!(await crypto.ed25519Verify(key.keyBytes, headPayload, headSignature))) {
|
|
82
|
+
return unverified("the log head is not signed by the key that signed the statement, so it says nothing about it");
|
|
83
|
+
}
|
|
84
|
+
const headSize = head.tree_size;
|
|
85
|
+
const headRoot = hexToBytes(head.root_hash);
|
|
86
|
+
const statementRoot = hexToBytes(statement.sth_root_hash);
|
|
87
|
+
// A head SMALLER than one the same issuer signed into a statement means the
|
|
88
|
+
// log went backwards. An append-only structure cannot do that, so this needs
|
|
89
|
+
// no proof to refuse.
|
|
90
|
+
if (headSize < treeSize) {
|
|
91
|
+
return {
|
|
92
|
+
verdict: ANCHOR_INCONSISTENT,
|
|
93
|
+
detail: `the statement is anchored to tree size ${treeSize} but the log now publishes ` +
|
|
94
|
+
`${headSize} — an append-only log does not shrink`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (headSize === treeSize) {
|
|
98
|
+
// Consistency is equality at the same size, and it still has to be checked
|
|
99
|
+
// rather than assumed: two different roots signed for one tree size is the
|
|
100
|
+
// fork, stated as plainly as it can be.
|
|
101
|
+
if (!bytesEqual(headRoot, statementRoot)) {
|
|
102
|
+
return {
|
|
103
|
+
verdict: ANCHOR_INCONSISTENT,
|
|
104
|
+
detail: `two different roots signed for tree size ${treeSize}: ` +
|
|
105
|
+
`${bytesToHex(statementRoot.slice(0, 8))} in the statement, ` +
|
|
106
|
+
`${bytesToHex(headRoot.slice(0, 8))} from the log`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
verdict: ANCHOR_CONFIRMED,
|
|
111
|
+
detail: `statement anchored to the log's current head (tree size ${headSize})`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
let proof;
|
|
115
|
+
try {
|
|
116
|
+
proof = await fetchConsistencyProof(apiUrl, treeSize, headSize);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
return unverified(`could not read a consistency proof: ${message(e)}`);
|
|
120
|
+
}
|
|
121
|
+
if (!(await verifyConsistency(crypto, treeSize, headSize, statementRoot, headRoot, proof))) {
|
|
122
|
+
return {
|
|
123
|
+
verdict: ANCHOR_INCONSISTENT,
|
|
124
|
+
detail: `the log cannot prove tree size ${treeSize} is a prefix of its current size ` +
|
|
125
|
+
`${headSize} — the statement is anchored to a different history`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
verdict: ANCHOR_CONFIRMED,
|
|
130
|
+
detail: `statement's head (tree size ${treeSize}) proved to be a prefix of the log's ` +
|
|
131
|
+
`current head (tree size ${headSize})`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function unverified(detail) {
|
|
135
|
+
return { verdict: ANCHOR_UNVERIFIED, detail };
|
|
136
|
+
}
|
|
137
|
+
function message(e) {
|
|
138
|
+
return e instanceof Error ? e.message : String(e);
|
|
139
|
+
}
|
|
140
|
+
function bytesEqual(a, b) {
|
|
141
|
+
if (a.length !== b.length)
|
|
142
|
+
return false;
|
|
143
|
+
for (let i = 0; i < a.length; i++)
|
|
144
|
+
if (a[i] !== b[i])
|
|
145
|
+
return false;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
/** Read GET /v1/log/head and validate every field the signature covers.
|
|
149
|
+
*
|
|
150
|
+
* `log_id` is one of them. The endpoint serves none today (handler/log.go
|
|
151
|
+
* signedTreeHeadResponse) and BurnLedger's signer refuses to issue a head that
|
|
152
|
+
* names a log, but buildTreeHeadPayload picks the signing domain from the
|
|
153
|
+
* field, so a head that carries one has to reach it: dropped, the head rebuilds
|
|
154
|
+
* under burnledger.sth.v3, fails its signature and is reported unattributable —
|
|
155
|
+
* while the record path verifies the very same bytes. cmd/cli/anchor.go reads
|
|
156
|
+
* exactly these five fields, and all three CLIs are held to one fixture,
|
|
157
|
+
* testdata/named_head_anchor.json, so a head from a log that stamps its id gets
|
|
158
|
+
* one verdict rather than three.
|
|
159
|
+
*/
|
|
160
|
+
async function fetchLogHead(apiUrl) {
|
|
161
|
+
const envelope = await getJson(`${base(apiUrl)}/v1/log/head`);
|
|
162
|
+
const body = unwrapData(envelope);
|
|
163
|
+
const treeSize = body.tree_size;
|
|
164
|
+
if (typeof treeSize !== "number" || !Number.isSafeInteger(treeSize) || treeSize < 0) {
|
|
165
|
+
throw new Error("log head: tree_size is not an exact non-negative integer");
|
|
166
|
+
}
|
|
167
|
+
requireHex(body, "root_hash", 32);
|
|
168
|
+
requireHex(body, "signature", 64);
|
|
169
|
+
// Absent and null both mean "names no log", as they do for Go's string
|
|
170
|
+
// field; anything else that is not a string is a head this build cannot
|
|
171
|
+
// read, which Go's decoder also refuses.
|
|
172
|
+
if (body.log_id != null && typeof body.log_id !== "string") {
|
|
173
|
+
throw new Error("log head: log_id is not a string");
|
|
174
|
+
}
|
|
175
|
+
// Validated here rather than left to the payload rebuild, so that a head with
|
|
176
|
+
// an unreadable timestamp reports the same thing the Go CLI reports.
|
|
177
|
+
try {
|
|
178
|
+
formatTimestamp(body.timestamp);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
throw new Error("log head: timestamp is not RFC 3339");
|
|
182
|
+
}
|
|
183
|
+
return body;
|
|
184
|
+
}
|
|
185
|
+
async function fetchConsistencyProof(apiUrl, oldSize, newSize) {
|
|
186
|
+
const url = `${base(apiUrl)}/v1/log/proof/consistency?old_size=${oldSize}&new_size=${newSize}`;
|
|
187
|
+
const body = unwrapData(await getJson(url));
|
|
188
|
+
const raw = body.proof;
|
|
189
|
+
// Go decodes `"proof": null` to an empty slice, and an empty proof is a real
|
|
190
|
+
// answer at some tree sizes. An absent or null field is that, not an error.
|
|
191
|
+
if (raw === null || raw === undefined)
|
|
192
|
+
return [];
|
|
193
|
+
if (!Array.isArray(raw))
|
|
194
|
+
throw new Error("consistency proof: proof is not an array");
|
|
195
|
+
return raw.map((h, i) => {
|
|
196
|
+
if (typeof h !== "string" || !/^[0-9a-fA-F]{64}$/.test(h)) {
|
|
197
|
+
throw new Error(`consistency proof: proof[${i}] is not 32 bytes of hex`);
|
|
198
|
+
}
|
|
199
|
+
return hexToBytes(h);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function base(apiUrl) {
|
|
203
|
+
return apiUrl.replace(/\/+$/, "");
|
|
204
|
+
}
|
|
205
|
+
/** The API wraps every response in {"data": ...}. A body with no data object is
|
|
206
|
+
* an answer this build cannot read — never a finding about the record. */
|
|
207
|
+
function unwrapData(parsed) {
|
|
208
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
209
|
+
throw new Error("response is not a JSON object");
|
|
210
|
+
}
|
|
211
|
+
const doc = parsed;
|
|
212
|
+
const data = doc.data;
|
|
213
|
+
if (data === null || typeof data !== "object") {
|
|
214
|
+
throw new Error("response has no data object");
|
|
215
|
+
}
|
|
216
|
+
return data;
|
|
217
|
+
}
|
|
218
|
+
function requireHex(doc, field, bytes) {
|
|
219
|
+
const v = doc[field];
|
|
220
|
+
if (typeof v !== "string" || !new RegExp(`^[0-9a-fA-F]{${bytes * 2}}$`).test(v)) {
|
|
221
|
+
throw new Error(`log head: ${field} is not ${bytes} bytes of hex`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** Read one public log endpoint under the same size cap and timeout as the
|
|
225
|
+
* status fetch, and with the same absence of credentials: every endpoint this
|
|
226
|
+
* file touches is public by design, and sending an API key to whatever
|
|
227
|
+
* --api-url happens to point at is a credential leak the check never needed. */
|
|
228
|
+
async function getJson(url) {
|
|
229
|
+
const controller = new AbortController();
|
|
230
|
+
const timer = setTimeout(() => { controller.abort(); }, REQUEST_TIMEOUT_MS);
|
|
231
|
+
try {
|
|
232
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
233
|
+
const body = await readCapped(res);
|
|
234
|
+
if (!res.ok)
|
|
235
|
+
throw new Error(`API returned ${res.status}`);
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(body);
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
throw new Error(`parse response: ${message(e)}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Read a response body, stopping at the cap instead of buffering whatever a
|
|
248
|
+
* hostile server decides to send. The stream is cancelled on the way out, so an
|
|
249
|
+
* endless body costs one megabyte and not the process. */
|
|
250
|
+
async function readCapped(res) {
|
|
251
|
+
const reader = res.body?.getReader();
|
|
252
|
+
if (reader === undefined)
|
|
253
|
+
return "";
|
|
254
|
+
const chunks = [];
|
|
255
|
+
let total = 0;
|
|
256
|
+
for (;;) {
|
|
257
|
+
const { done, value } = await reader.read();
|
|
258
|
+
if (done)
|
|
259
|
+
break;
|
|
260
|
+
total += value.length;
|
|
261
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
262
|
+
await reader.cancel();
|
|
263
|
+
throw new Error(`read response: body exceeds ${MAX_RESPONSE_BYTES} bytes`);
|
|
264
|
+
}
|
|
265
|
+
chunks.push(value);
|
|
266
|
+
}
|
|
267
|
+
const joined = new Uint8Array(total);
|
|
268
|
+
let at = 0;
|
|
269
|
+
for (const c of chunks) {
|
|
270
|
+
joined.set(c, at);
|
|
271
|
+
at += c.length;
|
|
272
|
+
}
|
|
273
|
+
return new TextDecoder().decode(joined);
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=anchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor.js","sourceRoot":"","sources":["../../src/anchor.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/cli.d.ts
CHANGED
|
@@ -1,23 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** burnledger check — offline certificate verification.
|
|
3
3
|
* Exit codes: 0=valid, 1=invalid, 2=usage error */
|
|
4
|
+
import { type KeyListEvidence } from "./keys.js";
|
|
5
|
+
import { type AnchorResult } from "./anchor.js";
|
|
4
6
|
type Cert = Record<string, unknown>;
|
|
7
|
+
/** A published status statement, exactly as GET /v1/certificates/{id}/status
|
|
8
|
+
* serves it: hex strings, not decoded bytes. verifyCertificateWithStatus reads
|
|
9
|
+
* this shape, so nothing here re-encodes a field the signature covers. */
|
|
10
|
+
type StatusStatement = Record<string, unknown>;
|
|
5
11
|
/** Flag first, then environment — the precedence docs/cli.md promises and the
|
|
6
|
-
* Go CLI (cmd/cli/online.go
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
* Go CLI (cmd/cli/online.go resolveAPIBaseURL) implements. Exported for testing.
|
|
13
|
+
*
|
|
14
|
+
* It no longer resolves a key, and that is the change. GET
|
|
15
|
+
* /v1/certificates/{id}/status is unauthenticated by design: the audience for
|
|
16
|
+
* revocation is a relying party holding a certificate — a regulator, an
|
|
17
|
+
* auditor, the customer's customer — who has no account here and never will.
|
|
18
|
+
* Demanding an API key was what stopped that reader asking the one question the
|
|
19
|
+
* artifact exists to answer. */
|
|
20
|
+
export declare function resolveApiBaseUrl(apiUrl: string, env: Record<string, string | undefined>): string;
|
|
21
|
+
/** The API wraps every response in {"data": ...}; a caller may hand over either
|
|
22
|
+
* the envelope or the document inside it. Mirrors
|
|
23
|
+
* core.ParseCertificateStatusDocument, so this CLI and the Go one cannot
|
|
24
|
+
* disagree about which shape the endpoint returns. Exported for testing.
|
|
25
|
+
*
|
|
26
|
+
* A document that does not parse THROWS, and the caller turns that into a
|
|
27
|
+
* warning and a VALID_REVOCATION_UNKNOWN verdict. That is deliberate: an answer
|
|
28
|
+
* this build cannot read is silence about revocation, not a finding about the
|
|
29
|
+
* certificate. Handing a half-read document to the verifier instead would
|
|
30
|
+
* report "status statement signature is invalid" — a claim about the issuer —
|
|
31
|
+
* when what actually happened is that something returned nonsense.
|
|
32
|
+
*
|
|
33
|
+
* `status` itself is NOT validated against a known set. A statement saying
|
|
34
|
+
* SUSPENDED is a statement this build cannot read, and the verifier already has
|
|
35
|
+
* the right answer for that: it authenticates the signature first, then reports
|
|
36
|
+
* VALID_REVOCATION_UNKNOWN. Refusing it here would discard the statement before
|
|
37
|
+
* anyone authenticated it. */
|
|
38
|
+
export declare function unwrapStatusStatement(parsed: unknown): StatusStatement;
|
|
11
39
|
/** The pass/fail decision behind the exit code. A signature failure, a
|
|
12
|
-
* transparency FAILURE (thrown, not NOT_AVAILABLE)
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
* revocation, or a transparency FAILURE (thrown, not NOT_AVAILABLE) all make
|
|
41
|
+
* the certificate unusable. Revocation is no longer a separate input: it is
|
|
42
|
+
* part of the verdict verifyCertificateWithStatus returns. Exported for
|
|
43
|
+
* testing.
|
|
44
|
+
*
|
|
45
|
+
* An INCONSISTENT anchor refuses, mirroring exitCode in cmd/cli/check.go. It is
|
|
46
|
+
* not a doubt: it is a proof, from the issuer's own signatures, that the head a
|
|
47
|
+
* statement was anchored to is not a prefix of the head the log now publishes.
|
|
48
|
+
* An anchor that could NOT be checked does not refuse — the reader is told so,
|
|
49
|
+
* and the statement still stands on its own signature. */
|
|
50
|
+
export declare function certVerdict(sigResult: string, transparencyFailed: boolean, anchor?: AnchorResult): boolean;
|
|
51
|
+
export declare function formatOutput(cert: Cert, sigResult: string, transparency: string, transparencyFailed: boolean, status: StatusStatement | undefined, online: boolean, anchor?: AnchorResult, keyList?: KeyListEvidence): string;
|
|
21
52
|
/** Accept the raw API response too: GET /v1/certificates/{id} nests the
|
|
22
53
|
* signed certificate as data.certificate. Exported for testing. */
|
|
23
54
|
export declare function unwrapCertificate(parsed: Cert): Cert;
|
package/dist/esm/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA;mDACmD;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA;mDACmD;AAcnD,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAA6C,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3F,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACpC;;0EAE0E;AAC1E,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkC/C;;;;;;;;gCAQgC;AAChC,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtD,MAAM,CAIR;AA2ED;;;;;;;;;;;;;;;;8BAgB8B;AAC9B,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAoBtE;AAqCD;;;;;;;;;;0DAU0D;AAC1D,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,YAAY,GACpE,OAAO,CAGT;AAuDD,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAChF,MAAM,EAAE,eAAe,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,YAAY,EAC3E,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAiGR;AAgFD;mEACmE;AACnE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,CAUpD"}
|