init
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
// tests/crypto-store.test.js
|
||||
// 测试 crypto-store.js:主口令派生、AES-GCM 加解密、设置/解锁/锁定
|
||||
// 运行:node --test tests/crypto-store.test.js
|
||||
|
||||
const { describe, it, beforeEach } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
function createMemoryStorage() {
|
||||
const local = {};
|
||||
const session = {};
|
||||
const makeArea = (store) => ({
|
||||
async get(keys) {
|
||||
const result = {};
|
||||
if (Array.isArray(keys)) keys.forEach((k) => { if (k in store) result[k] = store[k]; });
|
||||
else if (typeof keys === "string") { if (keys in store) result[keys] = store[keys]; }
|
||||
else Object.assign(result, store);
|
||||
return result;
|
||||
},
|
||||
async set(items) { Object.assign(store, items); },
|
||||
async remove(keys) {
|
||||
if (Array.isArray(keys)) keys.forEach((k) => delete store[k]);
|
||||
else delete store[keys];
|
||||
},
|
||||
});
|
||||
return { local: makeArea(local), session: makeArea(session), localStore: local, sessionStore: session };
|
||||
}
|
||||
|
||||
function loadCryptoStore(storage) {
|
||||
global.chrome = { storage };
|
||||
delete require.cache[require.resolve("../crypto-store.js")];
|
||||
return require("../crypto-store.js");
|
||||
}
|
||||
|
||||
describe("crypto-store - 基础加解密", () => {
|
||||
it("encryptJson + decryptJson 往返一致", async () => {
|
||||
const storage = createMemoryStorage();
|
||||
const C = loadCryptoStore(storage);
|
||||
const salt = C.randomBytes(16);
|
||||
const key = await C.deriveKey("mypassword", salt, C.PBKDF2_ITERATIONS);
|
||||
const data = [{ id: "e1", name: "个人", configs: [{ domain: "a.com" }] }];
|
||||
const cipher = await C.encryptJson(data, key);
|
||||
const decrypted = await C.decryptJson(cipher, key);
|
||||
assert.deepEqual(decrypted, data);
|
||||
});
|
||||
|
||||
it("不同口令派生的密钥无法解密对方数据", async () => {
|
||||
const storage = createMemoryStorage();
|
||||
const C = loadCryptoStore(storage);
|
||||
const salt = C.randomBytes(16);
|
||||
const key1 = await C.deriveKey("password1", salt, C.PBKDF2_ITERATIONS);
|
||||
const key2 = await C.deriveKey("password2", salt, C.PBKDF2_ITERATIONS);
|
||||
const cipher = await C.encryptJson({ secret: "hello" }, key1);
|
||||
await assert.rejects(() => C.decryptJson(cipher, key2));
|
||||
});
|
||||
|
||||
it("b64encode / b64decode 往返一致", () => {
|
||||
const C = loadCryptoStore(createMemoryStorage());
|
||||
const buf = new Uint8Array([0, 1, 2, 253, 254, 255]);
|
||||
const encoded = C.b64encode(buf);
|
||||
const decoded = C.b64decode(encoded);
|
||||
assert.deepEqual(Array.from(decoded), Array.from(buf));
|
||||
});
|
||||
});
|
||||
|
||||
describe("crypto-store - 设置/解锁/锁定", () => {
|
||||
let C, storage;
|
||||
|
||||
beforeEach(() => {
|
||||
storage = createMemoryStorage();
|
||||
C = loadCryptoStore(storage);
|
||||
});
|
||||
|
||||
it("hasVault 初始为 false", async () => {
|
||||
assert.equal(await C.hasVault(), false);
|
||||
assert.equal(await C.isUnlocked(), false);
|
||||
});
|
||||
|
||||
it("setupVault 加密并解锁", async () => {
|
||||
const envs = [{ id: "e1", name: "个人", configs: [] }];
|
||||
const key = await C.setupVault("mypass123", envs);
|
||||
assert.ok(key);
|
||||
assert.equal(await C.hasVault(), true);
|
||||
assert.equal(await C.isUnlocked(), true);
|
||||
// local 中是密文,不是明文
|
||||
assert.ok(storage.localStore.vaultMeta);
|
||||
assert.ok(storage.localStore.encryptedVault);
|
||||
assert.equal(storage.localStore.environments, undefined);
|
||||
// session 中是明文
|
||||
assert.ok(storage.sessionStore.environments);
|
||||
});
|
||||
|
||||
it("setupVault 口令不能为空", async () => {
|
||||
await assert.rejects(() => C.setupVault("", []), /口令不能为空/);
|
||||
});
|
||||
|
||||
it("unlock 正确口令可解密", async () => {
|
||||
await C.setupVault("correct horse", [{ id: "e1", name: "x", configs: [] }]);
|
||||
// 先锁定
|
||||
await C.lock();
|
||||
assert.equal(await C.isUnlocked(), false);
|
||||
|
||||
const { environments, key } = await C.unlock("correct horse");
|
||||
assert.ok(key);
|
||||
assert.equal(environments.length, 1);
|
||||
assert.equal(environments[0].name, "x");
|
||||
assert.equal(await C.isUnlocked(), true);
|
||||
});
|
||||
|
||||
it("unlock 错误口令抛错", async () => {
|
||||
await C.setupVault("correct", []);
|
||||
await C.lock();
|
||||
await assert.rejects(() => C.unlock("wrong"), /口令错误/);
|
||||
// 错误口令后仍未解锁
|
||||
assert.equal(await C.isUnlocked(), false);
|
||||
});
|
||||
|
||||
it("lock 清除 session 明文但保留 local 密文", async () => {
|
||||
await C.setupVault("pw", [{ id: "e1", name: "x", configs: [] }]);
|
||||
assert.ok(storage.sessionStore.environments);
|
||||
await C.lock();
|
||||
assert.equal(storage.sessionStore.environments, undefined);
|
||||
// 密文还在
|
||||
assert.ok(storage.localStore.encryptedVault);
|
||||
});
|
||||
|
||||
it("unlock 未设置口令时抛错", async () => {
|
||||
await assert.rejects(() => C.unlock("anything"), /尚未设置口令/);
|
||||
});
|
||||
|
||||
it("lock 后可再次 unlock", async () => {
|
||||
await C.setupVault("pw", [{ id: "e1", name: "x", configs: [] }]);
|
||||
await C.lock();
|
||||
await C.unlock("pw");
|
||||
assert.equal(await C.isUnlocked(), true);
|
||||
const envs = (await C.unlock("pw")).environments;
|
||||
assert.equal(envs[0].name, "x");
|
||||
});
|
||||
});
|
||||
|
||||
describe("crypto-store - persistEncrypted", () => {
|
||||
it("用密钥重新加密 environments 写回 local", async () => {
|
||||
const storage = createMemoryStorage();
|
||||
const C = loadCryptoStore(storage);
|
||||
const key = await C.setupVault("pw", [{ id: "e1", name: "x", configs: [] }]);
|
||||
// 修改环境
|
||||
const newEnvs = [{ id: "e1", name: "x", configs: [{ domain: "a.com" }] }];
|
||||
await C.persistEncrypted(newEnvs, key);
|
||||
// 锁定后重新解锁,验证持久化生效
|
||||
await C.lock();
|
||||
const { environments } = await C.unlock("pw");
|
||||
assert.equal(environments[0].configs.length, 1);
|
||||
assert.equal(environments[0].configs[0].domain, "a.com");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user