Build hashes and recovery-phrase entropy come from ethers (closes #758) #767
No files matched your search
@@ -1647,11 +1647,10 @@ The development dependencies ship nowhere:
|
|||||||
|
|
||||||
### Crypto policy
|
### Crypto policy
|
||||||
|
|
||||||
Hashing, key derivation, signing and encryption go through `ethers` or
|
In the extension, hashing, key derivation, signing and encryption go through
|
||||||
`libsodium-wrappers-sumo`. No primitive is implemented in this repository. A new
|
`ethers` or `libsodium-wrappers-sumo`. A new recovery phrase takes its random
|
||||||
recovery phrase draws its random bytes from the browser's
|
bytes from `ethers`. `build.js` and `script/lib/package.js` also hash files with
|
||||||
`crypto.getRandomValues()`. The build tooling, `build.js` and `script/lib/`,
|
`ethers`. No primitive is implemented in this repository.
|
||||||
hashes files with Node's `crypto`; none of it ships in the extension.
|
|
||||||
|
|
||||||
### Content Security Policy
|
### Content Security Policy
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const crypto = require("crypto");
|
|
||||||
const { execSync } = require("child_process");
|
const { execSync } = require("child_process");
|
||||||
const esbuild = require("esbuild");
|
const esbuild = require("esbuild");
|
||||||
|
const { sha256 } = require("ethers");
|
||||||
const { resolveVersion } = require("./script/lib/version");
|
const { resolveVersion } = require("./script/lib/version");
|
||||||
const {
|
const {
|
||||||
BACKGROUND_ENTRY_PREFIX,
|
BACKGROUND_ENTRY_PREFIX,
|
||||||
@@ -314,10 +314,8 @@ function copyIcons(distDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sha256File(absPath) {
|
function sha256File(absPath) {
|
||||||
return crypto
|
// ethers prefixes the hex with 0x; the receipt holds bare hex.
|
||||||
.createHash("sha256")
|
return sha256(fs.readFileSync(absPath)).slice(2);
|
||||||
.update(fs.readFileSync(absPath))
|
|
||||||
.digest("hex");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the receipt for the files this build emitted. Deliberately records no
|
// Write the receipt for the files this build emitted. Deliberately records no
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const crypto = require("crypto");
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const ethers = require("ethers");
|
||||||
|
|
||||||
const { readZip, writeZip } = require("./zip");
|
const { readZip, writeZip } = require("./zip");
|
||||||
const { resolveVersion } = require("./version");
|
const { resolveVersion } = require("./version");
|
||||||
@@ -60,8 +60,9 @@ function fail(message) {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lowercase hex without the 0x prefix ethers adds, as sha256sum prints it.
|
||||||
function sha256(buf) {
|
function sha256(buf) {
|
||||||
return crypto.createHash("sha256").update(buf).digest("hex");
|
return ethers.sha256(buf).slice(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every regular file under dir, as archive-root-relative forward-slashed
|
// Every regular file under dir, as archive-root-relative forward-slashed
|
||||||
@@ -291,4 +292,9 @@ if (require.main === module) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { checkSelfContained, htmlReferences, manifestReferences };
|
module.exports = {
|
||||||
|
checkSelfContained,
|
||||||
|
htmlReferences,
|
||||||
|
manifestReferences,
|
||||||
|
sha256,
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Wallet operations: mnemonic generation, HD derivation, signing.
|
// Wallet operations: mnemonic generation, HD derivation, signing.
|
||||||
// All crypto delegated to ethers.js.
|
// All crypto delegated to ethers.js.
|
||||||
|
|
||||||
const { Mnemonic, HDNodeWallet, Wallet } = require("ethers");
|
const { Mnemonic, HDNodeWallet, Wallet, randomBytes } = require("ethers");
|
||||||
const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants");
|
const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants");
|
||||||
|
|
||||||
function generateMnemonic() {
|
function generateMnemonic() {
|
||||||
@@ -10,10 +10,7 @@ function generateMnemonic() {
|
|||||||
// settings toggle drives, which would let a user of a release build turn
|
// settings toggle drives, which would let a user of a release build turn
|
||||||
// the hardcoded, publicly known test phrase back on for real wallets.
|
// the hardcoded, publicly known test phrase back on for real wallets.
|
||||||
if (DEBUG) return DEBUG_MNEMONIC;
|
if (DEBUG) return DEBUG_MNEMONIC;
|
||||||
const m = Mnemonic.fromEntropy(
|
return Mnemonic.fromEntropy(randomBytes(16)).phrase;
|
||||||
globalThis.crypto.getRandomValues(new Uint8Array(16)),
|
|
||||||
);
|
|
||||||
return m.phrase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every extended key (xprv or xpub) entering the app goes through this.
|
// Every extended key (xprv or xpub) entering the app goes through this.
|
||||||
|
|||||||
+13
-2
@@ -1,4 +1,5 @@
|
|||||||
// The release archives: the zip container, and the self-containment rule.
|
// The release archives: the zip container, the self-containment rule, and the
|
||||||
|
// digest format in release/SHA256SUMS.
|
||||||
//
|
//
|
||||||
// script/package produces one archive per browser and script/lib/package.js
|
// script/package produces one archive per browser and script/lib/package.js
|
||||||
// decides what goes in it. The trap that rule exists for is real and specific:
|
// decides what goes in it. The trap that rule exists for is real and specific:
|
||||||
@@ -11,12 +12,14 @@
|
|||||||
// So the packager resolves every reference in the manifest and in every HTML
|
// So the packager resolves every reference in the manifest and in every HTML
|
||||||
// document, and fails on any that leaves the extension root. These are the
|
// document, and fails on any that leaves the extension root. These are the
|
||||||
// cases for that, plus the archive format itself — new code, and the thing the
|
// cases for that, plus the archive format itself — new code, and the thing the
|
||||||
// artifact is made of.
|
// artifact is made of. One more case checks the digest format in
|
||||||
|
// release/SHA256SUMS.
|
||||||
|
|
||||||
const {
|
const {
|
||||||
checkSelfContained,
|
checkSelfContained,
|
||||||
htmlReferences,
|
htmlReferences,
|
||||||
manifestReferences,
|
manifestReferences,
|
||||||
|
sha256,
|
||||||
} = require("../script/lib/package");
|
} = require("../script/lib/package");
|
||||||
const { readZip, writeZip } = require("../script/lib/zip");
|
const { readZip, writeZip } = require("../script/lib/zip");
|
||||||
|
|
||||||
@@ -142,6 +145,14 @@ describe("archive self-containment", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// release/SHA256SUMS is checked with `sha256sum -c`, which needs the digest
|
||||||
|
// as bare lowercase hex. The expected value is the FIPS 180-2 digest of "abc".
|
||||||
|
test("sha256 prints the digest as sha256sum does", () => {
|
||||||
|
expect(sha256(Buffer.from("abc"))).toBe(
|
||||||
|
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
describe("the zip container", () => {
|
describe("the zip container", () => {
|
||||||
const files = [
|
const files = [
|
||||||
{ name: "manifest.json", data: Buffer.from('{"a":1}') },
|
{ name: "manifest.json", data: Buffer.from('{"a":1}') },
|
||||||
|
|||||||
Reference in new issue
Block a user