/*
Unit tests for wba sign covering passportKeyToJwk; createWbaSigner (AC-2); independent verification (AC-3).
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the wba suite's contract for test-file scaffold coverage.
*/
import { describe, it, expect } from "vitest";
import * as ed from "@noble/ed25519";
import { verifyBytes } from "../signing/sign.ts";
import {
WBA_DIRECTORY_PATH,
createWbaSigner,
createOutboundAgentClient,
passportKeyToJwk,
jwkToPublicKeyBytes,
type WbaJwk,
} from "./wba-sign.ts";
const DIRECTORY_URL = `https://warpgogol.example${WBA_DIRECTORY_PATH}`;
const USER_AGENT = "WarpgogolBot/9.9.9 (+https://warpgogol.example)";
const PRIVATE_KEY_HEX = "b3f84ddb6973a143c4b2fc6b0b8d6b3cb33863b2e30dca94241029111e81e14f";
const privateKey = new Uint8Array(PRIVATE_KEY_HEX.match(/.{2}/g)!.map((b) => parseInt(b, 16)));
function signer(overrides: { keyId?: string } = {}) {
return createWbaSigner({
privateKeyHex: PRIVATE_KEY_HEX,
keyId: overrides.keyId ?? "v1",
directoryUrl: DIRECTORY_URL,
userAgent: USER_AGENT,
});
}
/**
* Independent verifier — rebuilds the RFC 9421 signature base from the
* emitted headers (no shared code path with the signer) and verifies with
* the published JWK material. AC-3.
*/
async function independentVerify(request: Request, jwk: WbaJwk): Promise {
const signatureInput = request.headers.get("Signature-Input")!;
const signature = request.headers.get("Signature")!;
const m = signatureInput.match(
/^sig1=\(([^)]*)\);keyid="([^"]+)";alg=ed25519;created=(\d+);expires=(\d+)$/,
);
if (!m) return false;
const covered = m[1].split(" ").map((s) => s.replaceAll('"', ""));
const url = new URL(request.url);
const lines: string[] = [];
for (const comp of covered) {
if (comp === "@method") lines.push(`"@method": ${request.method.toUpperCase()}`);
else if (comp === "@authority") lines.push(`"@authority": ${url.host}`);
else if (comp === "@path") lines.push(`"@path": ${url.pathname}`);
else if (comp === "signature-agent")
lines.push(`"signature-agent": ${request.headers.get("Signature-Agent")}`);
else if (comp === "content-digest")
lines.push(`"content-digest": ${request.headers.get("Content-Digest")}`);
else return false;
}
const params = signatureInput.slice("sig1=".length);
lines.push(`"@signature-params": ${params}`);
const sigB64 = signature.match(/^sig1=:([A-Za-z0-9+/=]+):$/)?.[1];
if (!sigB64) return false;
const sigBytes = new Uint8Array([...atob(sigB64)].map((c) => c.charCodeAt(0)));
return verifyBytes(
jwkToPublicKeyBytes(jwk),
new TextEncoder().encode(lines.join("\n")),
sigBytes,
);
}
describe("passportKeyToJwk", () => {
it("derives OKP JWK from hex, bytes, and multibase — kid is the passport version", async () => {
const pub = await ed.getPublicKeyAsync(privateKey);
const fromBytes = passportKeyToJwk(pub, "v1");
expect(fromBytes.kty).toBe("OKP");
expect(fromBytes.crv).toBe("Ed25519");
expect(fromBytes.kid).toBe("v1");
expect(fromBytes.alg).toBe("EdDSA");
expect(fromBytes.use).toBe("sig");
const fromHex = passportKeyToJwk(
[...pub].map((b) => b.toString(16).padStart(2, "0")).join(""),
"v1",
);
expect(fromHex.x).toBe(fromBytes.x);
// multibase "z" + base58btc — the same material must decode identically
const { toMultibase } = await import("../passport/sign.ts");
const fromMultibase = passportKeyToJwk(toMultibase(pub), "v1");
expect(fromMultibase.x).toBe(fromBytes.x);
// round-trip back to raw bytes
expect(jwkToPublicKeyBytes(fromBytes)).toEqual(pub);
});
});
describe("createWbaSigner (AC-2)", () => {
it("stamps Signature-Agent, Signature-Input, Signature on a GET", async () => {
const req = new Request("https://forge-site.example/agent-health");
const signed = await signer().sign(req, { created: 1759564800 });
expect(signed.headers.get("Signature-Agent")).toBe(DIRECTORY_URL);
expect(signed.headers.get("User-Agent")).toBe(USER_AGENT);
expect(signed.headers.get("Signature-Input")).toBe(
'sig1=("@method" "@authority" "@path" "signature-agent");keyid="v1";alg=ed25519;created=1759564800;expires=1759564860',
);
expect(signed.headers.get("Signature")).toMatch(/^sig1=:[A-Za-z0-9+/=]+:$/);
expect(signed.headers.get("Content-Digest")).toBeNull();
});
it("adds content-digest to covered components when a body exists", async () => {
const req = new Request("https://forge-site.example/api/agent/a2a", {
method: "POST",
body: JSON.stringify({ jsonrpc: "2.0", method: "SendMessage" }),
});
const signed = await signer().sign(req, { created: 1759564800 });
expect(signed.headers.get("Signature-Input")).toContain('"content-digest"');
expect(signed.headers.get("Content-Digest")).toMatch(/^sha-256=:[A-Za-z0-9+/=]+:$/);
// body survives signing — clone semantics keep the single-read stream intact
expect(await signed.text()).toContain("SendMessage");
});
});
describe("independent verification (AC-3)", () => {
it("a signed GET verifies against the published JWK", async () => {
const pub = await ed.getPublicKeyAsync(privateKey);
const jwk = passportKeyToJwk(pub, "v1");
const signed = await signer().sign(new Request("https://forge-site.example/health"), {
created: 1759564800,
});
expect(await independentVerify(signed, jwk)).toBe(true);
});
it("a signed POST with body verifies against the published JWK", async () => {
const pub = await ed.getPublicKeyAsync(privateKey);
const jwk = passportKeyToJwk(pub, "v1");
const signed = await signer().sign(
new Request("https://forge-site.example/api/agent/mcp", {
method: "POST",
body: '{"jsonrpc":"2.0"}',
}),
{ created: 1759564800 },
);
expect(await independentVerify(signed, jwk)).toBe(true);
});
it("a tampered signature does not verify", async () => {
const pub = await ed.getPublicKeyAsync(privateKey);
const jwk = passportKeyToJwk(pub, "v1");
const signed = await signer().sign(new Request("https://forge-site.example/health"), {
created: 1759564800,
});
const headers = new Headers(signed.headers);
headers.set(
"Signature",
"sig1=:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:",
);
const tampered = new Request(signed, { headers });
expect(await independentVerify(tampered, jwk)).toBe(false);
});
});
describe("createOutboundAgentClient", () => {
it("signs through the injected fetch when a signer is configured", async () => {
let captured: Request | null = null;
const client = createOutboundAgentClient({
signer: signer(),
userAgent: USER_AGENT,
fetchImpl: async (input) => {
captured = input instanceof Request ? input : new Request(input);
return new Response("ok");
},
});
await client.fetch("https://forge-site.example/health");
expect(captured!.headers.get("Signature")).toBeTruthy();
expect(captured!.headers.get("Signature-Agent")).toBe(DIRECTORY_URL);
});
it("degrades to identifying User-Agent only when no signer is configured", async () => {
let captured: Request | null = null;
const client = createOutboundAgentClient({
userAgent: USER_AGENT,
fetchImpl: async (input, init) => {
captured = input instanceof Request ? input : new Request(input, init);
return new Response("ok");
},
});
await client.fetch("https://forge-site.example/health");
expect(captured!.headers.get("Signature")).toBeNull();
expect(captured!.headers.get("Signature-Agent")).toBeNull();
expect(captured!.headers.get("User-Agent")).toBe(USER_AGENT);
});
it("propagates AbortSignal through the signed path — cancellation survives the wrap", async () => {
const controller = new AbortController();
let captured: Request | null = null;
const client = createOutboundAgentClient({
signer: signer(),
userAgent: USER_AGENT,
fetchImpl: async (input) => {
captured = input instanceof Request ? input : new Request(input);
return new Response("ok");
},
});
await client.fetch("https://forge-site.example/health", { signal: controller.signal });
expect(captured!.headers.get("Signature")).toBeTruthy();
// The re-wrapped Request follows the caller's signal — aborting the
// original must abort what transport received (fetchWithRetry timeouts
// depend on this).
controller.abort();
expect(captured!.signal.aborted).toBe(true);
});
it("signing failure falls back to unsigned and reports — never breaks the call", async () => {
const broken = {
keyId: "v1",
sign: async () => {
throw new Error("signing exploded");
},
};
let captured: Request | null = null;
let reported: unknown = null;
const client = createOutboundAgentClient({
signer: broken,
userAgent: USER_AGENT,
onSigningFailure: (e) => {
reported = e;
},
fetchImpl: async (input) => {
captured = input instanceof Request ? input : new Request(input);
return new Response("ok");
},
});
const res = await client.fetch("https://forge-site.example/health");
expect(res.status).toBe(200);
expect(reported).toBeInstanceOf(Error);
expect(captured!.headers.get("Signature")).toBeNull();
});
});
describe("key validation", () => {
it("rejects non-32-byte key material", () => {
expect(() => passportKeyToJwk(new Uint8Array(16), "v1")).toThrow(/32 bytes/);
expect(() =>
createWbaSigner({
privateKeyHex: "abcd",
keyId: "v1",
directoryUrl: DIRECTORY_URL,
userAgent: USER_AGENT,
}),
).toThrow(/32 bytes/);
});
});