feat: an "Allow weak passwords" setting lets a new wallet have a short password (closes #674)
Settings gains a Passwords section with an "Allow weak passwords" checkbox. It is off in a new profile, and only a stored true turns it on. While it is off, AddWallet refuses a password shorter than 12 characters, as before. While it is on, AddWallet accepts any password that is not empty. Key derivation is unchanged. The README describes the setting under Settings and AddWallet. Its Key Decisions section now states the Argon2id parameters and why they are light. The user guide lists the setting under Settings. Model: opus-5-5
This commit is contained in:
@@ -1787,18 +1787,20 @@ view would leave a wallet one click from deletion.
|
|||||||
without it, the lost-password route on DeleteWallet is the first they
|
without it, the lost-password route on DeleteWallet is the first they
|
||||||
would hear of it. The hint line reserves its height, so switching tabs
|
would hear of it. The hint line reserves its height, so switching tabs
|
||||||
cannot move the password fields under the pointer.
|
cannot move the password fields under the pointer.
|
||||||
|
- The password needs at least 12 characters. With "Allow weak passwords"
|
||||||
|
ticked in Settings, one character is enough.
|
||||||
- "Import" button, with the error line beside it so that it adds no height
|
- "Import" button, with the error line beside it so that it adds no height
|
||||||
to a screen whose button already starts near the bottom of the popup
|
to a screen whose button already starts near the bottom of the popup
|
||||||
- **Transitions**:
|
- **Transitions**:
|
||||||
- "Import" with a valid entry and a matching password of at least 12
|
- "Import" with a valid entry and a matching password that is long enough →
|
||||||
characters → creates the wallet, clears the navigation stack, and →
|
creates the wallet, clears the navigation stack, and → **Home**. When no
|
||||||
**Home**. When no address is active yet, as on the first wallet, the new
|
address is active yet, as on the first wallet, the new wallet's first
|
||||||
wallet's first address becomes the active one. The phrase and xprv modes
|
address becomes the active one. The phrase and xprv modes then scan for
|
||||||
then scan for further used addresses and report the count as a flash
|
further used addresses and report the count as a flash message. When the
|
||||||
message. When the scan finds two or more used addresses, they replace the
|
scan finds two or more used addresses, they replace the wallet's list,
|
||||||
wallet's list, which then holds the first address only if it was used. If
|
which then holds the first address only if it was used. If the first
|
||||||
the first address was active and is not in the new list, the new list's
|
address was active and is not in the new list, the new list's first
|
||||||
first address becomes the active one and `AUTISTMASK_ACTIVE_CHANGED` is
|
address becomes the active one and `AUTISTMASK_ACTIVE_CHANGED` is
|
||||||
broadcast.
|
broadcast.
|
||||||
- "Import" with a missing, short or mismatched password → full-sentence
|
- "Import" with a missing, short or mismatched password → full-sentence
|
||||||
error on the error line, no screen change
|
error on the error line, no screen change
|
||||||
@@ -2235,6 +2237,10 @@ view would leave a wallet one click from deletion.
|
|||||||
- Wallets: one row per wallet with its name (tap to rename inline), a
|
- Wallets: one row per wallet with its name (tap to rename inline), a
|
||||||
`[recovery phrase]` button on HD wallets only, and an `[x]` delete button,
|
`[recovery phrase]` button on HD wallets only, and an `[x]` delete button,
|
||||||
plus a "+ Add wallet" button
|
plus a "+ Add wallet" button
|
||||||
|
- Passwords:
|
||||||
|
- "Allow weak passwords" checkbox, unticked until the user ticks it
|
||||||
|
- A sentence saying the setting lets the user choose a password shorter
|
||||||
|
than 12 characters on AddWallet
|
||||||
- Tracked Tokens: one row per token tracked on the active network with an
|
- Tracked Tokens: one row per token tracked on the active network with an
|
||||||
`[x]` remove button, plus a "+ Add token" button
|
`[x]` remove button, plus a "+ Add token" button
|
||||||
- Display: "Show tracked tokens with zero balance" checkbox, "UTC
|
- Display: "Show tracked tokens with zero balance" checkbox, "UTC
|
||||||
@@ -3061,6 +3067,11 @@ logged.
|
|||||||
- The user's password is run through Argon2id (`crypto_pwhash`) to derive a
|
- The user's password is run through Argon2id (`crypto_pwhash`) to derive a
|
||||||
256-bit encryption key. Argon2id is memory-hard, making GPU/ASIC brute
|
256-bit encryption key. Argon2id is memory-hard, making GPU/ASIC brute
|
||||||
force attacks expensive.
|
force attacks expensive.
|
||||||
|
- Each use of the password runs one Argon2id derivation at libsodium's
|
||||||
|
interactive setting: `OPSLIMIT_INTERACTIVE` and `MEMLIMIT_INTERACTIVE`, 2
|
||||||
|
passes over 64 MiB. Every signature asks for the password, so a light
|
||||||
|
setting keeps signing quick. The password's length, not the derivation's
|
||||||
|
cost, is what protects the stored secret.
|
||||||
- The derived key encrypts the secret material using XSalsa20-Poly1305
|
- The derived key encrypts the secret material using XSalsa20-Poly1305
|
||||||
(`crypto_secretbox`), which provides authenticated encryption (the
|
(`crypto_secretbox`), which provides authenticated encryption (the
|
||||||
ciphertext cannot be tampered with without detection).
|
ciphertext cannot be tampered with without detection).
|
||||||
|
|||||||
@@ -425,6 +425,8 @@ see everything unfiltered.
|
|||||||
Click the gear icon on the home screen to access settings:
|
Click the gear icon on the home screen to access settings:
|
||||||
|
|
||||||
- **Wallets**: Your wallets, and "+ Add wallet".
|
- **Wallets**: Your wallets, and "+ Add wallet".
|
||||||
|
- **Passwords**: Tick "Allow weak passwords" to choose a password shorter than
|
||||||
|
12 characters when you add a wallet.
|
||||||
- **Tracked Tokens**: The ERC-20 tokens tracked across all addresses, and "+ Add
|
- **Tracked Tokens**: The ERC-20 tokens tracked across all addresses, and "+ Add
|
||||||
token".
|
token".
|
||||||
- **Display**: Toggle whether tracked tokens with zero balance are shown, switch
|
- **Display**: Toggle whether tracked tokens with zero balance are shown, switch
|
||||||
|
|||||||
@@ -980,6 +980,23 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-well p-3 mx-1 mb-3">
|
||||||
|
<h3 class="font-bold mb-1">Passwords</h3>
|
||||||
|
<label
|
||||||
|
class="text-xs flex items-center gap-1 cursor-pointer mb-1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="settings-allow-weak-passwords"
|
||||||
|
/>
|
||||||
|
Allow weak passwords
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-muted">
|
||||||
|
Lets you choose a password shorter than 12 characters
|
||||||
|
when you add a wallet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bg-well p-3 mx-1 mb-3">
|
<div class="bg-well p-3 mx-1 mb-3">
|
||||||
<h3 class="font-bold mb-1">Tracked Tokens</h3>
|
<h3 class="font-bold mb-1">Tracked Tokens</h3>
|
||||||
<p class="text-xs text-muted mb-2">
|
<p class="text-xs text-muted mb-2">
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ function validatePassword() {
|
|||||||
showError("add-wallet-password-error", "Please choose a password.");
|
showError("add-wallet-password-error", "Please choose a password.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (pw.length < 12) {
|
// "Allow weak passwords" in Settings lifts the 12-character minimum.
|
||||||
|
if (!state.allowWeakPasswords && pw.length < 12) {
|
||||||
showError(
|
showError(
|
||||||
"add-wallet-password-error",
|
"add-wallet-password-error",
|
||||||
"Password must be at least 12 characters.",
|
"Password must be at least 12 characters.",
|
||||||
|
|||||||
@@ -499,6 +499,12 @@ function init(_ctx) {
|
|||||||
await saveState();
|
await saveState();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("settings-allow-weak-passwords").checked = state.allowWeakPasswords;
|
||||||
|
$("settings-allow-weak-passwords").addEventListener("change", async () => {
|
||||||
|
state.allowWeakPasswords = $("settings-allow-weak-passwords").checked;
|
||||||
|
await saveState();
|
||||||
|
});
|
||||||
|
|
||||||
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
|
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
|
||||||
|
|
||||||
$("btn-settings-add-token").addEventListener(
|
$("btn-settings-add-token").addEventListener(
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ const DEFAULT_STATE = {
|
|||||||
// else.
|
// else.
|
||||||
useBlockscout: false,
|
useBlockscout: false,
|
||||||
checkRecipientsOnEtherscan: false,
|
checkRecipientsOnEtherscan: false,
|
||||||
|
// "Allow weak passwords" in Settings. While it is off, AddWallet refuses a
|
||||||
|
// password shorter than 12 characters.
|
||||||
|
allowWeakPasswords: false,
|
||||||
fraudContracts: [],
|
fraudContracts: [],
|
||||||
tokenHolderCache: {},
|
tokenHolderCache: {},
|
||||||
theme: "system",
|
theme: "system",
|
||||||
@@ -418,6 +421,9 @@ function normalizePersisted(saved) {
|
|||||||
// service.
|
// service.
|
||||||
out.useBlockscout = saved.useBlockscout === true;
|
out.useBlockscout = saved.useBlockscout === true;
|
||||||
out.checkRecipientsOnEtherscan = saved.checkRecipientsOnEtherscan === true;
|
out.checkRecipientsOnEtherscan = saved.checkRecipientsOnEtherscan === true;
|
||||||
|
// Anything but true is off: the setting lets AddWallet accept a short
|
||||||
|
// password.
|
||||||
|
out.allowWeakPasswords = saved.allowWeakPasswords === true;
|
||||||
// A list of contract addresses, floored the same way: send.js builds its
|
// A list of contract addresses, floored the same way: send.js builds its
|
||||||
// fraud set as `(state.fraudContracts || []).map((a) => a.toLowerCase())`
|
// fraud set as `(state.fraudContracts || []).map((a) => a.toLowerCase())`
|
||||||
// and filterTransactions() maps the same list through normalizeAddress(),
|
// and filterTransactions() maps the same list through normalizeAddress(),
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// "Allow weak passwords" in Settings is off until the user ticks it. While it
|
||||||
|
// is off, AddWallet refuses a password shorter than 12 characters. While it is
|
||||||
|
// on, AddWallet accepts any password that is not empty
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/674). These boot the real popup,
|
||||||
|
// set the checkbox in Settings as the user does, and then add a wallet.
|
||||||
|
|
||||||
|
jest.mock("../src/shared/vault", () => ({
|
||||||
|
decryptWithPassword: jest.fn(),
|
||||||
|
encryptWithPassword: jest.fn(async () => ({
|
||||||
|
salt: "salt",
|
||||||
|
nonce: "nonce",
|
||||||
|
ciphertext: "ciphertext",
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const {
|
||||||
|
bootPopup,
|
||||||
|
cleanupPopup,
|
||||||
|
unversionedValidProfile,
|
||||||
|
} = require("./support/popupBoot");
|
||||||
|
|
||||||
|
const CHECKBOX = "settings-allow-weak-passwords";
|
||||||
|
const ERROR = "add-wallet-password-error";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupPopup();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Boots a popup that holds one wallet and opens Settings, where the checkbox
|
||||||
|
// starts unticked. Ticks it when `allow` is true. Then opens AddWallet and
|
||||||
|
// presses the die for a recovery phrase.
|
||||||
|
async function openAddWallet(allow) {
|
||||||
|
const page = await bootPopup(unversionedValidProfile());
|
||||||
|
await page.click("btn-settings");
|
||||||
|
const checkbox = page.node(CHECKBOX);
|
||||||
|
expect(checkbox.checked).toBe(false);
|
||||||
|
if (allow) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
for (const fn of checkbox.listeners.change) await fn();
|
||||||
|
expect(page.storage.read("autistmask").allowWeakPasswords).toBe(true);
|
||||||
|
}
|
||||||
|
await page.click("btn-main-add-wallet");
|
||||||
|
await page.click("btn-generate-phrase");
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types `password` into both password fields and presses Import.
|
||||||
|
async function importWith(page, password) {
|
||||||
|
page.node("add-wallet-password").value = password;
|
||||||
|
page.node("add-wallet-password-confirm").value = password;
|
||||||
|
await page.click("btn-add-wallet-confirm");
|
||||||
|
}
|
||||||
|
|
||||||
|
function storedWalletCount(page) {
|
||||||
|
return page.storage.read("autistmask").wallets.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Allow weak passwords", () => {
|
||||||
|
test("unticked: a password shorter than 12 characters is refused", async () => {
|
||||||
|
const page = await openAddWallet(false);
|
||||||
|
await importWith(page, "short");
|
||||||
|
expect(page.text(ERROR)).toBe(
|
||||||
|
"Password must be at least 12 characters.",
|
||||||
|
);
|
||||||
|
expect(storedWalletCount(page)).toBe(1);
|
||||||
|
expect(page.visibleViews()).toEqual(["add-wallet"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ticked: a password shorter than 12 characters is accepted", async () => {
|
||||||
|
const page = await openAddWallet(true);
|
||||||
|
await importWith(page, "short");
|
||||||
|
expect(page.text(ERROR)).toBe("");
|
||||||
|
expect(storedWalletCount(page)).toBe(2);
|
||||||
|
expect(page.visibleViews()).toEqual(["main"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([false, true])(
|
||||||
|
"an empty password is refused (ticked: %s)",
|
||||||
|
async (allow) => {
|
||||||
|
const page = await openAddWallet(allow);
|
||||||
|
await importWith(page, "");
|
||||||
|
expect(page.text(ERROR)).toBe("Please choose a password.");
|
||||||
|
expect(storedWalletCount(page)).toBe(1);
|
||||||
|
expect(page.visibleViews()).toEqual(["add-wallet"]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -629,8 +629,15 @@ const CONTRACT = [
|
|||||||
// the falsy answer is also the DEFAULT_STATE default.
|
// the falsy answer is also the DEFAULT_STATE default.
|
||||||
falsy: [0],
|
falsy: [0],
|
||||||
})),
|
})),
|
||||||
// Each lets a request reach another service, so anything but true is off.
|
// Anything but true is off for these three settings:
|
||||||
...["useBlockscout", "checkRecipientsOnEtherscan"].map((field) => ({
|
// - useBlockscout and checkRecipientsOnEtherscan let a request reach
|
||||||
|
// another service.
|
||||||
|
// - allowWeakPasswords lets AddWallet accept a short password.
|
||||||
|
...[
|
||||||
|
"useBlockscout",
|
||||||
|
"checkRecipientsOnEtherscan",
|
||||||
|
"allowWeakPasswords",
|
||||||
|
].map((field) => ({
|
||||||
field,
|
field,
|
||||||
kind: KIND.SCALAR,
|
kind: KIND.SCALAR,
|
||||||
hostile: [42, "notabool", { a: 1 }],
|
hostile: [42, "notabool", { a: 1 }],
|
||||||
|
|||||||
Reference in New Issue
Block a user