项目文件夹

文件
wehub-resource-sync 70bf21e064
Prerelease / build (push) Has been skipped
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:11 +08:00

178 行
5.3 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import { describe, it } from "vitest";
import {
decryptString,
encryptString,
generateKey,
parseEncryptedEnvelope,
} from "../../src/credential-store/crypto";
describe("crypto", () => {
describe("generateKey", () => {
it("returns 32 bytes", ({ expect }) => {
expect(generateKey().length).toBe(32);
});
it("returns different bytes on every call", ({ expect }) => {
const a = generateKey();
const b = generateKey();
// Probability of collision is 1 in 2^256.
expect(Buffer.from(a).equals(Buffer.from(b))).toBe(false);
});
});
describe("encryptString → decryptString round-trip", () => {
it("recovers the original plaintext", ({ expect }) => {
const key = generateKey();
const plaintext = "hello world";
const envelope = encryptString(plaintext, key);
expect(decryptString(envelope, key)).toBe(plaintext);
});
it("recovers a multi-line UTF-8 TOML-like payload", ({ expect }) => {
const key = generateKey();
const plaintext = [
'oauth_token = "abc"',
'refresh_token = "def"',
'scopes = ["account:read", "user:read"]',
'expiration_time = "2099-01-01T00:00:00.000Z"',
].join("\n");
const envelope = encryptString(plaintext, key);
expect(decryptString(envelope, key)).toBe(plaintext);
});
it("works for an empty plaintext", ({ expect }) => {
const key = generateKey();
const envelope = encryptString("", key);
expect(decryptString(envelope, key)).toBe("");
});
it("produces a different IV on every call", ({ expect }) => {
const key = generateKey();
const ivs = new Set<string>();
for (let i = 0; i < 32; i++) {
ivs.add(encryptString("identical", key).iv);
}
// Birthday paradox at 32 samples × 96 bits IV ≈ 0 collisions expected.
expect(ivs.size).toBe(32);
});
});
describe("decryptString failure modes", () => {
it("throws when the auth tag is wrong", ({ expect }) => {
const key = generateKey();
const envelope = encryptString("hello", key);
// Flip a single bit in the tag.
const tampered = {
...envelope,
tag: Buffer.from(
Buffer.from(envelope.tag, "base64").map((b, i) =>
i === 0 ? b ^ 0x01 : b
)
).toString("base64"),
};
expect(() => decryptString(tampered, key)).toThrow();
});
it("throws when the ciphertext is tampered with", ({ expect }) => {
const key = generateKey();
const envelope = encryptString("hello", key);
const tampered = {
...envelope,
ciphertext: Buffer.from(
Buffer.from(envelope.ciphertext, "base64").map((b, i) =>
i === 0 ? b ^ 0x01 : b
)
).toString("base64"),
};
expect(() => decryptString(tampered, key)).toThrow();
});
it("throws when the wrong key is used", ({ expect }) => {
const k1 = generateKey();
const k2 = generateKey();
const envelope = encryptString("hello", k1);
expect(() => decryptString(envelope, k2)).toThrow();
});
it("throws on an unsupported envelope version", ({ expect }) => {
const key = generateKey();
const envelope = encryptString("hello", key);
const futureVersion = { ...envelope, v: 2 as 1 };
expect(() => decryptString(futureVersion, key)).toThrow(
/Unsupported encrypted envelope version/
);
});
it("throws when the key is the wrong length", ({ expect }) => {
const envelope = encryptString("hello", generateKey());
const shortKey = new Uint8Array(16);
expect(() => decryptString(envelope, shortKey)).toThrow(
/requires a 32-byte key/
);
});
it("throws when the IV length is wrong", ({ expect }) => {
const key = generateKey();
const envelope = encryptString("hello", key);
const truncatedIv = {
...envelope,
iv: Buffer.alloc(8).toString("base64"),
};
expect(() => decryptString(truncatedIv, key)).toThrow(
/Expected 12-byte IV/
);
});
it("throws when the auth tag length is wrong", ({ expect }) => {
const key = generateKey();
const envelope = encryptString("hello", key);
const shortTag = {
...envelope,
tag: Buffer.alloc(12).toString("base64"),
};
expect(() => decryptString(shortTag, key)).toThrow(
/Expected 16-byte auth tag/
);
});
});
describe("encryptString validation", () => {
it("throws when the key is the wrong length", ({ expect }) => {
expect(() => encryptString("hello", new Uint8Array(16))).toThrow(
/requires a 32-byte key/
);
});
});
describe("parseEncryptedEnvelope", () => {
it("returns the envelope when the input is well-formed", ({ expect }) => {
const envelope = encryptString("hi", generateKey());
expect(parseEncryptedEnvelope(envelope)).toEqual(envelope);
});
it("returns undefined for unknown version", ({ expect }) => {
const envelope = encryptString("hi", generateKey());
expect(parseEncryptedEnvelope({ ...envelope, v: 99 })).toBeUndefined();
});
it("returns undefined for missing fields", ({ expect }) => {
expect(
parseEncryptedEnvelope({ v: 1, alg: "AES-256-GCM" })
).toBeUndefined();
});
it("returns undefined for non-object input", ({ expect }) => {
expect(parseEncryptedEnvelope("nope")).toBeUndefined();
expect(parseEncryptedEnvelope(null)).toBeUndefined();
expect(parseEncryptedEnvelope(undefined)).toBeUndefined();
});
it("returns undefined when alg label is wrong", ({ expect }) => {
const envelope = encryptString("hi", generateKey());
expect(
parseEncryptedEnvelope({ ...envelope, alg: "DES" })
).toBeUndefined();
});
});
});