コンテンツにスキップ

リクエストの署名

Last reviewed: 9 months ago

HMACおよびSHA-256アルゴリズムを使用して署名されたリクエストを検証するか、403を返します。

Worker内でWeb Crypto APIsを使用して、署名されたリクエストを検証および生成できます。

以下のWorkerは次のことを行います:

  • リクエストURLが/generate/で始まる場合、/generate//に置き換え、結果のパスにタイムスタンプを付加して署名し、レスポンスボディに完全な署名済みURLを返します。

  • その他のリクエストURLについては、署名されたURLを検証し、リクエストを通過させます。

import { Buffer } from "node:buffer";
const encoder = new TextEncoder();
// HMACトークンの有効期限(秒)
const EXPIRY = 60;
export default {
/**
*
* @param {Request} request
* @param {{SECRET_DATA: string}} env
* @returns
*/
async fetch(request, env) {
// 対称鍵として使用するための秘密データが必要です。これは
// 暗号化された秘密としてWorkerに添付する必要があります。
// https://developers.cloudflare.com/workers/configuration/secrets/を参照してください。
const secretKeyData = encoder.encode(
env.SECRET_DATA ?? "my secret symmetric key",
);
// 'sign'および'verify'操作のために秘密をCryptoKeyとしてインポートします
const key = await crypto.subtle.importKey(
"raw",
secretKeyData,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"],
);
const url = new URL(request.url);
// これは/generateへの認証されていないアクセスを許可するデモンストレーションWorkerです
// 実際のアプリケーションでは、ユーザーが認証されているときのみ
// 署名されたURLを生成できるようにする必要があります
if (url.pathname.startsWith("/generate/")) {
url.pathname = url.pathname.replace("/generate/", "/");
const timestamp = Math.floor(Date.now() / 1000);
// これは、検証可能なリクエストに関するすべてのデータを含みます
// ここではタイムスタンプとパス名のみを署名しますが、通常は
// より多くのデータ(たとえば、URLのホスト名やクエリパラメータ)を含めることが望ましいです
const dataToAuthenticate = `${url.pathname}${timestamp}`;
const mac = await crypto.subtle.sign(
"HMAC",
key,
encoder.encode(dataToAuthenticate),
);
// https://developers.cloudflare.com/workers/runtime-apis/nodejs/
// でWorkers内のNode.js APIの使用に関する詳細を参照してください
const base64Mac = Buffer.from(mac).toString("base64");
url.searchParams.set("verify", `${timestamp}-${base64Mac}`);
return new Response(`${url.pathname}${url.search}`);
// /generate以外のすべてのリクエストを検証
} else {
// 最小限の必要なクエリパラメータがあることを確認します。
if (!url.searchParams.has("verify")) {
return new Response("クエリパラメータが不足しています", { status: 403 });
}
const [timestamp, hmac] = url.searchParams.get("verify").split("-");
const assertedTimestamp = Number(timestamp);
const dataToAuthenticate = `${url.pathname}${assertedTimestamp}`;
const receivedMac = Buffer.from(hmac, "base64");
// timing攻撃に対抗するためにcrypto.subtle.verify()を使用します。HMACは
// 対称鍵を使用するため、crypto.subtle.sign()を呼び出して
// 文字列比較を行うことで実装できますが、これは安全ではありません。文字列比較は
// 最初の不一致で中断され、潜在的な攻撃者に情報を漏らします。
const verified = await crypto.subtle.verify(
"HMAC",
key,
receivedMac,
encoder.encode(dataToAuthenticate),
);
if (!verified) {
return new Response("無効なMAC", { status: 403 });
}
// 署名されたリクエストは1分後に期限切れになります。この値は特定の使用ケースに依存する必要があります
if (Date.now() / 1000 > assertedTimestamp + EXPIRY) {
return new Response(
`URLは${new Date((assertedTimestamp + EXPIRY) * 1000)}に期限切れです`,
{ status: 403 },
);
}
}
return fetch(new URL(url.pathname, "https://example.com"), request);
},
};

WAFを使用して署名されたリクエストを検証する

提供された署名リクエストの例コードは、is_timed_hmac_valid_v0()ルール言語関数と互換性があります。これは、Workerスクリプトによって署名されたリクエストをWAFカスタムルールを使用して検証できることを意味します。