/** 协议与密钥测试:HMAC 签名、防重放、防篡改、时间窗、wmh_ Key 格式 */ import { describe, expect, test } from "bun:test"; import { canonicalString, computeSignature, generateWmailKey, isValidWmailKey, verifyNodeRequest, } from "../shared/src/index.ts"; import { makeTestEnv, signedEmailRequest, 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("HMAC 请求签名", () => { test("canonical 串格式符合 docs/04", () => { const c = canonicalString({ method: "POST", path: "/api/x", timestamp: "123", nonce: "n", bodyHash: "h", }); expect(c).toBe("POST\n/api/x\n123\nn\nh"); }); test("正确签名可通过校验", async () => { const body = new TextEncoder().encode("hello"); const headers = new Headers({ "Authorization": "WmailHub-Node node_1", "X-WmailHub-Timestamp": String(Math.floor(Date.now() / 1000)), "X-WmailHub-Nonce": "nonce-1", "X-WmailHub-Body-SHA256": await (async () => { const { sha256Hex } = await import("../shared/src/index.ts"); return sha256Hex(body); })(), "X-WmailHub-Signature": await computeSignature("s", { method: "POST", path: "/p", timestamp: String(Math.floor(Date.now() / 1000)), nonce: "nonce-1", bodyHash: await (async () => { const { sha256Hex } = await import("../shared/src/index.ts"); return sha256Hex(body); })(), }), "X-WmailHub-Protocol": "1", }); const result = await verifyNodeRequest({ secret: "s", method: "POST", url: "http://x/p", body, headers, }); expect(result.ok).toBe(true); }); test("错误 secret 签名被拒(伪造 Node 请求)", async () => { const test = await makeTestEnv(); const body = new TextEncoder().encode(TEST_MAIL); const req = await signedEmailRequest(test, body, { secret: "wns_wrong_secret" }); const resp = await test.app.fetch(req, test.env); expect(resp.status).toBe(401); }); test("body 篡改被拒", async () => { const test = await makeTestEnv(); const body = new TextEncoder().encode(TEST_MAIL); const tampered = new TextEncoder().encode(TEST_MAIL + "injected"); const req = await signedEmailRequest(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 body = new TextEncoder().encode(TEST_MAIL); const old = String(Math.floor(Date.now() / 1000) - 3600); // 1 小时前 const req = await signedEmailRequest(test, body, { 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(); const body = new TextEncoder().encode(TEST_MAIL); const req1 = await signedEmailRequest(test, body); const resp1 = await test.app.fetch(req1, test.env); expect(resp1.status).toBe(202); // 取第一次请求的 nonce 重放(重新签名同一 nonce) const url = `http://localhost/api/internal/nodes/${test.nodeId}/emails`; const { signNodeRequest } = await import("../shared/src/index.ts"); const headers = await signNodeRequest(test.nodeSecret, test.nodeId, "POST", url, body); headers["Content-Type"] = "message/rfc822"; const resp2a = await test.app.fetch(new Request(url, { method: "POST", headers, body }), test.env); expect(resp2a.status).toBe(202); // 抓第二次请求的完整头,原样重发构成 replay(同 nonce 同签名) const headers3 = await signNodeRequest(test.nodeSecret, test.nodeId, "POST", url, body); headers3["Content-Type"] = "message/rfc822"; const replayReq = () => new Request(url, { method: "POST", headers: headers3, body }); const respFirst = await test.app.fetch(replayReq(), test.env); expect(respFirst.status).toBe(202); const resp3 = await test.app.fetch(replayReq(), test.env); expect(resp3.status).toBe(401); const data = (await resp3.json()) as { error: string }; expect(data.error).toContain("replay"); }); test("错误协议版本被拒", async () => { const test = await makeTestEnv(); const body = new TextEncoder().encode(TEST_MAIL); const req = await signedEmailRequest(test, body); const headers = new Headers(req.headers); headers.set("X-WmailHub-Protocol", "9"); const resp = await test.app.fetch( new Request(req.url, { method: "POST", headers, body }), test.env, ); expect(resp.status).toBe(400); }); });