リクエストの署名
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); },};import { Buffer } from "node:buffer";
const encoder = new TextEncoder();
// HMACトークンの有効期限(秒)const EXPIRY = 60;
interface Env { SECRET_DATA: string;}export default { async fetch(request, env): Promise<Response> { // 対称鍵として使用するための秘密データが必要です。これは // 暗号化された秘密として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内のNodeJS 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); },} satisfies ExportedHandler<Env>;提供された署名リクエストの例コードは、is_timed_hmac_valid_v0()ルール言語関数と互換性があります。これは、Workerスクリプトによって署名されたリクエストをWAFカスタムルールを使用して検証できることを意味します。