import { describe, expect, it } from "vitest"; import { utf8ToBytes } from "../../src/crypto/encoding"; import { exportEncryptedPskc, importEncryptedPskc } from "../../src/otp/pskc"; import type { OtpProfile } from "../../src/otp/profile"; const profiles: OtpProfile[] = [ { kind: "totp", account: "alice@example.test", issuer: "Example & Sons", secret: utf8ToBytes("12345678901234567890"), algorithm: "SHA-256", digits: 8, period: 45, epoch: 10, counter: 0n, extensions: new Map(), }, { kind: "hotp", account: "hardware token", issuer: "Example", secret: utf8ToBytes("abcdefghijklmnopqrst"), algorithm: "SHA-1", digits: 6, period: 30, counter: 1042n, extensions: new Map(), }, ]; describe("encrypted PSKC", () => { it("round-trips authenticated password-encrypted collections", async () => { const exported = await exportEncryptedPskc( profiles, "correct horse battery staple", { iterations: 100_000 }, ); expect(exported).toContain("PBKDF2-params"); expect(exported).toContain( 'Algorithm="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2"', ); expect(exported).toContain("'); expect(exported).not.toContain("12345678901234567890"); const result = await importEncryptedPskc( exported, "correct horse battery staple", ); expect(result.profiles).toHaveLength(2); expect(result.profiles[0]).toMatchObject({ kind: "totp", account: "alice@example.test", issuer: "Example & Sons", algorithm: "SHA-256", digits: 8, period: 45, epoch: 10, }); expect(result.profiles[1]).toMatchObject({ kind: "hotp", counter: 1042n, }); expect([...result.profiles[0]!.secret]).toEqual([...profiles[0]!.secret]); }); it("rejects wrong passwords and unauthenticated changes", async () => { const exported = await exportEncryptedPskc( profiles.slice(0, 1), "correct horse battery staple", { iterations: 100_000 }, ); await expect( importEncryptedPskc(exported, "incorrect password"), ).rejects.toThrow(/incorrect|damaged/u); const tampered = exported.replace( /([^<])/u, (_match, first: string) => `${first === "A" ? "B" : "A"}`, ); await expect( importEncryptedPskc(tampered, "correct horse battery staple"), ).rejects.toThrow(/ValueMAC/u); }); it("rejects XML entity declarations", async () => { await expect( importEncryptedPskc( ']>', "password", ), ).rejects.toThrow(/entity/u); }); });