
はじめに
6回目の投稿となります。
だんでらいおんです。
AWS SNSに触れた際、HTTP/HTTPSエンドポイントに送信されるクエリ形式のメッセージに対して、署名検証手順をPHPで実施する方法について興味を持ったため、その方法を検証しました。
AWS SNSとは?
AWSによると・・・
Amazon Simple Notification Service (Amazon SNS) は、パブリッシャー (プロデューサー) からサブスクライバー (コンシューマー) へのメッセージ配信を提供するフルマネージドサービスです。発行者は、論理アクセスポイントおよび通信チャネルであるトピックにメッセージを送信することで、受信者と非同期的に通信します。
https://docs.aws.amazon.com/ja_jp/sns/latest/dg/welcome.html
非同期的なメッセージングを活用することでコンポーネント間の依存を減らせるため、システム全体に安定性や柔軟性を持たせることがメリットのようです。
AWS SNSメッセージにおける署名検証とは?
AWSによると・・・
HTTP クエリベースのリクエストを使用する際に Amazon SNS メッセージの署名を検証すると、メッセージの真正性と整合性が保証されます。
https://docs.aws.amazon.com/ja_jp/sns/latest/dg/sns-verify-signature-of-message-verify-message-signature.html
Amazon SNS から発信され、転送中に改ざんされていないことを確認することを指すようです。
署名検証の手順は?
下記で署名検証手順を確認可能です。
「HTTP クエリベースのリクエストを使用する場合の Amazon SNS メッセージの署名の検証」
【補足】AWS SNSのHTTP/HTTPS 通知において、取得可能な情報は下記で確認可能です。
HTTP/HTTPS 通知の JSON 形式
https://docs.aws.amazon.com/ja_jp/sns/latest/dg/http-notification-json.html
署名検証処理を確認してみる
今回は、下記2つの署名を比較する処理を確認していきたいと思います。
・配信側が作成した署名:$signature
・配信されたSNSメッセージ情報を元に受信側が作成した署名:$data
<?php
$json = file_get_contents('php://input');
$message = json_decode($json, true);
$cert = file_get_contents($message['SigningCertURL']);
$pubkey = openssl_pkey_get_public($cert);
// 検証対象文字列を構築(Amazon公式仕様に準拠)
function build_string_to_sign($message) {
$fields = [];
if (isset($message['Message'])) $fields[] = "Message\n{$message['Message']}";
if (isset($message['MessageId'])) $fields[] = "MessageId\n{$message['MessageId']}";
if (isset($message['Subject'])) $fields[] = "Subject\n{$message['Subject']}";
if (isset($message['Timestamp'])) $fields[] = "Timestamp\n{$message['Timestamp']}";
if (isset($message['TopicArn'])) $fields[] = "TopicArn\n{$message['TopicArn']}";
if (isset($message['Type'])) $fields[] = "Type\n{$message['Type']}";
return implode("\n", $fields) . "\n";
}
// 受信側の署名
$data = build_string_to_sign($message);
// 送信側の署名
$signature = base64_decode($message['Signature']);
$is_signature_valid = openssl_verify($data, $signature, $pubkey, OPENSSL_ALGO_SHA1);
// 検証結果の確認
if ($is_signature_valid !== 1) {
file_put_contents('log.txt', $message['Message']."失敗!!");
http_response_code(403);
exit("Invalid signature");
} else {
file_put_contents('log.txt', $message['Message']."成功!!");
http_response_code(200);
echo "OK";
}
上記ファイルをサーバに配置して、該当サーバにSNSメッセージのテスト配信を実施します。
(SNSサブスクリプションの購読処理は実施済みです。)

ログを確認したところ、署名検証を実施できているようです。

まとめ
簡単にですが、AWS SNSメッセージの署名検証手順などを確認してみました。
今回は署名の検証部分のみですが、 実際には「証明書URLの検証」や「署名検証するメッセージタイプの限定」などといった対応も必要になるようです。
今後も気になったものは、手を動かして確認していこうと思います。