署名の検証
署名の検証
Webhook署名は、Boxから送信されたWebhookペイロードが改ざんされていないことを確認するために役立ちます。署名により、中間者攻撃または再生攻撃が成功する可能性を大幅に低減できます。
署名を構成すると、Boxは通知の本文の暗号化ダイジェストを生成し、これをWebhookペイロードのヘッダーに添付します。アプリケーションがペイロードを受信したら、同じダイジェストを計算し、それを受信したダイジェストと比較して署名を検証してください。ダイジェストが一致しない場合、ペイロードは信頼できません。
署名キーを頻繁に変更することで、保護レベルをさらに高めることができます。古いキーと新しいキーをスムーズに切り替えられるよう、Boxでは同時に2つの署名キーをサポートしています。
署名設定
アプリケーションの通知に署名を添付するには、まず、アプリケーション用の署名キーを生成する必要があります。
アプリケーションのキーを構成するには、以下の手順に従います。
- 開発者コンソールでアプリケーションに移動します。
- [Webhook] タブをクリックします。
- [署名キーを管理] ボタンをクリックします。
- [キーを生成] ボタンをクリックして、キーを構成します。
プライマリキーまたはセカンダリキーを生成したら、その値をコピーします。この値は、Webhookペイロードの検証で必要になります。これで、すべてのWebhookにBOX-SIGNATURE-PRIMARY
およびBOX-SIGNATURE-SECONDARY
ヘッダーペイロードが含まれるようになります。
SDKによる署名の検証
手動で署名を検証することもできますが、Box公式SDKには便利なメソッドが用意されています。
// 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
}
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(f'The webhook message is validated to: {is_validated}')
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
);
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
}
手動による署名の検証
署名の検証は基本的に以下の手順で行います。
タイムスタンプの検証
ペイロードのBOX-DELIVERY-TIMESTAMP
ヘッダーのタイムスタンプが10分以内のものであることを確認します。
var timestamp = headers['BOX-DELIVERY-TIMESTAMP'];
var date = Date.parse(timestamp);
var expired = Date.now() - date > 10*60*1000;
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
HMAC署名の計算
開発者コンソールでアプリケーションに構成され ている2つの署名のいずれかを使用して、ペイロードのHMACを計算します。
最初にペイロード本文のバイトを追加してから、BOX-DELIVERY-TIMESTAMP
ヘッダーにあるタイムスタンプのバイトを追加してください。
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);
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()
Base64変換
HMACをBase64
でエンコードされたダイジェストに変換します。
var digest1 = hmac1.digest('base64');
var digest2 = hmac2.digest('base64');
import base64
digest1 = base64.b64encode(hmac1)
digest2 = base64.b64encode(hmac2)
署名の比較
エンコードされたダイジェストをBOX-SIGNATURE-PRIMARY
またはBOX-SIGNATURE-SECONDARY
ヘッダーの値と比較します。
BOX-SIGNATURE-PRIMARY
ヘッダーの値はプライマリキーで作成されたダイジェストと比較し、BOX-SIGNATURE-SECONDARY
ヘッダーの値はセカンダリキーで作成されたダイジェストと比較してください。
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)
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)
署名のローテーション
有効にした場合、BoxはすべてのWebhookペイロードで2つの署名を送信します。少なくとも1つの署名が有効であれば、アプリケーションはペイロードを信頼できます。一度に1つの署名キーを更新すると、アプリケーションは常に少なくとも1つの有効な署名を含むペイロードを受信することになります。
循環の手順
以下の手順は、開発者コンソールでプライマリキーとセカンダリキーを作成済みで、どちらかのキーを置き換える準備ができていることを前提としています。
これらの手順に従うことにより、競合することなく、2つの新しいキーを使ってアプリケーションを構成できます。
- 開発者コンソールで、[Webhook] タブに移動します。
- [署名キーを管理] をクリックします。
- [リセット] ボタンをクリックしてプライマリキーを変更します。
- 新しいプライマリキーでアプリケーションを更新します。アプリケーションは、引き続き古いプライマリキーを含む通知を受信する可能性がありますが、セカンダリキーがまだ有効であるため、Webhookは正しく処理されます。
- 古いプライマリキーを持つWebhookが動作していないことを確認できたら、同じプロセスを使用してセカンダリキーを更新できます。