JunshuO
ServiceNow Employee

HMAC Validation in ServiceNow Securing Webhook Integrations with CertificateEncryption.png

C3シリーズのその他の記事もご覧ください。

 

:warning: @Jo_o Salvador 氏のコメントもぜひご覧ください。HTTP 認証プロファイルを使用して HMAC 検証を実装する、ノーコードのプラットフォームネイティブアプローチが紹介されています。独自にスクリプトを記述する必要がない場合は、その必要はありません。この記事は、より高度なユースケースや、内部でどのように機能するかを理解したい場合に役立ちます。

 

私が行った統合のほとんどは、素晴らしくシンプルな基本認証で、後は忘れていました。通常、暗号化に最も近いのは、シリアルボックスの裏にある謎を解き明かすための赤いプラスチック製デコーダーくらいのものでした。そのため、Replicate AI のドキュメントで、CreatorCon C3 への生成されたアバター URL を含む REST メッセージを検証するために、SHA-256 を使用して生成された署名で秘密のハンドシェイクを実行し、HMAC 検証と呼ばれるものを行う必要があると知らされた時...

 

私は、公開されている非認証のスクリプト REST API Webhook を誰も見つけられないと心から考えていました。

 

ただし、皆さん賢明な方々なので、そのような決定を後悔されるでしょうから、この記事では CertificateEncryption API を使用して Scripted REST API で HMAC 検証を機能させる方法を詳しく説明します。

 

HMAC 検証とは何ですか?

 

技術的な詳細に入る前に、まず、どのような場合に HMAC 検証が必要になるのかについて説明します。以前の C3 の記事のいくつかで説明したように、Webhook は、API プロバイダーとサードパーティアプリケーションが、通常は何らかのイベントが発生した後に、アプリケーションにメッセージを送り返す方法です。私たちの場合、Replicate AI は、アバターが AI モデルによって生成された際に、C3 に REST メッセージを送信します。

 

問題は、悪意のあるユーザーが同じ「アバター作成」メッセージを C3 に送信するのを防ぐ術があるか、ということです。現実的には...何もありません。そのため、アバターが作成されたというメッセージが Replicate AI からのものであることを検証する必要があります。通常、これは API キーを介して行われますが、それはまさに Replicate AI を呼び出す際に使用するものです。しかし、ここが難しい点です。Replicate AI は API ですが、彼らは私たちのアプリを呼び出しています。実際、彼らは彼らの API を使用するすべてのアプリを呼び出しています。

 

一方で、Replicate は、応答しているすべてのアプリの認証資格情報を保存し、各アプリに対して適切な方法で認証できるようにすることもできます。あるいは、クライアントアプリが、Webhook メッセージが Replicate AI からのものであり、改ざんされていないことを検証できる手段を提供するという方法もあります。

 

それが HMAC 検証の目的です。それは、彼らがあなたに送るメッセージに施される小さなワックスシールのようで、メッセージが Replicate AI からのものであり、誰もメッセージを改ざんしていないことを検証できます。

 

HMAC 検証の仕組み

 

プロセスに入る前に、この議論における重要な用語をいくつか説明しましょう。

 

  • API: Webhook を呼び出すサードパーティ API。
  • アプリ: Webhook エンドポイントを含むお使いのアプリケーション。
  • 秘密鍵 (シークレット): API とアプリの両方が持つ共有秘密鍵であり、他の全員には秘密にされています。API は通常、この秘密鍵 (通常は文字列値) を提供します。
  • メッセージ: 検証が必要な Webhook エンドポイントに送信されるペイロード。
  • 署名: API とアプリの両方が、秘密鍵と API が指定するプロセスを使用して生成する文字列値。

 

用語はさておき、HMAC 検証のプロセスは実際には非常に簡単です。

 

  1. API は、API が指定する秘密鍵とプロセスを使用して署名を生成します。
  2. API は署名とメッセージをアプリの Webhook エンドポイントに送信します。
  3. アプリは、API が指定する秘密鍵とプロセスを使用して署名を生成します。
  4. アプリは、API 署名をアプリが生成した署名と比較します。
    1. 署名が一致する場合、メッセージは API からのものであり、変更されていないことが検証されます。
    2. 署名が次のいずれかと一致しない場合:
      1. メッセージは API からのものとして信頼しないでください。
      2. メッセージは、中間者攻撃によって変更された可能性があります。
      3. アプリの署名生成コードを台無しにしてしまった可能性があります。

 

