Build hashes and recovery-phrase entropy come from ethers (closes #758) #767

Open
clawbot wants to merge 1 commits from issue-758-crypto-through-ethers into next
pull from: issue-758-crypto-through-ethers
5 changed files with 31 additions and 20 deletions

No files matched your search

+4 -5
View File
@@ -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
+3 -5
View File
@@ -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
+9 -3
View File
@@ -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,
};
+2 -5
View File
@@ -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
View File
@@ -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}') },