Webhook署名の検証

ガイドWebhookWebhookの処理Webhook署名の検証

Webhook署名の検証

中間者攻撃や再生攻撃からアプリケーションを保護するには、Webhook署名の検証が非常に重要です。検証により、Webhookペイロードが実際にBoxから送信されたものであること、およびペイロードの内容が転送中に変更されていないことを確認できます。

SDKによる検証

独自のコードを使用して手動でSDKを検証することもできますが、SDKには便利なメソッドが用意されています。

.NET
using Box.V2.Managers;

var body = "{\"type\":\"webhook_event\",\"webhook\":{\"id\":\"1234567890\"},\"trigger\":\"FILE.UPLOADED\",\"source\":{\"id\":\"1234567890\",\"type\":\"file\",\"name\":\"Test.txt\"}}";
var headers = new Dictionary<string, string>()
{
    { "box-delivery-id", "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f" },
    { "box-delivery-timestamp", "2020-01-01T00:00:00-07:00" },
    { "box-signature-algorithm", "HmacSHA256" } ,
    { "box-signature-primary", "6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=" },
    { "box-signature-secondary", "v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=" },
    { "box-signature-version", "1" }
};
var primaryKey = "Fd28OJrZ8oNxkgmS7TbjXNgrG8v";
var secondaryKey = "KWkROAOiof4zhYUHbAmiVn63cMj"

bool isValid = BoxWebhooksManager.VerifyWebhook(
    deliveryTimestamp: headers["box-delivery-timestamp"],
    signaturePrimary: headers["box-signature-primary"],
    signatureSecondary: headers["box-signature-secondary"],
    payload: body,
    primaryWebhookKey: primaryKey,
    secondaryWebhookKey: secondaryKey
);
Java
// Webhook message contents are shown for demonstration purposes
// Normally these would come from your HTTP handler

// Webhook message HTTP body
String messagePayload = "{"
    + "\"type\":\"webhook_event","
    + "\"webhook\":{"
    +   "\"id\":\"1234567890\""
    + "},"
    + "\"trigger\":\"FILE.UPLOADED\","
    + "\"source\":{"
    +   "\"id\":\"1234567890\","
    +   "\"type\":\"file\","
    +   "\"name\":\"Test.txt\""
    + "}}";

// Webhook message HTTP headers
Map<String, String> messageHeaders = new HashMap<String, String>();
headers.put("BOX-DELIVERY-ID", "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f");
headers.put("BOX-DELIVERY-TIMESTAMP", "2020-01-01T00:00:00-07:00");
headers.put("BOX-SIGNATURE-ALGORITHM", "HmacSHA256");
headers.put("BOX-SIGNATURE-PRIMARY", "6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=");
headers.put("BOX-SIGNATURE-SECONDARY", "v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=");
headers.put("BOX-SIGNATURE-VERSION", "1");

// Your application's webhook keys, obtained from the Box Developer Console
String primaryKey = "4py2I9eSFb0ezXH5iPeQRcFK1LRLCdip";
String secondaryKey = "Aq5EEEjAu4ssbz8n9UMu7EerI0LKj2TL";

BoxWebHookSignatureVerifier verifier = new BoxWebHookSignatureVerifier(primaryKey, secondaryKey);
boolean isValidMessage = verifier.verify(
    headers.get("BOX-SIGNATURE-VERSION"),
    headers.get("BOX-SIGNATURE-ALGORITHM"),
    headers.get("BOX-SIGNATURE-PRIMARY"),
    headers.get("BOX-SIGNATURE-SECONDARY"),
    messagePayload,
    headers.get("BOX-DELIVERY-TIMESTAMP")
);

if (isValidMessage) {
    // Message is valid, handle it
} else {
    // Message is invalid, reject it
}
Python
body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}'
headers = {
    'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f',
    'box-delivery-timestamp': '2020-01-01T00:00:00-07:00',
    'box-signature-algorithm': 'HmacSHA256',
    'box-signature-primary': '4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=',
    'box-signature-secondary': 'yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=',
    'box-signature-version': '1',
}
is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key)
print('The webhook message is validated to: {0}'.format(is_validated))
Node
const BoxSDK = require('box-node-sdk');
let body = '{"type":"webhook_event","webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}',
	headers = {
		'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f',
		'box-delivery-timestamp': '2020-01-01T00:00:00-07:00',
		'box-signature-algorithm': 'HmacSHA256',
		'box-signature-primary': '6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=',
		'box-signature-secondary': 'v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=',
		'box-signature-version': '1'
	},
	primaryKey = 'SamplePrimaryKey',
	secondaryKey = 'SampleSecondaryKey';

