import { describe, expect, it } from "vitest"; import { utf8ToBytes } from "../../src/crypto/encoding"; import { decryptOtpBackup, encryptOtpBackup, } from "../../src/otp/encrypted-backup"; import type { OtpProfile } from "../../src/otp/profile"; const profile: OtpProfile = { kind: "totp", secret: utf8ToBytes("12345678901234567890"), issuer: "Example", account: "alice", algorithm: "SHA-256", digits: 8, period: 45, epoch: 12, counter: 0n, extensions: new Map([["image", "none"]]), }; describe("encrypted OTP backups", () => { it("round-trips profiles through authenticated encryption", async () => { const encrypted = await encryptOtpBackup( [profile], "correct horse battery staple", ); expect(encrypted).not.toContain("JBSWY"); const restored = await decryptOtpBackup( encrypted, "correct horse battery staple", ); expect(restored[0]).toMatchObject({ issuer: "Example", account: "alice", epoch: 12, counter: 0n, }); expect([...restored[0]!.secret]).toEqual([...profile.secret]); }); it("rejects a wrong password and tampering", async () => { const encrypted = await encryptOtpBackup( [profile], "correct horse battery staple", ); await expect( decryptOtpBackup(encrypted, "different secure password"), ).rejects.toThrow(/authentication failed/iu); const parsed = JSON.parse(encrypted) as { ciphertext: string }; parsed.ciphertext = `${parsed.ciphertext.slice(0, -2)}AA`; await expect( decryptOtpBackup(JSON.stringify(parsed), "correct horse battery staple"), ).rejects.toThrow(/authentication failed/iu); }); });