🧊

Cloudflare WorkersでGitHubのWebhooksを受け取りハッシュを検証する

いわゆるWebhooksは、改ざん防止のためのハッシュをつけることができる。

それを受け取り側で検証するコードはこういう風に書くっていうメモ。Node.jsの場合は調べればいっぱい出てくるけど、最近のWorker環境系ではズバリなやつが見つからなかったので。

コード

const encoder = new TextEncoder();
/**
 * @param {string} signature
 * @param {[string, string]} payloadAndSecret
 * @returns {Promise<boolean>}
 */
const verifySignature = async (signature, [payload, secret]) => {
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["verify"]
  );

  const verified = await crypto.subtle.verify(
    "HMAC",
    key,
    hexToBytes(signature),
    encoder.encode(payload)
  );

  return verified;

  /**
   * @param {string} hex
   * @returns {ArrayBuffer}
   */
  function hexToBytes(hex) {
    const bytes = new Uint8Array(hex.length / 2);
    for (let c = 0; c < hex.length; c += 2)
      bytes[c / 2] = parseInt(hex.substring(c, c + 2), 16);

    return bytes.buffer;
  }
};


const handleFetch = async (req, env, _ctx) => {
  const signature = req.headers.get("X-Hub-Signature-256") ?? "N/A";
  const payload = await req.text();
  // sha256=xxx...
  const verified = await verifySignature(signature.slice(7), [
    payload,
    env.GITHUB_WEBHOOK_SECRET,
  ]);

  if (!verified) return new Response("Not Found", { status: 404 });

  const push = JSON.parse(payload);
  // ...
};

GitHubのWebhooksはSHA256のほうが推奨らしいのでそっちを使って検証する。肝心のところはWeb Cryptoに丸投げ。

いったん`text()`で受け取って、あとで`JSON.parse()`するのがなるほどなって感じ。