Files
zemion 82bb01b13f
Verify / verify (push) Canceled after 0s
Release Crypto Tools 0.2.0
2026-09-02 10:40:23 +02:00

90 lines
3.0 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
MAX_TOTAL_PBKDF2_ITERATIONS,
decryptEncryptedPkcs8,
inspectEncryptedPkcs8,
reservePbkdf2Work,
} from "../../src/crypto/pbes2";
import { inspectCryptoInput } from "../../src/crypto/inspection";
import {
encryptedPbes2Payload,
encryptedPkcs8,
encryptedPkcs8Pem as pem,
} from "../fixtures/pbes2";
describe("PBES2 encrypted PKCS #8", () => {
it("enforces one aggregate PBKDF2 work budget", () => {
const budget = { iterations: 0 };
reservePbkdf2Work(budget, 10_000_000, "first object");
reservePbkdf2Work(budget, 10_000_000, "second object");
expect(budget.iterations).toBe(MAX_TOTAL_PBKDF2_ITERATIONS);
expect(() => reservePbkdf2Work(budget, 1, "third object")).toThrow(
/aggregate PBKDF2 safety budget/u,
);
});
it("inspects supported parameters and decrypts only with the password", async () => {
const bytes = await encryptedPkcs8("correct horse");
expect(inspectEncryptedPkcs8(bytes)).toMatchObject({
scheme: "PBES2",
kdf: "PBKDF2",
prf: "SHA-256",
iterations: 12_000,
cipher: "AES-CBC",
keyLength: 256,
});
await expect(decryptEncryptedPkcs8(bytes, "wrong battery")).rejects.toThrow(
/decryption failed/u,
);
const decrypted = await decryptEncryptedPkcs8(bytes, "correct horse");
expect(decrypted.key).toMatchObject({ algorithm: "EC", curve: "P-256" });
decrypted.bytes.fill(0);
});
it("integrates password-gated decryption into PEM inspection", async () => {
const bytes = await encryptedPkcs8("local secret");
const withoutPassword = await inspectCryptoInput(pem(bytes));
expect(withoutPassword.items[0]?.facts["Decryption status"]).toBe(
"Not attempted",
);
const withPassword = await inspectCryptoInput(pem(bytes), new Date(), {
password: "local secret",
});
expect(withPassword.items[0]?.facts["Private-key algorithm"]).toContain(
"EC",
);
});
it("wipes decrypted private-key bytes after PEM and DER inspection", async () => {
const bytes = await encryptedPkcs8("ephemeral secret");
const fill = vi.spyOn(Uint8Array.prototype, "fill");
try {
await inspectCryptoInput(pem(bytes), new Date(), {
password: "ephemeral secret",
});
await inspectCryptoInput(bytes, new Date(), {
password: "ephemeral secret",
});
expect(fill.mock.calls.filter(([value]) => value === 0)).toHaveLength(2);
} finally {
fill.mockRestore();
}
});
it("wipes decrypted bytes when the plaintext is not valid PKCS #8", async () => {
const bytes = await encryptedPbes2Payload(
"wrong structure",
new TextEncoder().encode("valid AES-CBC padding; invalid PKCS #8"),
);
const fill = vi.spyOn(Uint8Array.prototype, "fill");
try {
await expect(
decryptEncryptedPkcs8(bytes, "wrong structure"),
).rejects.toThrow(/PKCS #8|DER|SEQUENCE/u);
expect(fill.mock.calls.filter(([value]) => value === 0)).toHaveLength(1);
} finally {
fill.mockRestore();
}
});
});