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'
}

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

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化する」を参照ください。

コメント

株式会社CONFRAGE ITソリューション事業部をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む

タイトルとURLをコピーしました