| 1 | // Test host: what Codewhale does for the plugin it spawns. The first stdin |
| 2 | // line carries the per-connection decision key (and the ledger key), and a |
| 3 | // tools/call the user approved carries an attested decision in _meta. |
| 4 | import crypto from "node:crypto"; |
| 5 | |
| 6 | export const TEST_DECISION_KEY = "11".repeat(32); |
| 7 | export const TEST_LEDGER_KEY = "22".repeat(32); |
| 8 | |
| 9 | export function hostKeysLine({ ledger = true } = {}) { |
| 10 | return JSON.stringify({ |
| 11 | jsonrpc: "2.0", |
| 12 | method: "codewhale/host_keys", |
| 13 | params: { decision_key: TEST_DECISION_KEY, ...(ledger ? { ledger_key: TEST_LEDGER_KEY } : {}) }, |
| 14 | }) + "\n"; |
| 15 | } |
| 16 | |
| 17 | /** tools/call params with a decision attested under the test key. */ |
| 18 | export function attest(params, key = TEST_DECISION_KEY) { |
| 19 | if (!params || typeof params.name !== "string") return params; |
| 20 | const argsJson = JSON.stringify(params.arguments ?? {}); |
| 21 | const nonce = crypto.randomBytes(16).toString("hex"); |
| 22 | const mac = crypto.createHmac("sha256", Buffer.from(key, "hex")) |
| 23 | .update(Buffer.concat([Buffer.from(params.name), Buffer.from([0]), Buffer.from(argsJson), Buffer.from([0]), Buffer.from(nonce)])) |
| 24 | .digest("hex"); |
| 25 | return { ...params, _meta: { ...(params._meta ?? {}), "codewhale/user_decision": { nonce, args_json: argsJson, mac } } }; |
| 26 | } |
| 27 | |
| 28 | export const attestParams = (method, params) => (method === "tools/call" ? attest(params) : params); |
| 29 | |
| 30 | /** Sign a persisted allow the way the plugin does under TEST_LEDGER_KEY. */ |
| 31 | export function ledgerMac(computerId, key, entry) { |
| 32 | return crypto.createHmac("sha256", Buffer.from(TEST_LEDGER_KEY, "hex")) |
| 33 | .update(`${computerId}\0${key}\0${entry.decision}\0${entry.at ?? ""}`) |
| 34 | .digest("hex"); |
| 35 | } |
| 36 |