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' }
通知の場合、署名する文字列は以下のフォーマットになります。
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
SNSフォーマットで渡ってくるリクエストボディの情報から上記の署名文字列を作成する必要があります。Subjectは省略可能なので、省略されていた場合は署名文字列にSubjectは含みません。あと改行コードが必要となります。
またこの署名文字列が、通知の場合、サブスクリプションの確認削除で異なります。
使用モジュール
pemファイル(公開鍵)の中身を取得するためにsuperagentを使用します。
署名検証するためにcryptoモジュールを使用します。
インストールします。
npm i --save 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; };
verifyメソッドでデジタル署名の検証を行います。
引数 | 値 |
---|---|
第一引数 | 公開鍵 |
第二引数 | 署名 |
第三引数 | 署名エンコード方式 |
trueが返ってくれば署名検証OKです。
aws-js-sns-message-validator
https://github.com/aws/aws-js-sns-message-validatorのモジュールを使えば簡単に実装できるようです。
hashのアルゴリズムがJavaとnode.jsで違うようです。
crypto.createVerify('RSA-SHA1')
使い方は「AWS SNSからのリクエストを検証するaws-js-sns-message-validatorをPromise化する」を参照ください。

KHI入社して退社。今はCONFRAGEで正社員です。関西で140-170/80~120万から受け付けております^^
得意技はJS(ES6),Java,AWSの大体のリソースです
コメントはやさしくお願いいたします^^
座右の銘は、「狭き門より入れ」「願わくは、我に七難八苦を与えたまえ」です^^
コメント