66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { describe, expect, test, vi } from "vitest";
|
|
|
|
const { argon2SyncSpy } = vi.hoisted(() => ({ argon2SyncSpy: vi.fn() }));
|
|
vi.mock("node:crypto", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("node:crypto")>();
|
|
argon2SyncSpy.mockImplementation(actual.argon2Sync);
|
|
return { ...actual, argon2Sync: argon2SyncSpy };
|
|
});
|
|
|
|
import { verifyPassword } from "../src/auth/password.js";
|
|
|
|
interface Argon2Vector {
|
|
password: string;
|
|
phc: string;
|
|
}
|
|
|
|
const vectors = JSON.parse(readFileSync(
|
|
resolve(import.meta.dirname, "fixtures/argon2id-vectors.json"),
|
|
"utf8",
|
|
)) as Argon2Vector[];
|
|
|
|
describe("local Argon2id password verification", () => {
|
|
test("accepts every committed Go-generated vector", () => {
|
|
expect(vectors.length).toBeGreaterThan(0);
|
|
for (const vector of vectors) {
|
|
expect(verifyPassword(vector.password, vector.phc)).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("rejects a one-byte password change", () => {
|
|
for (const vector of vectors) {
|
|
expect(verifyPassword(`${vector.password}!`, vector.phc)).toBe(false);
|
|
}
|
|
});
|
|
|
|
test("rejects ill-formed Unicode instead of authenticating as U+FFFD", () => {
|
|
const replacementPassword = "correct horse battery stap\uFFFD";
|
|
const loneSurrogatePassword = "correct horse battery stap\uD800";
|
|
const replacementPasswordHash = "$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw$+tAXzgaQVnNaonNvgevyG6UKlaKcwyRMi1mESNk0BvQ";
|
|
|
|
expect(verifyPassword(replacementPassword, replacementPasswordHash)).toBe(true);
|
|
expect(verifyPassword(loneSurrogatePassword, replacementPasswordHash)).toBe(false);
|
|
});
|
|
|
|
test("rejects malformed and oversized PHC parameters before Argon2 allocation", () => {
|
|
const password = vectors[0].password;
|
|
const digest = vectors[0].phc.split("$")[5];
|
|
const salt = vectors[0].phc.split("$")[4];
|
|
const cases = [
|
|
`$argon2id$v=19$m=262145,t=1,p=1$${salt}$${digest}`,
|
|
`$argon2id$v=19$m=65536,t=11,p=1$${salt}$${digest}`,
|
|
`$argon2id$v=19$m=65536,t=3,p=5$${salt}$${digest}`,
|
|
`$argon2id$v=19$m=65536,t=3,p=1$${salt}$${"A".repeat(88)}`,
|
|
`$argon2id$v=19$m=65536,t=3,p=1$${salt}=$${digest}`,
|
|
];
|
|
|
|
for (const phc of cases) {
|
|
argon2SyncSpy.mockClear();
|
|
expect(verifyPassword(password, phc)).toBe(false);
|
|
expect(argon2SyncSpy).not.toHaveBeenCalled();
|
|
}
|
|
});
|
|
});
|