let isValid = BoxSDK.validateWebhookMessage(body, headers, primaryKey, secondaryKey);
if (isValid) {
	// message is valid, accept
} else {
	// message is NOT valid, reject
}

手動での検証

SDKのメソッドを使用しない場合、署名の検証は基本的に以下の手順で行います。

1. 有効なタイムスタンプの確認

初めに、ペイロードのBOX-DELIVERY-TIMESTAMPヘッダーのタイムスタンプが10分以内のものであることを確認します。

Node
var timestamp = headers['BOX-DELIVERY-TIMESTAMP'];
var date = Date.parse(timestamp);
var expired = Date.now() - date > 10*60*1000;
Python
import dateutil.parser
import pytz
import datetime

timestamp = headers["BOX-DELIVERY-TIMESTAMP"]
date = dateutil.parser.parse(timestamp).astimezone(pytz.utc)

now = datetime.datetime.now(pytz.utc)
delta = datetime.timedelta(minutes=10)
expiry_date = now - deltaMinutes

expired = date >= expiry_date

2. HMAC署名の計算

次に、開発者コンソールのアプリケーションの設定にある2つの署名のいずれかを使用して、ペイロードのHMACを計算します。

最初にペイロードの本文のバイトを追加し、次にBOX-DELIVERY-TIMESTAMPヘッダーにあるタイムスタンプのバイトを追加します。

Node
var crypto = require('crypto');

var primaryKey = '...';
var secondaryKey = '...';

var payload = '{"type":"webhook_event"...}';

var hmac1 = crypto.createHmac('sha256', primaryKey);
hmac1.update(payload);
hmac1.update(timestamp);

var hmac2 = crypto.createHmac('sha256', secondaryKey);
hmac2.update(payload);
hmac2.update(timestamp);
Python
import hmac
import hashlib

primary_key = '...'
secondary_key = '...'

payload = "{\"type\":\"webhook_event\"...}"

bytes = bytes(payload, 'utf-8') + bytes(timestamp, 'utf-8')

hmac1 = hmac.new(primary_key, bytes, hashlib.sha256).digest()
hmac2 = hmac.new(secondary_key, bytes, hashlib.sha256).digest()

4. base64への変換

HMACをBase64でエンコードされたダイジェストに変換します。

Node
var digest1 = hmac1.digest('base64');
var digest2 = hmac2.digest('base64');
Python
import base64

digest1 = base64.b64encode(hmac1)
digest2 = base64.b64encode(hmac2)

5. 署名の比較

最後に、エンコードされたダイジェストをBOX-SIGNATURE-PRIMARYまたはBOX-SIGNATURE-SECONDARYヘッダーの値と比較します。

BOX-SIGNATURE-PRIMARYヘッダーの値はプライマリキーで作成されたダイジェストと比較し、BOX-SIGNATURE-SECONDARYヘッダーの値はセカンダリキーで作成されたダイジェストと比較してください。

Node
var signature1 = headers['BOX-SIGNATURE-SECONDARY'];
var signature2 = headers['BOX-SIGNATURE-PRIMARY'];

var primarySignatureValid = digest1 === signature1
var secondarySignatureValid = digest2 === signature2

var valid = !expired && (primarySignatureValid || secondarySignatureValid)
Python
signature1 = headers["BOX-SIGNATURE-SECONDARY"]
signature2 = headers["BOX-SIGNATURE-PRIMARY"]

primary_sig_valid = digest1 === signature1
secondary_sig_valid = digest2 === signature2

valid = !expired && (primary_sig_valid || secondary_sig_valid)

HTTPヘッダー名では大文字と小文字が区別されないため、クライアントでは、すべてのヘッダーの名前を標準化された小文字または大文字の形式に変換してから、ヘッダーの値を確認するのが理想的です。