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)