import { describe, it, expect } from "vitest"; import { deriveKey, generateSalt } from "./argon2"; import { encrypt, decrypt } from "../background/aes"; describe("vault crypto", () => { it("round-trips plaintext through encrypt/decrypt", async () => { const salt = generateSalt(); const key = await deriveKey("correct-horse", salt); const { nonce, ciphertext, tag } = await encrypt(key, "hello peach"); const plain = await decrypt(key, nonce, ciphertext, tag); expect(plain).toBe("hello peach"); }); it("rejects tampered ciphertext", async () => { const salt = generateSalt(); const key = await deriveKey("correct-horse", salt); const { nonce, ciphertext, tag } = await encrypt(key, "secret data"); // Flip the first nibble to a guaranteed-different value. Appending a // fixed "ff" was a ~1/256 flake: when the random ciphertext already // ended in ff the "tamper" was a no-op and decrypt rightly succeeded. const tampered = (ciphertext[0] === "f" ? "0" : "f") + ciphertext.slice(1); await expect(decrypt(key, nonce, tampered, tag)).rejects.toThrow(); }); it("rejects wrong password", async () => { const salt = generateSalt(); const key1 = await deriveKey("right-password", salt); const { nonce, ciphertext, tag } = await encrypt(key1, "secret"); const key2 = await deriveKey("wrong-password", salt); await expect(decrypt(key2, nonce, ciphertext, tag)).rejects.toThrow(); }); it("generates fresh nonce on each encrypt", async () => { const salt = generateSalt(); const key = await deriveKey("test", salt); const e1 = await encrypt(key, "data"); const e2 = await encrypt(key, "data"); expect(e1.nonce).not.toBe(e2.nonce); }); });