Replicate AI の HMAC 実装

 

注意すべき点の 1 つは、API が異なれば、署名を生成するためのプロセスが異なる場合があるということです。API から送信された署名と一致する有効な署名を取得する唯一の方法は、そのプロセスに厳密に従うことです。それについては後で詳しく説明します。

 

例として、Replicate AI は次のプロセスを使用しました。

 

Webhook の構造

 

Replicate AI には、各 Webhook に 3 つの重要な HTTP ヘッダーが含まれています。

 

webhook-id:msg_1234567890abcdef
webhook-timestamp:1640995200
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo=

 

  • webhook-id: この特定の Webhook 配信の一意の識別子。
  • webhook-timestamp: Webhook が送信された日時を示す Unix タイムスタンプ。
  • webhook-signature: バージョンプレフィックスを持つ署名のスペース区切りリスト。

 

署名の生成

 

署名を生成するには、Replicate AI はまず署名済みコンテンツを作成します。これは次のように行われます。

 

const signedContent = `${webhook_id}.${webhook_timestamp}.${body}`;

 

また、whsec_C2FVsBQIhrscChlQIMV+b5sSYspob7oDのような秘密鍵も提供されます。奇妙なことに、実際の秘密鍵は先頭のwhsec_を除いた部分であり、これは少し混乱を招きました。署名を生成するために、Replicate AI は秘密鍵、署名済みコンテンツ、および SHA-256 アルゴリズムを使用します。

 

ServiceNow HMAC 検証の実装

 

これらの詳細を使用して、ServiceNow 側では次のことを行う必要がありました。

 

  1. HTTP ヘッダーを抽出します。
  2. メッセージコンテンツを取得します。
  3. 署名済みコンテンツを構築します。
  4. 署名を生成します。

 

Replicate API の詳細の一部を Script Include にラップするのが妥当だったので、以下にその Script Include (一部の無関係な関数は除いて) を示します。

 

var ReplicateAI = Class.create();
ReplicateAI.prototype = {
    initialize: function () {
        this.token = '';
    },

    getWebhookSigningSecret: function () {
        return gs.getProperty('x_snc_cctcg_photo.replicate_ai_secret_key');
    },

    updateWebhookSigningSecret: function () {
        this.getAPIToken();
        const request = new sn_ws.RESTMessageV2();
        request.setEndpoint('https://api.replicate.com/v1/webhooks/default/secret');
        request.setHttpMethod('GET');
        request.setRequestHeader('Authorization', `Bearer ${this.token}`);
        request.setRequestHeader('Content-Type', "application/json");
        const resp = JSON.parse(request.execute().getBody());
        gs.setProperty('x_snc_cctcg_photo.replicate_ai_secret_key', resp.key);
    },

    isWebhookValid: function (request, body) {
        const secret = this.getWebhookSigningSecret().split('_')[1]; // Remove whsec_ prefix
        const signatures = request.getHeader('webhook-signature').split(' ');
        const webhookId = request.getHeader('webhook-id'); 
        const timestamp = request.getHeader('webhook-timestamp');

        return signatures.some((signature) => {
            let signatureBase64 = signature.split(',')[1]; // Remove version prefix (v1,)
            const signedContent = `${webhookId}.${timestamp}.${body}`;

            const mac = new CertificateEncryption();
            const generatedSignature = mac.generateMac(secret, 'HmacSHA256', signedContent);
            
            return signatureBase64 == generatedSignature;
        });
    },

    getAPIToken: function () {
        if (this.token) {
            return;
        }
        const credentialAlias = 'your_credential_alias_sys_id';
        const provider = new sn_cc.StandardCredentialsProvider();
        const cred = provider.getCredentialByAliasID(credentialAlias);
        this.token = cred.getAttribute('api_key');
    },

    type: 'ReplicateAI'
};

 

