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
package/dist/cjs/verify.js
CHANGED
|
@@ -17,18 +17,21 @@
|
|
|
17
17
|
* output at issuance time (transparency_status=PENDING, no transparency).
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.VALID_REVOCATION_UNKNOWN = exports.VALID_KEY_WINDOW_UNKNOWN = exports.VALID_KEY_COMPROMISED_LATER = exports.KEY_COMPROMISED = exports.KEY_OUTSIDE_VALIDITY = void 0;
|
|
20
|
+
exports.VALID_REVOCATION_UNKNOWN = exports.VALID_KEY_WINDOW_UNKNOWN = exports.VALID_KEY_COMPROMISED_LATER = exports.KEY_COMPROMISED = exports.KEY_OUTSIDE_VALIDITY = exports.KNOWN_FORMAT_VERSIONS = void 0;
|
|
21
21
|
exports.hexToBytes = hexToBytes;
|
|
22
22
|
exports.bytesToHex = bytesToHex;
|
|
23
23
|
exports.publicKeyFromHex = publicKeyFromHex;
|
|
24
|
+
exports.keyIsUsable = keyIsUsable;
|
|
24
25
|
exports.evaluateKey = evaluateKey;
|
|
25
26
|
exports.certificateAnchor = certificateAnchor;
|
|
26
27
|
exports.verifyCertificate = verifyCertificate;
|
|
27
28
|
exports.verifyTransparency = verifyTransparency;
|
|
28
29
|
exports.issuanceBytes = issuanceBytes;
|
|
29
30
|
exports.formatTimestamp = formatTimestamp;
|
|
31
|
+
exports.buildTreeHeadPayload = buildTreeHeadPayload;
|
|
30
32
|
exports.verifyConsistency = verifyConsistency;
|
|
31
33
|
exports.buildCertificateStatusPayload = buildCertificateStatusPayload;
|
|
34
|
+
exports.buildKeyListPayload = buildKeyListPayload;
|
|
32
35
|
exports.verifyCertificateWithStatus = verifyCertificateWithStatus;
|
|
33
36
|
const errors_js_1 = require("./errors.js");
|
|
34
37
|
// ---------------------------------------------------------------------------
|
|
@@ -49,11 +52,69 @@ const PAYLOAD_TYPE_ATTESTATION_V5 = "burnledger.attestation.v5";
|
|
|
49
52
|
const PAYLOAD_TYPE_CERTIFICATE_V5 = "burnledger.certificate.v5";
|
|
50
53
|
const FORMAT_VERSION_V5 = "5.0";
|
|
51
54
|
const FORMAT_VERSION_V4 = "4.0";
|
|
55
|
+
// v6 renames the artifact and nothing else: the shape is byte-for-byte v5, and
|
|
56
|
+
// only the tag and the version string differ. It still takes its own tag, for
|
|
57
|
+
// the same reason v4 and v5 each took one -- a signature over a payload calling
|
|
58
|
+
// itself a verification record must not verify as one calling itself a
|
|
59
|
+
// certificate. Two names for one shape sharing a separator is exactly what a
|
|
60
|
+
// separator prevents.
|
|
61
|
+
const PAYLOAD_TYPE_ATTESTATION_V6 = "burnledger.attestation.v6";
|
|
62
|
+
const PAYLOAD_TYPE_VERIFICATION_RECORD_V6 = "burnledger.verification_record.v6";
|
|
63
|
+
const FORMAT_VERSION_V6 = "6.0";
|
|
64
|
+
// v7 changes the SHAPE, not just the name: every system entry gains
|
|
65
|
+
// read_only_enforcement and transport_security, and the issuer gains the
|
|
66
|
+
// algorithm identifier. Its tags move for the reason v4, v5 and v6 each moved
|
|
67
|
+
// theirs, and this time the bytes under them genuinely differ.
|
|
68
|
+
const PAYLOAD_TYPE_ATTESTATION_V7 = "burnledger.attestation.v7";
|
|
69
|
+
const PAYLOAD_TYPE_VERIFICATION_RECORD_V7 = "burnledger.verification_record.v7";
|
|
70
|
+
const FORMAT_VERSION_V7 = "7.0";
|
|
71
|
+
const FORMAT_VERSION_V3 = "3.0";
|
|
72
|
+
/**
|
|
73
|
+
* The only signature scheme this SDK can check. A v7 record names its own
|
|
74
|
+
* algorithm, so a record naming anything else must be refused with its own
|
|
75
|
+
* code rather than fed to ed25519Verify and reported as a bad signature.
|
|
76
|
+
*/
|
|
77
|
+
const ALGORITHM_ED25519 = "ed25519";
|
|
78
|
+
/**
|
|
79
|
+
* Every format this build can read, oldest first. Mirrors core.FormatVersions()
|
|
80
|
+
* in the Go source; the cross-language conformance corpus carries a fixture per
|
|
81
|
+
* version, so a version present in one language and missing here fails there.
|
|
82
|
+
*
|
|
83
|
+
* Frozen because `readonly` is erased at build time: it stops a TypeScript
|
|
84
|
+
* caller from writing to the array, and does nothing at all to the JavaScript
|
|
85
|
+
* this compiles to — which is what the browser bundle actually runs, where any
|
|
86
|
+
* script on the page could otherwise push a version onto it.
|
|
87
|
+
*/
|
|
88
|
+
exports.KNOWN_FORMAT_VERSIONS = Object.freeze([
|
|
89
|
+
FORMAT_VERSION_V3,
|
|
90
|
+
FORMAT_VERSION_V4,
|
|
91
|
+
FORMAT_VERSION_V5,
|
|
92
|
+
FORMAT_VERSION_V6,
|
|
93
|
+
FORMAT_VERSION_V7,
|
|
94
|
+
]);
|
|
95
|
+
/**
|
|
96
|
+
* Returns the version, or throws if this SDK cannot read it.
|
|
97
|
+
*
|
|
98
|
+
* Rejects a non-string too: JSON carrying `6.0` as a NUMBER would compare
|
|
99
|
+
* unequal to every known version and silently take the oldest branch, which is
|
|
100
|
+
* the same trap as an unknown version wearing a different hat.
|
|
101
|
+
*/
|
|
102
|
+
function checkFormatVersion(version) {
|
|
103
|
+
if (typeof version !== "string" || !exports.KNOWN_FORMAT_VERSIONS.includes(version)) {
|
|
104
|
+
throw new errors_js_1.VerificationError(`unsupported certificate_format_version ${JSON.stringify(version)}; ` +
|
|
105
|
+
`this version of the SDK reads ${exports.KNOWN_FORMAT_VERSIONS.join(", ")}. ` +
|
|
106
|
+
"The record may be genuine and simply newer than this library.", errors_js_1.UNSUPPORTED_FORMAT_VERSION);
|
|
107
|
+
}
|
|
108
|
+
return version;
|
|
109
|
+
}
|
|
52
110
|
const PAYLOAD_TYPE_TREE_HEAD = "burnledger.sth.v3";
|
|
111
|
+
// The domain for a tree head that names its log. See buildTreeHeadPayload.
|
|
112
|
+
const PAYLOAD_TYPE_TREE_HEAD_V7 = "burnledger.sth.v7";
|
|
53
113
|
const PAYLOAD_TYPE_LOG_LEAF = "burnledger.log_leaf.v3";
|
|
54
114
|
// There is deliberately no verification payload type: v3 produces those facts
|
|
55
115
|
// in the same enclave call that signs the certificate (ADR-016 §2).
|
|
56
116
|
const PAYLOAD_TYPE_CERTIFICATE_STATUS = "burnledger.certificate_status.v3";
|
|
117
|
+
const PAYLOAD_TYPE_KEY_LIST = "burnledger.key_list.v3";
|
|
57
118
|
function hexToBytes(hex) {
|
|
58
119
|
const len = hex.length >>> 1;
|
|
59
120
|
const out = new Uint8Array(len);
|
|
@@ -140,6 +201,29 @@ exports.KEY_OUTSIDE_VALIDITY = "KEY_OUTSIDE_VALIDITY";
|
|
|
140
201
|
exports.KEY_COMPROMISED = "KEY_COMPROMISED";
|
|
141
202
|
exports.VALID_KEY_COMPROMISED_LATER = "VALID_KEY_COMPROMISED_LATER";
|
|
142
203
|
exports.VALID_KEY_WINDOW_UNKNOWN = "VALID_KEY_WINDOW_UNKNOWN";
|
|
204
|
+
/**
|
|
205
|
+
* The revocation states a statement may assert. Mirrors
|
|
206
|
+
* core.ValidCertificateStatusValue: anything else means the statement was read
|
|
207
|
+
* but not understood, which is not the same as "not revoked".
|
|
208
|
+
*/
|
|
209
|
+
const KNOWN_STATUS_VALUES = new Set(["ACTIVE", "REVOKED"]);
|
|
210
|
+
/**
|
|
211
|
+
* The key verdicts that permit relying on a signature: VALID plus the two soft
|
|
212
|
+
* verdicts ADR-017 §2 says a reader must be SHOWN rather than refused. Anything
|
|
213
|
+
* not named here is refused, including a verdict added in a later release.
|
|
214
|
+
*/
|
|
215
|
+
const USABLE_KEY_VERDICTS = new Set([
|
|
216
|
+
"VALID",
|
|
217
|
+
exports.VALID_KEY_WINDOW_UNKNOWN,
|
|
218
|
+
exports.VALID_KEY_COMPROMISED_LATER,
|
|
219
|
+
]);
|
|
220
|
+
/** Whether a key verdict permits relying on a signature. Mirrors
|
|
221
|
+
* core.KeyIsUsable; exported so the key-list verifier (keys.ts) refuses the
|
|
222
|
+
* same set requireUsableKey refuses rather than keeping a second copy of it —
|
|
223
|
+
* a second copy is exactly how #684's fourth answer came to exist. */
|
|
224
|
+
function keyIsUsable(verdict) {
|
|
225
|
+
return USABLE_KEY_VERDICTS.has(verdict);
|
|
226
|
+
}
|
|
143
227
|
/** Whether a key was authorized to sign at anchor time `t`.
|
|
144
228
|
*
|
|
145
229
|
* Returns `"VALID"` when the key clears. The anchor is seconds since the epoch
|
|
@@ -182,7 +266,17 @@ function evaluateKey(pki, anchor) {
|
|
|
182
266
|
// for. Fail closed, as the boolean it replaces did.
|
|
183
267
|
return "UNKNOWN_KEY";
|
|
184
268
|
}
|
|
185
|
-
/**
|
|
269
|
+
/**
|
|
270
|
+
* The anchor a certificate supplies, or null when it supplies none.
|
|
271
|
+
*
|
|
272
|
+
* READS AN UNSIGNED FIELD. `transparency` is stripped before signing, so on a
|
|
273
|
+
* certificate handed to a verifier the timestamp is holder-editable. Use
|
|
274
|
+
* {@link verifiedCertificateAnchor} anywhere the anchor decides whether a key
|
|
275
|
+
* may be relied on; this is safe only where the same timestamp is inside a
|
|
276
|
+
* payload the caller goes on to verify, which is what verifyTransparency does.
|
|
277
|
+
*
|
|
278
|
+
* Exported and unchanged because it is published API.
|
|
279
|
+
*/
|
|
186
280
|
function certificateAnchor(certificate) {
|
|
187
281
|
const transparency = certificate.transparency;
|
|
188
282
|
if (transparency == null)
|
|
@@ -197,35 +291,95 @@ function certificateAnchor(certificate) {
|
|
|
197
291
|
return null;
|
|
198
292
|
}
|
|
199
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* The anchor, but only once the tree head carrying it has been checked against
|
|
296
|
+
* the key being evaluated.
|
|
297
|
+
*
|
|
298
|
+
* The bypass this closes: for a key declared compromised, evaluateKey answers
|
|
299
|
+
* KEY_COMPROMISED with no anchor and VALID_KEY_COMPROMISED_LATER when the
|
|
300
|
+
* anchor predates the compromise — and the second is a verdict requireUsableKey
|
|
301
|
+
* passes through as the RESULT of verifyCertificate. Since the timestamp is
|
|
302
|
+
* covered by no signature, a holder could backdate it and have a certificate
|
|
303
|
+
* signed with a compromised key reported as verified-with-a-caveat. Deleting
|
|
304
|
+
* `transparency` failed closed; keeping a doctored one did not.
|
|
305
|
+
*
|
|
306
|
+
* A head that does not verify yields no anchor, which is the same conservative
|
|
307
|
+
* answer as a certificate carrying no transparency block at all.
|
|
308
|
+
*/
|
|
309
|
+
async function verifiedCertificateAnchor(crypto, certificate, pki) {
|
|
310
|
+
const anchor = certificateAnchor(certificate);
|
|
311
|
+
if (anchor === null)
|
|
312
|
+
return null;
|
|
313
|
+
const transparency = certificate.transparency;
|
|
314
|
+
const sth = transparency.signed_tree_head;
|
|
315
|
+
try {
|
|
316
|
+
const headPayload = buildTreeHeadPayload(sth);
|
|
317
|
+
const headSig = decodeSignature(sth.signature);
|
|
318
|
+
if (!(await crypto.ed25519Verify(pki.keyBytes, headPayload, headSig)))
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
return anchor;
|
|
325
|
+
}
|
|
200
326
|
/** Throws for a key that cannot be used at all; returns the verdict otherwise.
|
|
201
327
|
*
|
|
202
328
|
* VALID_KEY_COMPROMISED_LATER and VALID_KEY_WINDOW_UNKNOWN are returned rather
|
|
203
329
|
* than thrown because they are not refusals — they are results a caller must
|
|
204
330
|
* show the reader instead of collapsing into green.
|
|
205
331
|
*/
|
|
206
|
-
function requireUsableKey(pki, anchor, keyId
|
|
332
|
+
function requireUsableKey(pki, anchor, keyId,
|
|
333
|
+
// Named, because this is also the key that signs a certificate-status
|
|
334
|
+
// statement, and "issuer key is compromised" would send a reader looking at
|
|
335
|
+
// the wrong key.
|
|
336
|
+
role = "issuer key") {
|
|
207
337
|
const verdict = evaluateKey(pki, anchor);
|
|
208
338
|
if (verdict === exports.KEY_COMPROMISED) {
|
|
209
|
-
throw new errors_js_1.VerificationError(
|
|
339
|
+
throw new errors_js_1.VerificationError(`${role} is compromised: ${keyId}`);
|
|
210
340
|
}
|
|
211
341
|
if (verdict === exports.KEY_OUTSIDE_VALIDITY) {
|
|
212
|
-
throw new errors_js_1.VerificationError(
|
|
342
|
+
throw new errors_js_1.VerificationError(`${role} was not valid when it signed: ${keyId}`);
|
|
213
343
|
}
|
|
214
344
|
if (verdict === "UNKNOWN_KEY") {
|
|
215
|
-
throw new errors_js_1.VerificationError(
|
|
345
|
+
throw new errors_js_1.VerificationError(`${role} has an unusable status "${pki.keyStatus}": ${keyId}`);
|
|
346
|
+
}
|
|
347
|
+
// An ALLOW-list, matching core.KeyIsUsable, which was inverted away from
|
|
348
|
+
// exactly the shape above. Naming the refusals and returning everything else
|
|
349
|
+
// means a verdict added to evaluateKey later becomes a PASS by default — and
|
|
350
|
+
// the value returned here is propagated out of verifyCertificate as its
|
|
351
|
+
// result, so "everything else" is rendered to a reader as "verified, with a
|
|
352
|
+
// note". Go would refuse the same document. That is #684's three-verifiers-
|
|
353
|
+
// two-answers divergence, in the direction that calls a bad document good.
|
|
354
|
+
if (!USABLE_KEY_VERDICTS.has(verdict)) {
|
|
355
|
+
throw new errors_js_1.VerificationError(`${role} returned a verdict this SDK does not recognize (${verdict}): ${keyId}`);
|
|
216
356
|
}
|
|
217
357
|
return verdict;
|
|
218
358
|
}
|
|
219
359
|
/** Verify all signatures on a deletion certificate offline. */
|
|
220
360
|
async function verifyCertificate(crypto, certificate, publicKeys) {
|
|
361
|
+
// Step 0: can this SDK read the format at all? Every check below rebuilds
|
|
362
|
+
// signed bytes from the version, so an unreadable version makes all of them
|
|
363
|
+
// meaningless - and "unknown issuer key" would be the wrong thing to tell
|
|
364
|
+
// someone holding a record that is merely newer than this library.
|
|
365
|
+
const formatVersion = checkFormatVersion(certificate.certificate_format_version);
|
|
221
366
|
const issuer = certificate.issuer;
|
|
367
|
+
// Step 0b: can this SDK check the algorithm the record names? Asked before
|
|
368
|
+
// any signature, because verifying an Ed25519 signature over a record that
|
|
369
|
+
// says it was signed with something else answers a question nobody asked.
|
|
370
|
+
// Below v7 no record names one, so there is nothing to check.
|
|
371
|
+
if (formatVersion === FORMAT_VERSION_V7 && issuer.algorithm !== ALGORITHM_ED25519) {
|
|
372
|
+
throw new errors_js_1.VerificationError(`record names signature algorithm ${JSON.stringify(issuer.algorithm)}; ` +
|
|
373
|
+
`this version of the SDK verifies ${ALGORITHM_ED25519}. ` +
|
|
374
|
+
"The record may be genuine and simply signed with a scheme this library does not implement.", errors_js_1.UNSUPPORTED_ALGORITHM);
|
|
375
|
+
}
|
|
222
376
|
const keyId = issuer.key_id;
|
|
223
377
|
const pki = publicKeys.get(keyId);
|
|
224
378
|
if (pki === undefined)
|
|
225
379
|
throw new errors_js_1.VerificationError(`unknown issuer key: ${keyId}`);
|
|
226
380
|
// The key check runs before any signature check: a signature verified
|
|
227
381
|
// against a key nobody vouches for proves nothing about the issuer.
|
|
228
|
-
const keyVerdict = requireUsableKey(pki,
|
|
382
|
+
const keyVerdict = requireUsableKey(pki, await verifiedCertificateAnchor(crypto, certificate, pki), keyId);
|
|
229
383
|
// 1. Certificate signature FIRST — nothing below may trust a field until the
|
|
230
384
|
// bytes carrying it are covered by a verified signature.
|
|
231
385
|
const certPayload = buildCertificatePayload(certificate);
|
|
@@ -348,6 +502,17 @@ async function verifyTransparency(crypto, certificate, publicKeys) {
|
|
|
348
502
|
throw new errors_js_1.VerificationError(`transparency.tree_size (${unsignedSize}) does not match the signed tree head ` +
|
|
349
503
|
`(${treeSize}); it is not covered by any signature`);
|
|
350
504
|
}
|
|
505
|
+
// Same rule for the log id (Q31). transparency.log_id sits beside log_url and
|
|
506
|
+
// is the field a reader looks at to answer "which log is this?", and nothing
|
|
507
|
+
// signs it — so a holder could relabel a genuine proof as belonging to a
|
|
508
|
+
// different log while every signature still checked out. A head predating log
|
|
509
|
+
// ids has neither side set and passes.
|
|
510
|
+
const signedLogId = sth.log_id ?? "";
|
|
511
|
+
const unsignedLogId = transparency.log_id ?? "";
|
|
512
|
+
if (unsignedLogId !== signedLogId) {
|
|
513
|
+
throw new errors_js_1.VerificationError(`transparency.log_id (${JSON.stringify(unsignedLogId)}) does not match the signed ` +
|
|
514
|
+
`tree head (${JSON.stringify(signedLogId)}); it is not covered by any signature`);
|
|
515
|
+
}
|
|
351
516
|
if (!(await verifyInclusion(crypto, leaf, index, treeSize, proofHashes, root))) {
|
|
352
517
|
throw new errors_js_1.VerificationError("merkle inclusion proof is invalid");
|
|
353
518
|
}
|
|
@@ -582,31 +747,85 @@ function civilFromDays(days) {
|
|
|
582
747
|
// ---------------------------------------------------------------------------
|
|
583
748
|
function buildAttestationPayload(subject, att, certFormatVersion) {
|
|
584
749
|
const attestedAt = formatTimestamp(att.attested_at);
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
: null,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
750
|
+
// v7 signs two per-system measurements v6 leaves on a mutable row. Gated on
|
|
751
|
+
// the record's OWN version: emitting them for an older format would rebuild
|
|
752
|
+
// bytes no signer ever produced and reject every record already issued.
|
|
753
|
+
const isV7 = certFormatVersion === FORMAT_VERSION_V7;
|
|
754
|
+
const systems = att.systems.map((s) => {
|
|
755
|
+
const sys = {
|
|
756
|
+
canonical_version: s.canonical_version ?? null,
|
|
757
|
+
connector_type: s.connector_type,
|
|
758
|
+
hash_scope: s.hash_scope,
|
|
759
|
+
merkle_root: s.merkle_root
|
|
760
|
+
? toHex(s.merkle_root)
|
|
761
|
+
: null,
|
|
762
|
+
// The system's own observation time, not the envelope's.
|
|
763
|
+
observed_at: formatTimestamp(s.observed_at),
|
|
764
|
+
query_hash: toHex(s.query_hash),
|
|
765
|
+
record_count: s.record_count,
|
|
766
|
+
system_id: s.system_id,
|
|
767
|
+
system_name: s.system_name,
|
|
768
|
+
};
|
|
769
|
+
if (isV7) {
|
|
770
|
+
sys.read_only_enforcement = requireMeasured(s.read_only_enforcement, "read_only_enforcement", s.system_name);
|
|
771
|
+
sys.transport_security = requireMeasured(s.transport_security, "transport_security", s.system_name);
|
|
772
|
+
}
|
|
773
|
+
return sys;
|
|
774
|
+
});
|
|
599
775
|
const payload = {
|
|
600
776
|
attested_at: attestedAt,
|
|
601
|
-
payload_type: certFormatVersion
|
|
602
|
-
? PAYLOAD_TYPE_ATTESTATION_V5
|
|
603
|
-
: PAYLOAD_TYPE_ATTESTATION,
|
|
777
|
+
payload_type: attestationPayloadType(certFormatVersion),
|
|
604
778
|
proof_mode: att.proof_mode,
|
|
605
779
|
subject_hash: toHex(subject.identifier_hash),
|
|
606
780
|
systems,
|
|
607
781
|
};
|
|
608
782
|
return canonicalJson(payload);
|
|
609
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* Read a v7 measured field that MUST be a non-empty string.
|
|
786
|
+
*
|
|
787
|
+
* A missing or non-string value cannot be turned into `""` and canonicalized:
|
|
788
|
+
* the signer never emits an empty measurement, so an empty one here would
|
|
789
|
+
* rebuild bytes no signature covers and be reported as forgery. Refusing with a
|
|
790
|
+
* document-shape message says the true thing — this record is malformed, not
|
|
791
|
+
* this record is fake.
|
|
792
|
+
*/
|
|
793
|
+
function requireMeasured(value, field, systemName) {
|
|
794
|
+
if (typeof value !== "string" || value === "") {
|
|
795
|
+
throw new errors_js_1.VerificationError(`system ${JSON.stringify(systemName)}: ${field} is missing from a 7.0 record, ` +
|
|
796
|
+
"which signs it; the document is incomplete rather than unverifiable");
|
|
797
|
+
}
|
|
798
|
+
return value;
|
|
799
|
+
}
|
|
800
|
+
/** Domain separator for the record itself, by certificate format version. */
|
|
801
|
+
function certificatePayloadType(version) {
|
|
802
|
+
checkFormatVersion(version);
|
|
803
|
+
if (version === FORMAT_VERSION_V7)
|
|
804
|
+
return PAYLOAD_TYPE_VERIFICATION_RECORD_V7;
|
|
805
|
+
if (version === FORMAT_VERSION_V6)
|
|
806
|
+
return PAYLOAD_TYPE_VERIFICATION_RECORD_V6;
|
|
807
|
+
if (version === FORMAT_VERSION_V5)
|
|
808
|
+
return PAYLOAD_TYPE_CERTIFICATE_V5;
|
|
809
|
+
if (version === FORMAT_VERSION_V4)
|
|
810
|
+
return PAYLOAD_TYPE_CERTIFICATE_V4;
|
|
811
|
+
return PAYLOAD_TYPE_CERTIFICATE;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Domain separator for the attestation carried by a record of that format. It
|
|
815
|
+
* follows the record's version because a verifier rebuilds the attestation
|
|
816
|
+
* payload from the record it is checking, and must reproduce the exact bytes
|
|
817
|
+
* the enclave signed.
|
|
818
|
+
*/
|
|
819
|
+
function attestationPayloadType(version) {
|
|
820
|
+
checkFormatVersion(version);
|
|
821
|
+
if (version === FORMAT_VERSION_V7)
|
|
822
|
+
return PAYLOAD_TYPE_ATTESTATION_V7;
|
|
823
|
+
if (version === FORMAT_VERSION_V6)
|
|
824
|
+
return PAYLOAD_TYPE_ATTESTATION_V6;
|
|
825
|
+
if (version === FORMAT_VERSION_V5)
|
|
826
|
+
return PAYLOAD_TYPE_ATTESTATION_V5;
|
|
827
|
+
return PAYLOAD_TYPE_ATTESTATION;
|
|
828
|
+
}
|
|
610
829
|
function buildCertificatePayload(cert) {
|
|
611
830
|
const att = cert.attestation;
|
|
612
831
|
const issuer = cert.issuer;
|
|
@@ -614,19 +833,28 @@ function buildCertificatePayload(cert) {
|
|
|
614
833
|
// One list, keyed by system_id (ADR-016 §2). v2 signed an attestation list
|
|
615
834
|
// and a verification list joined only on the human-editable system_name,
|
|
616
835
|
// which made a partial deletion indistinguishable from a complete one.
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
836
|
+
const version = cert.certificate_format_version;
|
|
837
|
+
const isV7 = version === FORMAT_VERSION_V7;
|
|
838
|
+
const systems = (cert.systems ?? []).map((s) => {
|
|
839
|
+
const sys = {
|
|
840
|
+
attested_at: formatTimestamp(s.attested_at),
|
|
841
|
+
attested_count: s.attested_count,
|
|
842
|
+
canonical_version: s.canonical_version ?? null,
|
|
843
|
+
connector_type: s.connector_type,
|
|
844
|
+
hash_scope: s.hash_scope,
|
|
845
|
+
merkle_root: s.merkle_root ? toHex(s.merkle_root) : null,
|
|
846
|
+
query_hash: toHex(s.query_hash),
|
|
847
|
+
system_id: s.system_id,
|
|
848
|
+
system_name: s.system_name,
|
|
849
|
+
verified_at: formatTimestamp(s.verified_at),
|
|
850
|
+
verified_count: s.verified_count,
|
|
851
|
+
};
|
|
852
|
+
if (isV7) {
|
|
853
|
+
sys.read_only_enforcement = requireMeasured(s.read_only_enforcement, "read_only_enforcement", s.system_name);
|
|
854
|
+
sys.transport_security = requireMeasured(s.transport_security, "transport_security", s.system_name);
|
|
855
|
+
}
|
|
856
|
+
return sys;
|
|
857
|
+
});
|
|
630
858
|
// The attestation block carries no system list of its own: the merged list
|
|
631
859
|
// above is a superset of it. verification_signature is gone entirely.
|
|
632
860
|
const attObj = {
|
|
@@ -637,20 +865,28 @@ function buildCertificatePayload(cert) {
|
|
|
637
865
|
// Fields added after v3 are gated on the certificate's OWN version. A v3
|
|
638
866
|
// certificate must reconstruct to the same bytes forever; reading these
|
|
639
867
|
// unconditionally would break every certificate already issued.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
868
|
+
// v6 carries v5's shape exactly, so it takes every gate v5 takes. Naming
|
|
869
|
+
// these "Plus" rather than testing equality at each use is what stops a new
|
|
870
|
+
// version from silently missing one.
|
|
871
|
+
const isV5Plus = version === FORMAT_VERSION_V5 || version === FORMAT_VERSION_V6 || isV7;
|
|
872
|
+
const isV4Plus = version === FORMAT_VERSION_V4 || isV5Plus;
|
|
643
873
|
const issuerObj = {
|
|
644
874
|
key_id: issuer.key_id,
|
|
645
875
|
name: issuer.name,
|
|
646
876
|
public_key: toHex(issuer.public_key),
|
|
647
877
|
};
|
|
878
|
+
// v7 signs the algorithm, and does so unconditionally: unlike legal_entity
|
|
879
|
+
// and enclave_pcr0, "which scheme signed this" is never unknown to a signer,
|
|
880
|
+
// so a v7 record missing it is malformed rather than merely sparse.
|
|
881
|
+
if (isV7) {
|
|
882
|
+
issuerObj.algorithm = requireMeasured(issuer.algorithm, "issuer.algorithm", "issuer");
|
|
883
|
+
}
|
|
648
884
|
if (isV4Plus && typeof issuer.legal_entity === "string" && issuer.legal_entity !== "") {
|
|
649
885
|
issuerObj.legal_entity = issuer.legal_entity;
|
|
650
886
|
}
|
|
651
887
|
// Signed from v5, and omitted when absent or empty: a build with no
|
|
652
888
|
// measurement signs none, and "" is a value a reader could mistake for one.
|
|
653
|
-
if (
|
|
889
|
+
if (isV5Plus && typeof issuer.enclave_pcr0 === "string" && issuer.enclave_pcr0 !== "") {
|
|
654
890
|
issuerObj.enclave_pcr0 = issuer.enclave_pcr0;
|
|
655
891
|
}
|
|
656
892
|
// v5 drops identifier_type_hint: signed, but always the constant "custom",
|
|
@@ -658,7 +894,7 @@ function buildCertificatePayload(cert) {
|
|
|
658
894
|
const subjectObj = {
|
|
659
895
|
identifier_hash: toHex(subject.identifier_hash),
|
|
660
896
|
};
|
|
661
|
-
if (!
|
|
897
|
+
if (!isV5Plus) {
|
|
662
898
|
subjectObj.identifier_type_hint = subject.identifier_type_hint;
|
|
663
899
|
}
|
|
664
900
|
// status and revocation are deliberately absent (ADR-016 §3): a signature
|
|
@@ -671,11 +907,7 @@ function buildCertificatePayload(cert) {
|
|
|
671
907
|
certificate_id: cert.certificate_id,
|
|
672
908
|
issued_at: formatTimestamp(cert.issued_at),
|
|
673
909
|
issuer: issuerObj,
|
|
674
|
-
payload_type:
|
|
675
|
-
? PAYLOAD_TYPE_CERTIFICATE_V5
|
|
676
|
-
: isV4Plus
|
|
677
|
-
? PAYLOAD_TYPE_CERTIFICATE_V4
|
|
678
|
-
: PAYLOAD_TYPE_CERTIFICATE,
|
|
910
|
+
payload_type: certificatePayloadType(version),
|
|
679
911
|
subject: subjectObj,
|
|
680
912
|
systems,
|
|
681
913
|
};
|
|
@@ -718,15 +950,37 @@ function attestationSystems(cert) {
|
|
|
718
950
|
observed_at: s.attested_at,
|
|
719
951
|
merkle_root: s.merkle_root,
|
|
720
952
|
canonical_version: s.canonical_version,
|
|
953
|
+
// Carried through so the v7 attestation payload can be rebuilt from the
|
|
954
|
+
// record. Undefined on older formats, where buildAttestationPayload never
|
|
955
|
+
// reads them.
|
|
956
|
+
read_only_enforcement: s.read_only_enforcement,
|
|
957
|
+
transport_security: s.transport_security,
|
|
721
958
|
}));
|
|
722
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Port of Go's BuildTreeHeadPayload.
|
|
962
|
+
*
|
|
963
|
+
* A head that names its log is signed in a DIFFERENT DOMAIN from one that does
|
|
964
|
+
* not (Q31). That is what makes log_id unstrippable: removing it moves the
|
|
965
|
+
* rebuild into burnledger.sth.v3 and adding one moves it into
|
|
966
|
+
* burnledger.sth.v7, and the signature fails either way. Heads signed before
|
|
967
|
+
* log ids existed carry none and reproduce exactly the bytes they always did.
|
|
968
|
+
*
|
|
969
|
+
* Exported for anchor.ts, which rebuilds the preimage of a head fetched from
|
|
970
|
+
* GET /v1/log/head. Not re-exported from index.ts: it is an internal seam
|
|
971
|
+
* between two modules of this package, not published surface.
|
|
972
|
+
*/
|
|
723
973
|
function buildTreeHeadPayload(head) {
|
|
974
|
+
const logId = head.log_id;
|
|
975
|
+
const named = typeof logId === "string" && logId !== "";
|
|
724
976
|
const payload = {
|
|
725
|
-
payload_type: PAYLOAD_TYPE_TREE_HEAD,
|
|
977
|
+
payload_type: named ? PAYLOAD_TYPE_TREE_HEAD_V7 : PAYLOAD_TYPE_TREE_HEAD,
|
|
726
978
|
root_hash: toHex(head.root_hash),
|
|
727
979
|
timestamp: formatTimestamp(head.timestamp),
|
|
728
980
|
tree_size: head.tree_size,
|
|
729
981
|
};
|
|
982
|
+
if (named)
|
|
983
|
+
payload.log_id = logId;
|
|
730
984
|
return canonicalJson(payload);
|
|
731
985
|
}
|
|
732
986
|
// ---------------------------------------------------------------------------
|
|
@@ -881,6 +1135,42 @@ function buildCertificateStatusPayload(stmt) {
|
|
|
881
1135
|
}
|
|
882
1136
|
return canonicalJson(payload);
|
|
883
1137
|
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Port of Go's BuildKeyListPayload (core/key_list.go).
|
|
1140
|
+
*
|
|
1141
|
+
* The key array is sorted by key_id before signing: canonical JSON sorts
|
|
1142
|
+
* object keys but does nothing to array order, and the server has no reason to
|
|
1143
|
+
* preserve any particular order across a rotation. Optional entry fields are
|
|
1144
|
+
* omitted, never emitted as null, for the reason buildCertificateStatusPayload
|
|
1145
|
+
* omits them. `keys` is the array exactly as HANDED to the verifier — the
|
|
1146
|
+
* signature covers the published entries, not a parsed reinterpretation of
|
|
1147
|
+
* them, which is what makes an edited key_status detectable.
|
|
1148
|
+
*/
|
|
1149
|
+
function buildKeyListPayload(doc) {
|
|
1150
|
+
const entries = doc.keys.map((e) => {
|
|
1151
|
+
const k = {};
|
|
1152
|
+
if (e.compromised_from != null)
|
|
1153
|
+
k.compromised_from = e.compromised_from;
|
|
1154
|
+
k.key_id = e.key_id;
|
|
1155
|
+
k.key_status = e.key_status;
|
|
1156
|
+
if (e.not_after != null)
|
|
1157
|
+
k.not_after = e.not_after;
|
|
1158
|
+
if (e.not_before != null && e.not_before !== "")
|
|
1159
|
+
k.not_before = e.not_before;
|
|
1160
|
+
k.public_key = e.public_key;
|
|
1161
|
+
return k;
|
|
1162
|
+
});
|
|
1163
|
+
entries.sort((a, b) => (a.key_id < b.key_id ? -1 : a.key_id === b.key_id ? 0 : 1));
|
|
1164
|
+
const payload = {
|
|
1165
|
+
payload_type: PAYLOAD_TYPE_KEY_LIST,
|
|
1166
|
+
keys: entries,
|
|
1167
|
+
statement_expires_at: formatTimestamp(doc.statement_expires_at),
|
|
1168
|
+
statement_issued_at: formatTimestamp(doc.statement_issued_at),
|
|
1169
|
+
sth_root_hash: toHex(doc.sth_root_hash),
|
|
1170
|
+
sth_tree_size: doc.sth_tree_size,
|
|
1171
|
+
};
|
|
1172
|
+
return canonicalJson(payload);
|
|
1173
|
+
}
|
|
884
1174
|
/** The verdict when a certificate verified but nothing said whether it was revoked. */
|
|
885
1175
|
exports.VALID_REVOCATION_UNKNOWN = "VALID_REVOCATION_UNKNOWN";
|
|
886
1176
|
/**
|
|
@@ -896,28 +1186,43 @@ exports.VALID_REVOCATION_UNKNOWN = "VALID_REVOCATION_UNKNOWN";
|
|
|
896
1186
|
* | input | result |
|
|
897
1187
|
* |---|---|
|
|
898
1188
|
* | fresh statement, REVOKED | throws VerificationError |
|
|
899
|
-
* | fresh statement, ACTIVE | `"VALID"
|
|
900
|
-
* | absent or expired statement | `"VALID_REVOCATION_UNKNOWN"
|
|
1189
|
+
* | fresh statement, ACTIVE | `"VALID"`, or a soft verdict on either key |
|
|
1190
|
+
* | absent or expired statement | `"VALID_REVOCATION_UNKNOWN"`, or the certificate key's soft verdict |
|
|
1191
|
+
* | statement key unusable (ADR-017) | throws VerificationError |
|
|
901
1192
|
*
|
|
902
|
-
* The
|
|
1193
|
+
* The third row is the point: `verifyCertificate` answers VALID there, which
|
|
903
1194
|
* reads as "not revoked" and is not something it checked.
|
|
904
1195
|
*/
|
|
905
1196
|
async function verifyCertificateWithStatus(crypto, certificate, publicKeys, status, now) {
|
|
1197
|
+
// A refusal throws out of verifyCertificate, so `base` is VALID or one of the
|
|
1198
|
+
// two soft verdicts. Returning a soft verdict here skipped every check below
|
|
1199
|
+
// it: a revoked certificate signed by a compromised-later key answered
|
|
1200
|
+
// VALID_KEY_COMPROMISED_LATER and its statement was never authenticated. The
|
|
1201
|
+
// verdict is held instead, and resolved against what the statement says.
|
|
906
1202
|
const base = await verifyCertificate(crypto, certificate, publicKeys);
|
|
907
|
-
if (base !== "VALID") {
|
|
908
|
-
return base;
|
|
909
|
-
}
|
|
910
1203
|
if (status == null) {
|
|
911
|
-
return exports.VALID_REVOCATION_UNKNOWN;
|
|
1204
|
+
return resolveWithBase(base, exports.VALID_REVOCATION_UNKNOWN);
|
|
912
1205
|
}
|
|
913
1206
|
const keyId = status.key_id;
|
|
914
1207
|
const info = keyId ? publicKeys.get(keyId) : undefined;
|
|
915
1208
|
if (!info) {
|
|
916
1209
|
throw new errors_js_1.VerificationError(`status statement signed by unknown key: ${keyId}`);
|
|
917
1210
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1211
|
+
// The whole ADR-017 table, not just the deprecated boolean. `revoked` is
|
|
1212
|
+
// never set by a key loaded from /.well-known/burnledger-keys — that endpoint
|
|
1213
|
+
// publishes key_status — so this check read `false` for a key the issuer had
|
|
1214
|
+
// declared COMPROMISED, and whoever held that key could mint an ACTIVE
|
|
1215
|
+
// statement for a revoked certificate that every SDK and the browser bundle
|
|
1216
|
+
// rendered as a signed "not revoked". core.VerifyCertificateWithStatus has
|
|
1217
|
+
// always evaluated it.
|
|
1218
|
+
//
|
|
1219
|
+
// The anchor is the verifier's own clock, not a field inside the statement: a
|
|
1220
|
+
// statement must already be inside its own validity window to be served at
|
|
1221
|
+
// all, and `now` is a time this verifier observed rather than one the signing
|
|
1222
|
+
// key asserted about itself.
|
|
1223
|
+
const when = now ?? new Date();
|
|
1224
|
+
const at = formatTimestamp(when.toISOString());
|
|
1225
|
+
const statusKeyVerdict = requireUsableKey(info, parseRfc3339(at), keyId, "status statement key");
|
|
921
1226
|
const payload = buildCertificateStatusPayload(status);
|
|
922
1227
|
// decodeSignature, not hexToBytes: signatures arrive as hex, base64 or a byte
|
|
923
1228
|
// array depending on the producer, and every other signature on this path
|
|
@@ -945,17 +1250,42 @@ async function verifyCertificateWithStatus(crypto, certificate, publicKeys, stat
|
|
|
945
1250
|
}
|
|
946
1251
|
// Compared in the normalized form the payload signs, so the freshness check
|
|
947
1252
|
// cannot disagree with what was signed about the same two instants.
|
|
948
|
-
const at = formatTimestamp((now ?? new Date()).toISOString());
|
|
949
1253
|
const issued = formatTimestamp(status.statement_issued_at);
|
|
950
1254
|
const expires = formatTimestamp(status.statement_expires_at);
|
|
951
1255
|
if (at < issued || at >= expires) {
|
|
952
1256
|
// Stale is not a weaker answer, it is no answer — including for a REVOKED
|
|
953
1257
|
// statement, which must never decay into VALID.
|
|
954
|
-
return exports.VALID_REVOCATION_UNKNOWN;
|
|
1258
|
+
return resolveWithBase(base, exports.VALID_REVOCATION_UNKNOWN);
|
|
955
1259
|
}
|
|
956
1260
|
if (status.status === "REVOKED") {
|
|
957
|
-
|
|
1261
|
+
// Coded, because this is the only status-path rejection that is a fact
|
|
1262
|
+
// about the certificate rather than a failure to authenticate the
|
|
1263
|
+
// statement. A caller that cannot tell the two apart will render an
|
|
1264
|
+
// unsigned statement as a revocation.
|
|
1265
|
+
throw new errors_js_1.VerificationError("certificate has been revoked", errors_js_1.CERTIFICATE_REVOKED);
|
|
1266
|
+
}
|
|
1267
|
+
// A state this SDK does not recognize is NOT active. This tested only for
|
|
1268
|
+
// REVOKED and treated everything else as a pass, so a signed, fresh,
|
|
1269
|
+
// correctly-bound statement saying "SUSPENDED" — or "revoked" in the wrong
|
|
1270
|
+
// case — was reported as revocation CHECKED AND PASSED. Mirrors
|
|
1271
|
+
// core.ValidCertificateStatusValue.
|
|
1272
|
+
if (!KNOWN_STATUS_VALUES.has(status.status)) {
|
|
1273
|
+
return resolveWithBase(base, exports.VALID_REVOCATION_UNKNOWN);
|
|
958
1274
|
}
|
|
959
|
-
|
|
1275
|
+
// A soft verdict on the status key is the result, not a footnote — but it is
|
|
1276
|
+
// reported only once the statement has authenticated and been read, so it can
|
|
1277
|
+
// neither speak for an unverified statement nor suppress a revocation.
|
|
1278
|
+
return resolveWithBase(base, statusKeyVerdict);
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Picks what to report when the certificate's own key returned a soft verdict
|
|
1282
|
+
* and the status path also has something to say. A doubt about the key that
|
|
1283
|
+
* signed the certificate questions the whole document, so it outranks a doubt
|
|
1284
|
+
* about revocation or about the statement's own key; only one verdict string
|
|
1285
|
+
* comes back, and this is the one a reader most needs. Mirrors
|
|
1286
|
+
* core.resolveWithBase.
|
|
1287
|
+
*/
|
|
1288
|
+
function resolveWithBase(base, verdict) {
|
|
1289
|
+
return base !== "VALID" ? base : verdict;
|
|
960
1290
|
}
|
|
961
1291
|
//# sourceMappingURL=verify.js.map
|