CDNetworks ドキュメントセンター Edge Application Developer Tools Request Signature and Legitimacy Verification

Request Signature and Legitimacy Verification

最終更新日:2024-09-03 17:00:38

この例では、Edge Cloud Apps関数Web Crypto APIを使用して、特定のWebサイトリソースへのアクセスを保護する方法を示します。実例として、http://ecatest.cdnetworks.com/ECA-test/pet-shop-website-template/about.htmlにあるペットショップの「会社概要」ページを使用します。このページにアクセスするには、HMACおよびSHA-256アルゴリズムを使用して生成された有効な署名が必要です。Edge Cloud Apps関数は、クライアントに代わって送信リクエストに署名したり、受信した署名付きリクエストを検証したりして、セキュリティゲートキーパーとして機能できます。権限のないリクエストは、エラーページに遭遇します。

利点

  • **セキュリティの強化:**暗号化署名を使用すると、不正アクセスを効果的に防止し、機密性の高いWebサイトリソースを保護できます。
  • **エッジ暗号化:**Edge Cloud Apps関数とWeb Crypto APIを活用することで、エッジで直接暗号化操作を実行できるようになり、レイテンシーが短縮され、効率が向上します。
  • **柔軟な制御:**開発者は、署名アルゴリズムと検証ロジックをカスタマイズして、特定の要件に基づいて柔軟なアクセス制御戦略を実装できます。

コード例

const SECRET_KEY = "my secret symmetric key";

// リクエストを処理し、リクエストメソッドに基づいて署名または検証を行います
async function handleRequest(request) {
    if (request.method === "POST") {
        return signRequest(request);
    } else {
        return verifyRequest(request);
    } 
}

// リクエスト署名を検証します
async function verifyRequest(request) {
    const url = new URL(request.url);

    // リクエストに必要なクエリパラメータが含まれていることを確認します
    if (!url.searchParams.has("mac") || !url.searchParams.has("expiry")) {
        return failPage("クエリパラメータがありません");
    }

    const encoder = new TextEncoder();
    const secretKeyData = encoder.encode(SECRET_KEY);

    // 署名検証のためにキーをインポートします
    const key = await crypto.subtle.importKey(
        "raw",
        secretKeyData,
        { name: "HMAC", hash: "SHA-256" },
        false,
        ["verify"],
    );

    // 検証が必要なデータの抽出:パスと有効期限のタイムスタンプ
    const expiry = Number(url.searchParams.get("expiry"));
    const dataToAuthenticate = url.pathname + expiry;

    // 受信したBase64エンコードされたMACをUint8Arrayに変換します
    const receivedMacBase64 = url.searchParams.get("mac");
    const receivedMac = byteStringToUint8Array(atob(receivedMacBase64));

    // タイミング攻撃を防ぐためにcrypto.subtle.verify()を使用して署名を検証します
    const verified = await crypto.subtle.verify(
        "HMAC",
        key,
        receivedMac,
        encoder.encode(dataToAuthenticate),
    );

    // 検証に失敗した場合は、エラーページを返します
    if (!verified) {
        return failPage("無効なMAC");
    }

    // URLの有効期限が切れているかどうかを確認します
    if (Date.now() > expiry) {
        return failPage(`${new Date(expiry)}にURLの有効期限が切れました`);
    }

    // 検証に成功しました。リソースへのアクセスを許可します
    return fetch(request);
}

// ByteStringをUint8Arrayに変換します
function byteStringToUint8Array(byteString) {
    const ui = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; ++i) {
        ui[i] = byteString.charCodeAt(i);
    }
    return ui;
}

// リクエストに署名します
async function signRequest(request) {
    const url = new URL(request.url);
    return generateSignedUrl(url);
}

// 署名付きURLを生成します
async function generateSignedUrl(url) {
    // 署名生成のためにキーをインポートします
    const encoder = new TextEncoder();
    const secretKeyData = encoder.encode(SECRET_KEY);
    const key = await crypto.subtle.importKey(
      "raw",
      secretKeyData,
      { name: "HMAC", hash: "SHA-256" },
      false,
      ["sign"],
    );
  
    // URLの有効期限を設定します
    const expirationMs = 18000; // 18秒
    const expiry = Date.now() + expirationMs;
    const dataToAuthenticate = url.pathname + expiry;
  
    // 署名を生成します
    const mac = await crypto.subtle.sign("HMAC", key, encoder.encode(dataToAuthenticate));
  
    // 署名をBase64エンコードされた文字列に変換します
    const base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)));
  
    // 署名と有効期限をURLクエリパラメータに追加します
    url.searchParams.set("mac", base64Mac);
    url.searchParams.set("expiry", expiry);
  
    return new Response(url);
  }

// エラーページを生成します
function failPage(msg) {
    let page = FAILPAGE.replace('{{error-msg}}', msg);
    return new Response(page, {headers: {"Content-Type": "text/html; charset=utf-8"}});
}

// エラーページのHTMLテンプレート
const FAILPAGE = `
<!DOCTYPE html>
<html>
<head>
<title>エラーページ</title>
<style>
// ... ページスタイル ... 
</style>
</head>
<body>
<div class="container">
<h1>エラー</h1>
<p>アクセスしようとしているURLは正しく署名されていません: {{error-msg}}</p>
<div class="url-section">
    <div class="url-wrapper">
        <p><span class="url-display" id="current-url" style="display: block;"></span></p>
    </div>
    <div class="button-wrapper">
        <button class="url-button" type="button" onclick="getSignedUrl()">URLに署名する</button>
    </div>
    <div class="url-wrapper" id="signed-url-section">
        <p><strong>署名付きURLは18秒後に期限切れになります</strong></p>
        <p><span class="url-display" id="signed-url"></span></p>
    </div>
</div>
</div>

<script>
// ... ページスクリプト ... 
</script>
</body>
</html>
`;
                    
addEventListener("fetch", event => {
    return event.respondWith(handleRequest(event.request));
});

注意

Edge Cloud Apps関数は、Web Crypto APIを完全にサポートしており、エッジでの暗号化と復号化のニーズを満たすことができます。Edge Cloud Apps関数を使用して、クライアントに代わってリクエストに署名したり、受信した署名付きリクエストを検証したりすることで、不正アクセス(ホットリンク保護)を効果的に防止できます。