このisWebhookValid関数 (上記22行目) はプロセスのエントリポイントであり、Scripted REST API 内から実行されます (下の 8 行目)。

 

(function process(request, response) {
    const log = new Log();
    const replicate = new ReplicateAI();
    const resp = request.body.data;
    
    try {
        // Critical: Use JSON.stringify to ensure exact body representation
        if (!replicate.isWebhookValid(request, JSON.stringify(resp))) {
            log.insert({
                type: 'Error',
                message: 'Unauthorized webhook attempt',
                payload: resp,
            });
            response.setStatus(401);
            return;
        }

        // Process validated webhook...
        if (resp && resp.output && resp.output.length) {
            // Handle successful AI processing
            resp.output.forEach((generatedImage) => {
                // Your business logic here
            });
        } else if (resp && resp.error) {
            // Handle AI processing errors
            log.insert({
                type: 'Error',
                message: 'AI processing failed',
                payload: resp,
            });
        }

    } catch (err) {
        log.insert({
            type: 'Error',
            message: 'Webhook processing error',
            payload: err.toString(),
        });
        response.setStatus(500);
    }
})(request, response);

 

これら 2 つのスクリプトは、C3 アプリの HMAC 検証のバックボーンを形成しており、上記のプロセスを実行しているだけであることがわかります。

 

間違いと HMAC デバッグが厄介な理由

 

正直に言うと、「最悪」という言葉は HMAC 検証のフラストレーションを正当に評価しません。プロセスはとても簡単に見えますが、問題は、署名が API 署名と一致しない場合、役立つ情報は一切ありません。部分一致も、「惜しい」という情報も、「3 行目のエラー」といった情報もありません。ただ、ブール値と、他のどんなでたらめなランダム文字列とも、また関数が返す他のすべてのでたらめなランダム文字列とも完全に異なる、でたらめなランダム文字列がいくつか返されるだけです。

 

それでは、私がその過程で犯したすべての間違いを見ていきましょう。

 

間違い #1: アルゴリズム名の混乱

 

// This is what I tried first (because that's what the Replicate docs said)
mac.generateMac(secret, 'SHA-256', signedContent);

// But ServiceNow wants this format
mac.generateMac(secret, 'HmacSHA256', signedContent);

 

残念ながら、ServiceNow には、どのアルゴリズム名が機能し、どれが機能しないかを教えてくれる列挙型はありません。また、CertificateEncryption API の異なる関数が異なる名前を使用する可能性もあるようです。これは私にとって非常に混乱しましたし、正直なところ、SHA-256が実際に機能するかどうかは分かりません。しかし、この特定の関数の例にはHmacSHA256が記載されており、それが最終的に私にとっては機能しました。ただし、これらのアルゴリズムには注意が必要です。

 

間違い #2: 秘密鍵プレフィックスの問題

 

// I forgot to strip this for way too long
const secret = 'whsec_C2FVsBQIhrscChlQIMV+b5sSYspob7oD';

// You need to remove the whsec_ prefix
const secret = 'C2FVsBQIhrscChlQIMV+b5sSYspob7oD';

 

しばらくの間、署名が失敗し続け、その理由がわかりませんでした。他のすべては適切に設定できていると感じていましたが、Replicate AI のプロセスで見落としていたことの 1 つは、whsec_秘密鍵を取得するためにはこれを剥ぎ取る必要があったことです。署名を生成する際には、これらの些細な詳細が非常に重要です。秘密鍵のこれら 6 文字の違いは、劇的に異なる署名につながり、結果として得られる署名にはその理由を示すものは何もありません。

 

間違い #3: ボディコンテンツの処理

 

// Non-Strings will get silently converted to strings like [Object object]
const body = request.body.data; 

// You need the exact string representation
const body = JSON.stringify(request.body.data);

 

繰り返しになりますが、署名生成への入力は非常に重要です。JavaScript は、最も役に立たない方法で、ほぼすべてのものを文字列に喜んで変換します。

 

間違い #4: Base64 エンコーディングの混乱

 

私のキーはすでに Replicate から Base64 でエンコードされていましたが、キーのエンコードを示す ServiceNow の例に従っていました。そのため、すでに Base64 でエンコードされた文字列を再度 Base64 エンコードしてしまっていました。二重にスタンプされたものに三重にスタンプできないのと同じで、間違った結果が得られました。

 

役に立つアドバイス

 

ご覧のとおり、HMAC 署名は入力の変更に非常に敏感です。そのため、自身のプロセスは API のプロセスと完全に一致している必要があります。そうしないと、一致する署名が取得されません。デバッグは非常に苦痛でしたが、次回のためにいくつかの教訓を学びました。

 

役に立つアドバイス #1: すべてをログに記録する

 

ログ記録でデバッグする場合でも、Script Debugger を使用する場合でも、入力と出力をよく理解する必要があります。入力がどのように出力に変換されるかについて多くの可視性がないため、入力が目的の入力と一致していることを確認し、どこが違うのかを把握するのが最善の方法です。

 

役に立つアドバイス #2: 最初に既知の値でテストする

 

また、これらの入力と出力はテストデータを作成するのに役立つため、記録することをお勧めします。長時間実行されるプロセスを扱う可能性が高いため、テストのために署名検証を REST プロセスから切り離すと役立つ場合があります。入力と出力がわかっていると、これらのテストの実行がはるかに簡単になります。API が検証済みのサンプルデータセットを提供してくれる場合は、さらに良いでしょう。

 

役に立つアドバイス #3: 核心から取り組む

 

通常、スクリプトを記述する際は、まずトップから始めて、プロセスを下に進めていきます。入力の機密性が高く、予期しない結果に対する洞察が不足しているため、まずはgenerateMac関数から開始し、そこから段階的に検証を進めることをお勧めします。既知の値でその関数をテストし、適切なアルゴリズムが使用され、正しい入力が正しい場所に設定されていることを確認してください。次に、入力を一度に 1 つずつより汎用的なものにします。これにより、どの値がエラーの原因となっているのかを特定しやすくなります。

役に立つアドバイス #4: API の検証プロセスを書き出す

 

すべての手順と要件の簡単なチェックリストを作成すると役立ちます。HMAC 検証に関する Replicate AI のドキュメントはかなり堅牢です。しかし、コードをデバッグしているときは、「このプロセスで、この要件を満たしただろうか」と確認できるような手軽なチェックリストが必要だと感じました。これらにチェックを入れることで、見落としていた点を見つけやすくなりました。

 

  • ✓ Webhook ID、タイムスタンプ、本文をピリオドで連結する。
  • ✓ 受け取ったとおりに生の本文を使用する。
  • ✓ 秘密鍵から「whsec_」プレフィックスを取り除く。
  • ✓ 署名からバージョンプレフィックスを削除する (「v1,」)。
  • ✓ SHA-256を使用する。

 

役に立つアドバイス #5: AI に助けを求めることを恐れないでください

 

これは後になって考えたことですが、おそらく AI に Replicate AI ドキュメントへのリンクを提供して、問題が発生した場合にスクリプトのトラブルシューティングを手伝ってもらうことができたのではないかと思います。ロジックの問題は通常、従来のコードツールでは検出されませんが、AI はこの種のロジックの問題を処理するためのまったく新しい機能セットを追加します。


この記事は機械翻訳されております。最新は元となる記事をご覧ください: https://www.servicenow.com/community/developer-advocate-blog/hmac-validation-in-servicenow-securing-...。

免責事項: 一部の日本語は、翻訳ソフトウェアを使用してお客様の便宜のために翻訳されています。正確な翻訳をご提供できるよう相当な努力を払っておりますが、いかなる自動翻訳も人間の翻訳者に代わすることはなく、そのようなことは意図されておりません。翻訳は「現状のまま」提供されています。他言語への翻訳の的確性、信頼性または正確性については、明示または黙示を問わず、いかなる保証も行われません。翻訳ソフトには限界があるため、一部のコンテンツが正確に翻訳されていない場合があります。これらの資料の公用言語は英語です。翻訳の際に生じる相違または不一致は、コンプライアンスまたは履行の目的に関しては拘束力を有さず、法的効力はないものとします。

This article was machine translated and replicated from the original source: https://www.servicenow.com/community/developer-advocate-blog/hmac-validation-in-servicenow-securing-...。