@blamejs/core 0.17.11 → 0.17.13
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/CHANGELOG.md +4 -0
- package/NOTICE +1 -1
- package/index.js +2 -0
- package/lib/app-shutdown.js +23 -20
- package/lib/audit.js +7 -5
- package/lib/content-digest.js +2 -1
- package/lib/crypto.js +252 -8
- package/lib/daemon.js +268 -24
- package/lib/db-declare-view.js +2 -2
- package/lib/db.js +4 -1
- package/lib/dsr.js +1 -1
- package/lib/i18n-messageformat.js +3 -2
- package/lib/log-stream-otlp-grpc.js +5 -1
- package/lib/log-stream-otlp.js +5 -1
- package/lib/log-stream.js +5 -2
- package/lib/middleware/bot-guard.js +5 -9
- package/lib/outbox.js +1 -1
- package/lib/pid-probe.js +55 -0
- package/lib/pqc-agent.js +8 -1
- package/lib/redact.js +54 -0
- package/lib/safe-object.js +80 -0
- package/lib/self-update-standalone-verifier.js +74 -27
- package/lib/self-update.js +497 -87
- package/lib/ssrf-guard.js +52 -0
- package/lib/vendor/MANIFEST.json +11 -11
- package/lib/vendor/public-suffix-list.dat +7 -41
- package/lib/vendor/public-suffix-list.data.js +5269 -5285
- package/lib/watcher.js +89 -17
- package/lib/webhook-dispatcher.js +1 -1
- package/lib/ws-client.js +17 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/redact.js
CHANGED
|
@@ -1055,8 +1055,62 @@ function installForPosture(posture, primitives) {
|
|
|
1055
1055
|
});
|
|
1056
1056
|
}
|
|
1057
1057
|
|
|
1058
|
+
var TEXT_REDACT_MARKER = "[redacted]";
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* @primitive b.redact.redactText
|
|
1062
|
+
* @signature b.redact.redactText(str)
|
|
1063
|
+
* @since 0.17.13
|
|
1064
|
+
* @status stable
|
|
1065
|
+
* @related b.redact.redact
|
|
1066
|
+
*
|
|
1067
|
+
* Scrub credentials EMBEDDED in a free-text string in place, keeping the
|
|
1068
|
+
* surrounding prose — for log messages and other operator-facing text where a
|
|
1069
|
+
* secret may be interpolated mid-sentence. Unlike `redact` (structured,
|
|
1070
|
+
* whole-value, anchored), this uses word-boundary fragment replacement so
|
|
1071
|
+
* "login failed: <jwt> for bob" keeps everything but the jwt. Detects PEM
|
|
1072
|
+
* blocks, JWTs, AWS access keys, URL-userinfo passwords, bearer tokens,
|
|
1073
|
+
* `key=secret` assignments, SSN/EIN, and Luhn-valid PANs. The high-entropy
|
|
1074
|
+
* api-key-shape detector is deliberately excluded (on free text it eats
|
|
1075
|
+
* ordinary IDs / hashes / base64). Drop-safe: never throws (it runs on the
|
|
1076
|
+
* hot-path log-emit sink); on any error it returns a fully-masked marker rather
|
|
1077
|
+
* than the raw input. Every quantifier is length-capped (ReDoS backstop).
|
|
1078
|
+
*
|
|
1079
|
+
* @example
|
|
1080
|
+
* b.redact.redactText("token=AKIAIOSFODNN7EXAMPLE ok"); // → "token=[redacted] ok"
|
|
1081
|
+
*/
|
|
1082
|
+
function redactText(str) {
|
|
1083
|
+
if (typeof str !== "string" || str.length === 0) return str;
|
|
1084
|
+
try {
|
|
1085
|
+
return str
|
|
1086
|
+
// PEM / OpenSSH private-key blocks (multi-line).
|
|
1087
|
+
.replace(/-----BEGIN [A-Z0-9 ]{1,40}-----[\s\S]{0,8192}?-----END [A-Z0-9 ]{1,40}-----/g, TEXT_REDACT_MARKER)
|
|
1088
|
+
// JWT — three base64url segments, word-boundary anchored for embedding.
|
|
1089
|
+
.replace(/\beyJ[A-Za-z0-9_-]{1,4096}\.[A-Za-z0-9_-]{1,8192}\.[A-Za-z0-9_-]{1,4096}\b/g, TEXT_REDACT_MARKER)
|
|
1090
|
+
// AWS access-key IDs.
|
|
1091
|
+
.replace(/\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASCA)[A-Z0-9]{16}\b/g, TEXT_REDACT_MARKER)
|
|
1092
|
+
// Credentials in a URL userinfo: scheme://user:secret@host.
|
|
1093
|
+
.replace(/([a-z][a-z0-9+.-]{0,32}:\/\/[^\s:@/]{1,256}:)[^\s@/]{1,256}(@)/gi, "$1" + TEXT_REDACT_MARKER + "$2")
|
|
1094
|
+
// Bearer tokens.
|
|
1095
|
+
.replace(/\b([Bb]earer\s{1,4})[A-Za-z0-9._~+/-]{8,4096}=*/g, "$1" + TEXT_REDACT_MARKER)
|
|
1096
|
+
// key=secret / password: secret style assignments.
|
|
1097
|
+
.replace(/\b((?:api[_-]?key|access[_-]?token|secret|password|passwd|pwd|token)\s{0,4}[=:]\s{0,4})[^\s&;"']{6,4096}/gi, "$1" + TEXT_REDACT_MARKER)
|
|
1098
|
+
// SSN / EIN.
|
|
1099
|
+
.replace(/\b\d{3}-\d{2}-\d{4}\b/g, TEXT_REDACT_MARKER)
|
|
1100
|
+
.replace(/\b\d{2}-\d{7}\b/g, TEXT_REDACT_MARKER)
|
|
1101
|
+
// Credit-card / PAN — Luhn-validated so ordinary long digit runs survive.
|
|
1102
|
+
.replace(/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{1,7}\b/g, function (m) {
|
|
1103
|
+
var inner = m.replace(/[\s-]/g, "");
|
|
1104
|
+
return (inner.length >= 13 && inner.length <= 19 && _luhnCheck(inner)) ? TEXT_REDACT_MARKER : m;
|
|
1105
|
+
});
|
|
1106
|
+
} catch (_e) {
|
|
1107
|
+
return TEXT_REDACT_MARKER;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1058
1111
|
module.exports = {
|
|
1059
1112
|
redact: redact,
|
|
1113
|
+
redactText: redactText,
|
|
1060
1114
|
registerFieldRule: registerFieldRule,
|
|
1061
1115
|
registerValueDetector: registerValueDetector,
|
|
1062
1116
|
classifyDefaults: classifyDefaults,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module b.safeObject
|
|
6
|
+
* @nav Validation
|
|
7
|
+
* @title Safe object access
|
|
8
|
+
* @order 100
|
|
9
|
+
*
|
|
10
|
+
* @intro
|
|
11
|
+
* Prototype-pollution-safe own-property access. Interpolators, template
|
|
12
|
+
* engines, and structured readers that look up a key by name on an
|
|
13
|
+
* attacker-influenced object must treat an inherited / prototype-chain key
|
|
14
|
+
* (`__proto__`, `constructor`, `toString`) as ABSENT — never read it into
|
|
15
|
+
* rendered output. This composes that guard into one primitive so every
|
|
16
|
+
* consumer routes through the same hardened read instead of hand-rolling
|
|
17
|
+
* `Object.prototype.hasOwnProperty.call(o, k) ? o[k] : undefined` (which a
|
|
18
|
+
* `__proto__` accessor property can still defeat).
|
|
19
|
+
*
|
|
20
|
+
* @card
|
|
21
|
+
* Prototype-pollution-safe own-property get/set — the single guard every
|
|
22
|
+
* interpolator composes.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @primitive b.safeObject.ownProp
|
|
27
|
+
* @signature b.safeObject.ownProp(obj, key)
|
|
28
|
+
* @since 0.17.13
|
|
29
|
+
* @status stable
|
|
30
|
+
* @related b.safeObject.ownSet
|
|
31
|
+
*
|
|
32
|
+
* Returns the value of `obj`'s OWN property `key`, or `undefined` when `key`
|
|
33
|
+
* is not an own property (inherited / prototype-chain keys read as absent).
|
|
34
|
+
* Uses `Object.getOwnPropertyDescriptor` rather than `hasOwnProperty` + index
|
|
35
|
+
* read, so a `__proto__`-injected accessor property cannot run on read. A
|
|
36
|
+
* defined-but-`undefined` own value returns `undefined` (callers treat that as
|
|
37
|
+
* absent, matching the prior guarded reads).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* b.safeObject.ownProp({ a: 1 }, "a"); // → 1
|
|
41
|
+
* b.safeObject.ownProp({}, "toString"); // → undefined (inherited)
|
|
42
|
+
* b.safeObject.ownProp({}, "__proto__"); // → undefined
|
|
43
|
+
*/
|
|
44
|
+
function ownProp(obj, key) {
|
|
45
|
+
if (obj === null || obj === undefined) return undefined;
|
|
46
|
+
var d = Object.getOwnPropertyDescriptor(obj, key);
|
|
47
|
+
return d ? d.value : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @primitive b.safeObject.ownSet
|
|
52
|
+
* @signature b.safeObject.ownSet(obj, key, value)
|
|
53
|
+
* @since 0.17.13
|
|
54
|
+
* @status stable
|
|
55
|
+
* @related b.safeObject.ownProp
|
|
56
|
+
*
|
|
57
|
+
* Sets `obj[key] = value` as a plain own data property via
|
|
58
|
+
* `Object.defineProperty`, so a `__proto__` / `constructor` / accessor key on
|
|
59
|
+
* the prototype chain cannot intercept the write or pollute the prototype.
|
|
60
|
+
* Returns `obj`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* var o = {};
|
|
64
|
+
* b.safeObject.ownSet(o, "__proto__", { polluted: 1 });
|
|
65
|
+
* ({}).polluted; // → undefined (Object.prototype not touched)
|
|
66
|
+
*/
|
|
67
|
+
function ownSet(obj, key, value) {
|
|
68
|
+
Object.defineProperty(obj, key, {
|
|
69
|
+
value: value,
|
|
70
|
+
writable: true,
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
});
|
|
74
|
+
return obj;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
ownProp: ownProp,
|
|
79
|
+
ownSet: ownSet,
|
|
80
|
+
};
|
|
@@ -88,6 +88,17 @@
|
|
|
88
88
|
var nodeCrypto = require("node:crypto");
|
|
89
89
|
var nodeFs = require("node:fs");
|
|
90
90
|
|
|
91
|
+
// _svErr — throw shape for this module. A plain Error (zero-dep — no framework
|
|
92
|
+
// error class) carrying a machine-readable `.kind` so a framework caller
|
|
93
|
+
// (b.selfUpdate.verify) can map the failure to its own typed error class
|
|
94
|
+
// without parsing English message text. Operators consuming the module
|
|
95
|
+
// standalone still get the human-readable `.message` unchanged.
|
|
96
|
+
function _svErr(kind, message) {
|
|
97
|
+
var e = new Error(message);
|
|
98
|
+
e.kind = kind;
|
|
99
|
+
return e;
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
// _streamHashAndVerify — read the asset in 64 KiB chunks, feed each
|
|
92
103
|
// chunk into sha256, sha3-512, AND the signature verifier in parallel.
|
|
93
104
|
// Single pass over the file; no in-memory copy. node:crypto's
|
|
@@ -103,18 +114,18 @@ function _detectAlg(pubkeyPem) {
|
|
|
103
114
|
try {
|
|
104
115
|
key = nodeCrypto.createPublicKey(pubkeyPem);
|
|
105
116
|
} catch (e) {
|
|
106
|
-
throw
|
|
117
|
+
throw _svErr("bad-pubkey", "standalone-verifier: pubkey PEM did not parse: " +
|
|
107
118
|
(e && e.message ? e.message : String(e)));
|
|
108
119
|
}
|
|
109
120
|
var t = key.asymmetricKeyType;
|
|
110
121
|
if (t === "ec") {
|
|
111
122
|
var curve = key.asymmetricKeyDetails && key.asymmetricKeyDetails.namedCurve;
|
|
112
123
|
if (curve === "P-384" || curve === "secp384r1") return { alg: "ecdsa-p384", key: key };
|
|
113
|
-
throw
|
|
124
|
+
throw _svErr("unsupported-key", "standalone-verifier: unsupported EC curve '" + curve + "' (need P-384)");
|
|
114
125
|
}
|
|
115
126
|
if (t === "ed25519") return { alg: "ed25519", key: key };
|
|
116
127
|
if (t === "ml-dsa-87" || t === "ml-dsa") return { alg: "ml-dsa-87", key: key };
|
|
117
|
-
throw
|
|
128
|
+
throw _svErr("unsupported-key", "standalone-verifier: unrecognized pubkey type '" + t + "' " +
|
|
118
129
|
"(need ecdsa-p384, ed25519, or ml-dsa-87)");
|
|
119
130
|
}
|
|
120
131
|
|
|
@@ -163,7 +174,7 @@ function _looksLikeDerEcdsa(sig) {
|
|
|
163
174
|
|
|
164
175
|
/**
|
|
165
176
|
* @primitive b.selfUpdate.standaloneVerifier.verify
|
|
166
|
-
* @signature b.selfUpdate.standaloneVerifier.verify(assetPath, signaturePath, pubkeyPem)
|
|
177
|
+
* @signature b.selfUpdate.standaloneVerifier.verify(assetPath, signaturePath, pubkeyPem, opts?)
|
|
167
178
|
* @since 0.9.13
|
|
168
179
|
* @status stable
|
|
169
180
|
* @related b.selfUpdate.verify
|
|
@@ -175,12 +186,21 @@ function _looksLikeDerEcdsa(sig) {
|
|
|
175
186
|
* Streams the asset in 64 KiB chunks through SHA-256 + SHA-3-512 + the
|
|
176
187
|
* signature verifier in parallel — single allocation peak (one buffer
|
|
177
188
|
* sized to fstat(asset).size for Ed25519 / ML-DSA-87, ECDSA P-384 needs
|
|
178
|
-
* no buffer because createVerify is incremental).
|
|
189
|
+
* no buffer because createVerify is incremental). The signature commits
|
|
190
|
+
* to a SHA3-512 digest and the ECDSA encoding is dispatched by structure
|
|
191
|
+
* (DER SEQUENCE vs raw IEEE-P1363), so both encodings of a SHA3-512-signed
|
|
192
|
+
* P-384 sidecar verify.
|
|
179
193
|
*
|
|
180
|
-
* Returns `{ ok, sha3_512, sha256, alg }` on success;
|
|
181
|
-
* unrecognized pubkey shape, missing files, or signature
|
|
182
|
-
* `alg` is one of `"ecdsa-p384"`, `"ed25519"`, `"ml-dsa-87"`
|
|
183
|
-
* detected from the pubkey PEM).
|
|
194
|
+
* Returns `{ ok, sha3_512, sha256, alg, bytes, digests }` on success;
|
|
195
|
+
* throws on unrecognized pubkey shape, missing files, or signature
|
|
196
|
+
* mismatch. `alg` is one of `"ecdsa-p384"`, `"ed25519"`, `"ml-dsa-87"`
|
|
197
|
+
* (auto-detected from the pubkey PEM). `bytes` is the verified asset byte
|
|
198
|
+
* count; `digests` maps each requested `opts.extraDigests` name to its
|
|
199
|
+
* hex digest (computed in the same single pass).
|
|
200
|
+
*
|
|
201
|
+
* @opts
|
|
202
|
+
* maxAssetBytes: number, // asset-size ceiling (default 2 GiB); refuse a larger asset before hashing
|
|
203
|
+
* extraDigests: array, // additional node:crypto digest names to compute in the same stream
|
|
184
204
|
*
|
|
185
205
|
* @example
|
|
186
206
|
* var verifier = require("./standalone-verifier");
|
|
@@ -193,15 +213,26 @@ function _looksLikeDerEcdsa(sig) {
|
|
|
193
213
|
* if (!result.ok) process.exit(1);
|
|
194
214
|
* process.stdout.write("verified " + result.alg + " sha3-512=" + result.sha3_512 + "\n");
|
|
195
215
|
*/
|
|
196
|
-
function verify(assetPath, signaturePath, pubkeyPem) {
|
|
216
|
+
function verify(assetPath, signaturePath, pubkeyPem, opts) {
|
|
217
|
+
opts = opts || {};
|
|
218
|
+
// Asset-size ceiling — override the default via opts.maxAssetBytes (the
|
|
219
|
+
// framework caller passes its own maxBytes). A self-update bundle (SEA) is
|
|
220
|
+
// intentionally large, so the default ceiling is generous (2 GiB), but it
|
|
221
|
+
// stops a signaturePath/assetPath pointed at an unbounded file from OOM-ing.
|
|
222
|
+
var maxAssetBytes = (typeof opts.maxAssetBytes === "number" && isFinite(opts.maxAssetBytes) &&
|
|
223
|
+
opts.maxAssetBytes > 0)
|
|
224
|
+
? opts.maxAssetBytes
|
|
225
|
+
: (2 * 1024 * 1024 * 1024); // allow:raw-byte-literal — zero-dep module, 2 GiB asset ceiling
|
|
226
|
+
var extraDigests = Array.isArray(opts.extraDigests) ? opts.extraDigests : [];
|
|
227
|
+
|
|
197
228
|
if (typeof assetPath !== "string" || assetPath.length === 0) {
|
|
198
|
-
throw
|
|
229
|
+
throw _svErr("bad-input", "standalone-verifier.verify: assetPath must be a non-empty string");
|
|
199
230
|
}
|
|
200
231
|
if (typeof signaturePath !== "string" || signaturePath.length === 0) {
|
|
201
|
-
throw
|
|
232
|
+
throw _svErr("bad-input", "standalone-verifier.verify: signaturePath must be a non-empty string");
|
|
202
233
|
}
|
|
203
234
|
if (typeof pubkeyPem !== "string" || pubkeyPem.indexOf("-----BEGIN ") !== 0) {
|
|
204
|
-
throw
|
|
235
|
+
throw _svErr("bad-input", "standalone-verifier.verify: pubkeyPem must be a PEM-encoded public key string");
|
|
205
236
|
}
|
|
206
237
|
|
|
207
238
|
// Open both files BEFORE parsing the pubkey so we own stable fds
|
|
@@ -212,7 +243,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
212
243
|
try {
|
|
213
244
|
assetFd = nodeFs.openSync(assetPath, "r");
|
|
214
245
|
} catch (e) {
|
|
215
|
-
throw
|
|
246
|
+
throw _svErr("asset-not-found", "standalone-verifier.verify: asset not found at " + assetPath +
|
|
216
247
|
" — " + (e && e.message ? e.message : String(e)));
|
|
217
248
|
}
|
|
218
249
|
var sigFd;
|
|
@@ -220,7 +251,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
220
251
|
sigFd = nodeFs.openSync(signaturePath, "r");
|
|
221
252
|
} catch (e) {
|
|
222
253
|
nodeFs.closeSync(assetFd);
|
|
223
|
-
throw
|
|
254
|
+
throw _svErr("sig-not-found", "standalone-verifier.verify: signature not found at " + signaturePath +
|
|
224
255
|
" — " + (e && e.message ? e.message : String(e)));
|
|
225
256
|
}
|
|
226
257
|
var signature;
|
|
@@ -231,7 +262,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
231
262
|
// from OOM-ing if signaturePath is pointed at a giant file. Zero-dep by
|
|
232
263
|
// contract — inline literal, cannot import C.BYTES.
|
|
233
264
|
if (sigStat.size > 64 * 1024) { // allow:raw-byte-literal — zero-dep module
|
|
234
|
-
throw
|
|
265
|
+
throw _svErr("sig-too-large", "standalone-verifier.verify: signature file implausibly large (" +
|
|
235
266
|
sigStat.size + " bytes)");
|
|
236
267
|
}
|
|
237
268
|
signature = Buffer.allocUnsafe(sigStat.size);
|
|
@@ -241,7 +272,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
241
272
|
}
|
|
242
273
|
if (signature.length === 0) {
|
|
243
274
|
nodeFs.closeSync(assetFd);
|
|
244
|
-
throw
|
|
275
|
+
throw _svErr("sig-empty", "standalone-verifier.verify: signature file is empty");
|
|
245
276
|
}
|
|
246
277
|
|
|
247
278
|
var detected;
|
|
@@ -249,7 +280,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
249
280
|
detected = _detectAlg(pubkeyPem);
|
|
250
281
|
} catch (e) {
|
|
251
282
|
nodeFs.closeSync(assetFd);
|
|
252
|
-
throw e;
|
|
283
|
+
throw e; // carries _detectAlg's .kind (bad-pubkey / unsupported-key)
|
|
253
284
|
}
|
|
254
285
|
var alg = detected.alg;
|
|
255
286
|
var key = detected.key;
|
|
@@ -273,15 +304,24 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
273
304
|
// time.
|
|
274
305
|
var assetStat = nodeFs.fstatSync(assetFd);
|
|
275
306
|
// Bound the asset alloc before Buffer.allocUnsafe(assetStat.size): a self-update
|
|
276
|
-
// bundle (SEA) is intentionally large, so the ceiling is generous (2 GiB
|
|
277
|
-
// it stops a signaturePath/assetPath
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
307
|
+
// bundle (SEA) is intentionally large, so the ceiling is generous (2 GiB by
|
|
308
|
+
// default, or opts.maxAssetBytes), but it stops a signaturePath/assetPath
|
|
309
|
+
// pointed at an unbounded file from OOM-ing the verifier before any byte is
|
|
310
|
+
// hashed.
|
|
311
|
+
if (assetStat.size > maxAssetBytes) {
|
|
312
|
+
nodeFs.closeSync(assetFd);
|
|
313
|
+
throw _svErr("asset-too-large", "standalone-verifier.verify: asset implausibly large (" +
|
|
314
|
+
assetStat.size + " bytes) — exceeds the " + maxAssetBytes + "-byte asset ceiling");
|
|
282
315
|
}
|
|
283
316
|
var sha256 = nodeCrypto.createHash("sha256");
|
|
284
317
|
var sha3 = nodeCrypto.createHash("sha3-512");
|
|
318
|
+
// Additional operator-requested digests, computed in the same single pass so
|
|
319
|
+
// a framework caller can report an audit digest (sha-512 / shake256 / …)
|
|
320
|
+
// without a second read of the asset.
|
|
321
|
+
var extraHashers = [];
|
|
322
|
+
for (var xd = 0; xd < extraDigests.length; xd += 1) {
|
|
323
|
+
extraHashers.push({ name: extraDigests[xd], hash: nodeCrypto.createHash(extraDigests[xd]) });
|
|
324
|
+
}
|
|
285
325
|
var verifier = (alg === "ecdsa-p384") ? nodeCrypto.createVerify("sha3-512") : null;
|
|
286
326
|
var fullBuf = null;
|
|
287
327
|
var fullOff = 0;
|
|
@@ -305,6 +345,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
305
345
|
var slice = chunk.subarray(0, n);
|
|
306
346
|
sha256.update(slice);
|
|
307
347
|
sha3.update(slice);
|
|
348
|
+
for (var xh = 0; xh < extraHashers.length; xh += 1) extraHashers[xh].hash.update(slice);
|
|
308
349
|
if (verifier) verifier.update(slice);
|
|
309
350
|
if (fullBuf) {
|
|
310
351
|
slice.copy(fullBuf, fullOff);
|
|
@@ -319,7 +360,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
319
360
|
// beyond what the clamp let through. Both cases mean the hashers
|
|
320
361
|
// and verifier saw a different byte set than the on-disk file.
|
|
321
362
|
if (fullOff !== assetStat.size) {
|
|
322
|
-
throw
|
|
363
|
+
throw _svErr("size-race", "standalone-verifier.verify: asset '" + assetPath +
|
|
323
364
|
"' changed size during read (expected " + assetStat.size +
|
|
324
365
|
" bytes per fstat, read " + fullOff +
|
|
325
366
|
" bytes) — refusing to return a hash that may not match the on-disk file");
|
|
@@ -327,6 +368,10 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
327
368
|
|
|
328
369
|
var sha256Hex = sha256.digest("hex");
|
|
329
370
|
var sha3Hex = sha3.digest("hex");
|
|
371
|
+
var digests = {};
|
|
372
|
+
for (var dh = 0; dh < extraHashers.length; dh += 1) {
|
|
373
|
+
digests[extraHashers[dh].name] = extraHashers[dh].hash.digest("hex");
|
|
374
|
+
}
|
|
330
375
|
|
|
331
376
|
var ok = false;
|
|
332
377
|
if (alg === "ecdsa-p384") {
|
|
@@ -351,7 +396,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
351
396
|
} else {
|
|
352
397
|
// assetFd was already closed by the read loop's `finally`; this is a
|
|
353
398
|
// pure fail-closed refusal, same as the `if (!ok)` path below.
|
|
354
|
-
throw
|
|
399
|
+
throw _svErr("bad-sig-encoding", "standalone-verifier.verify: ecdsa-p384 signature is neither a " +
|
|
355
400
|
"well-formed DER SEQUENCE nor a raw " + (coordLen * 2) +
|
|
356
401
|
"-byte IEEE-P1363 pair (length " + signature.length +
|
|
357
402
|
") — refusing to guess the encoding");
|
|
@@ -366,7 +411,7 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
366
411
|
}
|
|
367
412
|
|
|
368
413
|
if (!ok) {
|
|
369
|
-
throw
|
|
414
|
+
throw _svErr("verify-failed", "standalone-verifier.verify: " + alg + " signature INVALID for " +
|
|
370
415
|
assetPath + " (sha3-512=" + sha3Hex.slice(0, 16) + "...). " + // 16-char hex prefix for forensic display, not bytes
|
|
371
416
|
"Either the asset was tampered with after signing, the signature " +
|
|
372
417
|
"doesn't match this asset, or the pubkey doesn't match the signing key.");
|
|
@@ -377,6 +422,8 @@ function verify(assetPath, signaturePath, pubkeyPem) {
|
|
|
377
422
|
sha3_512: sha3Hex,
|
|
378
423
|
sha256: sha256Hex,
|
|
379
424
|
alg: alg,
|
|
425
|
+
bytes: fullOff,
|
|
426
|
+
digests: digests,
|
|
380
427
|
};
|
|
381
428
|
}
|
|
382
429
|
|