AWS SNSの通知(Notification)の署名を検証する方法(node.js)

AWS SNSの通知(Notification)の署名を検証する方法(node.js)

SNSの確認や通知はヘッダ情報を偽装してしまってなりすましされてしまう可能性がある為、リクエストから渡ってくる情報で署名検証を行う必要があります。

今回はSNS+エンドポイントがhttpsでAPI Gateway+Lamda(node.js)の構成です。

現状、SNSのエンドポイントのAPI GatewayはPrivate APIをサポートしていませんので、公開APIを使用するしかありません。その為だれでもこのAPIを実行出来てしまう状態なので、署名検証が必要になります。

以下サイトはJavaのサンプルコードがありますが(117pあたり)、node.jsのサンプルコードがなかったのでnode.jsで書いてみました。

https://docs.aws.amazon.com/ja_jp/sns/latest/dg/sns-dg.pdf

SNSフォーマット(通知)

SNSの通知の場合のフォーマット例です。

{
Type: 'Notification',
MessageId: 'f052946e-46cf-5d2a-64d3-cc6b9ced1db4',
TopicArn: 'arn:aws:sns:ap-northeast-1:111122223333:topicname',
Subject: 'タイトル',
Message: '本文',
Timestamp: '2020-10-18T05:51:30.450Z',
SignatureVersion: '1',
Signature: 'gft4k51Y1Wi5dCAPKMPGJoVBs0vylKPfaqVA6LG3PLnBj3jyVqY7BuQm3GgkFzpUOERyf9LTT3+V5ct2Y6/xLuG0Todu2cep/CvxAVcG2KK9JCX8lriPucSLb5yyM/PHTT11PsN5k/VQzRlY3pB6DH3CqPVkb6V5HZqIHMk4KCgo0kJ3658E0Wh0pv3pyltGpb0VqKIRKtzC1xAhITzsBS3tyo8RoLxrW6ksYWhjTYhIFRrNIXH7MXFXyUcCU4BOuzQgIEdnkh1j6+xRvGYA4YiQAsX7IpywwMOAvmfvwwigt2putza6QbvyxQxq8UYyN3uEaSAGk5uRL38w8me1xg==',
SigningCertURL: 'https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-x11cb10b3e1f29c757802d767120f7a3.pem',
UnsubscribeURL: 'https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:111122223333:topicname:27843c78-2ad7-4f1f-8172-7348a14c22ac'
}

view raw
gistfile1.txt
hosted with ❤ by GitHub

通知の場合、署名する文字列は以下のフォーマットになります。

Message
本文
MessageId
f052946e-46cf-5d2a-64d3-cc6b9ced1db4 // uuidみたいなもの
Subject
タイトル
Timestamp
2019-01-31T04:37:04.321Z
TopicArn
arn:aws:sns:ap-northeast-1:111122223333:topicname
Type
Notification

view raw
gistfile1.txt
hosted with ❤ by GitHub

SNSフォーマットで渡ってくるリクエストボディの情報から上記の署名文字列を作成する必要があります。Subjectは省略可能なので、省略されていた場合は署名文字列にSubjectは含みません。あと改行コードが必要となります。

またこの署名文字列が、通知の場合、サブスクリプションの確認削除で異なります。

使用モジュール

pemファイル(公開鍵)の中身を取得するためにsuperagentを使用します。

署名検証するためにcryptoモジュールを使用します。

インストールします。

署名検証ソース(node.js)

SignatureVersionは”1″でなければいけません。このチェックも必要です。

const crypto = require('crypto')
const superagent = require('superagent')
const aws = require('aws-sdk')
const sns = new aws.SNS({
apiVersion: '2010-03-31',
region: 'ap-northeast-1'
});
exports.handler = async (event) => {
const message = JSON.parse(event.body)
const pemFile = await superagent.get(message.SigningCertURL)
const pemContent = pemFile.body.toString('utf-8')
const verify = crypto.createVerify('sha1WithRSAEncryption')
const arr = ['Message', 'MessageId', 'Subject', 'Timestamp', 'TopicArn', 'Type']
arr.forEach(key => {
if (key in message) {
verify.write(`${key}\n${message[key]}\n`)
}
})
verify.end()
const result = verify.verify(pemContent, message.Signature, 'base64')
console.log(result) // trueなら署名検証OK
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};

view raw
node.js
hosted with ❤ by GitHub

verifyメソッドでデジタル署名の検証を行います。

引数
第一引数 公開鍵
第二引数 署名
第三引数 署名エンコード方式

trueが返ってくれば署名検証OKです。

aws-js-sns-message-validator

https://github.com/aws/aws-js-sns-message-validatorのモジュールを使えば簡単に実装できるようです。

hashのアルゴリズムがJavaとnode.jsで違うようです。

使い方は「AWS SNSからのリクエストを検証するaws-js-sns-message-validatorをPromise化する」を参照ください。

スポンサーリンク
  • このエントリーをはてなブックマークに追加
  • Evernoteに保存Evernoteに保存
スポンサーリンク

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA