57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
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);
|
|
});
|
|
});
|