@trops/dash-core 0.1.601 → 0.1.602
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/electron/index.js +152 -1
- package/dist/electron/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -13232,6 +13232,51 @@ async function verifyBufferSignature({ bytes, signature, publicKey }) {
|
|
|
13232
13232
|
return ed.verifyAsync(sigBytes, digest, pubBytes);
|
|
13233
13233
|
}
|
|
13234
13234
|
|
|
13235
|
+
// --- Manifest body signature (Phase 5D, audit P1 #24) ---
|
|
13236
|
+
//
|
|
13237
|
+
// Mirror of dash-registry/src/lib/crypto.ts#signManifestBody /
|
|
13238
|
+
// verifyManifestSignature. The registry's `/download` response carries
|
|
13239
|
+
// `{manifest_signature, manifest_signature_keyid}` over the rest of
|
|
13240
|
+
// the body. The installer verifies before consuming `downloadUrl` so
|
|
13241
|
+
// a MITM that swaps any field is caught client-side.
|
|
13242
|
+
//
|
|
13243
|
+
// Both sides must canonicalize identically: strip the two signature
|
|
13244
|
+
// fields, sort remaining keys recursively, no whitespace.
|
|
13245
|
+
|
|
13246
|
+
const CURRENT_MANIFEST_SIGNATURE_KEYID = "v1";
|
|
13247
|
+
|
|
13248
|
+
function canonicalizeManifestBody(body) {
|
|
13249
|
+
if (!body || typeof body !== "object") {
|
|
13250
|
+
throw new Error("manifest body must be an object");
|
|
13251
|
+
}
|
|
13252
|
+
const stripped = { ...body };
|
|
13253
|
+
delete stripped.manifest_signature;
|
|
13254
|
+
delete stripped.manifest_signature_keyid;
|
|
13255
|
+
return canonicalJsonStringify(stripped);
|
|
13256
|
+
}
|
|
13257
|
+
|
|
13258
|
+
/**
|
|
13259
|
+
* Verify a manifest signature against the registry root public key.
|
|
13260
|
+
* Returns true/false; never throws on a bad signature — caller
|
|
13261
|
+
* decides what to do based on mode (off/warn/strict).
|
|
13262
|
+
*
|
|
13263
|
+
* @param {object} args
|
|
13264
|
+
* @param {object} args.body — full response body INCLUDING signature fields (ignored on canonicalize)
|
|
13265
|
+
* @param {string} args.signature — base64 Ed25519 signature
|
|
13266
|
+
* @param {string} args.registryRootPublicKey — base64
|
|
13267
|
+
* @returns {Promise<boolean>}
|
|
13268
|
+
*/
|
|
13269
|
+
async function verifyManifestSignature({
|
|
13270
|
+
body,
|
|
13271
|
+
signature,
|
|
13272
|
+
registryRootPublicKey,
|
|
13273
|
+
}) {
|
|
13274
|
+
const message = new TextEncoder().encode(canonicalizeManifestBody(body));
|
|
13275
|
+
const sigBytes = base64ToBytes(signature);
|
|
13276
|
+
const rootPubBytes = base64ToBytes(registryRootPublicKey);
|
|
13277
|
+
return ed.verifyAsync(sigBytes, message, rootPubBytes);
|
|
13278
|
+
}
|
|
13279
|
+
|
|
13235
13280
|
var publisherCrypto = {
|
|
13236
13281
|
canonicalJsonStringify,
|
|
13237
13282
|
computeFingerprint: computeFingerprint$1,
|
|
@@ -13239,8 +13284,16 @@ var publisherCrypto = {
|
|
|
13239
13284
|
verifyPublisherCert: verifyPublisherCert$1,
|
|
13240
13285
|
signBuffer,
|
|
13241
13286
|
verifyBufferSignature,
|
|
13287
|
+
verifyManifestSignature,
|
|
13288
|
+
CURRENT_MANIFEST_SIGNATURE_KEYID,
|
|
13242
13289
|
// internal helpers — exported for tests
|
|
13243
|
-
_internal: {
|
|
13290
|
+
_internal: {
|
|
13291
|
+
bytesToBase64,
|
|
13292
|
+
base64ToBytes,
|
|
13293
|
+
bytesToHex,
|
|
13294
|
+
sha256,
|
|
13295
|
+
canonicalizeManifestBody,
|
|
13296
|
+
},
|
|
13244
13297
|
};
|
|
13245
13298
|
|
|
13246
13299
|
/**
|
|
@@ -13320,6 +13373,8 @@ function requireVerifyRegistryInstall () {
|
|
|
13320
13373
|
const {
|
|
13321
13374
|
verifyPublisherCert,
|
|
13322
13375
|
verifyBufferSignature,
|
|
13376
|
+
verifyManifestSignature,
|
|
13377
|
+
CURRENT_MANIFEST_SIGNATURE_KEYID,
|
|
13323
13378
|
} = publisherCrypto;
|
|
13324
13379
|
const { getRegistryRootPublicKey } = registryRootPublicKey;
|
|
13325
13380
|
|
|
@@ -13486,8 +13541,83 @@ function requireVerifyRegistryInstall () {
|
|
|
13486
13541
|
return { verified: true, reason: null, mode, warnings };
|
|
13487
13542
|
}
|
|
13488
13543
|
|
|
13544
|
+
/**
|
|
13545
|
+
* Phase 5D (audit P1 #24): verify the signature on the /download
|
|
13546
|
+
* response BODY before the caller consumes downloadUrl / zipSignature /
|
|
13547
|
+
* publisherCert from it. Closes the MITM vector where a swapped
|
|
13548
|
+
* response could redirect the installer to a different ZIP signed by
|
|
13549
|
+
* an attacker-controlled (but still legitimate) publisher cert.
|
|
13550
|
+
*
|
|
13551
|
+
* Same off/warn/strict modes as the zip+cert verifier above — the
|
|
13552
|
+
* env var `DASH_REGISTRY_VERIFY_SIGNED_INSTALL` controls both.
|
|
13553
|
+
*
|
|
13554
|
+
* The signature is computed server-side over the canonical JSON of
|
|
13555
|
+
* the body minus the two signature fields themselves. Verification
|
|
13556
|
+
* here re-canonicalizes the same way and checks against the bundled
|
|
13557
|
+
* registry root public key.
|
|
13558
|
+
*
|
|
13559
|
+
* @param {object} args
|
|
13560
|
+
* @param {object} args.responseBody — the full parsed JSON body from /download
|
|
13561
|
+
* @returns {Promise<{verified, reason, mode, warnings}>}
|
|
13562
|
+
*/
|
|
13563
|
+
async function verifyDownloadManifest({ responseBody }) {
|
|
13564
|
+
const mode = readMode();
|
|
13565
|
+
const warnings = [];
|
|
13566
|
+
|
|
13567
|
+
if (mode === "off") {
|
|
13568
|
+
return { verified: true, reason: null, mode, warnings };
|
|
13569
|
+
}
|
|
13570
|
+
|
|
13571
|
+
if (!responseBody || typeof responseBody !== "object") {
|
|
13572
|
+
return applyMode(mode, "MANIFEST_BODY_MISSING", warnings);
|
|
13573
|
+
}
|
|
13574
|
+
|
|
13575
|
+
const signature = responseBody.manifest_signature;
|
|
13576
|
+
const keyid = responseBody.manifest_signature_keyid;
|
|
13577
|
+
|
|
13578
|
+
// Unsigned case: legacy registry deployments that haven't been
|
|
13579
|
+
// upgraded yet won't include these fields. In `warn` mode we log
|
|
13580
|
+
// + proceed so the rollout doesn't break existing installs; in
|
|
13581
|
+
// `strict` mode we refuse.
|
|
13582
|
+
if (!signature || !keyid) {
|
|
13583
|
+
return applyMode(mode, "UNSIGNED_MANIFEST", warnings);
|
|
13584
|
+
}
|
|
13585
|
+
|
|
13586
|
+
// Today only one root key is bundled. When rotation lands, this
|
|
13587
|
+
// becomes a lookup over an array of trusted public keys keyed by
|
|
13588
|
+
// keyid; unknown keyid → fail closed.
|
|
13589
|
+
if (keyid !== CURRENT_MANIFEST_SIGNATURE_KEYID) {
|
|
13590
|
+
return applyMode(
|
|
13591
|
+
mode,
|
|
13592
|
+
`MANIFEST_SIGNATURE_UNKNOWN_KEYID: ${keyid}`,
|
|
13593
|
+
warnings,
|
|
13594
|
+
);
|
|
13595
|
+
}
|
|
13596
|
+
|
|
13597
|
+
let ok = false;
|
|
13598
|
+
try {
|
|
13599
|
+
ok = await verifyManifestSignature({
|
|
13600
|
+
body: responseBody,
|
|
13601
|
+
signature,
|
|
13602
|
+
registryRootPublicKey: getRegistryRootPublicKey(),
|
|
13603
|
+
});
|
|
13604
|
+
} catch (err) {
|
|
13605
|
+
return applyMode(
|
|
13606
|
+
mode,
|
|
13607
|
+
`MANIFEST_SIGNATURE_ERROR: ${err.message || err}`,
|
|
13608
|
+
warnings,
|
|
13609
|
+
);
|
|
13610
|
+
}
|
|
13611
|
+
if (!ok) {
|
|
13612
|
+
return applyMode(mode, "MANIFEST_SIGNATURE_INVALID", warnings);
|
|
13613
|
+
}
|
|
13614
|
+
|
|
13615
|
+
return { verified: true, reason: null, mode, warnings };
|
|
13616
|
+
}
|
|
13617
|
+
|
|
13489
13618
|
verifyRegistryInstall = {
|
|
13490
13619
|
verifyDownloadedPackage,
|
|
13620
|
+
verifyDownloadManifest,
|
|
13491
13621
|
_readMode: readMode,
|
|
13492
13622
|
};
|
|
13493
13623
|
return verifyRegistryInstall;
|
|
@@ -14842,6 +14972,27 @@ var schedulerController_1 = schedulerController$2;
|
|
|
14842
14972
|
if (jsonData.error) {
|
|
14843
14973
|
throw new Error(`Download failed: ${jsonData.error}`);
|
|
14844
14974
|
}
|
|
14975
|
+
|
|
14976
|
+
// Phase 5D (P1 #24): verify the manifest signature BEFORE
|
|
14977
|
+
// trusting any field in this response. A MITM that swapped
|
|
14978
|
+
// downloadUrl or any signing-metadata field gets caught here
|
|
14979
|
+
// because the signature won't verify against the bundled
|
|
14980
|
+
// registry root key. Mode is shared with the existing
|
|
14981
|
+
// zip+cert verifier — DASH_REGISTRY_VERIFY_SIGNED_INSTALL.
|
|
14982
|
+
const {
|
|
14983
|
+
verifyDownloadManifest,
|
|
14984
|
+
} = requireVerifyRegistryInstall();
|
|
14985
|
+
const manifestVerify = await verifyDownloadManifest({
|
|
14986
|
+
responseBody: jsonData,
|
|
14987
|
+
});
|
|
14988
|
+
if (!manifestVerify.verified && manifestVerify.mode === "warn") {
|
|
14989
|
+
console.warn(
|
|
14990
|
+
`[widgetRegistry] Installing with unverified download manifest ` +
|
|
14991
|
+
`for "${widgetName}" — reason: ${manifestVerify.reason}. Set ` +
|
|
14992
|
+
`DASH_REGISTRY_VERIFY_SIGNED_INSTALL=strict to refuse.`,
|
|
14993
|
+
);
|
|
14994
|
+
}
|
|
14995
|
+
|
|
14845
14996
|
if (jsonData.downloadUrl) {
|
|
14846
14997
|
const zipResponse = await fetch(jsonData.downloadUrl);
|
|
14847
14998
|
if (!zipResponse.ok) {
|