67 lines
2.8 KiB
JavaScript
67 lines
2.8 KiB
JavaScript
|
|
// node --test TheseusNavigator/dev/vault-pin.test.cjs
|
||
|
|
// lib/vault-pin.cjs with a fake OS keystore and no TPM, so a wrong PIN here
|
||
|
|
// never costs a real TPM dictionary-attack strike.
|
||
|
|
"use strict";
|
||
|
|
const test = require("node:test");
|
||
|
|
const assert = require("node:assert/strict");
|
||
|
|
const fs = require("node:fs");
|
||
|
|
const os = require("node:os");
|
||
|
|
const path = require("node:path");
|
||
|
|
const { createVaultPin, MAX_FAILS, PIN_MIN, PIN_MAX } = require("../lib/vault-pin.cjs");
|
||
|
|
|
||
|
|
const fakeSafe = {
|
||
|
|
isEncryptionAvailable: () => true,
|
||
|
|
getSelectedStorageBackend: () => "gnome_libsecret",
|
||
|
|
encryptString: (s) => Buffer.from("SEALED:" + s),
|
||
|
|
decryptString: (b) => { const s = b.toString(); if (!s.startsWith("SEALED:")) throw new Error("bad seal"); return s.slice(7); },
|
||
|
|
};
|
||
|
|
const noTpm = { supported: () => false };
|
||
|
|
const make = () => {
|
||
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "vpin-"));
|
||
|
|
const file = path.join(dir, "vault-pin.json");
|
||
|
|
return Object.assign(createVaultPin({ file, safeStorage: fakeSafe, tpm: noTpm }), { file });
|
||
|
|
};
|
||
|
|
|
||
|
|
test("lengths 6 to 8 are accepted, others refused", async () => {
|
||
|
|
assert.equal(PIN_MIN, 6); assert.equal(PIN_MAX, 8);
|
||
|
|
const p = make();
|
||
|
|
for (const bad of ["12345", "123456789", "12a456", ""]) await assert.rejects(p.set(bad, "pw"), /6 to 8 digits/);
|
||
|
|
for (const good of ["123456", "1234567", "12345678"]) {
|
||
|
|
await p.set(good, "master");
|
||
|
|
assert.equal(p.status().length, good.length);
|
||
|
|
assert.equal(await p.open(good), "master");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test("a wrong-length PIN costs no strike", async () => {
|
||
|
|
const p = make();
|
||
|
|
await p.set("12345678", "master");
|
||
|
|
for (let i = 0; i < MAX_FAILS + 2; i++) {
|
||
|
|
await assert.rejects(p.open("123456"), (e) => e.code === "wrong-length" && e.length === 8);
|
||
|
|
}
|
||
|
|
assert.equal(p.status().fails, 0);
|
||
|
|
assert.equal(await p.open("12345678"), "master");
|
||
|
|
});
|
||
|
|
|
||
|
|
test("wrong PINs of the right length still lock after MAX_FAILS", async () => {
|
||
|
|
const p = make();
|
||
|
|
await p.set("1234567", "master");
|
||
|
|
for (let i = 1; i < MAX_FAILS; i++) await assert.rejects(p.open("7654321"), (e) => e.code === "wrong-pin" && e.remaining === MAX_FAILS - i);
|
||
|
|
await assert.rejects(p.open("7654321"), (e) => e.code === "locked");
|
||
|
|
await assert.rejects(p.open("1234567"), (e) => e.code === "locked");
|
||
|
|
});
|
||
|
|
|
||
|
|
test("records from 6-only builds (no len) read as 6 digits", async () => {
|
||
|
|
const p = make();
|
||
|
|
await p.set("123456", "master");
|
||
|
|
// Strip len, as an older build would have written it.
|
||
|
|
const rec = JSON.parse(fs.readFileSync(p.file, "utf8"));
|
||
|
|
const blob = JSON.parse(fakeSafe.decryptString(Buffer.from(rec.data, "base64")));
|
||
|
|
delete blob.len;
|
||
|
|
rec.data = fakeSafe.encryptString(JSON.stringify(blob)).toString("base64");
|
||
|
|
fs.writeFileSync(p.file, JSON.stringify(rec));
|
||
|
|
assert.equal(p.status().length, 6);
|
||
|
|
assert.equal(await p.open("123456"), "master");
|
||
|
|
await assert.rejects(p.open("1234567"), (e) => e.code === "wrong-length");
|
||
|
|
});
|