/** 协议与密钥测试:HMAC 签名、防重放、防篡改、时间窗、wmh_ Key 格式(Worker-Console 协议) */ import { describe, expect, test } from "bun:test"; import { canonicalString, computeSignature, generateWmailKey, isValidWmailKey, sha256Hex, verifyWorkerRequest, signWorkerRequest, } from "../shared/src/index.ts"; import { makeTestEnv, signedAuthorizeRequest, signedIngestRequest, TEST_MAIL } from "./helpers.ts"; describe("wmh_ Key 规范", () => { test("生成的 Key 以 wmh_ 开头且总长 32", () => { const key = generateWmailKey(); expect(key.startsWith("wmh_")).toBe(true); expect(key.length).toBe(32); expect(isValidWmailKey(key)).toBe(true); }); test("Key 随机且格式校验拒绝非法输入", () => { const a = generateWmailKey(); const b = generateWmailKey(); expect(a).not.toBe(b); expect(isValidWmailKey("wmh_short")).toBe(false); expect(isValidWmailKey("other_" + "x".repeat(28))).toBe(false); expect(isValidWmailKey("wmh_" + "x".repeat(27))).toBe(false); // 31 expect(isValidWmailKey("wmh_" + "x".repeat(29))).toBe(false); // 33 expect(isValidWmailKey("wmh_" + "!".repeat(28))).toBe(false); // 非法字符 }); }); describe("Worker-Console HMAC 请求签名", () => { test("canonical 串格式符合 docs/04", () => { const c = canonicalString({ method: "POST", path: "/internal/email/authorize", timestamp: "123", nonce: "n", bodyHash: "h", }); expect(c).toBe("POST\n/internal/email/authorize\n123\nn\nh"); }); test("正确签名可通过校验(Authorization: WmailHub-Worker )", async () => { const body = new TextEncoder().encode("hello"); const headers = new Headers({ "Authorization": "WmailHub-Worker wmailhub-email-x1", "X-WmailHub-Timestamp": String(Math.floor(Date.now() / 1000)), "X-WmailHub-Nonce": "nonce-1", "X-WmailHub-Body-SHA256": await sha256Hex(body), "X-WmailHub-Signature": await computeSignature("s", { method: "POST", path: "/p", timestamp: String(Math.floor(Date.now() / 1000)), nonce: "nonce-1", bodyHash: await sha256Hex(body), }), "X-WmailHub-Protocol": "1", }); const result = await verifyWorkerRequest({ secret: "s", method: "POST", url: "http://x/p", body, headers, }); expect(result.ok).toBe(true); if (result.ok) expect(result.workerId).toBe("wmailhub-email-x1"); }); test("错误 secret 签名被拒(伪造 Worker 请求)", async () => { const test = await makeTestEnv(); const req = await signedAuthorizeRequest(test, "hello@example.test", "a@b.c", { secret: "whs_wrong_secret" }); const resp = await test.app.fetch(req, test.env); expect(resp.status).toBe(401); }); test("未知 worker_id 被拒(公网匿名调用 /internal/email/*)", async () => { const test = await makeTestEnv(); const req = await signedAuthorizeRequest(test, "hello@example.test", "a@b.c", { workerId: "wmailhub-email-unknown" }); const resp = await test.app.fetch(req, test.env); expect(resp.status).toBe(401); }); test("body 篡改被拒(authorize 与 ingest 都校验 body hash)", async () => { const test = await makeTestEnv(); const body = new TextEncoder().encode(TEST_MAIL); const tampered = new TextEncoder().encode(TEST_MAIL + "injected"); const req = await signedIngestRequest(test, body, { tamperBody: tampered }); const resp = await test.app.fetch(req, test.env); expect(resp.status).toBe(401); }); test("过期 timestamp 超窗被拒", async () => { const test = await makeTestEnv(); const old = String(Math.floor(Date.now() / 1000) - 3600); // 1 小时前 const req = await signedAuthorizeRequest(test, "hello@example.test", "a@b.c", { timestamp: old }); const resp = await test.app.fetch(req, test.env); expect(resp.status).toBe(401); const data = (await resp.json()) as { error: string }; expect(data.error).toContain("timestamp"); }); test("nonce 重放被拒(replay attack)", async () => { const test = await makeTestEnv(); // 同一组签名头发两次:第一次 nonce 入库通过,第二次构成 replay const url = "http://localhost/internal/email/authorize"; const body = new TextEncoder().encode(JSON.stringify({ recipient: "hello@example.test", sender: "a@b.c" })); const headers = await signWorkerRequest(test.workerSecret, test.workerId, "POST", url, body); headers["Content-Type"] = "application/json"; const replayReq = () => new Request(url, { method: "POST", headers, body: body.slice() }); const respFirst = await test.app.fetch(replayReq(), test.env); expect(respFirst.status).toBe(200); const respReplay = await test.app.fetch(replayReq(), test.env); expect(respReplay.status).toBe(401); const data = (await respReplay.json()) as { error: string }; expect(data.error).toContain("replay"); }); test("错误协议版本被拒", async () => { const test = await makeTestEnv(); const req = await signedAuthorizeRequest(test, "hello@example.test", "a@b.c", { protocol: "9" }); const resp = await test.app.fetch(req, test.env); expect(resp.status).toBe(400); }); });