diff --git a/README.md b/README.md index e4a7310..f2d4b03 100644 --- a/README.md +++ b/README.md @@ -1647,11 +1647,10 @@ The development dependencies ship nowhere: ### Crypto policy -Hashing, key derivation, signing and encryption go through `ethers` or -`libsodium-wrappers-sumo`. No primitive is implemented in this repository. A new -recovery phrase draws its random bytes from the browser's -`crypto.getRandomValues()`. The build tooling, `build.js` and `script/lib/`, -hashes files with Node's `crypto`; none of it ships in the extension. +In the extension, hashing, key derivation, signing and encryption go through +`ethers` or `libsodium-wrappers-sumo`. A new recovery phrase takes its random +bytes from `ethers`. `build.js` and `script/lib/package.js` also hash files with +`ethers`. No primitive is implemented in this repository. ### Content Security Policy diff --git a/build.js b/build.js index 0cf68e4..7c2194e 100644 --- a/build.js +++ b/build.js @@ -1,8 +1,8 @@ const fs = require("fs"); const path = require("path"); -const crypto = require("crypto"); const { execSync } = require("child_process"); const esbuild = require("esbuild"); +const { sha256 } = require("ethers"); const { resolveVersion } = require("./script/lib/version"); const { BACKGROUND_ENTRY_PREFIX, @@ -314,10 +314,8 @@ function copyIcons(distDir) { } function sha256File(absPath) { - return crypto - .createHash("sha256") - .update(fs.readFileSync(absPath)) - .digest("hex"); + // ethers prefixes the hex with 0x; the receipt holds bare hex. + return sha256(fs.readFileSync(absPath)).slice(2); } // Write the receipt for the files this build emitted. Deliberately records no diff --git a/script/lib/package.js b/script/lib/package.js index f745968..a0d132d 100644 --- a/script/lib/package.js +++ b/script/lib/package.js @@ -25,9 +25,9 @@ "use strict"; -const crypto = require("crypto"); const fs = require("fs"); const path = require("path"); +const ethers = require("ethers"); const { readZip, writeZip } = require("./zip"); const { resolveVersion } = require("./version"); @@ -60,8 +60,9 @@ function fail(message) { throw new Error(message); } +// Lowercase hex without the 0x prefix ethers adds, as sha256sum prints it. 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 @@ -291,4 +292,9 @@ if (require.main === module) { } } -module.exports = { checkSelfContained, htmlReferences, manifestReferences }; +module.exports = { + checkSelfContained, + htmlReferences, + manifestReferences, + sha256, +}; diff --git a/src/shared/wallet.js b/src/shared/wallet.js index 674e10e..07a5d90 100644 --- a/src/shared/wallet.js +++ b/src/shared/wallet.js @@ -1,7 +1,7 @@ // Wallet operations: mnemonic generation, HD derivation, signing. // 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"); function generateMnemonic() { @@ -10,10 +10,7 @@ function generateMnemonic() { // settings toggle drives, which would let a user of a release build turn // the hardcoded, publicly known test phrase back on for real wallets. if (DEBUG) return DEBUG_MNEMONIC; - const m = Mnemonic.fromEntropy( - globalThis.crypto.getRandomValues(new Uint8Array(16)), - ); - return m.phrase; + return Mnemonic.fromEntropy(randomBytes(16)).phrase; } // Every extended key (xprv or xpub) entering the app goes through this. diff --git a/tests/packaging.test.js b/tests/packaging.test.js index b60a64a..fa7f100 100644 --- a/tests/packaging.test.js +++ b/tests/packaging.test.js @@ -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 // 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 // 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 -// artifact is made of. +// artifact is made of. One more case checks the digest format in +// release/SHA256SUMS. const { checkSelfContained, htmlReferences, manifestReferences, + sha256, } = require("../script/lib/package"); 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", () => { const files = [ { name: "manifest.json", data: Buffer.from('{"a":1}') },